PageRenderTime 97ms CodeModel.GetById 33ms app.highlight 59ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llcommon/lluri.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 612 lines | 497 code | 56 blank | 59 comment | 96 complexity | afa1174d9100594d8c4cc9766f00fa74 MD5 | raw file
  1/** 
  2 * @file lluri.cpp
  3 * @author Phoenix
  4 * @date 2006-02-08
  5 * @brief Implementation of the LLURI class.
  6 *
  7 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29#include "linden_common.h"
 30
 31#include "llapp.h"
 32#include "lluri.h"
 33#include "llsd.h"
 34#include <iomanip>
 35  
 36#include "lluuid.h"
 37
 38// system includes
 39#include <boost/tokenizer.hpp>
 40
 41void encode_character(std::ostream& ostr, std::string::value_type val)
 42{
 43	ostr << "%"
 44
 45	     << std::uppercase
 46	     << std::hex
 47	     << std::setw(2)
 48	     << std::setfill('0') 
 49
 50	     // VWR-4010 Cannot cast to U32 because sign-extension on 
 51	     // chars > 128 will result in FFFFFFC3 instead of F3.
 52	     << static_cast<S32>(static_cast<U8>(val))
 53
 54		// reset stream state
 55	     << std::nouppercase
 56	     << std::dec
 57	     << std::setfill(' ');
 58}
 59
 60// static
 61std::string LLURI::escape(
 62	const std::string& str,
 63	const std::string& allowed,
 64	bool is_allowed_sorted)
 65{
 66	// *NOTE: This size determination feels like a good value to
 67	// me. If someone wante to come up with a more precise heuristic
 68	// with some data to back up the assertion that 'sort is good'
 69	// then feel free to change this test a bit.
 70	if(!is_allowed_sorted && (str.size() > 2 * allowed.size()))
 71	{
 72		// if it's already sorted, or if the url is quite long, we
 73		// want to optimize this process.
 74		std::string sorted_allowed(allowed);
 75		std::sort(sorted_allowed.begin(), sorted_allowed.end());
 76		return escape(str, sorted_allowed, true);
 77	}
 78
 79	std::ostringstream ostr;
 80	std::string::const_iterator it = str.begin();
 81	std::string::const_iterator end = str.end();
 82	std::string::value_type c;
 83	if(is_allowed_sorted)
 84	{
 85		std::string::const_iterator allowed_begin(allowed.begin());
 86		std::string::const_iterator allowed_end(allowed.end());
 87		for(; it != end; ++it)
 88		{
 89			c = *it;
 90			if(std::binary_search(allowed_begin, allowed_end, c))
 91			{
 92				ostr << c;
 93			}
 94			else
 95			{
 96				encode_character(ostr, c);
 97			}
 98		}
 99	}
100	else
101	{
102		for(; it != end; ++it)
103		{
104			c = *it;
105			if(allowed.find(c) == std::string::npos)
106			{
107				encode_character(ostr, c);
108			}
109			else
110			{
111				ostr << c;
112			}
113		}
114	}
115	return ostr.str();
116}
117
118// static
119std::string LLURI::unescape(const std::string& str)
120{
121	std::ostringstream ostr;
122	std::string::const_iterator it = str.begin();
123	std::string::const_iterator end = str.end();
124	for(; it != end; ++it)
125	{
126		if((*it) == '%')
127		{
128			++it;
129			if(it == end) break;
130			U8 c = hex_as_nybble(*it++);
131			c = c << 4;
132			if (it == end) break;
133			c |= hex_as_nybble(*it);
134			ostr.put((char)c);
135		}
136		else
137		{
138			ostr.put(*it);
139		}
140	}
141	return ostr.str();
142}
143
144namespace
145{
146	const std::string unreserved()
147	{
148		static const std::string s =   
149			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
150			"0123456789"
151			"-._~";
152		return s;
153	}
154	const std::string sub_delims()
155	{
156		static const std::string s = "!$&'()*+,;=";
157		return s;
158	}
159
160	std::string escapeHostAndPort(const std::string& s)
161		{ return LLURI::escape(s, unreserved() + sub_delims() +":"); }
162	std::string escapePathComponent(const std::string& s)
163		{ return LLURI::escape(s, unreserved() + sub_delims() + ":@"); }
164	std::string escapeQueryVariable(const std::string& s)
165		{ return LLURI::escape(s, unreserved() + ":@!$'()*+,"); }	 // sub_delims - "&;=" + ":@"
166	std::string escapeQueryValue(const std::string& s)
167		{ return LLURI::escape(s, unreserved() + ":@!$'()*+,="); }	// sub_delims - "&;" + ":@"
168}
169
170//static
171std::string LLURI::escape(const std::string& str)
172{
173	static std::string default_allowed = unreserved();
174	static bool initialized = false;
175	if(!initialized)
176	{
177		std::sort(default_allowed.begin(), default_allowed.end());
178		initialized = true;
179	}
180	return escape(str, default_allowed, true);
181}
182
183LLURI::LLURI()
184{
185}
186
187LLURI::LLURI(const std::string& escaped_str)
188{
189	std::string::size_type delim_pos;
190	delim_pos = escaped_str.find(':');
191	std::string temp;
192	if (delim_pos == std::string::npos)
193	{
194		mScheme = "";
195		mEscapedOpaque = escaped_str;
196	}
197	else
198	{
199		mScheme = escaped_str.substr(0, delim_pos);
200		mEscapedOpaque = escaped_str.substr(delim_pos+1);
201	}
202
203	parseAuthorityAndPathUsingOpaque();
204
205	delim_pos = mEscapedPath.find('?');
206	if (delim_pos != std::string::npos)
207	{
208		mEscapedQuery = mEscapedPath.substr(delim_pos+1);
209		mEscapedPath = mEscapedPath.substr(0,delim_pos);
210	}
211}
212
213static BOOL isDefault(const std::string& scheme, U16 port)
214{
215	if (scheme == "http")
216		return port == 80;
217	if (scheme == "https")
218		return port == 443;
219	if (scheme == "ftp")
220		return port == 21;
221
222	return FALSE;
223}
224
225void LLURI::parseAuthorityAndPathUsingOpaque()
226{
227	if (mScheme == "http" || mScheme == "https" ||
228		mScheme == "ftp" || mScheme == "secondlife" || 
229		mScheme == "x-grid-location-info")
230	{
231		if (mEscapedOpaque.substr(0,2) != "//")
232		{
233			return;
234		}
235
236		std::string::size_type delim_pos, delim_pos2;
237		delim_pos = mEscapedOpaque.find('/', 2);
238		delim_pos2 = mEscapedOpaque.find('?', 2);
239		// no path, no query
240		if (delim_pos == std::string::npos &&
241			delim_pos2 == std::string::npos)
242		{
243			mEscapedAuthority = mEscapedOpaque.substr(2);
244			mEscapedPath = "";
245		}
246		// path exist, no query
247		else if (delim_pos2 == std::string::npos)
248		{
249			mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
250			mEscapedPath = mEscapedOpaque.substr(delim_pos);
251		}
252		// no path, only query
253		else if (delim_pos == std::string::npos ||
254				 delim_pos2 < delim_pos)
255		{
256			mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2);
257			// query part will be broken out later
258			mEscapedPath = mEscapedOpaque.substr(delim_pos2);
259		}
260		// path and query
261		else
262		{
263			mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
264			// query part will be broken out later
265			mEscapedPath = mEscapedOpaque.substr(delim_pos);
266		}
267	}
268	else if (mScheme == "about")
269	{
270		mEscapedPath = mEscapedOpaque;
271	}
272}
273
274LLURI::LLURI(const std::string& scheme,
275			 const std::string& userName,
276			 const std::string& password,
277			 const std::string& hostName,
278			 U16 port,
279			 const std::string& escapedPath,
280			 const std::string& escapedQuery)
281	: mScheme(scheme),
282	  mEscapedPath(escapedPath),
283	  mEscapedQuery(escapedQuery)
284{
285	std::ostringstream auth;
286	std::ostringstream opaque;
287
288	opaque << "//";
289	
290	if (!userName.empty())
291	{
292		auth << escape(userName);
293		if (!password.empty())
294		{
295			auth << ':' << escape(password);
296		}
297		auth << '@';
298	}
299	auth << hostName;
300	if (!isDefault(scheme, port))
301	{
302		auth << ':' << port;
303	}
304	mEscapedAuthority = auth.str();
305
306	opaque << mEscapedAuthority << escapedPath << escapedQuery;
307
308	mEscapedOpaque = opaque.str();
309}
310
311LLURI::~LLURI()
312{
313}
314
315// static
316LLURI LLURI::buildHTTP(const std::string& prefix,
317					   const LLSD& path)
318{
319	LLURI result;
320	
321	// TODO: deal with '/' '?' '#' in host_port
322	if (prefix.find("://") != prefix.npos)
323	{
324		// it is a prefix
325		result = LLURI(prefix);
326	}
327	else
328	{
329		// it is just a host and optional port
330		result.mScheme = "http";
331		result.mEscapedAuthority = escapeHostAndPort(prefix);
332	}
333
334	if (path.isArray())
335	{
336		// break out and escape each path component
337		for (LLSD::array_const_iterator it = path.beginArray();
338			 it != path.endArray();
339			 ++it)
340		{
341			lldebugs << "PATH: inserting " << it->asString() << llendl;
342			result.mEscapedPath += "/" + escapePathComponent(it->asString());
343		}
344	}
345	else if(path.isString())
346	{
347		result.mEscapedPath += "/" + escapePathComponent(path.asString());
348	} 
349	else if(path.isUndefined())
350	{
351	  // do nothing
352	}
353    else
354	{
355	  llwarns << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" 
356			  << path.type() << llendl;
357	}
358	result.mEscapedOpaque = "//" + result.mEscapedAuthority +
359		result.mEscapedPath;
360	return result;
361}
362
363// static
364LLURI LLURI::buildHTTP(const std::string& prefix,
365					   const LLSD& path,
366					   const LLSD& query)
367{
368	LLURI uri = buildHTTP(prefix, path);
369	// break out and escape each query component
370	uri.mEscapedQuery = mapToQueryString(query);
371	uri.mEscapedOpaque += uri.mEscapedQuery ;
372	uri.mEscapedQuery.erase(0,1); // trim the leading '?'
373	return uri;
374}
375
376// static
377LLURI LLURI::buildHTTP(const std::string& host,
378					   const U32& port,
379					   const LLSD& path)
380{
381	return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path);
382}
383
384// static
385LLURI LLURI::buildHTTP(const std::string& host,
386					   const U32& port,
387					   const LLSD& path,
388					   const LLSD& query)
389{
390	return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query);
391}
392
393std::string LLURI::asString() const
394{
395	if (mScheme.empty())
396	{
397		return mEscapedOpaque;
398	}
399	else
400	{
401		return mScheme + ":" + mEscapedOpaque;
402	}
403}
404
405std::string LLURI::scheme() const
406{
407	return mScheme;
408}
409
410std::string LLURI::opaque() const
411{
412	return unescape(mEscapedOpaque);
413}
414
415std::string LLURI::authority() const
416{
417	return unescape(mEscapedAuthority);
418}
419
420
421namespace {
422	void findAuthorityParts(const std::string& authority,
423							std::string& user,
424							std::string& host,
425							std::string& port)
426	{
427		std::string::size_type start_pos = authority.find('@');
428		if (start_pos == std::string::npos)
429		{
430			user = "";
431			start_pos = 0;
432		}
433		else
434		{
435			user = authority.substr(0, start_pos);
436			start_pos += 1;
437		}
438
439		std::string::size_type end_pos = authority.find(':', start_pos);
440		if (end_pos == std::string::npos)
441		{
442			host = authority.substr(start_pos);
443			port = "";
444		}
445		else
446		{
447			host = authority.substr(start_pos, end_pos - start_pos);
448			port = authority.substr(end_pos + 1);
449		}
450	}
451}
452	
453std::string LLURI::hostName() const
454{
455	std::string user, host, port;
456	findAuthorityParts(mEscapedAuthority, user, host, port);
457	return unescape(host);
458}
459
460std::string LLURI::userName() const
461{
462	std::string user, userPass, host, port;
463	findAuthorityParts(mEscapedAuthority, userPass, host, port);
464	std::string::size_type pos = userPass.find(':');
465	if (pos != std::string::npos)
466	{
467		user = userPass.substr(0, pos);
468	}
469	return unescape(user);
470}
471
472std::string LLURI::password() const
473{
474	std::string pass, userPass, host, port;
475	findAuthorityParts(mEscapedAuthority, userPass, host, port);
476	std::string::size_type pos = userPass.find(':');
477	if (pos != std::string::npos)
478	{
479		pass = userPass.substr(pos + 1);
480	}
481	return unescape(pass);
482}
483
484BOOL LLURI::defaultPort() const
485{
486	return isDefault(mScheme, hostPort());
487}
488
489U16 LLURI::hostPort() const
490{
491	std::string user, host, port;
492	findAuthorityParts(mEscapedAuthority, user, host, port);
493	if (port.empty())
494	{
495		if (mScheme == "http")
496			return 80;
497		if (mScheme == "https")
498			return 443;
499		if (mScheme == "ftp")
500			return 21;		
501		return 0;
502	}
503	return atoi(port.c_str());
504}	
505
506std::string LLURI::path() const
507{
508	return unescape(mEscapedPath);
509}
510
511LLSD LLURI::pathArray() const
512{
513	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
514	boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
515	tokenizer tokens(mEscapedPath, sep);
516	tokenizer::iterator it = tokens.begin();
517	tokenizer::iterator end = tokens.end();
518
519	LLSD params;
520	for ( ; it != end; ++it)
521	{
522		params.append(*it);
523	}
524	return params;
525}
526
527std::string LLURI::query() const
528{
529	return unescape(mEscapedQuery);
530}
531
532LLSD LLURI::queryMap() const
533{
534	return queryMap(mEscapedQuery);
535}
536
537// static
538LLSD LLURI::queryMap(std::string escaped_query_string)
539{
540	lldebugs << "LLURI::queryMap query params: " << escaped_query_string << llendl;
541
542	LLSD result = LLSD::emptyArray();
543	while(!escaped_query_string.empty())
544	{
545		// get tuple first
546		std::string tuple;
547		std::string::size_type tuple_begin = escaped_query_string.find('&');
548		if (tuple_begin != std::string::npos)
549		{
550			tuple = escaped_query_string.substr(0, tuple_begin);
551			escaped_query_string = escaped_query_string.substr(tuple_begin+1);
552		}
553		else
554		{
555			tuple = escaped_query_string;
556			escaped_query_string = "";
557		}
558		if (tuple.empty()) continue;
559
560		// parse tuple
561		std::string::size_type key_end = tuple.find('=');
562		if (key_end != std::string::npos)
563		{
564			std::string key = unescape(tuple.substr(0,key_end));
565			std::string value = unescape(tuple.substr(key_end+1));
566			lldebugs << "inserting key " << key << " value " << value << llendl;
567			result[key] = value;
568		}
569		else
570		{
571			lldebugs << "inserting key " << unescape(tuple) << " value true" << llendl;
572		    result[unescape(tuple)] = true;
573		}
574	}
575	return result;
576}
577
578std::string LLURI::mapToQueryString(const LLSD& queryMap)
579{
580	std::string query_string;
581	if (queryMap.isMap())
582	{
583		bool first_element = true;
584		LLSD::map_const_iterator iter = queryMap.beginMap();
585		LLSD::map_const_iterator end = queryMap.endMap();
586		std::ostringstream ostr;
587		for (; iter != end; ++iter)
588		{
589			if(first_element)
590			{
591				ostr << "?";
592				first_element = false;
593			}
594			else
595			{
596				ostr << "&";
597			}
598			ostr << escapeQueryVariable(iter->first);
599			if(iter->second.isDefined())
600			{
601				ostr << "=" <<  escapeQueryValue(iter->second.asString());
602			}
603		}
604		query_string = ostr.str();
605	}
606	return query_string;
607}
608
609bool operator!=(const LLURI& first, const LLURI& second)
610{
611	return (first.asString() != second.asString());
612}