/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 | } |