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