1
2 /*
3 * Copyright (C) Nginx, Inc.
4 * Copyright (C) Valentin V. Bartenev
5 */
6
7
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11
12
13 #define NGX_HTTP_V2_TABLE_SIZE 4096
14
15
16 static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c,
17 size_t size);
18
19
20 static ngx_http_v2_header_t ngx_http_v2_static_table[] = {
21 { ngx_string(":authority"), ngx_string("") },
22 { ngx_string(":method"), ngx_string("GET") },
23 { ngx_string(":method"), ngx_string("POST") },
24 { ngx_string(":path"), ngx_string("/") },
25 { ngx_string(":path"), ngx_string("/index.html") },
26 { ngx_string(":scheme"), ngx_string("http") },
27 { ngx_string(":scheme"), ngx_string("https") },
28 { ngx_string(":status"), ngx_string("200") },
29 { ngx_string(":status"), ngx_string("204") },
30 { ngx_string(":status"), ngx_string("206") },
31 { ngx_string(":status"), ngx_string("304") },
32 { ngx_string(":status"), ngx_string("400") },
33 { ngx_string(":status"), ngx_string("404") },
34 { ngx_string(":status"), ngx_string("500") },
35 { ngx_string("accept-charset"), ngx_string("") },
36 { ngx_string("accept-encoding"), ngx_string("gzip, deflate") },
37 { ngx_string("accept-language"), ngx_string("") },
38 { ngx_string("accept-ranges"), ngx_string("") },
39 { ngx_string("accept"), ngx_string("") },
40 { ngx_string("access-control-allow-origin"), ngx_string("") },
41 { ngx_string("age"), ngx_string("") },
42 { ngx_string("allow"), ngx_string("") },
43 { ngx_string("authorization"), ngx_string("") },
44 { ngx_string("cache-control"), ngx_string("") },
45 { ngx_string("content-disposition"), ngx_string("") },
46 { ngx_string("content-encoding"), ngx_string("") },
47 { ngx_string("content-language"), ngx_string("") },
48 { ngx_string("content-length"), ngx_string("") },
49 { ngx_string("content-location"), ngx_string("") },
50 { ngx_string("content-range"), ngx_string("") },
51 { ngx_string("content-type"), ngx_string("") },
52 { ngx_string("cookie"), ngx_string("") },
53 { ngx_string("date"), ngx_string("") },
54 { ngx_string("etag"), ngx_string("") },
55 { ngx_string("expect"), ngx_string("") },
56 { ngx_string("expires"), ngx_string("") },
57 { ngx_string("from"), ngx_string("") },
58 { ngx_string("host"), ngx_string("") },
59 { ngx_string("if-match"), ngx_string("") },
60 { ngx_string("if-modified-since"), ngx_string("") },
61 { ngx_string("if-none-match"), ngx_string("") },
62 { ngx_string("if-range"), ngx_string("") },
63 { ngx_string("if-unmodified-since"), ngx_string("") },
64 { ngx_string("last-modified"), ngx_string("") },
65 { ngx_string("link"), ngx_string("") },
66 { ngx_string("location"), ngx_string("") },
67 { ngx_string("max-forwards"), ngx_string("") },
68 { ngx_string("proxy-authenticate"), ngx_string("") },
69 { ngx_string("proxy-authorization"), ngx_string("") },
70 { ngx_string("range"), ngx_string("") },
71 { ngx_string("referer"), ngx_string("") },
72 { ngx_string("refresh"), ngx_string("") },
73 { ngx_string("retry-after"), ngx_string("") },
74 { ngx_string("server"), ngx_string("") },
75 { ngx_string("set-cookie"), ngx_string("") },
76 { ngx_string("strict-transport-security"), ngx_string("") },
77 { ngx_string("transfer-encoding"), ngx_string("") },
78 { ngx_string("user-agent"), ngx_string("") },
79 { ngx_string("vary"), ngx_string("") },
80 { ngx_string("via"), ngx_string("") },
81 { ngx_string("www-authenticate"), ngx_string("") },
82 };
83
84 #define NGX_HTTP_V2_STATIC_TABLE_ENTRIES \
85 (sizeof(ngx_http_v2_static_table) \
86 / sizeof(ngx_http_v2_header_t))
87
88
89 ngx_str_t *
ngx_http_v2_get_static_name(ngx_uint_t index)90 ngx_http_v2_get_static_name(ngx_uint_t index)
91 {
92 return &ngx_http_v2_static_table[index - 1].name;
93 }
94
95
96 ngx_str_t *
ngx_http_v2_get_static_value(ngx_uint_t index)97 ngx_http_v2_get_static_value(ngx_uint_t index)
98 {
99 return &ngx_http_v2_static_table[index - 1].value;
100 }
101
102
103 ngx_int_t
ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t * h2c,ngx_uint_t index,ngx_uint_t name_only)104 ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index,
105 ngx_uint_t name_only)
106 {
107 u_char *p;
108 size_t rest;
109 ngx_http_v2_header_t *entry;
110
111 if (index == 0) {
112 ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
113 "client sent invalid hpack table index 0");
114 return NGX_ERROR;
115 }
116
117 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
118 "http2 get indexed %s: %ui",
119 name_only ? "name" : "header", index);
120
121 index--;
122
123 if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) {
124 h2c->state.header = ngx_http_v2_static_table[index];
125 return NGX_OK;
126 }
127
128 index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES;
129
130 if (index < h2c->hpack.added - h2c->hpack.deleted) {
131 index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated;
132 entry = h2c->hpack.entries[index];
133
134 p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1);
135 if (p == NULL) {
136 return NGX_ERROR;
137 }
138
139 h2c->state.header.name.len = entry->name.len;
140 h2c->state.header.name.data = p;
141
142 rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data;
143
144 if (entry->name.len > rest) {
145 p = ngx_cpymem(p, entry->name.data, rest);
146 p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest);
147
148 } else {
149 p = ngx_cpymem(p, entry->name.data, entry->name.len);
150 }
151
152 *p = '\0';
153
154 if (name_only) {
155 return NGX_OK;
156 }
157
158 p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1);
159 if (p == NULL) {
160 return NGX_ERROR;
161 }
162
163 h2c->state.header.value.len = entry->value.len;
164 h2c->state.header.value.data = p;
165
166 rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data;
167
168 if (entry->value.len > rest) {
169 p = ngx_cpymem(p, entry->value.data, rest);
170 p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest);
171
172 } else {
173 p = ngx_cpymem(p, entry->value.data, entry->value.len);
174 }
175
176 *p = '\0';
177
178 return NGX_OK;
179 }
180
181 ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
182 "client sent out of bound hpack table index: %ui", index);
183
184 return NGX_ERROR;
185 }
186
187
188 ngx_int_t
ngx_http_v2_add_header(ngx_http_v2_connection_t * h2c,ngx_http_v2_header_t * header)189 ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c,
190 ngx_http_v2_header_t *header)
191 {
192 size_t avail;
193 ngx_uint_t index;
194 ngx_http_v2_header_t *entry, **entries;
195
196 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
197 "http2 table add: \"%V: %V\"",
198 &header->name, &header->value);
199
200 if (h2c->hpack.entries == NULL) {
201 h2c->hpack.allocated = 64;
202 h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE;
203 h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE;
204
205 h2c->hpack.entries = ngx_palloc(h2c->connection->pool,
206 sizeof(ngx_http_v2_header_t *)
207 * h2c->hpack.allocated);
208 if (h2c->hpack.entries == NULL) {
209 return NGX_ERROR;
210 }
211
212 h2c->hpack.storage = ngx_palloc(h2c->connection->pool,
213 h2c->hpack.free);
214 if (h2c->hpack.storage == NULL) {
215 return NGX_ERROR;
216 }
217
218 h2c->hpack.pos = h2c->hpack.storage;
219 }
220
221 if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len)
222 != NGX_OK)
223 {
224 return NGX_OK;
225 }
226
227 if (h2c->hpack.reused == h2c->hpack.deleted) {
228 entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t));
229 if (entry == NULL) {
230 return NGX_ERROR;
231 }
232
233 } else {
234 entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated];
235 }
236
237 avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos;
238
239 entry->name.len = header->name.len;
240 entry->name.data = h2c->hpack.pos;
241
242 if (avail >= header->name.len) {
243 h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data,
244 header->name.len);
245 } else {
246 ngx_memcpy(h2c->hpack.pos, header->name.data, avail);
247 h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage,
248 header->name.data + avail,
249 header->name.len - avail);
250 avail = NGX_HTTP_V2_TABLE_SIZE;
251 }
252
253 avail -= header->name.len;
254
255 entry->value.len = header->value.len;
256 entry->value.data = h2c->hpack.pos;
257
258 if (avail >= header->value.len) {
259 h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data,
260 header->value.len);
261 } else {
262 ngx_memcpy(h2c->hpack.pos, header->value.data, avail);
263 h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage,
264 header->value.data + avail,
265 header->value.len - avail);
266 }
267
268 if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) {
269
270 entries = ngx_palloc(h2c->connection->pool,
271 sizeof(ngx_http_v2_header_t *)
272 * (h2c->hpack.allocated + 64));
273 if (entries == NULL) {
274 return NGX_ERROR;
275 }
276
277 index = h2c->hpack.deleted % h2c->hpack.allocated;
278
279 ngx_memcpy(entries, &h2c->hpack.entries[index],
280 (h2c->hpack.allocated - index)
281 * sizeof(ngx_http_v2_header_t *));
282
283 ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries,
284 index * sizeof(ngx_http_v2_header_t *));
285
286 (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries);
287
288 h2c->hpack.entries = entries;
289
290 h2c->hpack.added = h2c->hpack.allocated;
291 h2c->hpack.deleted = 0;
292 h2c->hpack.reused = 0;
293 h2c->hpack.allocated += 64;
294 }
295
296 h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry;
297
298 return NGX_OK;
299 }
300
301
302 static ngx_int_t
ngx_http_v2_table_account(ngx_http_v2_connection_t * h2c,size_t size)303 ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size)
304 {
305 ngx_http_v2_header_t *entry;
306
307 size += 32;
308
309 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
310 "http2 table account: %uz free:%uz",
311 size, h2c->hpack.free);
312
313 if (size <= h2c->hpack.free) {
314 h2c->hpack.free -= size;
315 return NGX_OK;
316 }
317
318 if (size > h2c->hpack.size) {
319 h2c->hpack.deleted = h2c->hpack.added;
320 h2c->hpack.free = h2c->hpack.size;
321 return NGX_DECLINED;
322 }
323
324 do {
325 entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated];
326 h2c->hpack.free += 32 + entry->name.len + entry->value.len;
327 } while (size > h2c->hpack.free);
328
329 h2c->hpack.free -= size;
330
331 return NGX_OK;
332 }
333
334
335 ngx_int_t
ngx_http_v2_table_size(ngx_http_v2_connection_t * h2c,size_t size)336 ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size)
337 {
338 ssize_t needed;
339 ngx_http_v2_header_t *entry;
340
341 if (size > NGX_HTTP_V2_TABLE_SIZE) {
342 ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
343 "client sent invalid table size update: %uz", size);
344
345 return NGX_ERROR;
346 }
347
348 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
349 "http2 new hpack table size: %uz was:%uz",
350 size, h2c->hpack.size);
351
352 needed = h2c->hpack.size - size;
353
354 while (needed > (ssize_t) h2c->hpack.free) {
355 entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated];
356 h2c->hpack.free += 32 + entry->name.len + entry->value.len;
357 }
358
359 h2c->hpack.size = size;
360 h2c->hpack.free -= needed;
361
362 return NGX_OK;
363 }
364