GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
#include <stddef.h> /* size_t */ |
||
2 |
#include <math.h> /* log10 */ |
||
3 |
#include <stdio.h> /* printf, fflush, fprintf, fopen, fclose */ |
||
4 |
#include <string.h> /* strerror, memset, strncpy, memcpy, strlen, |
||
5 |
strtok, strtok_r */ |
||
6 |
|||
7 |
#include <sys/types.h> |
||
8 |
#include <ctype.h> /* isspace */ |
||
9 |
|||
10 |
#include "header.h" |
||
11 |
#include "alloc.h" |
||
12 |
#include "b64.h" |
||
13 |
#include "httpstatuscodes.h" |
||
14 |
#include "session.h" |
||
15 |
#include "log.h" |
||
16 |
#include "str.h" |
||
17 |
#include "subprotocols.h" |
||
18 |
#include "extensions.h" |
||
19 |
#include "predict.h" |
||
20 |
|||
21 |
/** |
||
22 |
* Acceptable HTTP methods |
||
23 |
*/ |
||
24 |
const char *methods[] = { |
||
25 |
"GET", |
||
26 |
// "OPTIONS", |
||
27 |
// "HEAD", |
||
28 |
// "POST", |
||
29 |
// "PUT", |
||
30 |
// "DELETE", |
||
31 |
// "TRACE", |
||
32 |
// "CONNECT" |
||
33 |
}; |
||
34 |
|||
35 |
/** |
||
36 |
* Acceptable HTTP versions |
||
37 |
*/ |
||
38 |
const char *versions[] = { |
||
39 |
// "HTTP/0.9", |
||
40 |
// "HTTP/1.0", |
||
41 |
"HTTP/1.1", |
||
42 |
// "HTTP/2.0", |
||
43 |
}; |
||
44 |
|||
45 |
/** |
||
46 |
* Trims a string for leading and trailing whitespace. |
||
47 |
* |
||
48 |
* @param str [char *] "The string to trim" |
||
49 |
* @return [char *] "The input string without leading and trailing spaces" |
||
50 |
*/ |
||
51 |
130 |
static inline char *trim(char* str) |
|
52 |
{ |
||
53 |
✓✓ | 130 |
if ( unlikely(NULL == str) ) { |
54 |
return NULL; |
||
55 |
} |
||
56 |
|||
57 |
✓✗ | 105 |
if ( unlikely(str[0] == '\0') ) { |
58 |
return str; |
||
59 |
} |
||
60 |
105 |
int start, end = strlen(str); |
|
61 |
✓✓ | 160 |
for (start = 0; isspace(str[start]); ++start) {} |
62 |
✓✗ | 105 |
if (str[start]) { |
63 |
✓✗✓✓ |
109 |
while (end > 0 && isspace(str[end-1])) |
64 |
4 |
--end; |
|
65 |
105 |
memmove(str, &str[start], end - start); |
|
66 |
} |
||
67 |
105 |
str[end - start] = '\0'; |
|
68 |
|||
69 |
105 |
return str; |
|
70 |
} |
||
71 |
|||
72 |
13 |
static inline void header_set_version(wss_header_t *header, char *v) { |
|
73 |
13 |
int version = strtol(strtok_r(NULL, "", &v), (char **) NULL, 10); |
|
74 |
|||
75 |
✓✗ | 13 |
if ( likely(header->ws_version < version) ) { |
76 |
✓✓ | 13 |
if ( unlikely(version == 4) ) { |
77 |
2 |
header->ws_type = HYBI04; |
|
78 |
2 |
header->ws_version = version; |
|
79 |
✓✓ | 11 |
} else if( unlikely(version == 5) ) { |
80 |
2 |
header->ws_type = HYBI05; |
|
81 |
2 |
header->ws_version = version; |
|
82 |
✓✓ | 9 |
} else if( unlikely(version == 6) ) { |
83 |
2 |
header->ws_type = HYBI06; |
|
84 |
2 |
header->ws_version = version; |
|
85 |
✓✓ | 7 |
} else if( unlikely(version == 7) ) { |
86 |
2 |
header->ws_type = HYBI07; |
|
87 |
2 |
header->ws_version = version; |
|
88 |
✓✓ | 5 |
} else if( unlikely(version == 8) ) { |
89 |
2 |
header->ws_type = HYBI10; |
|
90 |
2 |
header->ws_version = version; |
|
91 |
✓✗ | 3 |
} else if( likely(version == 13) ) { |
92 |
3 |
header->ws_type = RFC6455; |
|
93 |
3 |
header->ws_version = version; |
|
94 |
} |
||
95 |
} |
||
96 |
13 |
} |
|
97 |
|||
98 |
|||
99 |
/** |
||
100 |
* Parses a HTTP header into a header structure and returns the status code |
||
101 |
* appropriate. |
||
102 |
* |
||
103 |
* @param fd [int] "The filedescriptor" |
||
104 |
* @param header [wss_header_t *] "The header structure to fill" |
||
105 |
* @param config [wss_config_t *] "The configuration of the server" |
||
106 |
* @return [enum HttpStatus_Code] "The status code to return to the client" |
||
107 |
*/ |
||
108 |
60 |
enum HttpStatus_Code WSS_parse_header(int fd, wss_header_t *header, wss_config_t *config) { |
|
109 |
60 |
bool valid, in_use, double_clrf = false; |
|
110 |
60 |
size_t i, line_length; |
|
111 |
60 |
char *lineptr, *temp, *line, *sep, *accepted; |
|
112 |
60 |
char *tokenptr = NULL, *sepptr = NULL, *paramptr = NULL; |
|
113 |
60 |
char *token, *name; |
|
114 |
60 |
wss_subprotocol_t *proto; |
|
115 |
60 |
wss_extension_t *ext; |
|
116 |
60 |
char *exts = NULL; |
|
117 |
60 |
unsigned int header_size = 0; |
|
118 |
60 |
size_t extensions_length = 0; |
|
119 |
|||
120 |
60 |
WSS_log_trace("Parsing HTTP header"); |
|
121 |
|||
122 |
✓✓ | 60 |
if ( unlikely(NULL == header->content) ) { |
123 |
1 |
WSS_log_trace("No header content"); |
|
124 |
1 |
return HttpStatus_BadRequest; |
|
125 |
} |
||
126 |
|||
127 |
59 |
token = strtok_r(header->content, "\r", &tokenptr); |
|
128 |
|||
129 |
✓✓ | 59 |
if ( likely(tokenptr[0] == '\n') ) { |
130 |
57 |
tokenptr++; |
|
131 |
✓✓ | 2 |
} else if ( unlikely(tokenptr[0] != '\0') ) { |
132 |
1 |
WSS_log_trace("Line shall always end with newline character"); |
|
133 |
1 |
return HttpStatus_BadRequest; |
|
134 |
} |
||
135 |
|||
136 |
✓✓ | 58 |
if ( unlikely(NULL == token) ) { |
137 |
1 |
WSS_log_trace("No first line of header"); |
|
138 |
1 |
return HttpStatus_BadRequest; |
|
139 |
} |
||
140 |
|||
141 |
/** |
||
142 |
* Receiving and checking method of the request |
||
143 |
*/ |
||
144 |
✓✓ | 57 |
if ( unlikely(NULL == (header->method = strtok_r(token, " ", &lineptr))) ) { |
145 |
1 |
WSS_log_trace("Unable to parse header method"); |
|
146 |
1 |
return HttpStatus_BadRequest; |
|
147 |
} |
||
148 |
|||
149 |
✓✓ | 56 |
if ( unlikely(strinarray(header->method, methods, |
150 |
sizeof(methods)/sizeof(methods[0])) != 0) ) { |
||
151 |
2 |
WSS_log_trace("Method that the client is using is unknown."); |
|
152 |
2 |
return HttpStatus_MethodNotAllowed; |
|
153 |
} |
||
154 |
|||
155 |
/** |
||
156 |
* Receiving the path of the request |
||
157 |
*/ |
||
158 |
✓✓ | 54 |
if ( unlikely(NULL == (header->path = strtok_r(NULL, " ", &lineptr))) ) { |
159 |
1 |
WSS_log_trace("Unable to parse header path"); |
|
160 |
1 |
return HttpStatus_BadRequest; |
|
161 |
} |
||
162 |
|||
163 |
✓✓ | 53 |
if ( unlikely(strlen(header->path) > config->size_uri) ) { |
164 |
1 |
WSS_log_trace("The size of the request URI was too large"); |
|
165 |
1 |
return HttpStatus_URITooLong; |
|
166 |
✓✓✓✗ ✓✗✓✗ ✓✓ |
52 |
} else if ( unlikely(strncmp("/", header->path, sizeof(char)*1) != 0 && |
167 |
strncasecmp("ws://", header->path, sizeof(char)*5) != 0 && |
||
168 |
strncasecmp("wss://", header->path, sizeof(char)*6) != 0 && |
||
169 |
strncasecmp("http://", header->path, sizeof(char)*7) != 0 && |
||
170 |
strncasecmp("https://", header->path, sizeof(char)*8) != 0) ) { |
||
171 |
1 |
WSS_log_trace("The request URI is not absolute URI nor relative path."); |
|
172 |
1 |
return HttpStatus_BadRequest; |
|
173 |
} |
||
174 |
|||
175 |
/** |
||
176 |
* Receiving and checking version of the request |
||
177 |
*/ |
||
178 |
✓✓ | 51 |
if ( unlikely(NULL == (header->version = strtok_r(NULL, " ", &lineptr))) ) { |
179 |
1 |
WSS_log_trace("Unable to parse header version"); |
|
180 |
1 |
return HttpStatus_BadRequest; |
|
181 |
} |
||
182 |
|||
183 |
✓✓ | 50 |
if ( unlikely(strinarray(header->version, versions, |
184 |
sizeof(versions)/sizeof(versions[0])) != 0) ) { |
||
185 |
1 |
WSS_log_trace("HTTP version that the client is using is unknown."); |
|
186 |
1 |
return HttpStatus_HTTPVersionNotSupported; |
|
187 |
} |
||
188 |
|||
189 |
// We've reached the payload |
||
190 |
✓✗✓✓ ✓✗ |
49 |
if ( unlikely(strlen(token) >= 2 && tokenptr[0] == '\r' && tokenptr[1] == '\n') ) { |
191 |
22 |
tokenptr += 2; |
|
192 |
|||
193 |
✓✓ | 22 |
if ( unlikely(strlen(tokenptr) > config->size_payload) ) { |
194 |
1 |
WSS_log_trace("Payload size received is too large."); |
|
195 |
1 |
return HttpStatus_PayloadTooLarge; |
|
196 |
} |
||
197 |
|||
198 |
21 |
double_clrf = true; |
|
199 |
21 |
header->payload = tokenptr; |
|
200 |
21 |
token = NULL; |
|
201 |
} else { |
||
202 |
27 |
token = strtok_r(NULL, "\r", &tokenptr); |
|
203 |
|||
204 |
✓✓ | 27 |
if ( likely(tokenptr[0] == '\n') ) { |
205 |
25 |
tokenptr++; |
|
206 |
✓✓ | 2 |
} else if ( unlikely(tokenptr[0] != '\0') ) { |
207 |
1 |
WSS_log_trace("Line shall always end with newline character"); |
|
208 |
1 |
return HttpStatus_BadRequest; |
|
209 |
} |
||
210 |
} |
||
211 |
|||
212 |
✓✓ | 185 |
while ( likely(NULL != token) ) { |
213 |
143 |
header_size += strlen(token); |
|
214 |
|||
215 |
✓✓ | 143 |
if ( unlikely(header_size > config->size_header) ) { |
216 |
1 |
WSS_log_trace("Header size received is too large."); |
|
217 |
1 |
return HttpStatus_RequestHeaderFieldsTooLarge; |
|
218 |
} |
||
219 |
|||
220 |
142 |
line = strtok_r(token, ":", &lineptr); |
|
221 |
|||
222 |
✓✗ | 142 |
if ( likely(line != NULL) ) { |
223 |
142 |
line_length = strlen(line); |
|
224 |
✓✓ | 142 |
if ( line_length == strlen(SEC_WEBSOCKET_VERSION) && |
225 |
✓✗ | 14 |
strncasecmp(SEC_WEBSOCKET_VERSION, line, line_length) == 0 ) { |
226 |
// The |Sec-WebSocket-Version| header field MUST NOT appear more than once in an HTTP request. |
||
227 |
✓✓ | 14 |
if ( unlikely(header->ws_version != 0) ) { |
228 |
1 |
WSS_log_trace("Sec-WebSocket-Version must only appear once in header"); |
|
229 |
1 |
return HttpStatus_BadRequest; |
|
230 |
} |
||
231 |
|||
232 |
13 |
sep = trim(strtok_r(strtok_r(NULL, "", &lineptr), ",", &sepptr)); |
|
233 |
✓✓ | 26 |
while (NULL != sep) { |
234 |
13 |
header_set_version(header, sep); |
|
235 |
|||
236 |
13 |
sep = trim(strtok_r(NULL, ",", &sepptr)); |
|
237 |
} |
||
238 |
✓✓ | 128 |
} else if ( line_length == strlen(UPGRADE_STRING) && |
239 |
✓✗ | 20 |
strncasecmp(UPGRADE_STRING, line, line_length) == 0 ) { |
240 |
20 |
header->ws_upgrade = (strtok_r(NULL, "", &lineptr)+1); |
|
241 |
✓✓ | 108 |
} else if ( line_length == strlen(ORIGIN_STRING) && |
242 |
✓✓ | 16 |
strncasecmp(ORIGIN_STRING, line, line_length) == 0 ) { |
243 |
14 |
header->ws_origin = (strtok_r(NULL, "", &lineptr)+1); |
|
244 |
✓✓ | 94 |
} else if ( line_length == strlen(CONNECTION_STRING) && |
245 |
✓✗ | 20 |
strncasecmp(CONNECTION_STRING, line, line_length) == 0 ) { |
246 |
20 |
header->ws_connection = (strtok_r(NULL, "", &lineptr)+1); |
|
247 |
✓✓ | 74 |
} else if ( line_length == strlen(COOKIE_STRING) && |
248 |
✓✗ | 2 |
strncasecmp(COOKIE_STRING, line, line_length) == 0 ) { |
249 |
2 |
header->cookies = (strtok_r(NULL, "", &lineptr)+1); |
|
250 |
✓✓ | 72 |
} else if ( line_length == strlen(SEC_WEBSOCKET_PROTOCOL) && |
251 |
✓✗ | 14 |
strncasecmp(SEC_WEBSOCKET_PROTOCOL, line, line_length) == 0 ) { |
252 |
✓✗ | 14 |
if (NULL == header->ws_protocol) { |
253 |
14 |
sep = trim(strtok_r(strtok_r(NULL, "", &lineptr), ",", &sepptr)); |
|
254 |
✓✗✓✓ |
14 |
if (NULL != sep && NULL != (proto = WSS_find_subprotocol(sep))) { |
255 |
2 |
header->ws_protocol = proto; |
|
256 |
} else { |
||
257 |
✓✓ | 18 |
while (NULL != (sep = trim(strtok_r(NULL, ",", &sepptr)))) { |
258 |
✓✓ | 12 |
if (NULL != (proto = WSS_find_subprotocol(sep))) { |
259 |
6 |
header->ws_protocol = proto; |
|
260 |
6 |
break; |
|
261 |
} |
||
262 |
} |
||
263 |
} |
||
264 |
} |
||
265 |
✓✓ | 58 |
} else if ( line_length == strlen(SEC_WEBSOCKET_ORIGIN) && |
266 |
✓✗ | 6 |
strncasecmp(SEC_WEBSOCKET_ORIGIN, line, line_length) == 0 ) { |
267 |
6 |
header->ws_origin = (strtok_r(NULL, "", &lineptr)+1); |
|
268 |
✓✓ | 52 |
} else if ( line_length == strlen(SEC_WEBSOCKET_KEY) && |
269 |
✓✗ | 14 |
strncasecmp(SEC_WEBSOCKET_KEY, line, line_length) == 0 ) { |
270 |
//The |Sec-WebSocket-Key| header field MUST NOT appear more than once in an HTTP request. |
||
271 |
✓✓ | 14 |
if ( unlikely(header->ws_key != NULL) ) { |
272 |
1 |
WSS_log_trace("Sec-WebSocket-Key must only appear once in header"); |
|
273 |
1 |
return HttpStatus_BadRequest; |
|
274 |
} |
||
275 |
13 |
header->ws_key = (strtok_r(NULL, "", &lineptr)+1); |
|
276 |
✓✓ | 38 |
} else if ( line_length == strlen(SEC_WEBSOCKET_EXTENSIONS) && |
277 |
✓✗ | 8 |
strncasecmp(SEC_WEBSOCKET_EXTENSIONS, line, line_length) == 0 ) { |
278 |
8 |
line = trim(strtok_r(NULL, "", &lineptr)); |
|
279 |
8 |
line_length = strlen(line); |
|
280 |
✗✓ | 8 |
if ( NULL == (exts = WSS_realloc((void **)&exts, extensions_length, (extensions_length+line_length+1)*sizeof(char))) ) { |
281 |
WSS_log_error("Unable to allocate space for extensions string"); |
||
282 |
return HttpStatus_InternalServerError; |
||
283 |
} |
||
284 |
|||
285 |
8 |
memcpy(exts+extensions_length, line, line_length); |
|
286 |
8 |
extensions_length += line_length; |
|
287 |
✓✓ | 30 |
} else if ( line_length == strlen(HOST_STRING) && |
288 |
✓✓ | 22 |
strncasecmp(HOST_STRING, line, line_length) == 0 ) { |
289 |
20 |
header->host = (strtok_r(NULL, "", &lineptr)+1); |
|
290 |
✓✓ | 10 |
} else if ( line_length == strlen(WEBSOCKET_PROTOCOL_STRING) && |
291 |
✓✓ | 8 |
strncasecmp(WEBSOCKET_PROTOCOL_STRING, line, line_length) == 0 ) { |
292 |
4 |
header->ws_type = HIXIE75; |
|
293 |
✓✗ | 4 |
if (NULL == header->ws_protocol) { |
294 |
4 |
sep = trim(strtok_r(strtok_r(NULL, "", &lineptr), ",", &sepptr)); |
|
295 |
✓✗✓✓ |
4 |
if (NULL != sep && NULL != (proto = WSS_find_subprotocol(sep))) { |
296 |
2 |
header->ws_protocol = proto; |
|
297 |
} else { |
||
298 |
✓✗ | 2 |
while (NULL != (sep = trim(strtok_r(NULL, ",", &sepptr)))) { |
299 |
✓✗ | 2 |
if (NULL != (proto = WSS_find_subprotocol(sep))) { |
300 |
2 |
header->ws_protocol = proto; |
|
301 |
2 |
break; |
|
302 |
} |
||
303 |
} |
||
304 |
} |
||
305 |
} |
||
306 |
✓✓ | 6 |
} else if ( line_length == strlen(SEC_WEBSOCKET_KEY1) && |
307 |
✓✓ | 4 |
strncasecmp(SEC_WEBSOCKET_KEY1, line, line_length) == 0 ) { |
308 |
2 |
header->ws_type = HIXIE76; |
|
309 |
2 |
header->ws_key1 = (strtok_r(NULL, "", &lineptr)+1); |
|
310 |
✓✓ | 4 |
} else if ( line_length == strlen(SEC_WEBSOCKET_KEY2) && |
311 |
✓✗ | 2 |
strncasecmp(SEC_WEBSOCKET_KEY2, line, line_length) == 0 ) { |
312 |
2 |
header->ws_type = HIXIE76; |
|
313 |
2 |
header->ws_key2 = (strtok_r(NULL, "", &lineptr)+1); |
|
314 |
} |
||
315 |
} |
||
316 |
|||
317 |
// We've reached the payload |
||
318 |
✓✗✓✓ ✓✗ |
140 |
if ( unlikely(strlen(tokenptr) >= 2 && tokenptr[0] == '\r' && tokenptr[1] == '\n') ) { |
319 |
21 |
tokenptr += 2; |
|
320 |
|||
321 |
✓✓ | 21 |
if ( unlikely(strlen(tokenptr) > config->size_payload) ) { |
322 |
1 |
WSS_log_trace("Payload size received is too large."); |
|
323 |
1 |
return HttpStatus_PayloadTooLarge; |
|
324 |
} |
||
325 |
|||
326 |
20 |
double_clrf = true; |
|
327 |
20 |
header->payload = tokenptr; |
|
328 |
20 |
temp = NULL; |
|
329 |
} else { |
||
330 |
119 |
temp = strtok_r(NULL, "\r", &tokenptr); |
|
331 |
|||
332 |
✓✓ | 119 |
if ( likely(tokenptr[0] == '\n') ) { |
333 |
118 |
tokenptr++; |
|
334 |
✓✗ | 1 |
} else if ( unlikely(tokenptr[0] != '\0') ) { |
335 |
1 |
WSS_log_trace("Line shall always end with newline character"); |
|
336 |
1 |
return HttpStatus_BadRequest; |
|
337 |
} |
||
338 |
} |
||
339 |
|||
340 |
✓✓✓✓ |
138 |
if ( unlikely(temp == NULL && header->ws_type == HIXIE76) ) { |
341 |
2 |
header->ws_type = HIXIE76; |
|
342 |
2 |
header->ws_key3 = header->payload; |
|
343 |
} |
||
344 |
|||
345 |
token = temp; |
||
346 |
} |
||
347 |
|||
348 |
✓✓ | 42 |
if ( !double_clrf ) { |
349 |
1 |
WSS_log_trace("Double CLRF required to distinguish between header and body"); |
|
350 |
1 |
return HttpStatus_BadRequest; |
|
351 |
} |
||
352 |
|||
353 |
✓✓ | 41 |
if ( NULL != exts ) { |
354 |
2 |
sep = trim(strtok_r(exts, ",", &sepptr)); |
|
355 |
✓✓ | 8 |
while(NULL != sep) { |
356 |
6 |
in_use = false; |
|
357 |
6 |
sep = trim(strtok_r(sep, ";", ¶mptr)); |
|
358 |
|||
359 |
✗✓ | 6 |
if ( NULL == (name = WSS_copy(sep, strlen(sep)+1)) ) { |
360 |
WSS_log_error("Unable to allocate space for extension structure"); |
||
361 |
return HttpStatus_InternalServerError; |
||
362 |
} |
||
363 |
|||
364 |
✓✓ | 6 |
if (NULL != (ext = WSS_find_extension(name))) { |
365 |
✓✓ | 4 |
for (i = 0; i < header->ws_extensions_count; i++) { |
366 |
✗✓ | 2 |
if ( unlikely(header->ws_extensions[i]->ext == ext) ) { |
367 |
in_use = true; |
||
368 |
break; |
||
369 |
} |
||
370 |
} |
||
371 |
|||
372 |
✓✓ | 4 |
if ( likely(! in_use) ) { |
373 |
2 |
ext->open(fd, trim(paramptr), &accepted, &valid); |
|
374 |
✓✗ | 2 |
if ( likely(valid) ) { |
375 |
✗✓ | 2 |
if ( unlikely(NULL == (header->ws_extensions = WSS_realloc((void **) &header->ws_extensions, header->ws_extensions_count*sizeof(wss_ext_t *), (header->ws_extensions_count+1)*sizeof(wss_ext_t *)))) ) { |
376 |
WSS_log_error("Unable to allocate space for extension"); |
||
377 |
return HttpStatus_InternalServerError; |
||
378 |
} |
||
379 |
|||
380 |
✗✓ | 2 |
if ( unlikely(NULL == (header->ws_extensions[header->ws_extensions_count] = WSS_malloc(sizeof(wss_ext_t))))) { |
381 |
WSS_log_error("Unable to allocate space for extension structure"); |
||
382 |
return HttpStatus_InternalServerError; |
||
383 |
} |
||
384 |
2 |
header->ws_extensions[header->ws_extensions_count]->ext = ext; |
|
385 |
2 |
header->ws_extensions[header->ws_extensions_count]->name = name; |
|
386 |
2 |
header->ws_extensions[header->ws_extensions_count]->accepted = accepted; |
|
387 |
2 |
header->ws_extensions_count += 1; |
|
388 |
} |
||
389 |
} |
||
390 |
} |
||
391 |
|||
392 |
6 |
sep = trim(strtok_r(NULL, ",", &sepptr)); |
|
393 |
} |
||
394 |
2 |
WSS_free((void **)&exts); |
|
395 |
} |
||
396 |
|||
397 |
✓✓✓✗ ✓✗✓✗ ✓✗✓✓ ✓✓✓✗ ✓✗✓✓ ✓✗✓✗ ✓✓ |
41 |
if ( unlikely(header->ws_type == UNKNOWN |
398 |
&& header->ws_version == 0 |
||
399 |
&& header->ws_key1 == NULL |
||
400 |
&& header->ws_key2 == NULL |
||
401 |
&& header->ws_key3 == NULL |
||
402 |
&& header->ws_key == NULL |
||
403 |
&& header->ws_upgrade != NULL |
||
404 |
&& header->ws_connection != NULL |
||
405 |
&& strlen(header->ws_upgrade) == strlen(WEBSOCKET_STRING) |
||
406 |
&& strncasecmp(WEBSOCKET_STRING, header->ws_upgrade, strlen(WEBSOCKET_STRING)) == 0 |
||
407 |
&& strlen(header->ws_connection) == strlen(UPGRADE_STRING) |
||
408 |
&& strncasecmp(UPGRADE_STRING, header->ws_connection, strlen(UPGRADE_STRING)) == 0 |
||
409 |
&& header->host != NULL |
||
410 |
&& header->ws_origin != NULL) ) { |
||
411 |
4 |
header->ws_type = HIXIE75; |
|
412 |
} |
||
413 |
|||
414 |
return HttpStatus_OK; |
||
415 |
} |
||
416 |
|||
417 |
/** |
||
418 |
* Upgrades a HTTP header, that is returns switching protocols response if |
||
419 |
* the header contains the required options. |
||
420 |
* |
||
421 |
* @param header [wss_header_t *] "The header structure to fill" |
||
422 |
* @param config [wss_config_t *] "The configuration of the server" |
||
423 |
* @param re [regex_t *] "The regex to validate path" |
||
424 |
* @return [enum HttpStatus_Code] "The status code to return to the client" |
||
425 |
*/ |
||
426 |
57 |
enum HttpStatus_Code WSS_upgrade_header(wss_header_t *header, wss_config_t * config, regex_t *re) { |
|
427 |
57 |
int err; |
|
428 |
57 |
char msg[1024]; |
|
429 |
57 |
char *sep; |
|
430 |
57 |
char *sepptr = NULL; |
|
431 |
57 |
unsigned char *key = NULL; |
|
432 |
57 |
unsigned long key_length; |
|
433 |
|||
434 |
57 |
WSS_log_trace("Upgrading HTTP header"); |
|
435 |
|||
436 |
✓✗✓✓ |
57 |
if ( unlikely(strncasecmp("http://", header->path, sizeof(char)*7) == 0 || |
437 |
strncasecmp("https://", header->path, sizeof(char)*8) == 0) ) { |
||
438 |
1 |
WSS_log_trace("Header path does not contain valid protocol"); |
|
439 |
1 |
return HttpStatus_UpgradeRequired; |
|
440 |
} |
||
441 |
|||
442 |
56 |
WSS_log_trace("Validating request_uri"); |
|
443 |
|||
444 |
✓✗ | 56 |
if (re != NULL) { |
445 |
// It is recommended to specify paths in the config file |
||
446 |
56 |
err = regexec(re, header->path, 0, NULL, 0); |
|
447 |
✓✓ | 56 |
if ( unlikely(err == REG_NOMATCH) ) { |
448 |
4 |
WSS_log_trace("Path is not allowed: %s", header->path); |
|
449 |
4 |
return HttpStatus_NotFound; |
|
450 |
} |
||
451 |
|||
452 |
✗✓ | 52 |
if ( unlikely(err != 0) ) { |
453 |
regerror(err, re, msg, 1024); |
||
454 |
WSS_log_error("Unable to exec regex: %s", msg); |
||
455 |
return HttpStatus_InternalServerError; |
||
456 |
} |
||
457 |
} |
||
458 |
|||
459 |
52 |
WSS_log_trace("Validating host"); |
|
460 |
|||
461 |
// It is recommended to specify hosts in the config file |
||
462 |
✓✗ | 52 |
if ( likely(config->hosts_length > 0) ) { |
463 |
✓✓ | 52 |
if ( unlikely(strinarray(header->host, (const char **)config->hosts, config->hosts_length) != 0) ) { |
464 |
6 |
WSS_log_trace("Host is not allowed: %s", header->host); |
|
465 |
6 |
return HttpStatus_BadRequest; |
|
466 |
} |
||
467 |
} |
||
468 |
|||
469 |
46 |
WSS_log_trace("Validating upgrade header"); |
|
470 |
|||
471 |
✓✓✓✓ ✓✗ |
46 |
if ( unlikely(NULL == header->ws_upgrade || strlen(header->ws_upgrade) < strlen(WEBSOCKET_STRING) || |
472 |
✓✓✗✓ |
42 |
strncasecmp(WEBSOCKET_STRING, header->ws_upgrade, strlen(WEBSOCKET_STRING)) != 0) || (header->ws_type <= HIXIE76 && strncasecmp(WEBSOCKET_UPPERCASE_STRING, header->ws_upgrade, strlen(WEBSOCKET_UPPERCASE_STRING)) != 0) ) { |
473 |
4 |
WSS_log_trace("Invalid upgrade header value"); |
|
474 |
4 |
return HttpStatus_UpgradeRequired; |
|
475 |
} |
||
476 |
|||
477 |
42 |
WSS_log_trace("Validating connection header"); |
|
478 |
|||
479 |
✓✓✓✓ |
42 |
if ( unlikely(NULL == header->ws_connection || strlen(header->ws_connection) < strlen(UPGRADE_STRING)) ) { |
480 |
4 |
WSS_log_trace("Invalid connection header value"); |
|
481 |
4 |
return HttpStatus_UpgradeRequired; |
|
482 |
} |
||
483 |
|||
484 |
38 |
sep = trim(strtok_r(header->ws_connection, ",", &sepptr)); |
|
485 |
✓✓ | 38 |
if ( unlikely(NULL == sep) ) { |
486 |
2 |
WSS_log_trace("Invalid connection header value"); |
|
487 |
2 |
return HttpStatus_UpgradeRequired; |
|
488 |
} |
||
489 |
|||
490 |
38 |
do { |
|
491 |
✓✓ | 38 |
if ( likely(strncasecmp(UPGRADE_STRING, sep, strlen(UPGRADE_STRING)) == 0) ) { |
492 |
break; |
||
493 |
} |
||
494 |
|||
495 |
4 |
sep = trim(strtok_r(NULL, ",", &sepptr)); |
|
496 |
✓✓ | 4 |
} while ( likely(NULL != sep) ); |
497 |
|||
498 |
✓✓ | 36 |
if ( unlikely(sep == NULL) ) { |
499 |
2 |
WSS_log_trace("Invalid connection header value"); |
|
500 |
2 |
return HttpStatus_UpgradeRequired; |
|
501 |
} |
||
502 |
|||
503 |
34 |
WSS_log_trace("Validating origin"); |
|
504 |
|||
505 |
// It is recommended to specify origins in the config file |
||
506 |
✓✗ | 34 |
if ( likely(config->origins_length > 0) ) { |
507 |
✓✓ | 34 |
if ( unlikely(strinarray(header->ws_origin, (const char **)config->origins, config->origins_length) != 0) ) { |
508 |
4 |
WSS_log_trace("Origin is not allowed: %s", header->ws_origin); |
|
509 |
4 |
return HttpStatus_Forbidden; |
|
510 |
} |
||
511 |
} |
||
512 |
|||
513 |
30 |
WSS_log_trace("Validating websocket version"); |
|
514 |
|||
515 |
✓✓ | 30 |
switch (header->ws_type) { |
516 |
case RFC6455: |
||
517 |
case HYBI10: |
||
518 |
case HYBI07: |
||
519 |
12 |
break; |
|
520 |
18 |
default: |
|
521 |
18 |
WSS_log_trace("Invalid websocket version"); |
|
522 |
18 |
return HttpStatus_NotImplemented; |
|
523 |
} |
||
524 |
|||
525 |
12 |
WSS_log_trace("Validating websocket key"); |
|
526 |
|||
527 |
✓✓ | 12 |
if ( unlikely(NULL == header->ws_key) ) { |
528 |
2 |
WSS_log_trace("Invalid websocket key"); |
|
529 |
2 |
WSS_free((void **) &key); |
|
530 |
2 |
return HttpStatus_UpgradeRequired; |
|
531 |
} |
||
532 |
|||
533 |
10 |
key = b64_decode_ex(header->ws_key, strlen(header->ws_key), &key_length); |
|
534 |
✓✗✓✓ |
10 |
if ( unlikely(NULL == key || key_length != SEC_WEBSOCKET_KEY_LENGTH) ) { |
535 |
2 |
WSS_log_trace("Invalid websocket key"); |
|
536 |
2 |
WSS_free((void **) &key); |
|
537 |
2 |
return HttpStatus_UpgradeRequired; |
|
538 |
} |
||
539 |
8 |
WSS_free((void **) &key); |
|
540 |
|||
541 |
8 |
WSS_log_trace("Accepted handshake, switching protocol"); |
|
542 |
|||
543 |
8 |
return HttpStatus_SwitchingProtocols; |
|
544 |
} |
||
545 |
|||
546 |
/** |
||
547 |
* Frees a HTTP header structure |
||
548 |
* |
||
549 |
* @param header [wss_header_t *] "The HTTP header to free" |
||
550 |
* @return [void] |
||
551 |
*/ |
||
552 |
60 |
void WSS_free_header(wss_header_t *header) { |
|
553 |
60 |
size_t i; |
|
554 |
✓✗ | 60 |
if ( likely(NULL != header) ) { |
555 |
✓✓ | 62 |
for (i = 0; i < header->ws_extensions_count; i++) { |
556 |
✓✗ | 2 |
if ( likely(NULL != header->ws_extensions[i]) ) { |
557 |
2 |
WSS_free((void **) &header->ws_extensions[i]->accepted); |
|
558 |
2 |
WSS_free((void **) &header->ws_extensions[i]->name); |
|
559 |
2 |
WSS_free((void **) &header->ws_extensions[i]); |
|
560 |
} |
||
561 |
} |
||
562 |
60 |
WSS_free((void **) &header->ws_extensions); |
|
563 |
60 |
WSS_free((void **) &header->content); |
|
564 |
} |
||
565 |
60 |
WSS_free((void **) &header); |
|
566 |
60 |
} |
Generated by: GCOVR (Version 4.2) |