Coverage Report

Created: 2026-11-07 04:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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