Coverage Report

Created: 2026-10-17 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/h2o/lib/common/url.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2014,2015 DeNA Co., Ltd.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
#include <inttypes.h>
23
#include <sys/socket.h>
24
#include <sys/types.h>
25
#include <sys/un.h>
26
#include "h2o/memory.h"
27
#include "h2o/string_.h"
28
#include "h2o/url.h"
29
30
const h2o_url_scheme_t H2O_URL_SCHEME_HTTP = {{H2O_STRLIT("http")}, 80, 0};
31
const h2o_url_scheme_t H2O_URL_SCHEME_HTTPS = {{H2O_STRLIT("https")}, 443, 1};
32
const h2o_url_scheme_t H2O_URL_SCHEME_MASQUE = {{H2O_STRLIT("masque")}, 65535, 0 /* ??? masque might or might not be over TLS */};
33
const h2o_url_scheme_t H2O_URL_SCHEME_FASTCGI = {{H2O_STRLIT("fastcgi")}, 65535, 0};
34
35
static int decode_hex(int ch)
36
125k
{
37
125k
    if ('0' <= ch && ch <= '9')
38
24.5k
        return ch - '0';
39
101k
    if ('A' <= ch && ch <= 'F')
40
13.8k
        return ch - 'A' + 0xa;
41
87.5k
    if ('a' <= ch && ch <= 'f')
42
18.4k
        return ch - 'a' + 0xa;
43
69.0k
    return -1;
44
87.5k
}
45
46
static size_t handle_special_paths(const char *path, size_t off, size_t last_slash)
47
58.4k
{
48
58.4k
    size_t orig_off = off, part_size = off - last_slash;
49
50
58.4k
    if (part_size == 2 && path[off - 1] == '.') {
51
3.82k
        --off;
52
54.6k
    } else if (part_size == 3 && path[off - 2] == '.' && path[off - 1] == '.') {
53
3.76k
        off -= 2;
54
3.76k
        if (off > 1) {
55
63.5k
            for (--off; path[off - 1] != '/'; --off)
56
60.3k
                ;
57
3.19k
        }
58
3.76k
    }
59
58.4k
    return orig_off - off;
60
58.4k
}
61
62
/* Perform path normalization and URL decoding in one pass.
63
 * See h2o_req_t for the purpose of @norm_indexes. */
64
static h2o_iovec_t rebuild_path(h2o_mem_pool_t *pool, const char *src, size_t src_len, size_t *query_at, size_t **norm_indexes,
65
                                int *null_char_found)
66
13.2k
{
67
13.2k
    char *dst;
68
13.2k
    size_t src_off = 0, dst_off = 0, last_slash, rewind;
69
70
13.2k
    { /* locate '?', and set len to the end of input path */
71
13.2k
        const char *q = memchr(src, '?', src_len);
72
13.2k
        if (q != NULL) {
73
793
            src_len = *query_at = q - src;
74
12.4k
        } else {
75
12.4k
            *query_at = SIZE_MAX;
76
12.4k
        }
77
13.2k
    }
78
79
    /* dst can be 1 byte more than src if src is missing the prefixing '/' */
80
13.2k
    dst = h2o_mem_alloc_pool(pool, char, src_len + 1);
81
13.2k
    *norm_indexes = h2o_mem_alloc_pool(pool, *norm_indexes[0], (src_len + 1));
82
83
13.2k
    if (src[0] == '/')
84
3.28k
        src_off++;
85
13.2k
    last_slash = dst_off;
86
13.2k
    dst[dst_off] = '/';
87
13.2k
    (*norm_indexes)[dst_off] = src_off;
88
13.2k
    dst_off++;
89
90
    /* decode %xx */
91
1.43M
    while (src_off < src_len) {
92
1.42M
        int hi, lo;
93
1.42M
        char decoded;
94
95
1.42M
        if (src[src_off] == '%' && (src_off + 2 < src_len) && (hi = decode_hex(src[src_off + 1])) != -1 &&
96
41.7k
            (lo = decode_hex(src[src_off + 2])) != -1) {
97
15.1k
            decoded = (hi << 4) | lo;
98
15.1k
            src_off += 3;
99
1.40M
        } else {
100
1.40M
            decoded = src[src_off++];
101
1.40M
        }
102
1.42M
        if (decoded == '/') {
103
45.2k
            rewind = handle_special_paths(dst, dst_off, last_slash);
104
45.2k
            if (rewind > 0) {
105
7.05k
                dst_off -= rewind;
106
7.05k
                last_slash = dst_off - 1;
107
7.05k
                continue;
108
7.05k
            }
109
38.1k
            last_slash = dst_off;
110
38.1k
        }
111
1.41M
        dst[dst_off] = decoded;
112
1.41M
        (*norm_indexes)[dst_off] = src_off;
113
1.41M
        if (decoded == '\0')
114
18.9k
            *null_char_found = 1;
115
1.41M
        dst_off++;
116
1.41M
    }
117
13.2k
    rewind = handle_special_paths(dst, dst_off, last_slash);
118
13.2k
    dst_off -= rewind;
119
120
13.2k
    return h2o_iovec_init(dst, dst_off);
121
13.2k
}
122
123
h2o_iovec_t h2o_url_normalize_path(h2o_mem_pool_t *pool, const char *path, size_t len, size_t *query_at, size_t **norm_indexes,
124
                                   int *null_char_found)
