Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 1 | /* |
| 2 | * HPACK compressor (RFC7541) |
| 3 | * |
| 4 | * Copyright (C) 2014-2017 Willy Tarreau <willy@haproxy.org> |
| 5 | * Copyright (C) 2017 HAProxy Technologies |
| 6 | * |
| 7 | * Permission is hereby granted, free of charge, to any person obtaining |
| 8 | * a copy of this software and associated documentation files (the |
| 9 | * "Software"), to deal in the Software without restriction, including |
| 10 | * without limitation the rights to use, copy, modify, merge, publish, |
| 11 | * distribute, sublicense, and/or sell copies of the Software, and to |
| 12 | * permit persons to whom the Software is furnished to do so, subject to |
| 13 | * the following conditions: |
| 14 | * |
| 15 | * The above copyright notice and this permission notice shall be |
| 16 | * included in all copies or substantial portions of the Software. |
| 17 | * |
| 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
| 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 25 | * OTHER DEALINGS IN THE SOFTWARE. |
| 26 | */ |
| 27 | |
| 28 | #ifndef _COMMON_HPACK_ENC_H |
| 29 | #define _COMMON_HPACK_ENC_H |
| 30 | |
Willy Tarreau | bd5659b | 2018-12-11 07:44:19 +0100 | [diff] [blame] | 31 | #include <string.h> |
Willy Tarreau | 4c7e4b7 | 2020-05-27 12:58:42 +0200 | [diff] [blame] | 32 | #include <haproxy/api.h> |
Willy Tarreau | 2df026f | 2018-12-11 09:03:07 +0100 | [diff] [blame] | 33 | #include <common/buf.h> |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 34 | #include <common/http.h> |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 35 | #include <common/ist.h> |
| 36 | |
Willy Tarreau | 83061a8 | 2018-07-13 11:56:34 +0200 | [diff] [blame] | 37 | int hpack_encode_header(struct buffer *out, const struct ist n, |
| 38 | const struct ist v); |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 39 | |
Willy Tarreau | bad0a38 | 2018-12-11 07:44:00 +0100 | [diff] [blame] | 40 | /* Returns the number of bytes required to encode the string length <len>. The |
| 41 | * number of usable bits is an integral multiple of 7 plus 6 for the last byte. |
| 42 | * The maximum number of bytes returned is 4 (2097279 max length). Larger values |
| 43 | * return 0. |
| 44 | */ |
| 45 | static inline int hpack_len_to_bytes(size_t len) |
| 46 | { |
| 47 | ssize_t slen = len; |
| 48 | |
| 49 | slen -= 127; |
| 50 | if (__builtin_expect(slen < 0, 1)) |
| 51 | return 1; |
| 52 | if (slen < (1 << 14)) { |
| 53 | if (__builtin_expect(slen < (1 << 7), 1)) |
| 54 | return 2; |
| 55 | else |
| 56 | return 3; |
| 57 | } |
| 58 | if (slen < (1 << 21)) |
| 59 | return 4; |
| 60 | return 0; |
| 61 | } |
| 62 | |
| 63 | /* Encodes <len> into <out>+<pos> and return the new position. The caller is |
| 64 | * responsible for checking for available room using hpack_len_to_bytes() |
| 65 | * first. |
| 66 | */ |
| 67 | static inline int hpack_encode_len(char *out, int pos, int len) |
| 68 | { |
| 69 | int code = len - 127; |
| 70 | |
| 71 | if (code < 0) { |
| 72 | out[pos++] = len; |
| 73 | } else { |
| 74 | out[pos++] = 127; |
| 75 | for (; code >= 128; code >>= 7) |
| 76 | out[pos++] = code | 128; |
| 77 | out[pos++] = code; |
| 78 | } |
| 79 | return pos; |
| 80 | } |
| 81 | |
Willy Tarreau | 30eb809 | 2018-12-11 06:16:45 +0100 | [diff] [blame] | 82 | /* Tries to encode header field index <idx> with short value <val> into the |
| 83 | * aligned buffer <out>. Returns non-zero on success, 0 on failure (buffer |
| 84 | * full). The caller is responsible for ensuring that the length of <val> is |
| 85 | * strictly lower than 127, and that <idx> is lower than 64 (static list only), |
| 86 | * and that the buffer is aligned (head==0). |
| 87 | */ |
| 88 | static inline int hpack_encode_short_idx(struct buffer *out, int idx, struct ist val) |
| 89 | { |
| 90 | if (out->data + 2 + val.len > out->size) |
| 91 | return 0; |
| 92 | |
| 93 | /* literal header field with incremental indexing */ |
| 94 | out->area[out->data++] = idx | 0x40; |
| 95 | out->area[out->data++] = val.len; |
| 96 | ist2bin(&out->area[out->data], val); |
| 97 | out->data += val.len; |
| 98 | return 1; |
| 99 | } |
| 100 | |
Willy Tarreau | bd5659b | 2018-12-11 07:44:19 +0100 | [diff] [blame] | 101 | /* Tries to encode header field index <idx> with long value <val> into the |
| 102 | * aligned buffer <out>. Returns non-zero on success, 0 on failure (buffer |
| 103 | * full). The caller is responsible for ensuring <idx> is lower than 64 (static |
| 104 | * list only), and that the buffer is aligned (head==0). |
| 105 | */ |
| 106 | static inline int hpack_encode_long_idx(struct buffer *out, int idx, struct ist val) |
| 107 | { |
| 108 | int len = out->data; |
| 109 | |
| 110 | if (!hpack_len_to_bytes(val.len) || |
| 111 | 1 + len + hpack_len_to_bytes(val.len) + val.len > out->size) |
| 112 | return 0; |
| 113 | |
| 114 | /* emit literal with indexing (7541#6.2.1) : |
| 115 | * [ 0 | 1 | Index (6+) ] |
| 116 | */ |
| 117 | out->area[len++] = idx | 0x40; |
| 118 | len = hpack_encode_len(out->area, len, val.len); |
| 119 | memcpy(out->area + len, val.ptr, val.len); |
| 120 | len += val.len; |
| 121 | |
| 122 | out->data = len; |
| 123 | return 1; |
| 124 | } |
| 125 | |
Willy Tarreau | 8895367 | 2018-12-10 18:04:42 +0100 | [diff] [blame] | 126 | /* Tries to encode a :status pseudo-header with the integer status <status> |
| 127 | * into the aligned buffer <out>. Returns non-zero on success, 0 on failure |
| 128 | * (buffer full). The caller is responsible for ensuring that the status is |
| 129 | * comprised between 100 and 999 inclusive and that the buffer is aligned. It's |
| 130 | * inlined because it's easily optimizable by the compiler. |
| 131 | */ |
| 132 | static inline int hpack_encode_int_status(struct buffer *out, unsigned int status) |
| 133 | { |
| 134 | int len = out->data; |
| 135 | int size = out->size; |
| 136 | unsigned char c = 0; |
| 137 | |
| 138 | /* try to emit a single byte code */ |
| 139 | len++; |
| 140 | if (__builtin_expect(len > size, 0)) |
| 141 | goto fail; |
| 142 | |
| 143 | c = (status <= 304) ? |
| 144 | (status <= 204) ? |
| 145 | (status == 204) ? 0x89 : |
| 146 | (status == 200) ? 0x88 : |
| 147 | 0: /* > 204 */ |
| 148 | (status == 304) ? 0x8b : |
| 149 | (status == 206) ? 0x8a : |
| 150 | 0: |
| 151 | (status <= 404) ? |
| 152 | (status == 404) ? 0x8d : |
| 153 | (status == 400) ? 0x8c : |
| 154 | 0: /* > 404 */ |
| 155 | (status == 500) ? 0x8e : |
| 156 | 0; |
| 157 | |
| 158 | if (c) |
| 159 | goto last; |
| 160 | |
| 161 | /* fall back to literal */ |
| 162 | len += 4; |
| 163 | if (__builtin_expect(len > size, 0)) |
| 164 | goto fail; |
| 165 | |
| 166 | /* basic encoding of the status code */ |
| 167 | out->area[len - 5] = 0x48; // indexed name -- name=":status" (idx 8) |
| 168 | out->area[len - 4] = 0x03; // 3 bytes status |
| 169 | out->area[len - 3] = '0' + status / 100; |
| 170 | out->area[len - 2] = '0' + status / 10 % 10; |
| 171 | c = '0' + status % 10; |
| 172 | last: |
| 173 | out->area[len - 1] = c; |
| 174 | out->data = len; |
| 175 | return 1; |
| 176 | fail: |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | /* Tries to encode a :status pseudo-header with the integer status <status> |
| 181 | * also represented by <str> into the aligned buffer <out>. Returns non-zero |
| 182 | * on success or 0 on failure (buffer full). The caller is responsible for |
| 183 | * ensuring that the status is comprised between 100 and 999 inclusive, that |
| 184 | * <str> contains a valid representation of the numerical value, and that the |
| 185 | * buffer is aligned. This version is preferred when the caller already knows |
| 186 | * a string representation of the status because it avoids the computation in |
| 187 | * the uncompressed case. It's inlined because it's easily optimizable. |
| 188 | */ |
| 189 | static inline int hpack_encode_str_status(struct buffer *out, unsigned int status, struct ist str) |
| 190 | { |
| 191 | /* don't try too hard, we already have the ASCII value for less common cases */ |
| 192 | if (status == 200 || status == 304) { |
| 193 | if (out->data >= out->size) |
| 194 | return 0; |
| 195 | out->area[out->data] = (status == 304) ? 0x8b : 0x88; |
| 196 | out->data++; |
| 197 | return 1; |
| 198 | } |
| 199 | return hpack_encode_short_idx(out, 8, str); // name=":status" (idx 8) |
| 200 | } |
| 201 | |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 202 | /* Tries to encode a :method pseudo-header with the method in <meth>, which |
| 203 | * also exists as a string in <str>, into the aligned buffer <out>. Returns |
| 204 | * non-zero on success or 0 on failure (buffer full). The caller is responsible |
| 205 | * for ensuring that the string matches <meth>, that it's smaller than 127 |
| 206 | * bytes, and that the buffer is aligned. If <meth> is unknown then using |
| 207 | * HTTP_METH_OTHER will lead to the string being encoded as a literal. It's |
| 208 | * inlined because it's easily optimizable. |
| 209 | */ |
| 210 | static inline int hpack_encode_method(struct buffer *out, enum http_meth_t meth, struct ist str) |
| 211 | { |
| 212 | if (out->data < out->size && meth == HTTP_METH_GET) |
| 213 | out->area[out->data++] = 0x82; // indexed field : idx[02]=(":method", "GET") |
| 214 | else if (out->data < out->size && meth == HTTP_METH_POST) |
| 215 | out->area[out->data++] = 0x83; // indexed field : idx[03]=(":method", "POST") |
| 216 | else |
| 217 | return hpack_encode_short_idx(out, 2, str); // name=":method" (idx 2) |
| 218 | return 1; |
| 219 | } |
| 220 | |
Willy Tarreau | 820b391 | 2018-12-10 19:16:20 +0100 | [diff] [blame] | 221 | /* Tries to encode a :scheme pseudo-header with the scheme in <scheme>, into |
| 222 | * the aligned buffer <out>. Returns non-zero on success or 0 on failure |
| 223 | * (buffer full). Only "http" and "https" are recognized and handled as indexed |
| 224 | * values, others are turned into short literals. The caller is responsible for |
| 225 | * ensuring that the scheme is smaller than 127 bytes, and that the buffer is |
| 226 | * aligned. Normally the compiler will detect constant strings in the comparison |
| 227 | * if the code remains inlined. |
| 228 | */ |
| 229 | static inline int hpack_encode_scheme(struct buffer *out, struct ist scheme) |
| 230 | { |
| 231 | if (out->data < out->size && isteq(scheme, ist("https"))) |
| 232 | out->area[out->data++] = 0x87; // indexed field : idx[07]=(":scheme", "https") |
| 233 | else if (out->data < out->size && isteq(scheme, ist("http"))) |
| 234 | out->area[out->data++] = 0x86; // indexed field : idx[06]=(":scheme", "http") |
| 235 | else |
| 236 | return hpack_encode_short_idx(out, 6, scheme); // name=":scheme" (idx 6) |
| 237 | return 1; |
| 238 | } |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 239 | |
Willy Tarreau | eaeeb68 | 2018-12-10 19:26:51 +0100 | [diff] [blame] | 240 | /* Tries to encode a :path pseudo-header with the path in <path>, into the |
| 241 | * aligned buffer <out>. Returns non-zero on success or 0 on failure (buffer |
| 242 | * full). The well-known values "/" and "/index.html" are recognized, and other |
| 243 | * ones are handled as literals. The caller is responsible for ensuring that |
| 244 | * the buffer is aligned. Normally the compiler will detect constant strings |
| 245 | * in the comparison if the code remains inlined. |
| 246 | */ |
| 247 | static inline int hpack_encode_path(struct buffer *out, struct ist path) |
| 248 | { |
| 249 | if (out->data < out->size && isteq(path, ist("/"))) |
| 250 | out->area[out->data++] = 0x84; // indexed field : idx[04]=(":path", "/") |
| 251 | else if (out->data < out->size && isteq(path, ist("/index.html"))) |
| 252 | out->area[out->data++] = 0x85; // indexed field : idx[05]=(":path", "/index.html") |
| 253 | else if (path.len < 127) |
| 254 | return hpack_encode_short_idx(out, 4, path); // name=":path" (idx 4) |
| 255 | else |
| 256 | return hpack_encode_long_idx(out, 4, path); // name=":path" (idx 4) |
| 257 | return 1; |
| 258 | } |
| 259 | |
| 260 | |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 261 | #endif /* _COMMON_HPACK_ENC_H */ |