lluri.cpp

Go to the documentation of this file.
00001 
00034 #include "linden_common.h"
00035 
00036 #include "llapp.h"
00037 #include "lluri.h"
00038 #include "llsd.h"
00039 #include <iomanip>
00040   
00041 #include "../llmath/lluuid.h"
00042 
00043 // system includes
00044 #include <boost/tokenizer.hpp>
00045 
00046 void encode_character(std::ostream& ostr, std::string::value_type val)
00047 {
00048         ostr << "%" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') 
00049              // VWR-4010 Cannot cast to U32 because sign-extension on 
00050              // chars > 128 will result in FFFFFFC3 instead of F3.
00051              << static_cast<S32>(static_cast<U8>(val));
00052 }
00053 
00054 // static
00055 std::string LLURI::escape(
00056         const std::string& str,
00057         const std::string& allowed,
00058         bool is_allowed_sorted)
00059 {
00060         // *NOTE: This size determination feels like a good value to
00061         // me. If someone wante to come up with a more precise heuristic
00062         // with some data to back up the assertion that 'sort is good'
00063         // then feel free to change this test a bit.
00064         if(!is_allowed_sorted && (str.size() > 2 * allowed.size()))
00065         {
00066                 // if it's already sorted, or if the url is quite long, we
00067                 // want to optimize this process.
00068                 std::string sorted_allowed(allowed);
00069                 std::sort(sorted_allowed.begin(), sorted_allowed.end());
00070                 return escape(str, sorted_allowed, true);
00071         }
00072 
00073         std::ostringstream ostr;
00074         std::string::const_iterator it = str.begin();
00075         std::string::const_iterator end = str.end();
00076         std::string::value_type c;
00077         if(is_allowed_sorted)
00078         {
00079                 std::string::const_iterator allowed_begin(allowed.begin());
00080                 std::string::const_iterator allowed_end(allowed.end());
00081                 for(; it != end; ++it)
00082                 {
00083                         c = *it;
00084                         if(std::binary_search(allowed_begin, allowed_end, c))
00085                         {
00086                                 ostr << c;
00087                         }
00088                         else
00089                         {
00090                                 encode_character(ostr, c);
00091                         }
00092                 }
00093         }
00094         else
00095         {
00096                 for(; it != end; ++it)
00097                 {
00098                         c = *it;
00099                         if(allowed.find(c) == std::string::npos)
00100                         {
00101                                 encode_character(ostr, c);
00102                         }
00103                         else
00104                         {
00105                                 ostr << c;
00106                         }
00107                 }
00108         }
00109         return ostr.str();
00110 }
00111 
00112 // static
00113 std::string LLURI::unescape(const std::string& str)
00114 {
00115         std::ostringstream ostr;
00116         std::string::const_iterator it = str.begin();
00117         std::string::const_iterator end = str.end();
00118         for(; it != end; ++it)
00119         {
00120                 if((*it) == '%')
00121                 {
00122                         ++it;
00123                         if(it == end) break;
00124                         U8 c = hex_as_nybble(*it++);
00125                         c = c << 4;
00126                         if (it == end) break;
00127                         c |= hex_as_nybble(*it);
00128                         ostr.put((char)c);
00129                 }
00130                 else
00131                 {
00132                         ostr.put(*it);
00133                 }
00134         }
00135         return ostr.str();
00136 }
00137 
00138 namespace
00139 {
00140         const std::string unreserved()
00141         {
00142                 static const std::string s =   
00143                         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
00144                         "0123456789"
00145                         "-._~";
00146                 return s;
00147         }
00148         const std::string sub_delims()
00149         {
00150                 static const std::string s = "!$&'()*+,;=";
00151                 return s;
00152         }
00153 
00154         std::string escapeHostAndPort(const std::string& s)
00155                 { return LLURI::escape(s, unreserved() + sub_delims() +":"); }
00156         std::string escapePathComponent(const std::string& s)
00157                 { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); }
00158         std::string escapeQueryVariable(const std::string& s)
00159                 { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); }        // sub_delims - "&;=" + ":@"
00160         std::string escapeQueryValue(const std::string& s)
00161                 { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); }      // sub_delims - "&;" + ":@"
00162 }
00163 
00164 // *TODO: Consider using curl. After http textures gets merged everywhere.
00165 // static
00166 std::string LLURI::escape(const std::string& str)
00167 {
00168         static std::string default_allowed(unreserved() + ":@!$'()*+,=/?&#;");
00169         static bool initialized = false;
00170         if(!initialized)
00171         {
00172                 std::sort(default_allowed.begin(), default_allowed.end());
00173                 initialized = true;
00174         }
00175         return escape(str, default_allowed, true);
00176 }
00177 
00178 LLURI::LLURI()
00179 {
00180 }
00181 
00182 LLURI::LLURI(const std::string& escaped_str)
00183 {
00184         std::string::size_type delim_pos;
00185         delim_pos = escaped_str.find(':');
00186         std::string temp;
00187         if (delim_pos == std::string::npos)
00188         {
00189                 mScheme = "";
00190                 mEscapedOpaque = escaped_str;
00191         }
00192         else
00193         {
00194                 mScheme = escaped_str.substr(0, delim_pos);
00195                 mEscapedOpaque = escaped_str.substr(delim_pos+1);
00196         }
00197 
00198         parseAuthorityAndPathUsingOpaque();
00199 
00200         delim_pos = mEscapedPath.find('?');
00201         if (delim_pos != std::string::npos)
00202         {
00203                 mEscapedQuery = mEscapedPath.substr(delim_pos+1);
00204                 mEscapedPath = mEscapedPath.substr(0,delim_pos);
00205         }
00206 }
00207 
00208 static BOOL isDefault(const std::string& scheme, U16 port)
00209 {
00210         if (scheme == "http")
00211                 return port == 80;
00212         if (scheme == "https")
00213                 return port == 443;
00214         if (scheme == "ftp")
00215                 return port == 21;
00216 
00217         return FALSE;
00218 }
00219 
00220 void LLURI::parseAuthorityAndPathUsingOpaque()
00221 {
00222         if (mScheme == "http" || mScheme == "https" ||
00223                 mScheme == "ftp" || mScheme == "secondlife" )
00224         {
00225                 if (mEscapedOpaque.substr(0,2) != "//")
00226                 {
00227                         return;
00228                 }
00229 
00230                 std::string::size_type delim_pos, delim_pos2;
00231                 delim_pos = mEscapedOpaque.find('/', 2);
00232                 delim_pos2 = mEscapedOpaque.find('?', 2);
00233                 // no path, no query
00234                 if (delim_pos == std::string::npos &&
00235                         delim_pos2 == std::string::npos)
00236                 {
00237                         mEscapedAuthority = mEscapedOpaque.substr(2);
00238                         mEscapedPath = "";
00239                 }
00240                 // path exist, no query
00241                 else if (delim_pos2 == std::string::npos)
00242                 {
00243                         mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
00244                         mEscapedPath = mEscapedOpaque.substr(delim_pos);
00245                 }
00246                 // no path, only query
00247                 else if (delim_pos == std::string::npos ||
00248                                  delim_pos2 < delim_pos)
00249                 {
00250                         mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2);
00251                         // query part will be broken out later
00252                         mEscapedPath = mEscapedOpaque.substr(delim_pos2);
00253                 }
00254                 // path and query
00255                 else
00256                 {
00257                         mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
00258                         // query part will be broken out later
00259                         mEscapedPath = mEscapedOpaque.substr(delim_pos);
00260                 }
00261         }
00262         else if (mScheme == "about")
00263         {
00264                 mEscapedPath = mEscapedOpaque;
00265         }
00266 }
00267 
00268 LLURI::LLURI(const std::string& scheme,
00269                          const std::string& userName,
00270                          const std::string& password,
00271                          const std::string& hostName,
00272                          U16 port,
00273                          const std::string& escapedPath,
00274                          const std::string& escapedQuery)
00275         : mScheme(scheme),
00276           mEscapedPath(escapedPath),
00277           mEscapedQuery(escapedQuery)
00278 {
00279         std::ostringstream auth;
00280         std::ostringstream opaque;
00281 
00282         opaque << "//";
00283         
00284         if (!userName.empty())
00285         {
00286                 auth << escape(userName);
00287                 if (!password.empty())
00288                 {
00289                         auth << ':' << escape(password);
00290                 }
00291                 auth << '@';
00292         }
00293         auth << hostName;
00294         if (!isDefault(scheme, port))
00295         {
00296                 auth << ':' << port;
00297         }
00298         mEscapedAuthority = auth.str();
00299 
00300         opaque << mEscapedAuthority << escapedPath << escapedQuery;
00301 
00302         mEscapedOpaque = opaque.str();
00303 }
00304 
00305 LLURI::~LLURI()
00306 {
00307 }
00308 
00309 // static
00310 LLURI LLURI::buildHTTP(const std::string& prefix,
00311                                            const LLSD& path)
00312 {
00313         LLURI result;
00314         
00315         // TODO: deal with '/' '?' '#' in host_port
00316         if (prefix.find("://") != prefix.npos)
00317         {
00318                 // it is a prefix
00319                 result = LLURI(prefix);
00320         }
00321         else
00322         {
00323                 // it is just a host and optional port
00324                 result.mScheme = "http";
00325                 result.mEscapedAuthority = escapeHostAndPort(prefix);
00326         }
00327 
00328         if (path.isArray())
00329         {
00330                 // break out and escape each path component
00331                 for (LLSD::array_const_iterator it = path.beginArray();
00332                          it != path.endArray();
00333                          ++it)
00334                 {
00335                         lldebugs << "PATH: inserting " << it->asString() << llendl;
00336                         result.mEscapedPath += "/" + escapePathComponent(it->asString());
00337                 }
00338         }
00339         else if(path.isString())
00340         {
00341                 result.mEscapedPath += "/" + escapePathComponent(path.asString());
00342         } 
00343         else if(path.isUndefined())
00344         {
00345           // do nothing
00346         }
00347     else
00348         {
00349           llwarns << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" 
00350                           << path.type() << llendl;
00351         }
00352         result.mEscapedOpaque = "//" + result.mEscapedAuthority +
00353                 result.mEscapedPath;
00354         return result;
00355 }
00356 
00357 // static
00358 LLURI LLURI::buildHTTP(const std::string& prefix,
00359                                            const LLSD& path,
00360                                            const LLSD& query)
00361 {
00362         LLURI uri = buildHTTP(prefix, path);
00363         // break out and escape each query component
00364         uri.mEscapedQuery = mapToQueryString(query);
00365         uri.mEscapedOpaque += uri.mEscapedQuery ;
00366         uri.mEscapedQuery.erase(0,1); // trim the leading '?'
00367         return uri;
00368 }
00369 
00370 // static
00371 LLURI LLURI::buildHTTP(const std::string& host,
00372                                            const U32& port,
00373                                            const LLSD& path)
00374 {
00375         return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path);
00376 }
00377 
00378 // static
00379 LLURI LLURI::buildHTTP(const std::string& host,
00380                                            const U32& port,
00381                                            const LLSD& path,
00382                                            const LLSD& query)
00383 {
00384         return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query);
00385 }
00386 
00387 std::string LLURI::asString() const
00388 {
00389         if (mScheme.empty())
00390         {
00391                 return mEscapedOpaque;
00392         }
00393         else
00394         {
00395                 return mScheme + ":" + mEscapedOpaque;
00396         }
00397 }
00398 
00399 std::string LLURI::scheme() const
00400 {
00401         return mScheme;
00402 }
00403 
00404 std::string LLURI::opaque() const
00405 {
00406         return unescape(mEscapedOpaque);
00407 }
00408 
00409 std::string LLURI::authority() const
00410 {
00411         return unescape(mEscapedAuthority);
00412 }
00413 
00414 
00415 namespace {
00416         void findAuthorityParts(const std::string& authority,
00417                                                         std::string& user,
00418                                                         std::string& host,
00419                                                         std::string& port)
00420         {
00421                 std::string::size_type start_pos = authority.find('@');
00422                 if (start_pos == std::string::npos)
00423                 {
00424                         user = "";
00425                         start_pos = 0;
00426                 }
00427                 else
00428                 {
00429                         user = authority.substr(0, start_pos);
00430                         start_pos += 1;
00431                 }
00432 
00433                 std::string::size_type end_pos = authority.find(':', start_pos);
00434                 if (end_pos == std::string::npos)
00435                 {
00436                         host = authority.substr(start_pos);
00437                         port = "";
00438                 }
00439                 else
00440                 {
00441                         host = authority.substr(start_pos, end_pos - start_pos);
00442                         port = authority.substr(end_pos + 1);
00443                 }
00444         }
00445 }
00446         
00447 std::string LLURI::hostName() const
00448 {
00449         std::string user, host, port;
00450         findAuthorityParts(mEscapedAuthority, user, host, port);
00451         return unescape(host);
00452 }
00453 
00454 std::string LLURI::userName() const
00455 {
00456         std::string user, userPass, host, port;
00457         findAuthorityParts(mEscapedAuthority, userPass, host, port);
00458         std::string::size_type pos = userPass.find(':');
00459         if (pos != std::string::npos)
00460         {
00461                 user = userPass.substr(0, pos);
00462         }
00463         return unescape(user);
00464 }
00465 
00466 std::string LLURI::password() const
00467 {
00468         std::string pass, userPass, host, port;
00469         findAuthorityParts(mEscapedAuthority, userPass, host, port);
00470         std::string::size_type pos = userPass.find(':');
00471         if (pos != std::string::npos)
00472         {
00473                 pass = userPass.substr(pos + 1);
00474         }
00475         return unescape(pass);
00476 }
00477 
00478 BOOL LLURI::defaultPort() const
00479 {
00480         return isDefault(mScheme, hostPort());
00481 }
00482 
00483 U16 LLURI::hostPort() const
00484 {
00485         std::string user, host, port;
00486         findAuthorityParts(mEscapedAuthority, user, host, port);
00487         if (port.empty())
00488         {
00489                 if (mScheme == "http")
00490                         return 80;
00491                 if (mScheme == "https")
00492                         return 443;
00493                 if (mScheme == "ftp")
00494                         return 21;              
00495                 return 0;
00496         }
00497         return atoi(port.c_str());
00498 }       
00499 
00500 std::string LLURI::path() const
00501 {
00502         return unescape(mEscapedPath);
00503 }
00504 
00505 LLSD LLURI::pathArray() const
00506 {
00507         typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
00508         boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
00509         tokenizer tokens(mEscapedPath, sep);
00510         tokenizer::iterator it = tokens.begin();
00511         tokenizer::iterator end = tokens.end();
00512 
00513         LLSD params;
00514         for ( ; it != end; ++it)
00515         {
00516                 params.append(*it);
00517         }
00518         return params;
00519 }
00520 
00521 std::string LLURI::query() const
00522 {
00523         return unescape(mEscapedQuery);
00524 }
00525 
00526 LLSD LLURI::queryMap() const
00527 {
00528         return queryMap(mEscapedQuery);
00529 }
00530 
00531 // static
00532 LLSD LLURI::queryMap(std::string escaped_query_string)
00533 {
00534         lldebugs << "LLURI::queryMap query params: " << escaped_query_string << llendl;
00535 
00536         LLSD result = LLSD::emptyArray();
00537         while(!escaped_query_string.empty())
00538         {
00539                 // get tuple first
00540                 std::string tuple;
00541                 std::string::size_type tuple_begin = escaped_query_string.find('&');
00542                 if (tuple_begin != std::string::npos)
00543                 {
00544                         tuple = escaped_query_string.substr(0, tuple_begin);
00545                         escaped_query_string = escaped_query_string.substr(tuple_begin+1);
00546                 }
00547                 else
00548                 {
00549                         tuple = escaped_query_string;
00550                         escaped_query_string = "";
00551                 }
00552                 if (tuple.empty()) continue;
00553 
00554                 // parse tuple
00555                 std::string::size_type key_end = tuple.find('=');
00556                 if (key_end != std::string::npos)
00557                 {
00558                         std::string key = unescape(tuple.substr(0,key_end));
00559                         std::string value = unescape(tuple.substr(key_end+1));
00560                         lldebugs << "inserting key " << key << " value " << value << llendl;
00561                         result[key] = value;
00562                 }
00563                 else
00564                 {
00565                         lldebugs << "inserting key " << unescape(tuple) << " value true" << llendl;
00566                     result[unescape(tuple)] = true;
00567                 }
00568         }
00569         return result;
00570 }
00571 
00572 std::string LLURI::mapToQueryString(const LLSD& queryMap)
00573 {
00574         std::string query_string;
00575         if (queryMap.isMap())
00576         {
00577                 bool first_element = true;
00578                 LLSD::map_const_iterator iter = queryMap.beginMap();
00579                 LLSD::map_const_iterator end = queryMap.endMap();
00580                 std::ostringstream ostr;
00581                 for (; iter != end; ++iter)
00582                 {
00583                         if(first_element)
00584                         {
00585                                 ostr << "?";
00586                                 first_element = false;
00587                         }
00588                         else
00589                         {
00590                                 ostr << "&";
00591                         }
00592                         ostr << escapeQueryVariable(iter->first);
00593                         if(iter->second.isDefined())
00594                         {
00595                                 ostr << "=" <<  escapeQueryValue(iter->second.asString());
00596                         }
00597                 }
00598                 query_string = ostr.str();
00599         }
00600         return query_string;
00601 }

Generated on Fri May 16 08:32:09 2008 for SecondLife by  doxygen 1.5.5