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 : }
|