/root/doris/be/src/util/url_parser.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "util/url_parser.h" |
19 | | |
20 | | #include <ctype.h> |
21 | | #include <stdint.h> |
22 | | |
23 | | #include <algorithm> |
24 | | #include <limits> |
25 | | #include <string> |
26 | | #include <utility> |
27 | | |
28 | | #include "core/string_ref.h" |
29 | | #include "util/string_search.hpp" |
30 | | |
31 | | namespace doris { |
32 | | #include "common/compile_check_begin.h" |
33 | | const StringRef UrlParser::_s_url_authority("AUTHORITY", 9); |
34 | | const StringRef UrlParser::_s_url_file("FILE", 4); |
35 | | const StringRef UrlParser::_s_url_host("HOST", 4); |
36 | | const StringRef UrlParser::_s_url_path("PATH", 4); |
37 | | const StringRef UrlParser::_s_url_protocol("PROTOCOL", 8); |
38 | | const StringRef UrlParser::_s_url_query("QUERY", 5); |
39 | | const StringRef UrlParser::_s_url_ref("REF", 3); |
40 | | const StringRef UrlParser::_s_url_userinfo("USERINFO", 8); |
41 | | const StringRef UrlParser::_s_url_port("PORT", 4); |
42 | | const StringRef UrlParser::_s_protocol("://", 3); |
43 | | const StringRef UrlParser::_s_at("@", 1); |
44 | | const StringRef UrlParser::_s_slash("/", 1); |
45 | | const StringRef UrlParser::_s_colon(":", 1); |
46 | | const StringRef UrlParser::_s_question("?", 1); |
47 | | const StringRef UrlParser::_s_hash("#", 1); |
48 | | const StringSearch UrlParser::_s_protocol_search(&_s_protocol); |
49 | | const StringSearch UrlParser::_s_at_search(&_s_at); |
50 | | const StringSearch UrlParser::_s_slash_search(&_s_slash); |
51 | | const StringSearch UrlParser::_s_colon_search(&_s_colon); |
52 | | const StringSearch UrlParser::_s_question_search(&_s_question); |
53 | | const StringSearch UrlParser::_s_hash_search(&_s_hash); |
54 | | |
55 | 162 | bool UrlParser::find_query_component(const StringRef& url, StringRef* query) { |
56 | | // The query component starts right after the first '?'. |
57 | 162 | int32_t query_pos = _s_question_search.search(&url); |
58 | 162 | if (query_pos < 0) { |
59 | | // Query component is missing. |
60 | 15 | return false; |
61 | 15 | } |
62 | | |
63 | | // The first '#' bounds the query component from the right, so this single search also |
64 | | // provides the position of the fragment. |
65 | 147 | int32_t fragment_pos = _s_hash_search.search(&url); |
66 | 147 | if (fragment_pos >= 0 && fragment_pos < query_pos) { |
67 | | // The '#' comes before the '?', so the '?' and everything behind it belongs to the |
68 | | // fragment and url has no query component. |
69 | 31 | return false; |
70 | 31 | } |
71 | | |
72 | 116 | int32_t query_start = query_pos + cast_set<int32_t>(_s_question.size); |
73 | 116 | int32_t query_end = fragment_pos >= 0 ? fragment_pos : cast_set<int32_t>(url.size); |
74 | 116 | *query = url.substring(query_start, query_end - query_start); |
75 | 116 | return true; |
76 | 147 | } |
77 | | |
78 | 44 | StringRef UrlParser::find_authority(const StringRef& protocol_end) { |
79 | | // The authority component runs from the end of '://' up to the first '/', '?' or '#', |
80 | | // whichever comes first. |
81 | 44 | int32_t end_pos = _s_slash_search.search(&protocol_end); |
82 | 44 | int32_t question_pos = _s_question_search.search(&protocol_end); |
83 | 44 | if (question_pos >= 0 && (end_pos < 0 || question_pos < end_pos)) { |
84 | 1 | end_pos = question_pos; |
85 | 1 | } |
86 | 44 | int32_t hash_pos = _s_hash_search.search(&protocol_end); |
87 | 44 | if (hash_pos >= 0 && (end_pos < 0 || hash_pos < end_pos)) { |
88 | 2 | end_pos = hash_pos; |
89 | 2 | } |
90 | 44 | return protocol_end.substring(0, end_pos); |
91 | 44 | } |
92 | | |
93 | 130 | bool UrlParser::parse_url(const StringRef& url, UrlPart part, StringRef* result) { |
94 | 130 | result->data = nullptr; |
95 | 130 | result->size = 0; |
96 | | // Remove leading and trailing spaces. |
97 | 130 | StringRef trimmed_url = url.trim(); |
98 | | |
99 | | // All parts require checking for the _s_protocol. |
100 | 130 | int32_t protocol_pos = _s_protocol_search.search(&trimmed_url); |
101 | 130 | if (protocol_pos < 0) { |
102 | 8 | return false; |
103 | 8 | } |
104 | | |
105 | | // Positioned to first char after '://'. |
106 | 122 | StringRef protocol_end = trimmed_url.substring(protocol_pos + _s_protocol.size); |
107 | | |
108 | 122 | switch (part) { |
109 | 11 | case AUTHORITY: { |
110 | 11 | *result = find_authority(protocol_end); |
111 | 11 | break; |
112 | 0 | } |
113 | | |
114 | 13 | case FILE: |
115 | 23 | case PATH: { |
116 | 23 | int32_t slash_pos = _s_slash_search.search(&protocol_end); |
117 | 23 | int32_t question_pos = _s_question_search.search(&protocol_end); |
118 | 23 | int32_t hash_pos = _s_hash_search.search(&protocol_end); |
119 | 23 | int32_t start_pos = slash_pos; |
120 | 23 | if (part == FILE && (start_pos < 0 || (question_pos >= 0 && question_pos < start_pos))) { |
121 | 5 | start_pos = question_pos; |
122 | 5 | } |
123 | 23 | if (start_pos < 0 || (part == PATH && question_pos >= 0 && question_pos < start_pos) || |
124 | 23 | (hash_pos >= 0 && hash_pos < start_pos)) { |
125 | | // Return empty string. This is what Hive does. |
126 | 5 | return true; |
127 | 5 | } |
128 | | |
129 | 18 | StringRef path_start = protocol_end.substring(start_pos); |
130 | 18 | int32_t end_pos; |
131 | | |
132 | 18 | if (part == FILE) { |
133 | | // End _s_at '#'. |
134 | 9 | end_pos = _s_hash_search.search(&path_start); |
135 | 9 | } else { |
136 | | // End string _s_at next '?' or '#'. |
137 | 9 | end_pos = _s_question_search.search(&path_start); |
138 | 9 | int32_t path_hash_pos = _s_hash_search.search(&path_start); |
139 | 9 | if (end_pos < 0 || (path_hash_pos >= 0 && path_hash_pos < end_pos)) { |
140 | 9 | end_pos = path_hash_pos; |
141 | 9 | } |
142 | 9 | } |
143 | | |
144 | 18 | *result = path_start.substring(0, end_pos); |
145 | 18 | break; |
146 | 23 | } |
147 | | |
148 | 18 | case HOST: { |
149 | 18 | StringRef authority = find_authority(protocol_end); |
150 | 18 | int32_t userinfo_end = -1; |
151 | 256 | for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) { |
152 | 238 | if (authority.data[i] == '@') { |
153 | 1 | userinfo_end = i; |
154 | 1 | } |
155 | 238 | } |
156 | | |
157 | 18 | StringRef host_start = authority.substring(userinfo_end + 1); |
158 | 18 | auto end_pos = cast_set<int32_t>(host_start.size); |
159 | 18 | if (!host_start.empty() && host_start.data[0] == '[') { |
160 | 12 | for (int32_t i = 1; std::cmp_less(i, host_start.size); ++i) { |
161 | 12 | if (host_start.data[i] == ']') { |
162 | 1 | end_pos = i + 1; |
163 | 1 | break; |
164 | 1 | } |
165 | 12 | } |
166 | 17 | } else { |
167 | 17 | int32_t colon_pos = _s_colon_search.search(&host_start); |
168 | 17 | if (colon_pos >= 0) { |
169 | 1 | end_pos = colon_pos; |
170 | 1 | } |
171 | 17 | } |
172 | 18 | *result = host_start.substring(0, end_pos); |
173 | 18 | break; |
174 | 23 | } |
175 | | |
176 | 8 | case PROTOCOL: { |
177 | 8 | *result = trimmed_url.substring(0, protocol_pos); |
178 | 8 | break; |
179 | 23 | } |
180 | | |
181 | 39 | case QUERY: { |
182 | | // The query component starts at the first '?' and ends before the '#' that starts the |
183 | | // fragment, so a url whose first '#' comes before its first '?' has no query. |
184 | 39 | StringRef query; |
185 | 39 | if (!find_query_component(protocol_end, &query)) { |
186 | | // Indicate no query was found. |
187 | 14 | return false; |
188 | 14 | } |
189 | | |
190 | 25 | *result = query; |
191 | 25 | break; |
192 | 39 | } |
193 | | |
194 | 8 | case REF: { |
195 | | // Find '#'. |
196 | 8 | int32_t start_pos = _s_hash_search.search(&protocol_end); |
197 | | |
198 | 8 | if (start_pos < 0) { |
199 | | // Indicate no user and pass were given. |
200 | 8 | return false; |
201 | 8 | } |
202 | | |
203 | 0 | *result = protocol_end.substring(start_pos + _s_hash.size); |
204 | 0 | break; |
205 | 8 | } |
206 | | |
207 | 2 | case USERINFO: { |
208 | 2 | StringRef authority = find_authority(protocol_end); |
209 | 2 | int32_t end_pos = -1; |
210 | 37 | for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) { |
211 | 35 | if (authority.data[i] == '@') { |
212 | 1 | end_pos = i; |
213 | 1 | } |
214 | 35 | } |
215 | | |
216 | 2 | if (end_pos < 0) { |
217 | | // Indicate no user and pass were given. |
218 | 1 | return false; |
219 | 1 | } |
220 | | |
221 | 1 | *result = authority.substring(0, end_pos); |
222 | 1 | break; |
223 | 2 | } |
224 | | |
225 | 13 | case PORT: { |
226 | 13 | StringRef authority = find_authority(protocol_end); |
227 | 13 | int32_t userinfo_end = -1; |
228 | 212 | for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) { |
229 | 199 | if (authority.data[i] == '@') { |
230 | 1 | userinfo_end = i; |
231 | 1 | } |
232 | 199 | } |
233 | | |
234 | 13 | StringRef host_start = authority.substring(userinfo_end + 1); |
235 | 13 | int32_t port_start = -1; |
236 | 13 | if (!host_start.empty() && host_start.data[0] == '[') { |
237 | 12 | for (int32_t i = 1; std::cmp_less(i, host_start.size); ++i) { |
238 | 12 | if (host_start.data[i] == ']') { |
239 | 1 | if (std::cmp_less(i + 1, host_start.size) && host_start.data[i + 1] == ':') { |
240 | 1 | port_start = i + 2; |
241 | 1 | } |
242 | 1 | break; |
243 | 1 | } |
244 | 12 | } |
245 | 12 | } else { |
246 | 12 | int32_t colon_pos = _s_colon_search.search(&host_start); |
247 | 12 | if (colon_pos >= 0) { |
248 | 5 | port_start = colon_pos + 1; |
249 | 5 | } |
250 | 12 | } |
251 | 13 | if (port_start < 0) { |
252 | 7 | return false; |
253 | 7 | } |
254 | | |
255 | 6 | *result = host_start.substring(port_start); |
256 | 6 | break; |
257 | 13 | } |
258 | | |
259 | 0 | case INVALID: |
260 | 0 | return false; |
261 | 122 | } |
262 | | |
263 | 87 | return true; |
264 | 122 | } |
265 | | |
266 | | bool UrlParser::parse_url_key(const StringRef& url, UrlPart part, const StringRef& key, |
267 | 76 | StringRef* result) { |
268 | | // Part must be query to ask for a specific query key. |
269 | 76 | if (part != QUERY) { |
270 | 9 | return false; |
271 | 9 | } |
272 | | |
273 | | // Remove leading and trailing spaces. |
274 | 67 | StringRef trimmed_url = url.trim(); |
275 | | |
276 | | // The key can only be found in the query component, which starts at the first '?' and ends |
277 | | // before the '#' that starts the fragment (if any). |
278 | 67 | StringRef query; |
279 | 67 | if (!find_query_component(trimmed_url, &query)) { |
280 | | // Query component is missing, the whole url is the path plus the fragment. |
281 | 20 | return false; |
282 | 20 | } |
283 | | |
284 | | // Search for the key inside the query component, ignoring malformed URLs for now. |
285 | 47 | StringSearch key_search(&key); |
286 | | // Offset of the next search inside the query component. The query component starts right |
287 | | // after the '?', so a key at offset 0 is a query key as well. |
288 | 47 | int32_t offset = 0; |
289 | | |
290 | 50 | while (std::cmp_less(offset, query.size)) { |
291 | | // Search for the key in the remaining part of the query component. |
292 | 48 | StringRef rest = query.substring(offset); |
293 | 48 | int32_t key_pos = key_search.search(&rest); |
294 | | |
295 | 48 | if (key_pos < 0) { |
296 | | // No (more) key in the query component. |
297 | 2 | break; |
298 | 2 | } |
299 | | |
300 | 46 | offset += key_pos; |
301 | | // The key must start the query component or be preceded by a '&'. |
302 | 46 | if (offset != 0 && query.data[offset - 1] != '&') { |
303 | | // The matched text is not a key, step over it and keep searching. |
304 | 1 | offset += cast_set<int32_t>(key.size); |
305 | 1 | continue; |
306 | 1 | } |
307 | | |
308 | | // Positioned to the char right after the key. |
309 | 45 | int32_t value_pos = offset + cast_set<int32_t>(key.size); |
310 | | |
311 | | // The key must be followed by a '=' and a value, otherwise the match cannot be a key. |
312 | 45 | if (value_pos >= cast_set<int32_t>(query.size) || query.data[value_pos] != '=') { |
313 | | // Step over the matched text and keep searching. |
314 | 2 | offset += cast_set<int32_t>(key.size); |
315 | 2 | continue; |
316 | 2 | } |
317 | | |
318 | 43 | ++value_pos; |
319 | | |
320 | | // Find the ending position of the key's value by matching '&'. |
321 | 43 | StringRef value_rest = query.substring(value_pos); |
322 | 43 | size_t value_end_rel_pos = value_rest.find_first_of('&'); |
323 | 43 | int32_t value_end_pos = value_end_rel_pos == std::numeric_limits<size_t>::max() |
324 | 43 | ? cast_set<int32_t>(query.size) |
325 | 43 | : value_pos + cast_set<int32_t>(value_end_rel_pos); |
326 | | // A duplicated key keeps the behaviour of returning the first value. |
327 | 43 | *result = query.substring(value_pos, value_end_pos - value_pos); |
328 | 43 | return true; |
329 | 45 | } |
330 | | |
331 | 4 | return false; |
332 | 47 | } |
333 | | |
334 | 206 | UrlParser::UrlPart UrlParser::get_url_part(const StringRef& part) { |
335 | | // Quick filter on requested URL part, based on first character. |
336 | | // Hive requires the requested URL part to be all upper case. |
337 | 206 | std::string part_str = part.to_string(); |
338 | 206 | transform(part_str.begin(), part_str.end(), part_str.begin(), ::toupper); |
339 | 206 | StringRef newPart = StringRef(part_str); |
340 | 206 | switch (newPart.data[0]) { |
341 | 11 | case 'A': { |
342 | 11 | if (!newPart.eq(_s_url_authority)) { |
343 | 0 | return INVALID; |
344 | 0 | } |
345 | | |
346 | 11 | return AUTHORITY; |
347 | 11 | } |
348 | | |
349 | 13 | case 'F': { |
350 | 13 | if (!newPart.eq(_s_url_file)) { |
351 | 0 | return INVALID; |
352 | 0 | } |
353 | | |
354 | 13 | return FILE; |
355 | 13 | } |
356 | | |
357 | 35 | case 'H': { |
358 | 35 | if (!newPart.eq(_s_url_host)) { |
359 | 0 | return INVALID; |
360 | 0 | } |
361 | | |
362 | 35 | return HOST; |
363 | 35 | } |
364 | | |
365 | 31 | case 'P': { |
366 | 31 | if (newPart.eq(_s_url_path)) { |
367 | 10 | return PATH; |
368 | 21 | } else if (newPart.eq(_s_url_protocol)) { |
369 | 8 | return PROTOCOL; |
370 | 13 | } else if (newPart.eq(_s_url_port)) { |
371 | 13 | return PORT; |
372 | 13 | } else { |
373 | 0 | return INVALID; |
374 | 0 | } |
375 | 31 | } |
376 | | |
377 | 106 | case 'Q': { |
378 | 106 | if (!newPart.eq(_s_url_query)) { |
379 | 0 | return INVALID; |
380 | 0 | } |
381 | | |
382 | 106 | return QUERY; |
383 | 106 | } |
384 | | |
385 | 8 | case 'R': { |
386 | 8 | if (!newPart.eq(_s_url_ref)) { |
387 | 0 | return INVALID; |
388 | 0 | } |
389 | | |
390 | 8 | return REF; |
391 | 8 | } |
392 | | |
393 | 2 | case 'U': { |
394 | 2 | if (!newPart.eq(_s_url_userinfo)) { |
395 | 0 | return INVALID; |
396 | 0 | } |
397 | | |
398 | 2 | return USERINFO; |
399 | 2 | } |
400 | | |
401 | 0 | default: |
402 | 0 | return INVALID; |
403 | 206 | } |
404 | 206 | } |
405 | | |
406 | 56 | StringRef UrlParser::extract_url(StringRef url, StringRef name) { |
407 | 56 | StringRef result("", 0); |
408 | | // Remove leading and trailing spaces. |
409 | 56 | StringRef trimmed_url = url.trim(); |
410 | | // The parameters can only be found in the query component, which starts at the first '?' |
411 | | // and ends before the '#' that starts the fragment (if any). |
412 | 56 | StringRef sub_url; |
413 | 56 | if (!find_query_component(trimmed_url, &sub_url)) { |
414 | | // this url no parameters. |
415 | | // Example: https://doris.apache.org/ |
416 | 12 | return result; |
417 | 12 | } |
418 | | |
419 | | // find '&' and '=', and extract target parameter |
420 | | // Example: k1=aa&k2=bb&k3=cc&test=dd |
421 | 44 | int64_t and_pod; |
422 | 44 | auto len = sub_url.size; |
423 | 44 | StringRef key_url; |
424 | 92 | while (true) { |
425 | 92 | if (len <= 0) { |
426 | 20 | break; |
427 | 20 | } |
428 | 72 | and_pod = sub_url.find_first_of('&'); |
429 | 72 | if (and_pod != -1) { |
430 | 40 | key_url = sub_url.substring(0, and_pod); |
431 | 40 | sub_url = sub_url.substring(and_pod + 1, len - and_pod - 1); |
432 | 40 | } else { |
433 | 32 | key_url = sub_url; |
434 | 32 | sub_url = result; |
435 | 32 | } |
436 | 72 | len = sub_url.size; |
437 | | |
438 | 72 | auto eq_pod = key_url.find_first_of('='); |
439 | 72 | if (eq_pod == -1) { |
440 | | // invalid url. like: k1&k2=bb |
441 | 4 | continue; |
442 | 4 | } |
443 | 68 | auto key_len = key_url.size; |
444 | 68 | auto key = key_url.substring(0, eq_pod); |
445 | 68 | if (name == key) { |
446 | 24 | return key_url.substring(eq_pod + 1, key_len - eq_pod - 1); |
447 | 24 | } |
448 | 68 | } |
449 | 20 | return result; |
450 | 44 | } |
451 | | #include "common/compile_check_end.h" |
452 | | } // namespace doris |