125
29.2k
{
126
29.2k
    h2o_iovec_t ret;
127
128
29.2k
    *query_at = SIZE_MAX;
129
29.2k
    *norm_indexes = NULL;
130
29.2k
    *null_char_found = 0;
131
132
29.2k
    if (len == 0) {
133
2.07k
        ret = h2o_iovec_init("/", 1);
134
2.07k
        return ret;
135
2.07k
    }
136
137
27.1k
    const char *p = path, *end = path + len;
138
139
27.1k
    if (path[0] != '/')
140
9.97k
        goto Rewrite;
141
142
197k
    for (; p + 1 < end; ++p) {
143
184k
        if ((p[0] == '/' && p[1] == '.') || p[0] == '%' || p[0] == '\0') {
144
            /* detect false positives as well */
145
3.27k
            goto Rewrite;
146
180k
        } else if (p[0] == '?') {
147
909
            *query_at = p - path;
148
909
            goto Return;
149
909
        }
150
184k
    }
151
25.8k
    for (; p < end; ++p) {
152
13.0k
        if (p[0] == '\0') {
153
13
            goto Rewrite;
154
12.9k
        } else if (p[0] == '?') {
155
114
            *query_at = p - path;
156
114
            goto Return;
157
114
        }
158
13.0k
    }
159
160
13.9k
Return:
161
13.9k
    ret.base = (char *)path;
162
13.9k
    ret.len = p - path;
163
13.9k
    return ret;
164
165
13.2k
Rewrite:
166
13.2k
    ret = rebuild_path(pool, path, len, query_at, norm_indexes, null_char_found);
167
13.2k
    if (ret.len == 0)
168
0
        goto RewriteError;
169
13.2k
    if (ret.base[0] != '/')
170
0
        goto RewriteError;
171
13.2k
    if (h2o_strstr(ret.base, ret.len, H2O_STRLIT("/../")) != SIZE_MAX)
172
0
        goto RewriteError;
173
13.2k
    if (ret.len >= 3 && memcmp(ret.base + ret.len - 3, "/..", 3) == 0)
174
0
        goto RewriteError;
175
13.2k
    return ret;
176
0
RewriteError:
177
0
    h2o_error_printf("failed to normalize path: `%.*s` => `%.*s`\n", (int)len, path, (int)ret.len, ret.base);
178
0
    ret = h2o_iovec_init("/", 1);
179
0
    return ret;
180
13.2k
}
181
182
static const char *parse_scheme(const char *s, const char *end, const h2o_url_scheme_t **scheme)
183
7.89k
{
184
7.89k
    if (end - s >= 5 && memcmp(s, "http:", 5) == 0) {
185
1.04k
        *scheme = &H2O_URL_SCHEME_HTTP;
186
1.04k
        return s + 5;
187
6.84k
    } else if (end - s >= 6 && memcmp(s, "https:", 6) == 0) {
188
35
        *scheme = &H2O_URL_SCHEME_HTTPS;
189
35
        return s + 6;
190
6.80k
    } else if (end - s >= 7 && memcmp(s, "masque:", 7) == 0) {
191
43
        *scheme = &H2O_URL_SCHEME_MASQUE;
192
43
        return s + 7;
193
43
    }
194
6.76k
    return NULL;
195
7.89k
}
196
197
const char *h2o_url_parse_hostport(const char *s, size_t len, h2o_iovec_t *host, uint16_t *port)
198
4.55k
{
199
4.55k
    const char *token_start = s, *token_end, *end = s + len;
200
201
4.55k
    *port = 65535;
202
203
4.55k
    if (token_start == end)
204
38
        return NULL;
205
206
4.51k
    if (*token_start == '[') {
207
        /* is IPv6 address */
208
3.72k
        ++token_start;
209
3.72k
        if ((token_end = memchr(token_start, ']', end - token_start)) == NULL)
210
35
            return NULL;
211
3.68k
        *host = h2o_iovec_init(token_start, token_end - token_start);
212
3.68k
        token_start = token_end + 1;
213
3.68k
    } else {
214
5.13M
        for (token_end = token_start; !(token_end == end || *token_end == '/' || *token_end == '?' || *token_end == ':');
215
5.13M
             ++token_end)
216
5.13M
            ;
217
792
        *host = h2o_iovec_init(token_start, token_end - token_start);
218
792
        token_start = token_end;
219
792
    }
220
221
    /* disallow zero-length host */
222
4.47k
    if (host->len == 0)
223
38
        return NULL;
224
225
    /* parse port */
226
4.43k
    if (token_start != end && *token_start == ':') {
227
358
        uint32_t p = 0;
228
434k
        for (++token_start; token_start != end; ++token_start) {
229
434k
            if ('0' <= *token_start && *token_start <= '9') {
230
1.09k
                p = p * 10 + *token_start - '0';
231
1.09k
                if (p >= 65535)
232
65
                    return NULL;
233
433k
            } else if (*token_start == '/' || *token_start == '?') {
234
85
                break;
235
85
            }
236
434k
        }
237
293
        *port = (uint16_t)p;
238
293
    }
239
240
4.37k
    return token_start;
241
4.43k
}
242
243
static int parse_authority_and_path(h2o_mem_pool_t *pool, const char *src, const char *url_end, h2o_url_t *parsed)
244
914
{
245
914
    const char *p = h2o_url_parse_hostport(src, url_end - src, &parsed->host, &parsed->_port);
246
914
    if (p == NULL)
247
176
        return -1;
248
738
    parsed->authority = h2o_iovec_init(src, p - src);
249
738
    if (p == url_end) {
250
400
        parsed->path = h2o_iovec_init(H2O_STRLIT("/"));
251
400
    } else if (*p == '/') {
252
109
        parsed->path = h2o_iovec_init(p, url_end - p);
253
229
    } else if (*p == '?') {
254
184
        parsed->path = h2o_concat(pool, h2o_iovec_init(H2O_STRLIT("/")), h2o_iovec_init(p, url_end - p));
255
184
    } else {
256
45
        return -1;
257
45
    }
258
693
    return 0;
259
738
}
260
261
int h2o_url_parse(h2o_mem_pool_t *pool, const char *url, size_t url_len, h2o_url_t *parsed)
262
7.89k
{
263
7.89k
    const char *url_end, *p;
264
265
7.89k
    if (url_len == SIZE_MAX)
266
0
        url_len = strlen(url);
267
7.89k
    url_end = url + url_len;
268
269
    /* check and skip scheme */
270
7.89k
    if ((p = parse_scheme(url, url_end, &parsed->scheme)) == NULL)
271
6.76k
        return -1;
272
273
    /* skip "//" */
274
1.12k
    if (!(url_end - p >= 2 && p[0] == '/' && p[1] == '/'))
275
212
        return -1;
276
914
    p += 2;
277
278
914
    return parse_authority_and_path(pool, p, url_end, parsed);
279
1.12k
}
280
281
int h2o_url_parse_relative(h2o_mem_pool_t *pool, const char *url, size_t url_len, h2o_url_t *parsed)
282
0
{
283
0
    const char *url_end, *p;
284
285
0
    if (url_len == SIZE_MAX)
286
0
        url_len = strlen(url);
287
0
    url_end = url + url_len;
288
289
    /* obtain scheme and port number */
290
0
    if ((p = parse_scheme(url, url_end, &parsed->scheme)) == NULL) {
291
0
        parsed->scheme = NULL;
292
0
        p = url;
293
0
    }
294
295
    /* handle "//" */
296
0
    if (url_end - p >= 2 && p[0] == '/' && p[1] == '/')
297
0
        return parse_authority_and_path(pool, p + 2, url_end, parsed);
298
299
    /* reset authority, host, port, and set path */
300
0
    parsed->authority = (h2o_iovec_t){NULL};
301
0
    parsed->host = (h2o_iovec_t){NULL};
302
0
    parsed->_port = 65535;
303
0
    parsed->path = h2o_iovec_init(p, url_end - p);
304
305
0
    return 0;
306
0
}
307
308
h2o_iovec_t h2o_url_resolve(h2o_mem_pool_t *pool, const h2o_url_t *base, const h2o_url_t *relative, h2o_url_t *dest)
309
0
{
310
0
    h2o_iovec_t base_path, relative_path, ret;
311
312
0
    assert(base->path.len != 0);
313
0
    assert(base->path.base[0] == '/');
314
315
0
    if (relative == NULL) {
316
        /* build URL using base copied to dest */
317
0
        *dest = *base;
318
0
        base_path = base->path;
319
0
        relative_path = h2o_iovec_init(NULL, 0);
320
0
        goto Build;
321
0
    }
322
323
    /* scheme */
324
0
    dest->scheme = relative->scheme != NULL ? relative->scheme : base->scheme;
325
326
    /* authority (and host:port) */
327
0
    if (relative->authority.base != NULL) {
328
0
        assert(relative->host.base != NULL);
329
0
        dest->authority = relative->authority;
330
0
        dest->host = relative->host;
331
0
        dest->_port = relative->_port;
332
0
    } else {
333
0
        assert(relative->host.base == NULL);
334
0
        assert(relative->_port == 65535);
335
0
        dest->authority = base->authority;
336
0
        dest->host = base->host;
337
0
        dest->_port = base->_port;
338
0
    }
339
340
    /* path */
341
0
    base_path = base->path;
342
0
    if (relative->path.base != NULL) {
343
0
        relative_path = relative->path;
344
0
        h2o_url_resolve_path(&base_path, &relative_path);
345
0
    } else {
346
0
        assert(relative->path.len == 0);
347
0
        relative_path = (h2o_iovec_t){NULL};
348
0
    }
349
350
0
Build:
351
    /* build the output */
352
0
    ret = h2o_concat(pool, dest->scheme->name, h2o_iovec_init(H2O_STRLIT("://")), dest->authority, base_path, relative_path);
353
    /* adjust dest */
354
0
    dest->authority.base = ret.base + dest->scheme->name.len + 3;
355
0
    dest->host.base = dest->authority.base;
356
0
    if (dest->authority.len != 0 && dest->authority.base[0] == '[')
357
0
        ++dest->host.base;
358
0
    dest->path.base = dest->authority.base + dest->authority.len;
359
0
    dest->path.len = ret.base + ret.len - dest->path.base;
360
361
0
    return ret;
362
0
}
363
364
void h2o_url_resolve_path(h2o_iovec_t *base, h2o_iovec_t *relative)
365
0
{
366
0
    size_t base_path_len = base->len, rel_path_offset = 0;
367
368
0
    if (relative->len != 0 && relative->base[0] == '/') {
369
0
        base_path_len = 0;
370
0
    } else {
371
        /* relative path */
372
0
        while (base->base[--base_path_len] != '/')
373
0
            ;
374
0
        while (rel_path_offset != relative->len) {
375
0
            if (relative->base[rel_path_offset] == '.') {
376
0
                if (relative->len - rel_path_offset >= 2 && relative->base[rel_path_offset + 1] == '.' &&
377
0
                    (relative->len - rel_path_offset == 2 || relative->base[rel_path_offset + 2] == '/')) {
378
0
                    if (base_path_len != 0) {
379
0
                        while (base->base[--base_path_len] != '/')
380
0
                            ;
381
0
                    }
382
0
                    rel_path_offset += relative->len - rel_path_offset == 2 ? 2 : 3;
383
0
                    continue;
384
0
                }
385
0
                if (relative->len - rel_path_offset == 1) {
386
0
                    rel_path_offset += 1;
387
0
                    continue;
388
0
                } else if (relative->base[rel_path_offset + 1] == '/') {
389
0
                    rel_path_offset += 2;
390
0
                    continue;
391
0
                }
392
0
            }
393
0
            break;
394
0
        }
395
0
        base_path_len += 1;
396
0
    }
397
398
0
    base->len = base_path_len;
399
0
    *relative = h2o_iovec_init(relative->base + rel_path_offset, relative->len - rel_path_offset);
400
0
}
401
402
void h2o_url_copy(h2o_mem_pool_t *pool, h2o_url_t *dest, const h2o_url_t *src)
403
3
{
404
3
    dest->scheme = src->scheme;
405
3
    dest->authority = h2o_strdup(pool, src->authority.base, src->authority.len);
406
3
    dest->host = h2o_strdup(pool, src->host.base, src->host.len);
407
3
    dest->path = h2o_strdup(pool, src->path.base, src->path.len);
408
3
    dest->_port = src->_port;
409
3
}
410
411
const char *h2o_url_host_to_sun(h2o_iovec_t host, struct sockaddr_un *sa)
412
3
{
413
21
#define PREFIX "unix:"
414
21
#define PREFIX_LEN (sizeof(PREFIX) - 1)
415
416
3
    if (host.len < PREFIX_LEN || memcmp(host.base, PREFIX, PREFIX_LEN) != 0)
417
0
        return h2o_url_host_to_sun_err_is_not_unix_socket;
418
419
3
    if (host.len == PREFIX_LEN)
420
0
        return "unix-domain socket path is empty";
421
3
    if (host.len - PREFIX_LEN >= sizeof(sa->sun_path))
422
0
        return "unix-domain socket path is too long";
423
424
3
    memset(sa, 0, sizeof(*sa));
425
3
    sa->sun_family = AF_UNIX;
426
3
    memcpy(sa->sun_path, host.base + PREFIX_LEN, host.len - PREFIX_LEN);
427
3
    return NULL;
428
429
3
#undef PREFIX_LEN
430
3
#undef PREFIX
431
3
}
432
433
const char h2o_url_host_to_sun_err_is_not_unix_socket[] = "supplied name does not look like an unix-domain socket";
434
435
int h2o_url_init_with_hostport(h2o_url_t *url, h2o_mem_pool_t *pool, const h2o_url_scheme_t *scheme, h2o_iovec_t host,
436
                               uint16_t port, h2o_iovec_t path)
