Coverage Report

Created: 2025-06-22 06:17

/src/h2o/lib/http2/casper.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku
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
#ifndef H2O_NO_OPENSSL_SUPPRESS_DEPRECATED
23
#define OPENSSL_SUPPRESS_DEPRECATED /* casper is legacy and we do not want to pay the cost of switchng away from SHA1_* */
24
#endif
25
#include <openssl/sha.h>
26
#include "golombset.h"
27
#include "h2o/string_.h"
28
#include "h2o/http2_casper.h"
29
30
0
#define COOKIE_NAME "h2o_casper"
31
0
#define COOKIE_ATTRIBUTES "; Path=/; Expires=Tue, 01 Jan 2030 00:00:00 GMT; Secure"
32
33
struct st_h2o_http2_casper_t {
34
    H2O_VECTOR(uint64_t) keys;
35
    unsigned capacity_bits;
36
    unsigned remainder_bits;
37
    h2o_iovec_t cookie_cache;
38
};
39
40
static unsigned calc_key(h2o_http2_casper_t *casper, const char *path, size_t path_len)
41
0
{
42
0
    SHA_CTX ctx;
43
0
    SHA1_Init(&ctx);
44
0
    SHA1_Update(&ctx, path, path_len);
45
46
0
    union {
47
0
        unsigned key;
48
0
        unsigned char bytes[SHA_DIGEST_LENGTH];
49
0
    } md;
50
0
    SHA1_Final(md.bytes, &ctx);
51
52
0
    return md.key & ((1 << casper->capacity_bits) - 1);
53
0
}
54
55
h2o_http2_casper_t *h2o_http2_casper_create(unsigned capacity_bits, unsigned remainder_bits)
56
0
{
57
0
    h2o_http2_casper_t *casper = h2o_mem_alloc(sizeof(*casper));
58
59
0
    memset(&casper->keys, 0, sizeof(casper->keys));
60
0
    casper->capacity_bits = capacity_bits;
61
0
    casper->remainder_bits = remainder_bits;
62
0
    casper->cookie_cache = (h2o_iovec_t){NULL};
63
64
0
    return casper;
65
0
}
66
67
void h2o_http2_casper_destroy(h2o_http2_casper_t *casper)
68
0
{
69
0
    free(casper->keys.entries);
70
0
    free(casper->cookie_cache.base);
71
0
    free(casper);
72
0
}
73
74
size_t h2o_http2_casper_num_entries(h2o_http2_casper_t *casper)
75
0
{
76
0
    return casper->keys.size;
77
0
}
78
79
int h2o_http2_casper_lookup(h2o_http2_casper_t *casper, const char *path, size_t path_len, int set)
80
0
{
81
0
    unsigned key = calc_key(casper, path, path_len);
82
0
    size_t i;
83
84
    /* FIXME use binary search */
85
0
    for (i = 0; i != casper->keys.size; ++i)
86
0
        if (key <= casper->keys.entries[i])
87
0
            break;
88
0
    if (i != casper->keys.size && key == casper->keys.entries[i])
89
0
        return 1;
90
0
    if (!set)
91
0
        return 0;
92
93
    /* we need to set a new value */
94
0
    free(casper->cookie_cache.base);
95
0
    casper->cookie_cache = (h2o_iovec_t){NULL};
96
0
    h2o_vector_reserve(NULL, &casper->keys, casper->keys.size + 1);
97
0
    memmove(casper->keys.entries + i + 1, casper->keys.entries + i, (casper->keys.size - i) * sizeof(casper->keys.entries[0]));
98
0
    ++casper->keys.size;
99
0
    casper->keys.entries[i] = key;
100
0
    return 0;
101
0
}
102
103
void h2o_http2_casper_consume_cookie(h2o_http2_casper_t *casper, const char *cookie, size_t cookie_len)
104
0
{
105
0
    h2o_iovec_t binary = {NULL};
106
0
    uint64_t tiny_keys_buf[128], *keys = tiny_keys_buf;
107
108
    /* check the name of the cookie */
109
0
    if (!(cookie_len > sizeof(COOKIE_NAME "=") - 1 && memcmp(cookie, H2O_STRLIT(COOKIE_NAME "=")) == 0))
110
0
        goto Exit;
111
112
    /* base64 decode */
113
0
    if ((binary = h2o_decode_base64url(NULL, cookie + sizeof(COOKIE_NAME "=") - 1, cookie_len - (sizeof(COOKIE_NAME "=") - 1)))
114
0
            .base == NULL)
115
0
        goto Exit;
116
117
    /* decode GCS, either using tiny_keys_buf or using heap */
118
0
    size_t capacity = sizeof(tiny_keys_buf) / sizeof(tiny_keys_buf[0]), num_keys;
119
0
    while (num_keys = capacity, golombset_decode(casper->remainder_bits, binary.base, binary.len, keys, &num_keys) != 0) {
120
0
        if (keys != tiny_keys_buf) {
121
0
            free(keys);
122
0
            keys = tiny_keys_buf; /* reset to something that would not trigger call to free(3) */
123
0
        }
124
0
        if (capacity >= (size_t)1 << casper->capacity_bits)
125
0
            goto Exit;
126
0
        capacity *= 2;
127
0
        keys = h2o_mem_alloc(capacity * sizeof(*keys));
128
0
    }
129
130
    /* copy or merge the entries */
131
0
    if (num_keys == 0) {
132
        /* nothing to do */
133
0
    } else if (casper->keys.size == 0) {
134
0
        h2o_vector_reserve(NULL, &casper->keys, num_keys);
135
0
        memcpy(casper->keys.entries, keys, num_keys * sizeof(*keys));
136
0
        casper->keys.size = num_keys;
137
0
    } else {
138
0
        uint64_t *orig_keys = casper->keys.entries;
139
0
        size_t num_orig_keys = casper->keys.size, orig_index = 0, new_index = 0;
140
0
        memset(&casper->keys, 0, sizeof(casper->keys));
141
0
        h2o_vector_reserve(NULL, &casper->keys, num_keys + num_orig_keys);
142
0
        do {
143
0
            if (orig_keys[orig_index] < keys[new_index]) {
144
0
                casper->keys.entries[casper->keys.size++] = orig_keys[orig_index++];
145
0
            } else if (orig_keys[orig_index] > keys[new_index]) {
146
0
                casper->keys.entries[casper->keys.size++] = keys[new_index++];
147
0
            } else {
148
0
                casper->keys.entries[casper->keys.size++] = orig_keys[orig_index];
149
0
                ++orig_index;
150
0
                ++new_index;
151
0
            }
152
0
        } while (orig_index != num_orig_keys && new_index != num_keys);
153
0
        if (orig_index != num_orig_keys) {
154
0
            do {
155
0
                casper->keys.entries[casper->keys.size++] = orig_keys[orig_index++];
156
0
            } while (orig_index != num_orig_keys);
157
0
        } else if (new_index != num_keys) {
158
0
            do {
159
0
                casper->keys.entries[casper->keys.size++] = keys[new_index++];
160
0
            } while (new_index != num_keys);
161
0
        }
162
0
        free(orig_keys);
163
0
    }
164
165
0
Exit:
166
0
    if (keys != tiny_keys_buf)
167
0
        free(keys);
168
0
    free(binary.base);
169
0
}
170
171
static size_t append_str(char *dst, const char *s, size_t l)
172
0
{
173
0
    memcpy(dst, s, l);
174
0
    return l;
175
0
}
176
177
h2o_iovec_t h2o_http2_casper_get_cookie(h2o_http2_casper_t *casper)
178
0
{
179
0
    if (casper->cookie_cache.base != NULL)
180
0
        return casper->cookie_cache;
181
182
0
    if (casper->keys.size == 0)
183
0
        return (h2o_iovec_t){NULL};
184
185
    /* encode as binary */
186
0
    char tiny_bin_buf[128], *bin_buf = tiny_bin_buf;
187
0
    size_t bin_capacity = sizeof(tiny_bin_buf), bin_size;
188
0
    while (bin_size = bin_capacity,
189
0
           golombset_encode(casper->remainder_bits, casper->keys.entries, casper->keys.size, bin_buf, &bin_size) != 0) {
190
0
        if (bin_buf != tiny_bin_buf)
191
0
            free(bin_buf);
192
0
        bin_capacity *= 2;
193
0
        bin_buf = h2o_mem_alloc(bin_capacity);
194
0
    }
195
196
0
    char *header_bytes = h2o_mem_alloc(sizeof(COOKIE_NAME "=" COOKIE_ATTRIBUTES) - 1 + (bin_size + 3) * 4 / 3);
197
0
    size_t header_len = 0;
198
199
0
    header_len += append_str(header_bytes + header_len, H2O_STRLIT(COOKIE_NAME "="));
200
0
    header_len += h2o_base64_encode(header_bytes + header_len, bin_buf, bin_size, 1);
201
0
    header_len += append_str(header_bytes + header_len, H2O_STRLIT(COOKIE_ATTRIBUTES));
202
203
0
    if (bin_buf != tiny_bin_buf)
204
0
        free(bin_buf);
205
206
0
    casper->cookie_cache = h2o_iovec_init(header_bytes, header_len);
207
0
    return casper->cookie_cache;
208
0
}