Line data Source code
1 :
2 : /* Copyright (c) 2013-2014, ahmet.bilgili@epfl.ch
3 : *
4 : * This file is part of Lunchbox <https://github.com/Eyescale/Lunchbox>
5 : *
6 : * This library is free software; you can redistribute it and/or modify it under
7 : * the terms of the GNU Lesser General Public License version 2.1 as published
8 : * by the Free Software Foundation.
9 : *
10 : * This library is distributed in the hope that it will be useful, but WITHOUT
11 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 : * details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public License
16 : * along with this library; if not, write to the Free Software Foundation, Inc.,
17 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 : */
19 :
20 : #include <lunchbox/uri.h>
21 :
22 : #include <boost/regex.hpp>
23 : #include <boost/algorithm/string_regex.hpp>
24 : #include <boost/lexical_cast.hpp>
25 : #include <sstream>
26 : #include <exception>
27 :
28 : namespace lunchbox
29 : {
30 : namespace
31 : {
32 19 : struct URIData
33 : {
34 19 : URIData() : port( 0 ) {}
35 :
36 : std::string scheme;
37 : std::string userinfo;
38 : std::string host;
39 : uint16_t port;
40 : std::string path;
41 : std::string query;
42 : std::string fragment;
43 : URI::KVMap queryMap;
44 : };
45 : }
46 :
47 : namespace detail
48 : {
49 : class uri_parse : public std::exception
50 : {
51 : public:
52 1 : uri_parse( const std::string& uri )
53 1 : {
54 1 : _error << "Error parsing URI string: " << uri << std::endl;
55 1 : }
56 :
57 : uri_parse( const uri_parse& excep )
58 : {
59 : _error << excep._error.str();
60 : }
61 :
62 1 : virtual ~uri_parse() throw() {}
63 :
64 0 : virtual const char* what() const throw() { return _error.str().c_str(); }
65 :
66 : private:
67 : std::stringstream _error;
68 : };
69 :
70 18 : class URI
71 : {
72 : public:
73 19 : explicit URI( const std::string& uri )
74 20 : {
75 19 : if( uri.empty( ))
76 21 : return;
77 :
78 16 : boost::match_results< std::string::const_iterator > results;
79 : boost::regex expr(
80 : "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#([^?]*))?$",
81 32 : boost::regex::perl | boost::regex::icase );
82 :
83 16 : if( !boost::regex_search( uri, results, expr ) )
84 1 : throw uri_parse( uri );
85 :
86 15 : _uriData.scheme = std::string( results[2].first, results[2].second );
87 :
88 15 : const std::string& userHost = std::string( results[4].first,
89 45 : results[4].second );
90 15 : if( !userHost.empty( ))
91 : {
92 7 : std::vector< std::string > splitUserHost;
93 14 : std::string hostPort;
94 : boost::algorithm::split( splitUserHost, userHost,
95 7 : boost::is_any_of( "@"));
96 7 : if( splitUserHost.size() == 2 ) // for ex: user:pass@hello.com:port
97 : {
98 1 : _uriData.userinfo = splitUserHost[ 0 ];
99 1 : hostPort = splitUserHost[ 1 ];
100 : }
101 : else
102 6 : hostPort = splitUserHost[ 0 ];
103 :
104 14 : std::vector< std::string > splitHostPort;
105 : boost::algorithm::split( splitHostPort, hostPort,
106 7 : boost::is_any_of( ":" ));
107 7 : _uriData.host = splitHostPort[ 0 ];
108 :
109 7 : if( splitHostPort.size() == 2 ) // for ex: myhost:port
110 : _uriData.port = boost::lexical_cast< uint16_t >(
111 9 : splitHostPort[ 1 ] );
112 : }
113 :
114 15 : _uriData.path = std::string( results[5].first, results[5].second );
115 15 : _uriData.query = std::string( results[7].first, results[7].second );
116 15 : _uriData.fragment = std::string( results[9].first, results[9].second );
117 :
118 : // from http://en.wikipedia.org/wiki/File_URI_scheme:
119 : // "file:///foo.txt" is okay, while "file://foo.txt" is not, although
120 : // some interpreters manage to handle the latter We are "some".
121 27 : const bool isFileURI = _uriData.scheme.empty() ||
122 27 : _uriData.scheme == "file";
123 15 : const bool hasHost = !_uriData.host.empty();
124 15 : const bool hasPath = !_uriData.path.empty();
125 15 : if( isFileURI && hasHost && !hasPath )
126 1 : _uriData.host.swap( _uriData.path );
127 :
128 : // parse query data into key-value pairs
129 30 : std::string query = _uriData.query;
130 32 : while( !query.empty( ))
131 : {
132 2 : const size_t nextPair = query.find( ',' );
133 2 : if( nextPair == 0 )
134 : {
135 0 : query = query.substr( 1 );
136 0 : continue;
137 : }
138 :
139 2 : const std::string pair = query.substr( 0, nextPair );
140 2 : if( nextPair == std::string::npos )
141 1 : query.clear();
142 : else
143 1 : query = query.substr( nextPair + 1 );
144 :
145 2 : const size_t eq = pair.find( '=' );
146 2 : if( eq == std::string::npos || eq == 0 )
147 0 : continue;
148 2 : _uriData.queryMap[ pair.substr( 0, eq ) ] = pair.substr( eq + 1 );
149 18 : }
150 : }
151 :
152 107 : URIData& getData() { return _uriData; }
153 : const URIData& getData() const { return _uriData; }
154 :
155 : private:
156 : URIData _uriData;
157 : };
158 :
159 : }
160 :
161 1 : URI::URI()
162 1 : : _impl( new detail::URI( std::string( )))
163 : {
164 1 : }
165 :
166 10 : URI::URI( const std::string &uri )
167 11 : : _impl( new detail::URI( uri ) )
168 : {
169 9 : }
170 :
171 8 : URI::URI( const char* uri )
172 8 : : _impl( new detail::URI( std::string( uri )))
173 : {
174 8 : }
175 :
176 0 : URI::URI( const URI& from )
177 0 : : _impl( new detail::URI( *from._impl ))
178 : {
179 0 : }
180 18 : lunchbox::URI::~URI()
181 : {
182 18 : delete _impl;
183 18 : }
184 :
185 0 : URI& URI::operator = ( const URI& rhs )
186 : {
187 0 : if( this != &rhs )
188 0 : *_impl = *rhs._impl;
189 0 : return *this;
190 : }
191 :
192 28 : const std::string &URI::getScheme() const
193 : {
194 28 : return _impl->getData().scheme;
195 : }
196 :
197 13 : const std::string &URI::getHost() const
198 : {
199 13 : return _impl->getData().host;
200 : }
201 :
202 9 : uint16_t URI::getPort() const
203 : {
204 9 : return _impl->getData().port;
205 : }
206 :
207 8 : const std::string &URI::getUserinfo() const
208 : {
209 8 : return _impl->getData().userinfo;
210 : }
211 :
212 20 : const std::string& URI::getPath() const
213 : {
214 20 : return _impl->getData().path;
215 : }
216 :
217 9 : const std::string& URI::getQuery() const
218 : {
219 9 : return _impl->getData().query;
220 : }
221 :
222 8 : const std::string &URI::getFragment() const
223 : {
224 8 : return _impl->getData().fragment;
225 : }
226 :
227 0 : URI::ConstKVIter URI::queryBegin() const
228 : {
229 0 : return _impl->getData().queryMap.begin();
230 : }
231 :
232 3 : URI::ConstKVIter URI::queryEnd() const
233 : {
234 3 : return _impl->getData().queryMap.end();
235 : }
236 :
237 8 : URI::ConstKVIter URI::findQuery( const std::string& key ) const
238 : {
239 8 : return _impl->getData().queryMap.find( key );
240 : }
241 :
242 1 : void URI::addQuery( const std::string& key, const std::string& value )
243 : {
244 1 : URIData& data = _impl->getData();
245 :
246 1 : data.queryMap[ key ] = value;
247 1 : data.fragment.clear();
248 :
249 : // Rebuild fragment string
250 1 : data.query.clear();
251 4 : BOOST_FOREACH( const URI::KVMap::value_type& pair, data.queryMap )
252 : {
253 3 : if( data.query.empty( ))
254 1 : data.query = pair.first + "=" + pair.second;
255 : else
256 2 : data.query += std::string( "," ) + pair.first + "=" + pair.second;
257 : }
258 1 : }
259 :
260 90 : }
|