437
0
{
438
0
    url->scheme = scheme;
439
0
    url->path = path;
440
441
0
    if (port == scheme->default_port) {
442
0
        url->_port = 65535;
443
0
        url->authority = h2o_strdup(pool, host.base, host.len);
444
0
        url->host = url->authority;
445
0
    } else {
446
0
        url->_port = port;
447
0
        char _port[sizeof(H2O_UINT16_LONGEST_STR)];
448
0
        int port_len = sprintf(_port, "%" PRIu16, port);
449
0
        if (port_len < 0)
450
0
            return -1;
451
452
0
        url->authority.len = host.len + 1 + port_len;
453
0
        url->authority.base = pool == NULL ? h2o_mem_alloc(url->authority.len) : h2o_mem_alloc_pool(pool, char, url->authority.len);
454
0
        memcpy(url->authority.base, host.base, host.len);
455
0
        memcpy(url->authority.base + host.len, ":", 1);
456
0
        memcpy(url->authority.base + host.len + 1, _port, port_len);
457
0
        url->host = h2o_iovec_init(url->authority.base, url->authority.len - 1 - port_len);
458
0
    }
459
460
0
    return 0;
461
0
}
462
463
int h2o_url_init_with_sun_path(h2o_url_t *url, h2o_mem_pool_t *pool, const h2o_url_scheme_t *scheme, h2o_iovec_t sun_path,
464
                               h2o_iovec_t path)
465
0
{
466
0
    url->scheme = scheme;
467
0
    url->path = path;
468
0
    url->_port = 65535;
469
470
0
#define PREFIX "[unix:"
471
0
#define SUFFIX "]"
472
0
    url->authority.len = strlen(PREFIX SUFFIX) + sun_path.len;
473
0
    url->authority.base = pool == NULL ? h2o_mem_alloc(url->authority.len) : h2o_mem_alloc_pool(pool, char, url->authority.len);
474
0
    memcpy(url->authority.base, PREFIX, sizeof(PREFIX) - 1);
475
0
    memcpy(url->authority.base + sizeof(PREFIX) - 1, sun_path.base, sun_path.len);
476
0
    memcpy(url->authority.base + url->authority.len - 1, SUFFIX, sizeof(SUFFIX) - 1);
477
0
#undef PREFIX
478
0
#undef SUFFIX
479
480
0
    url->host = h2o_iovec_init(url->authority.base + 1, url->authority.len - 2);
481
482
0
    return 0;
483
0
}