master
c 428 lines 17.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "http_header.h"
4
5 #include <string.h>
6 #include <strings.h>
7
8 static void web_client_enable_deflate(struct web_client *w, bool gzip) {
9 if(gzip)
10 web_client_flag_set(w, WEB_CLIENT_ENCODING_GZIP);
11 else
12 web_client_flag_set(w, WEB_CLIENT_ENCODING_DEFLATE);
13
14 if(!web_client_check_conn_unix(w) && !web_client_check_conn_tcp(w) && !web_client_check_conn_cloud(w))
15 return;
16
17 if(unlikely(w->response.zinitialized)) {
18 // compression has already been initialized for this client.
19 return;
20 }
21
22 if(unlikely(w->response.sent)) {
23 netdata_log_error("%llu: Cannot enable compression in the middle of a conversation.", w->id);
24 return;
25 }
26
27 w->response.zstream.zalloc = Z_NULL;
28 w->response.zstream.zfree = Z_NULL;
29 w->response.zstream.opaque = Z_NULL;
30
31 w->response.zstream.next_in = (Bytef *)w->response.data->buffer;
32 w->response.zstream.avail_in = 0;
33 w->response.zstream.total_in = 0;
34
35 w->response.zstream.next_out = w->response.zbuffer;
36 w->response.zstream.avail_out = 0;
37 w->response.zstream.total_out = 0;
38
39 w->response.zstream.zalloc = Z_NULL;
40 w->response.zstream.zfree = Z_NULL;
41 w->response.zstream.opaque = Z_NULL;
42
43 // Select GZIP compression: windowbits = 15 + 16 = 31
44 if(deflateInit2(&w->response.zstream, web_gzip_level, Z_DEFLATED, 15 + ((gzip)?16:0), 8, web_gzip_strategy) != Z_OK) {
45 netdata_log_error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
46 return;
47 }
48
49 w->response.zsent = 0;
50 w->response.zoutput = true;
51 w->response.zinitialized = true;
52
53 if(!web_client_check_conn_cloud(w))
54 // cloud sends the entire response at once, not in chunks
55 web_client_flag_set(w, WEB_CLIENT_CHUNKED_TRANSFER);
56
57 netdata_log_debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
58 }
59
60 static void http_header_origin(struct web_client *w, const char *v, size_t len __maybe_unused) {
61 freez(w->origin);
62 w->origin = strdupz(v);
63 }
64
65 static void http_header_connection(struct web_client *w, const char *v, size_t len __maybe_unused) {
66 if(strcasestr(v, "keep-alive"))
67 web_client_enable_keepalive(w);
68
69 // Check for WebSocket upgrade request
70 if(strcasestr(v, "upgrade"))
71 web_client_set_websocket_handshake(w);
72 }
73
74 static void http_header_dnt(struct web_client *w, const char *v, size_t len __maybe_unused) {
75 if(respect_web_browser_do_not_track_policy) {
76 if (*v == '0') web_client_disable_donottrack(w);
77 else if (*v == '1') web_client_enable_donottrack(w);
78 }
79 }
80
81 static void http_header_user_agent(struct web_client *w, const char *v, size_t len __maybe_unused) {
82 if(w->mode == HTTP_REQUEST_MODE_STREAM) {
83 freez(w->user_agent);
84 w->user_agent = strdupz(v);
85 }
86 }
87
88 static void http_header_accept(struct web_client *w, const char *v, size_t len __maybe_unused) {
89 web_client_flag_clear(w, WEB_CLIENT_FLAG_ACCEPT_JSON |
90 WEB_CLIENT_FLAG_ACCEPT_SSE |
91 WEB_CLIENT_FLAG_ACCEPT_TEXT);
92
93 for (const char *p = v; p && *p; ) {
94 while (*p == ' ' || *p == '\t' || *p == ',') p++;
95 if (!*p)
96 break;
97
98 const char *start = p;
99 while (*p && *p != ',' && *p != ';')
100 p++;
101 size_t length = (size_t)(p - start);
102
103 while (*p && *p != ',')
104 p++;
105
106 if (length == 0)
107 continue;
108
109 if (length >= strlen("application/json") &&
110 strncasecmp(start, "application/json", strlen("application/json")) == 0) {
111 web_client_flag_set(w, WEB_CLIENT_FLAG_ACCEPT_JSON);
112 }
113 else if (length >= strlen("text/event-stream") &&
114 strncasecmp(start, "text/event-stream", strlen("text/event-stream")) == 0) {
115 web_client_flag_set(w, WEB_CLIENT_FLAG_ACCEPT_SSE);
116 }
117 else if (length >= strlen("text/plain") &&
118 strncasecmp(start, "text/plain", strlen("text/plain")) == 0) {
119 web_client_flag_set(w, WEB_CLIENT_FLAG_ACCEPT_TEXT);
120 }
121 }
122 }
123
124 static void http_header_x_auth_token(struct web_client *w, const char *v, size_t len __maybe_unused) {
125 freez(w->auth_bearer_token);
126 w->auth_bearer_token = strdupz(v);
127 }
128
129 static void http_header_host(struct web_client *w, const char *v, size_t len) {
130 char buffer[NI_MAXHOST];
131 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
132 freez(w->server_host);
133 w->server_host = strdupz(buffer);
134 }
135
136 static void http_header_accept_encoding(struct web_client *w, const char *v, size_t len __maybe_unused) {
137 if(web_enable_gzip) {
138 if(strcasestr(v, "gzip"))
139 web_client_enable_deflate(w, true);
140
141 // does not seem to work
142 // else if(strcasestr(v, "deflate"))
143 // web_client_enable_deflate(w, 0);
144 }
145 }
146
147 static void http_header_x_forwarded_host(struct web_client *w, const char *v, size_t len) {
148 char buffer[NI_MAXHOST];
149 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
150 freez(w->forwarded_host);
151 w->forwarded_host = strdupz(buffer);
152 }
153
154 static void http_header_x_forwarded_for(struct web_client *w, const char *v, size_t len) {
155 if(len)
156 strncpyz(w->user_auth.forwarded_for, v,
157 (len < sizeof(w->user_auth.forwarded_for) - 1 ? len : sizeof(w->user_auth.forwarded_for) - 1));
158 }
159
160 static void http_header_x_transaction_id(struct web_client *w, const char *v, size_t len) {
161 char buffer[UUID_STR_LEN * 2];
162 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
163 (void) uuid_parse_flexi(buffer, w->transaction); // will not alter w->transaction if it fails
164 }
165
166 static void http_header_x_netdata_account_id(struct web_client *w, const char *v, size_t len) {
167 if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
168 char buffer[UUID_STR_LEN * 2];
169 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
170 (void) uuid_parse_flexi(buffer, w->user_auth.cloud_account_id.uuid); // will not alter w->cloud_account_id if it fails
171 }
172 }
173
174 static void http_header_x_netdata_role(struct web_client *w, const char *v, size_t len) {
175 if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
176 char buffer[100];
177 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
178 if (strcasecmp(buffer, "admin") == 0)
179 w->user_auth.user_role = HTTP_USER_ROLE_ADMIN;
180 else if(strcasecmp(buffer, "manager") == 0)
181 w->user_auth.user_role = HTTP_USER_ROLE_MANAGER;
182 else if(strcasecmp(buffer, "troubleshooter") == 0)
183 w->user_auth.user_role = HTTP_USER_ROLE_TROUBLESHOOTER;
184 else if(strcasecmp(buffer, "observer") == 0)
185 w->user_auth.user_role = HTTP_USER_ROLE_OBSERVER;
186 else if(strcasecmp(buffer, "member") == 0)
187 w->user_auth.user_role = HTTP_USER_ROLE_MEMBER;
188 else if(strcasecmp(buffer, "billing") == 0)
189 w->user_auth.user_role = HTTP_USER_ROLE_BILLING;
190 else
191 w->user_auth.user_role = HTTP_USER_ROLE_MEMBER;
192 }
193 }
194
195 static void http_header_x_netdata_permissions(struct web_client *w, const char *v, size_t len __maybe_unused) {
196 if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
197 HTTP_ACCESS access = http_access_from_hex(v);
198 web_client_set_permissions(w, access, w->user_auth.user_role, USER_AUTH_METHOD_CLOUD);
199 }
200 }
201
202 static void http_header_x_netdata_user_name(struct web_client *w, const char *v, size_t len) {
203 if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
204 strncpyz(w->user_auth.client_name, v, (len < sizeof(w->user_auth.client_name) - 1 ? len : sizeof(w->user_auth.client_name) - 1));
205 }
206 }
207
208 static void http_header_x_netdata_auth(struct web_client *w, const char *v, size_t len __maybe_unused) {
209 if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK)
210 // we don't need authorization bearer when the request comes from netdata cloud
211 return;
212
213 if(strncasecmp(v, "Bearer ", 7) == 0) {
214 v = &v[7];
215 while(*v && isspace((uint8_t)*v)) v++;
216 web_client_bearer_token_auth(w, v);
217 }
218 }
219
220 // MCP HTTP transport session identifier
221 static void http_header_mcp_session_id(struct web_client *w, const char *v, size_t len __maybe_unused) {
222 if (uuid_parse(v, w->mcp_session_id) != 0)
223 uuid_clear(w->mcp_session_id);
224 }
225
226 // Handle WebSocket-specific headers
227 static void http_header_upgrade(struct web_client *w, const char *v, size_t len __maybe_unused) {
228 if(strcasecmp(v, "websocket") == 0) {
229 web_client_set_websocket(w);
230 }
231 }
232
233 static void http_header_sec_websocket_key(struct web_client *w, const char *v, size_t len __maybe_unused) {
234 // Store the websocket key for later use in the handshake
235 freez(w->websocket.key);
236 w->websocket.key = strdupz(v);
237 }
238
239 static void http_header_sec_websocket_version(struct web_client *w, const char *v, size_t len __maybe_unused) {
240 // We only support version 13, which will be checked during handshake
241 // No need to store this as we only accept one version
242 if(strcmp(v, "13") != 0) {
243 netdata_log_debug(D_WEB_CLIENT, "%llu: WebSocket version %s not supported, only version 13 is supported", w->id, v);
244 web_client_clear_websocket(w);
245 }
246 }
247
248 static void http_header_sec_websocket_protocol(struct web_client *w, const char *v, size_t len __maybe_unused) {
249 // Store the requested protocols for later evaluation during handshake
250 w->websocket.protocol = WEBSOCKET_PROTOCOL_2id(v);
251 }
252
253 static void http_header_sec_websocket_extensions(struct web_client *w, const char *v, size_t len __maybe_unused) {
254 // Reset extension flags
255 w->websocket.ext_flags = WS_EXTENSION_NONE;
256
257 // Check if "permessage-deflate" is requested
258 if (strstr(v, "permessage-deflate") != NULL) {
259 // Parse extension parameters
260 char extension_copy[1024];
261 strncpyz(extension_copy, v, sizeof(extension_copy) - 1);
262
263 char *token, *saveptr;
264 token = strtok_r(extension_copy, ",", &saveptr);
265
266 while (token) {
267 // Trim leading/trailing spaces
268 char *ext = token;
269 while (*ext && isspace(*ext)) ext++;
270 char *end = ext + strlen(ext) - 1;
271 while (end > ext && isspace(*end)) *end-- = '\0';
272
273 // Check if this is permessage-deflate extension
274 if (strncmp(ext, "permessage-deflate", 18) == 0) {
275 w->websocket.ext_flags |= WS_EXTENSION_PERMESSAGE_DEFLATE;
276
277 // Parse parameters
278 char *params = ext + 18;
279 if (*params == ';') {
280 params++;
281
282 char *param, *param_saveptr;
283 param = strtok_r(params, ";", &param_saveptr);
284
285 while (param) {
286 // Trim leading/trailing spaces
287 while (*param && isspace(*param)) param++;
288 end = param + strlen(param) - 1;
289 while (end > param && isspace(*end)) *end-- = '\0';
290
291 // Client no context takeover
292 if (strcmp(param, "client_no_context_takeover") == 0)
293 w->websocket.ext_flags |= WS_EXTENSION_CLIENT_NO_CONTEXT_TAKEOVER;
294
295 // Server no context takeover
296 else if (strcmp(param, "server_no_context_takeover") == 0)
297 w->websocket.ext_flags |= WS_EXTENSION_SERVER_NO_CONTEXT_TAKEOVER;
298
299 // Server max window bits
300 else if (strncmp(param, "server_max_window_bits=", 23) == 0) {
301 w->websocket.server_max_window_bits = str2u(param + 23);
302 if(w->websocket.server_max_window_bits >= 8 && w->websocket.server_max_window_bits <= 15)
303 w->websocket.ext_flags |= WS_EXTENSION_SERVER_MAX_WINDOW_BITS;
304 }
305 // Server max window bits without value
306 else if (strcmp(param, "server_max_window_bits") == 0) {
307 w->websocket.ext_flags |= WS_EXTENSION_SERVER_MAX_WINDOW_BITS;
308 w->websocket.server_max_window_bits = 0; // Default
309 }
310
311 // Client max window bits with value
312 else if (strncmp(param, "client_max_window_bits=", 23) == 0) {
313 w->websocket.client_max_window_bits = str2u(param + 23);
314 if(w->websocket.client_max_window_bits >= 8 && w->websocket.client_max_window_bits <= 15)
315 w->websocket.ext_flags |= WS_EXTENSION_CLIENT_MAX_WINDOW_BITS;
316 }
317 // Client max window bits without value
318 else if (strcmp(param, "client_max_window_bits") == 0) {
319 w->websocket.ext_flags |= WS_EXTENSION_CLIENT_MAX_WINDOW_BITS;
320 w->websocket.client_max_window_bits = 0; // Default
321 }
322
323 param = strtok_r(NULL, ";", &param_saveptr);
324 }
325 }
326
327 break; // Found and parsed permessage-deflate
328 }
329
330 token = strtok_r(NULL, ",", &saveptr);
331 }
332
333 netdata_log_debug(D_WEB_CLIENT, "%llu: Client requested WebSocket extensions: %s, "
334 "enabled flags: %u, client_max_window_bits: %u, server_max_window_bits: %u",
335 w->id, v, w->websocket.ext_flags,
336 w->websocket.client_max_window_bits,
337 w->websocket.server_max_window_bits);
338 }
339 }
340
341 struct {
342 uint32_t hash;
343 const char *key;
344 void (*cb)(struct web_client *w, const char *value, size_t value_len);
345 } supported_headers[] = {
346 { .hash = 0, .key = "Origin", .cb = http_header_origin },
347 { .hash = 0, .key = "Connection", .cb = http_header_connection },
348 { .hash = 0, .key = "DNT", .cb = http_header_dnt },
349 { .hash = 0, .key = "User-Agent", .cb = http_header_user_agent},
350 { .hash = 0, .key = "Accept", .cb = http_header_accept },
351 { .hash = 0, .key = "X-Auth-Token", .cb = http_header_x_auth_token },
352 { .hash = 0, .key = "Host", .cb = http_header_host },
353 { .hash = 0, .key = "Accept-Encoding", .cb = http_header_accept_encoding },
354 { .hash = 0, .key = "X-Forwarded-Host", .cb = http_header_x_forwarded_host },
355 { .hash = 0, .key = "X-Forwarded-For", .cb = http_header_x_forwarded_for },
356 { .hash = 0, .key = "X-Transaction-Id", .cb = http_header_x_transaction_id },
357 { .hash = 0, .key = "X-Netdata-Account-Id", .cb = http_header_x_netdata_account_id },
358 { .hash = 0, .key = "X-Netdata-Role", .cb = http_header_x_netdata_role },
359 { .hash = 0, .key = "X-Netdata-Permissions", .cb = http_header_x_netdata_permissions },
360 { .hash = 0, .key = "X-Netdata-User-Name", .cb = http_header_x_netdata_user_name },
361 { .hash = 0, .key = "X-Netdata-Auth", .cb = http_header_x_netdata_auth },
362
363 // WebSocket headers
364 { .hash = 0, .key = "Upgrade", .cb = http_header_upgrade },
365 { .hash = 0, .key = "Sec-WebSocket-Key", .cb = http_header_sec_websocket_key },
366 { .hash = 0, .key = "Sec-WebSocket-Version", .cb = http_header_sec_websocket_version },
367 { .hash = 0, .key = "Sec-WebSocket-Protocol",.cb = http_header_sec_websocket_protocol },
368 { .hash = 0, .key = "Sec-WebSocket-Extensions",.cb = http_header_sec_websocket_extensions },
369
370 // for historical reasons.
371 // there are a few nightly versions of netdata UI that incorrectly use this instead of X-Netdata-Auth
372 { .hash = 0, .key = "Authorization", .cb = http_header_x_netdata_auth },
373
374 // MCP HTTP transport session identifier
375 { .hash = 0, .key = "Mcp-Session-Id", .cb = http_header_mcp_session_id },
376
377 // terminator
378 { .hash = 0, .key = NULL, .cb = NULL }
379 };
380
381 char *http_header_parse_line(struct web_client *w, char *s) {
382 if(unlikely(!supported_headers[0].hash)) {
383 // initialize the hashes, the first time it runs
384
385 for(size_t i = 0; supported_headers[i].key ;i++)
386 supported_headers[i].hash = simple_uhash(supported_headers[i].key);
387 }
388
389 char *e = s;
390
391 // find the colon
392 while(*e && *e != ':') e++;
393 if(!*e) return e;
394
395 // get the name
396 *e = '\0';
397
398 // find the value
399 char *v = e + 1, *ve;
400
401 // skip leading spaces from value
402 while(*v == ' ') v++;
403 ve = v;
404
405 // find the \r
406 while(*ve && *ve != '\r') ve++;
407 if(!*ve || ve[1] != '\n') {
408 *e = ':';
409 return ve;
410 }
411
412 // terminate the value
413 *ve = '\0';
414
415 uint32_t hash = simple_uhash(s);
416
417 for(size_t i = 0; supported_headers[i].key ;i++) {
418 if(likely(hash != supported_headers[i].hash || strcasecmp(s, supported_headers[i].key) != 0))
419 continue;
420
421 supported_headers[i].cb(w, v, ve - v);
422 break;
423 }
424
425 *e = ':';
426 *ve = '\r';
427 return ve;
428 }