File Coverage

src/panda/uri/URI.cc
Criterion Covered Total %
statement 184 235 78.3
branch 193 326 59.2
condition n/a
subroutine n/a
pod n/a
total 377 561 67.2


line stmt bran cond sub pod time code
1             #include
2             #include
3             #include
4             #include
5              
6             namespace panda { namespace uri {
7              
8 13           static std::unordered_map scheme_map;
9 13           static std::map scheme_ti_map;
10 13           static std::vector schemas;
11              
12             static URI::SchemeInfo* http_si;
13             static URI::SchemeInfo* https_si;
14              
15 13           const string URI::_empty;
16              
17 0           void URI::register_scheme (const string& scheme, uint16_t default_port, bool secure) {
18 0 0         register_scheme(scheme, &typeid(URI), [](const URI& u)->URI*{ return new URI(u); }, default_port, secure);
    0          
19 0           }
20              
21 117           void URI::register_scheme (const string& scheme, const std::type_info* ti, uricreator creator, uint16_t default_port, bool secure) {
22 117 50         if (scheme_map.find(scheme) != scheme_map.end())
    50          
23 0 0         throw std::invalid_argument("URI::register_scheme: scheme '" + scheme + "' has been already registered");
    0          
    0          
    0          
24 117           auto& inf = scheme_map[scheme];
25 117           inf.index = schemas.size();
26 117           inf.scheme = scheme;
27 117           inf.creator = creator;
28 117           inf.default_port = default_port;
29 117           inf.secure = secure;
30 117           inf.type_info = ti;
31 117           scheme_ti_map[ti] = &inf;
32 117 50         schemas.push_back(&inf);
33 117           }
34              
35              
36 13           static int init () {
37 72 50         URI::register_scheme("http", &typeid(URI::http), [](const URI& u)->URI*{ return new URI::http(u); }, 80 );
    50          
38 42 50         URI::register_scheme("https", &typeid(URI::https), [](const URI& u)->URI*{ return new URI::https(u); }, 443, true);
    50          
39 42 50         URI::register_scheme("ws", &typeid(URI::ws), [](const URI& u)->URI*{ return new URI::ws(u); }, 80 );
    50          
40 42 50         URI::register_scheme("wss", &typeid(URI::wss), [](const URI& u)->URI*{ return new URI::wss(u); }, 443, true);
    50          
41 42 50         URI::register_scheme("ftp", &typeid(URI::ftp), [](const URI& u)->URI*{ return new URI::ftp(u); }, 21 );
    50          
42 42 50         URI::register_scheme("socks5", &typeid(URI::socks), [](const URI& u)->URI*{ return new URI::socks(u); }, 1080 );
    50          
43 42 50         URI::register_scheme("ssh", &typeid(URI::ssh), [](const URI& u)->URI*{ return new URI::ssh(u); }, 22, true);
    50          
44 42 50         URI::register_scheme("telnet", &typeid(URI::telnet), [](const URI& u)->URI*{ return new URI::telnet(u); }, 23 );
    50          
45 42 50         URI::register_scheme("sftp", &typeid(URI::sftp), [](const URI& u)->URI*{ return new URI::sftp(u); }, 22, true);
    50          
46              
47 13 50         http_si = &scheme_map.find("http")->second;
48 13 50         https_si = &scheme_map.find("https")->second;
49              
50 13           return 0;
51             }
52 13           static const int __init = init();
53              
54 146           void URI::parse (const string& str) {
55 146           bool authority_has_pct = false;
56 146 100         bool ok = !(_flags & Flags::allow_extended_chars) ? _parse(str, authority_has_pct) : _parse_ext(str, authority_has_pct);
    50          
    50          
57              
58 146 100         if (!ok) {
59 7 50         clear();
60 7           return;
61             }
62              
63 139 50         if (authority_has_pct) {
64 0 0         decode_uri_component(_user_info, _user_info);
65 0 0         decode_uri_component(_host, _host);
66             }
67              
68 139 100         if (_qstr) ok_qstr();
69 139 100         if (_flags & Flags::allow_suffix_reference && !_host.length()) guess_suffix_reference();
    50          
    100          
    50          
70 146 50         sync_scheme_info();
71             }
72              
73 9           void URI::guess_suffix_reference () {
74             // try to find out if it was an url with leading authority ('ya.ru', 'ya.ru:80/a/b/c', 'user@mysite.com/a/b/c')
75             // in either case host is always empty and there are 2 cases
76             // 1) if no scheme -> host is first path part, port is absent
77             // 2) if scheme is present and first path part is a valid port -> scheme is host, first path part is port.
78             // otherwise leave parsed url unchanged as it has no leading authority
79 9 100         if (!_scheme.length()) {
80 3           size_t delim = _path.find('/');
81 3 100         if (delim == string::npos) {
82 1           _host = _path;
83 1           _path.clear();
84             } else {
85 2           _host.assign(_path, 0, delim);
86 2           _path.erase(0, delim);
87             }
88 3           return;
89             }
90              
91 6           bool ok = false;
92 6           size_t plen = _path.length();
93 6           const char* p = _path.data();
94 6           size_t i = 0;
95 18 100         for (; i < plen; ++i) {
96 16           char c = p[i];
97 16 100         if (c >= '0' && c <= '9') {
    100          
98 12           _port = _port * 10 + c - '0';
99 12           ok = true;
100 12           continue;
101             }
102 4 100         if (c != '/') { _port = 0; return; }
103 2           break;
104             }
105              
106 4 100         if (!ok) return;
107 2           _host = _scheme;
108 2           _scheme.clear();
109 2           _path.erase(0, i);
110             }
111              
112 337           string URI::to_string (bool relative) const {
113 337           sync_query_string();
114 337           size_t approx_len = _path.length() + _fragment.length() + _qstr.length() + 3;
115 337 100         if (!relative) approx_len += (_scheme.length()+3) + (_user_info.length()*3 + 1) + (_host.length()*3 + 6);
116 337           string str(approx_len);
117              
118 337 100         if (!relative) {
119 335 100         if (_scheme.length()) {
120 258 50         str += _scheme;
121 258 100         if (_host.length()) str += "://";
    50          
122 258 50         else str += ':';
123             }
124 77 100         else if (_host.length()) str += "//";
    50          
125              
126 335 100         if (_host.length()) {
127 245 100         if (_user_info.length()) {
128 50 50         _encode_uri_component_append(_user_info, str, URIComponent::user_info);
129 50 50         str += '@';
130             }
131              
132 245           const auto& chost = _host;
133 245 100         if (chost.front() == '[' && chost.back() == ']') str += _host;
    50          
    100          
    50          
134 210 50         else _encode_uri_component_append(_host, str, URIComponent::host);
135              
136 245 100         if (_port) {
137 65 50         str += ':';
138 335 50         str += string::from_number(_port);
    50          
139             }
140             }
141             }
142              
143 337 100         if (_path.length()) str += _path;
    50          
144 152 100         else if (relative) str += '/'; // relative path MUST NOT be empty
    50          
145              
146 337 100         if (_qstr.length()) {
147 103 50         str += '?';
148 103 50         str += _qstr; // as is, because already encoded either by raw_query setter or by compile_query
149             }
150              
151 337 100         if (_fragment.length()) {
152 98 50         str += '#';
153 98 50         str += _fragment;
154             }
155              
156 337           return str;
157             }
158              
159 9           void URI::parse_query () const {
160 9           enum { PARSE_MODE_KEY, PARSE_MODE_VAL, PARSE_MODE_WRITE } mode = PARSE_MODE_KEY;
161 9           int key_start = 0;
162 9           int key_end = 0;
163 9           int val_start = 0;
164 9           bool has_pct = false;
165 9 100         const char delim = _flags & Flags::query_param_semicolon ? ';' : '&';
166 9           const char* str = _qstr.data();
167 9           int len = _qstr.length();
168 9           _query.clear();
169              
170 133 100         if (len) for (int i = 0; i <= len; ++i) {
    100          
171 124 100         char c = (i == len) ? delim : str[i];
172 124 100         if (c == '=' && mode == PARSE_MODE_KEY) {
    50          
173 18           key_end = i;
174 18           mode = PARSE_MODE_VAL;
175 18           val_start = i+1;
176             }
177 106 100         else if (c == '%') {
178 3           has_pct = true;
179             }
180 103 100         else if (c == delim) {
181 19 100         if (mode == PARSE_MODE_KEY) {
182 1           key_end = i;
183 1           val_start = i;
184             }
185              
186 19 100         if (has_pct) {
187 6           string key, value;
188 3           size_t klen = key_end - key_start;
189 3 50         if (klen > 0) decode_uri_component(string_view(str+key_start, klen), key);
    50          
190              
191 3           size_t vlen = i - val_start;
192 3 50         if (vlen > 0) decode_uri_component(string_view(str+val_start, vlen), value);
    50          
193              
194 3           has_pct = false;
195 3 50         _query.emplace(key, value);
196             } else {
197 16 50         _query.emplace(_qstr.substr(key_start, key_end - key_start), _qstr.substr(val_start, i - val_start));
    50          
198             }
199              
200 19           mode = PARSE_MODE_KEY;
201 19           key_start = i+1;
202             }
203             }
204              
205 9           ok_qboth();
206 9           }
207              
208 7           void URI::compile_query () const {
209 7           _qstr.clear();
210 7 50         const char delim = _flags & Flags::query_param_semicolon ? ';' : '&';
211 7           auto begin = _query.cbegin();
212 7           auto end = _query.cend();
213              
214 7           size_t bufsize = 0;
215 25 100         for (auto it = begin; it != end; ++it) bufsize += (it->first.length() + it->second.length())*3 + 2;
216 7 50         if (bufsize) --bufsize;
217              
218 7 50         _qstr.reserve(bufsize);
219              
220 7 50         char* bufp = _qstr.buf();
221 7           char* ptr = bufp;
222 25 100         for (auto it = begin; it != end; ++it) {
223 18 100         if (it != begin) *ptr++ = delim;
224 18 50         ptr += encode_uri_component(it->first, ptr);
225 18           *ptr++ = '=';
226 18 50         ptr += encode_uri_component(it->second, ptr);
227             }
228 7           _qstr.length(ptr-bufp);
229              
230 7           ok_qboth();
231 7           }
232              
233 0           void URI::add_query (const Query& addquery) {
234 0 0         sync_query();
235 0           auto end = addquery.cend();
236 0 0         for (auto it = addquery.cbegin(); it != end; ++it) _query.emplace(it->first, it->second);
    0          
237 0           ok_query();
238 0           }
239              
240 5           const std::vector URI::path_segments () const {
241 5           size_t plen = _path.length();
242 5 100         if (!plen) return std::vector();
243 9           std::vector ret;
244 4 50         ret.reserve(7);
245 4           const char* p = _path.data();
246 4           size_t start = 0;
247 34 100         for (size_t i = 0; i < plen; ++i) {
248 30 100         if (p[i] != '/') continue;
249 8 100         if (i == start) { start++; continue; }
250 4 50         ret.push_back(decode_uri_component(string_view(p+start, i-start)));
    50          
251 4           start = i+1;
252             }
253 4 100         if (p[plen-1] != '/') ret.push_back(string(p+start, plen-start));
    50          
    50          
254 4           return ret;
255             }
256              
257 0           void URI::swap (URI& uri) {
258 0           std::swap(_scheme, uri._scheme);
259 0           std::swap(scheme_info, uri.scheme_info);
260 0           std::swap(_user_info, uri._user_info);
261 0           std::swap(_host, uri._host);
262 0           std::swap(_port, uri._port);
263 0           std::swap(_path, uri._path);
264 0           std::swap(_qstr, uri._qstr);
265 0           std::swap(_query, uri._query);
266 0           std::swap(_qrev, uri._qrev);
267 0           std::swap(_fragment, uri._fragment);
268 0           std::swap(_flags, uri._flags);
269 0           }
270              
271 154           void URI::sync_scheme_info () {
272 154 100         if (!_scheme) {
273 25           scheme_info = NULL;
274 154           return;
275             }
276              
277 129           auto len = _scheme.length();
278 129 100         if (len >= 4 && (_scheme[0]|0x20) == 'h' && (_scheme[1]|0x20) == 't' && (_scheme[2]|0x20) == 't' && (_scheme[3]|0x20) == 'p') {
    50          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    0          
    0          
    0          
    0          
279 87 100         if (len == 4) {
280 59           scheme_info = http_si;
281 59 50         _scheme = "http";
282             }
283 28 50         else if (len == 5 && (_scheme[4]|0x20) == 's') {
    50          
    50          
    50          
    50          
    0          
284 28           scheme_info = https_si;
285 28 50         _scheme = "https";
286 28           return;
287             }
288 59           return;
289             }
290              
291             // lowercase the scheme
292 42 50         char* p = _scheme.buf();
293 42           char* end = p + _scheme.length();
294 202 100         for (;p != end; ++p) *p = tolower(*p);
295              
296 42 50         auto it = scheme_map.find(_scheme);
297 42 100         if (it == scheme_map.cend()) scheme_info = NULL;
298 42           else scheme_info = &it->second;
299             }
300              
301 0           URI::SchemeInfo* URI::get_scheme_info (const std::type_info* ti) {
302 0 0         auto it = scheme_ti_map.find(ti);
303 0 0         return it == scheme_ti_map.end() ? nullptr : it->second;
304             }
305              
306 0           string URI::user () const {
307 0           size_t delim = _user_info.find(':');
308 0 0         if (delim == string::npos) return _user_info;
309 0           return _user_info.substr(0, delim);
310             }
311              
312 0           void URI::user (const string& user) {
313 0           size_t delim = _user_info.find(':');
314 0 0         if (delim == string::npos) _user_info = user;
315 0           else _user_info.replace(0, delim, user);
316 0           }
317              
318 0           string URI::password () const {
319 0           size_t delim = _user_info.find(':');
320 0 0         if (delim == string::npos) return string();
321 0           return _user_info.substr(delim+1);
322             }
323              
324 0           void URI::password (const string& password) {
325 0           size_t delim = _user_info.find(':');
326 0 0         if (delim == string::npos) {
327 0           _user_info += ':';
328 0           _user_info += password;
329             }
330 0           else _user_info.replace(delim+1, string::npos, password);
331 0           }
332              
333 0           std::ostream& operator<< (std::ostream& os, const URI& uri) {
334 0 0         string tmp = uri.to_string();
335 0 0         return os.write(tmp.data(), tmp.length());
336             }
337              
338 52 50         }}
    50