LCOV - code coverage report
Current view: top level - servus - uri.cpp (source / functions) Hit Total Coverage
Test: Servus Lines: 190 199 95.5 %
Date: 2018-10-03 03:09:57 Functions: 43 48 89.6 %

          Line data    Source code
       1             : /* Copyright (c) 2013-2016, Human Brain Project
       2             :  *                          Ahmet.Bilgili@epfl.ch
       3             :  *                          Juan Hernando <jhernando@fi.upm.es>
       4             :  *
       5             :  * This file is part of Servus <https://github.com/HBPVIS/Servus>
       6             :  *
       7             :  * This library is free software; you can redistribute it and/or modify it under
       8             :  * the terms of the GNU Lesser General Public License version 3.0 as published
       9             :  * by the Free Software Foundation.
      10             :  *
      11             :  * This library is distributed in the hope that it will be useful, but WITHOUT
      12             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      13             :  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
      14             :  * details.
      15             :  *
      16             :  * You should have received a copy of the GNU Lesser General Public License
      17             :  * along with this library; if not, write to the Free Software Foundation, Inc.,
      18             :  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      19             :  */
      20             : 
      21             : #include "uri.h"
      22             : 
      23             : #include <algorithm>
      24             : #include <cassert>
      25             : #include <exception>
      26             : #include <iostream>
      27             : #include <sstream>
      28             : #include <stdexcept>
      29             : 
      30             : namespace servus
      31             : {
      32             : namespace
      33             : {
      34          41 : struct URIData
      35             : {
      36          39 :     URIData()
      37          39 :         : port(0)
      38             :     {
      39          39 :     }
      40             : 
      41           4 :     bool operator==(const URIData& rhs) const
      42             :     {
      43          11 :         return (userinfo == rhs.userinfo && host == rhs.host &&
      44           6 :                 port == rhs.port && path == rhs.path && query == rhs.query &&
      45           6 :                 fragment == rhs.fragment && queryMap == rhs.queryMap);
      46             :     }
      47             : 
      48             :     std::string scheme;
      49             :     std::string userinfo;
      50             :     std::string host;
      51             :     uint16_t port;
      52             :     std::string path;
      53             :     std::string query;
      54             :     std::string fragment;
      55             :     URI::KVMap queryMap;
      56             : };
      57             : }
      58             : 
      59             : namespace detail
      60             : {
      61             : class uri_parse : public std::exception
      62             : {
      63             : public:
      64           5 :     explicit uri_parse(const std::string& uri)
      65           5 :     {
      66           5 :         _error = std::string("Error parsing URI string: ") + uri;
      67           5 :     }
      68             : 
      69             :     uri_parse(const uri_parse& excep) { _error = excep._error; }
      70           5 :     virtual ~uri_parse() throw() {}
      71           0 :     virtual const char* what() const throw() { return _error.c_str(); }
      72             : private:
      73             :     std::string _error;
      74             : };
      75             : 
      76             : enum URIPart
      77             : {
      78             :     SCHEME = 0,
      79             :     AUTHORITY,
      80             :     PATH,
      81             :     QUERY,
      82             :     FRAGMENT
      83             : };
      84             : 
      85         103 : bool _parseURIPart(std::string& input, const URIPart& part, std::string& output)
      86             : {
      87             : #ifndef NDEBUG
      88         103 :     const char requireFirst[] = {0, 0, 0, '?', '#'};
      89             : #endif
      90         103 :     const char* const separators[] = {"://", "/?#", "?#", "#", ""};
      91         103 :     const char* const disallowed[] = {"/?#", 0, 0, 0, 0};
      92         103 :     const bool fullSeparator[] = {true, false, false, false, false};
      93         103 :     const bool needsSeparator[] = {true, false, false, false, false};
      94         103 :     const size_t skip[] = {0, 0, 0, 1, 1};
      95         103 :     const size_t postSkip[] = {3, 0, 0, 0, 0};
      96         103 :     const size_t pos = fullSeparator[part]
      97         171 :                            ? input.find(separators[part])
      98         171 :                            : input.find_first_of(separators[part]);
      99             : 
     100         103 :     if (pos == std::string::npos)
     101             :     {
     102          41 :         if (needsSeparator[part])
     103             :         {
     104          10 :             output = "";
     105          10 :             return true;
     106             :         }
     107             :     }
     108         124 :     else if (pos == 0 ||
     109          78 :              (disallowed[part] && input.find_first_of(disallowed[part]) < pos))
     110             :     {
     111           9 :         output = "";
     112           9 :         return true;
     113             :     }
     114             :     // If the separator is not the first character, assert that parts requiring
     115             :     // an initial character find it.
     116          84 :     assert(!requireFirst[part] || pos == 0 || input[0] == requireFirst[part]);
     117             : 
     118             :     // If the separator was found at pos == 0, the returned string is empty
     119          84 :     assert(input.size() >= skip[part]);
     120          84 :     output = input.substr(skip[part], pos - skip[part]);
     121          84 :     input = pos == std::string::npos ? "" : input.substr(pos + postSkip[part]);
     122          84 :     return true;
     123             : }
     124             : 
     125          16 : void _parseAuthority(URIData& data, const std::string& auth)
     126             : {
     127          16 :     const size_t atPos = auth.find_first_of('@');
     128          16 :     if (atPos != std::string::npos)
     129           9 :         data.userinfo = auth.substr(0, atPos);
     130          16 :     const size_t hostPos = atPos == std::string::npos ? 0 : atPos + 1;
     131          16 :     const size_t colonPos = auth.find_first_of(':', hostPos);
     132          16 :     if (colonPos != std::string::npos)
     133             :     {
     134          26 :         const std::string port = auth.substr(colonPos + 1);
     135          13 :         char* end = 0;
     136          13 :         data.port = ::strtol(port.c_str(), &end, 10);
     137          13 :         if (port.empty() || end != port.c_str() + port.length())
     138           3 :             throw std::runtime_error(port + " is not a valid port number");
     139             :     }
     140             :     // Works regardless of colonPos == npos
     141          13 :     data.host = auth.substr(hostPos, colonPos - hostPos);
     142          13 :     if (data.host.empty())
     143           0 :         throw std::invalid_argument("");
     144          13 : }
     145             : 
     146          15 : void _parseQueryMap(URIData& data)
     147             : {
     148             :     // parse query data into key-value pairs
     149          30 :     std::string query = data.query;
     150             : 
     151          15 :     data.queryMap.clear();
     152          59 :     while (!query.empty())
     153             :     {
     154          22 :         const size_t nextPair = query.find('&');
     155          22 :         if (nextPair == 0)
     156             :         {
     157           0 :             query = query.substr(1);
     158           0 :             continue;
     159             :         }
     160             : 
     161          44 :         const std::string pair = query.substr(0, nextPair);
     162          22 :         if (nextPair == std::string::npos)
     163          12 :             query.clear();
     164             :         else
     165          10 :             query = query.substr(nextPair + 1);
     166             : 
     167          22 :         const size_t eq = pair.find('=');
     168          22 :         if (eq == 0) // empty key
     169           0 :             continue;
     170          22 :         if (eq == std::string::npos) // empty value
     171           2 :             data.queryMap[pair] = std::string();
     172             :         else
     173          20 :             data.queryMap[pair.substr(0, eq)] = pair.substr(eq + 1);
     174             :     }
     175          15 : }
     176             : 
     177          35 : void _toLower(std::string& str)
     178             : {
     179          35 :     std::transform(str.begin(), str.end(), str.begin(), ::tolower);
     180          35 : }
     181             : 
     182          36 : class URI
     183             : {
     184             : public:
     185          39 :     explicit URI(const std::string& uri)
     186          44 :     {
     187          39 :         if (uri.empty())
     188           4 :             return;
     189             : 
     190             :         try
     191             :         {
     192          35 :             _parseURI(uri);
     193             :         }
     194          10 :         catch (...)
     195             :         {
     196           5 :             throw uri_parse(uri);
     197             :         }
     198             :     }
     199             : 
     200         285 :     URIData& getData() { return _uriData; }
     201             :     const URIData& getData() const { return _uriData; }
     202             : private:
     203             :     URIData _uriData;
     204             : 
     205          35 :     void _parseURI(std::string input)
     206             :     {
     207          35 :         URIPart part = SCHEME;
     208         231 :         while (!input.empty())
     209             :         {
     210         103 :             switch (part)
     211             :             {
     212             :             case SCHEME:
     213          35 :                 _parseURIPart(input, part, _uriData.scheme);
     214          35 :                 _toLower(_uriData.scheme);
     215          62 :                 if (!_uriData.scheme.empty() &&
     216          49 :                     (!isalpha(_uriData.scheme[0]) ||
     217          24 :                      _uriData.scheme.find_first_not_of(
     218             :                          "abcdefghijlkmnopqrstuvwxyz0123456789+-.", 1) !=
     219             :                          std::string::npos))
     220             :                 {
     221           2 :                     throw std::invalid_argument("");
     222             :                 }
     223          97 :                 part = _uriData.scheme == "file" || _uriData.scheme.empty()
     224          45 :                            ? PATH
     225             :                            : AUTHORITY;
     226             :                 // from http://en.wikipedia.org/wiki/File_URI_scheme:
     227             :                 //  "file:///foo.txt" is okay, while "file://foo.txt"
     228             :                 // is not, although some interpreters manage to handle
     229             :                 // the latter. We are "some".
     230          33 :                 break;
     231             :             case AUTHORITY:
     232             :             {
     233          38 :                 std::string authority;
     234          19 :                 _parseURIPart(input, part, authority);
     235          19 :                 if (!authority.empty())
     236          16 :                     _parseAuthority(_uriData, authority);
     237          16 :                 part = PATH;
     238          16 :                 break;
     239             :             }
     240             :             case PATH:
     241          24 :                 _parseURIPart(input, part, _uriData.path);
     242          24 :                 part = QUERY;
     243          24 :                 break;
     244             :             case QUERY:
     245          14 :                 _parseURIPart(input, part, _uriData.query);
     246          14 :                 _parseQueryMap(_uriData);
     247          14 :                 part = FRAGMENT;
     248          14 :                 break;
     249             :             case FRAGMENT:
     250          11 :                 _parseURIPart(input, part, _uriData.fragment);
     251          11 :                 break;
     252             :             }
     253             :         }
     254          30 :     }
     255             : };
     256             : }
     257             : 
     258           4 : URI::URI()
     259           4 :     : _impl(new detail::URI(std::string()))
     260             : {
     261           4 : }
     262             : 
     263           5 : URI::URI(const std::string& uri)
     264           5 :     : _impl(new detail::URI(uri))
     265             : {
     266           5 : }
     267             : 
     268          30 : URI::URI(const char* uri)
     269          35 :     : _impl(new detail::URI(std::string(uri)))
     270             : {
     271          25 : }
     272             : 
     273           1 : URI::URI(const URI& from)
     274           1 :     : _impl(new detail::URI(*from._impl))
     275             : {
     276           1 : }
     277             : 
     278          70 : servus::URI::~URI()
     279             : {
     280          35 :     delete _impl;
     281          35 : }
     282             : 
     283           0 : URI& URI::operator=(const URI& rhs)
     284             : {
     285           0 :     if (this != &rhs)
     286           0 :         *_impl = *rhs._impl;
     287           0 :     return *this;
     288             : }
     289             : 
     290           5 : bool URI::operator==(const URI& rhs) const
     291             : {
     292           5 :     return this == &rhs || (_impl->getData() == rhs._impl->getData());
     293             : }
     294             : 
     295           3 : bool URI::operator!=(const URI& rhs) const
     296             : {
     297           3 :     return !(*this == rhs);
     298             : }
     299             : 
     300          37 : const std::string& URI::getScheme() const
     301             : {
     302          37 :     return _impl->getData().scheme;
     303             : }
     304             : 
     305          37 : const std::string& URI::getHost() const
     306             : {
     307          37 :     return _impl->getData().host;
     308             : }
     309             : 
     310           4 : std::string URI::getAuthority() const
     311             : {
     312           8 :     std::stringstream authority;
     313           4 :     if (!_impl->getData().userinfo.empty())
     314           2 :         authority << _impl->getData().userinfo << "@";
     315             :     // IPv6 IPs are not considered.
     316           4 :     authority << _impl->getData().host;
     317           4 :     if (_impl->getData().port)
     318           2 :         authority << ":" << _impl->getData().port;
     319           8 :     return authority.str();
     320             : }
     321             : 
     322          21 : uint16_t URI::getPort() const
     323             : {
     324          21 :     return _impl->getData().port;
     325             : }
     326             : 
     327          17 : const std::string& URI::getUserinfo() const
     328             : {
     329          17 :     return _impl->getData().userinfo;
     330             : }
     331             : 
     332          28 : const std::string& URI::getPath() const
     333             : {
     334          28 :     return _impl->getData().path;
     335             : }
     336             : 
     337          29 : const std::string& URI::getQuery() const
     338             : {
     339          29 :     return _impl->getData().query;
     340             : }
     341             : 
     342          30 : const std::string& URI::getFragment() const
     343             : {
     344          30 :     return _impl->getData().fragment;
     345             : }
     346             : 
     347           2 : void URI::setScheme(const std::string& scheme)
     348             : {
     349           2 :     _impl->getData().scheme = scheme;
     350           2 : }
     351             : 
     352           1 : void URI::setUserInfo(const std::string& userinfo)
     353             : {
     354           1 :     _impl->getData().userinfo = userinfo;
     355           1 : }
     356             : 
     357           5 : void URI::setHost(const std::string& host)
     358             : {
     359           5 :     _impl->getData().host = host;
     360           5 : }
     361             : 
     362           3 : void URI::setPort(const uint16_t port)
     363             : {
     364           3 :     _impl->getData().port = port;
     365           3 : }
     366             : 
     367           2 : void URI::setPath(const std::string& path)
     368             : {
     369           2 :     _impl->getData().path = path;
     370           2 : }
     371             : 
     372           1 : void URI::setQuery(const std::string& query)
     373             : {
     374           1 :     URIData& data = _impl->getData();
     375           1 :     data.query = query;
     376           1 :     detail::_parseQueryMap(data);
     377           1 : }
     378             : 
     379           4 : void URI::setFragment(const std::string& fragment)
     380             : {
     381           4 :     _impl->getData().fragment = fragment;
     382           4 : }
     383             : 
     384           2 : URI::ConstKVIter URI::queryBegin() const
     385             : {
     386           2 :     return _impl->getData().queryMap.begin();
     387             : }
     388             : 
     389          18 : URI::ConstKVIter URI::queryEnd() const
     390             : {
     391          18 :     return _impl->getData().queryMap.end();
     392             : }
     393             : 
     394          22 : URI::ConstKVIter URI::findQuery(const std::string& key) const
     395             : {
     396          22 :     return _impl->getData().queryMap.find(key);
     397             : }
     398             : 
     399           2 : void URI::addQuery(const std::string& key, const std::string& value)
     400             : {
     401           2 :     URIData& data = _impl->getData();
     402             : 
     403           2 :     data.queryMap[key] = value;
     404           2 :     data.fragment.clear();
     405             : 
     406             :     // Rebuild fragment string
     407           2 :     data.query.clear();
     408           6 :     for (URI::ConstKVIter i = queryBegin(); i != queryEnd(); ++i)
     409             :     {
     410           4 :         if (data.query.empty())
     411           2 :             data.query = i->first + "=" + i->second;
     412             :         else
     413           2 :             data.query += std::string("&") + i->first + "=" + i->second;
     414             :     }
     415           2 : }
     416          15 : }

Generated by: LCOV version 1.11