1 /*
2  * Copyright (c) 2023 Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 
9 #include <zephyr/ztest.h>
10 #include <zephyr/net/http/service.h>
11 #include <zephyr/net/http/server.h>
12 
13 static struct http_resource_detail detail[] = {
14 	{
15 		.type = HTTP_RESOURCE_TYPE_STATIC,
16 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
17 	},
18 	{
19 		.type = HTTP_RESOURCE_TYPE_DYNAMIC,
20 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
21 	},
22 	{
23 		.type = HTTP_RESOURCE_TYPE_WEBSOCKET,
24 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
25 	},
26 	{
27 		.type = HTTP_RESOURCE_TYPE_DYNAMIC,
28 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
29 	},
30 	{
31 		.type = HTTP_RESOURCE_TYPE_STATIC,
32 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
33 	},
34 	{
35 		.type = HTTP_RESOURCE_TYPE_STATIC_FS,
36 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
37 	},
38 };
39 
40 #define DETAIL(n) &detail[n]
41 #define RES(n) &detail[n]
42 
43 /*
44  * Two separate HTTP server instances (A and B), each with different static
45  * resources, listening on different ports. Static resources could be, for
46  * example, gzip compressed html, javascript, css, or image files which
47  * have fixed paths known at build time.
48  *
49  * REST endpoints could be considered as static resources, as long as
50  * the paths (and implementation-specific details) are known at compile time.
51  */
52 static const uint16_t service_A_port = 4242;
53 HTTP_SERVICE_DEFINE(service_A, "a.service.com", &service_A_port, 4, 2, DETAIL(0), NULL);
54 HTTP_RESOURCE_DEFINE(resource_0, service_A, "/", RES(0));
55 HTTP_RESOURCE_DEFINE(resource_1, service_A, "/index.html", RES(1));
56 HTTP_RESOURCE_DEFINE(resource_2, service_A, "/fs/*", RES(5));
57 
58 /* ephemeral port of 0 */
59 static uint16_t service_B_port;
60 HTTP_SERVICE_DEFINE(service_B, "b.service.com", &service_B_port, 7, 3, DETAIL(1), NULL);
61 HTTP_RESOURCE_DEFINE(resource_3, service_B, "/foo.htm", RES(2));
62 HTTP_RESOURCE_DEFINE(resource_4, service_B, "/bar/baz.php", RES(3));
63 
64 /*
65  * An "empty" HTTP service is one without static resources. For example, a
66  * service which loads resources from a filesystem that are determined at
67  * runtime.
68  */
69 static const uint16_t service_C_port = 5959;
70 HTTP_SERVICE_DEFINE_EMPTY(service_C, "192.168.1.1", &service_C_port, 5, 9, DETAIL(2), NULL);
71 
72 /* Wildcard resources */
73 static uint16_t service_D_port = service_A_port + 1;
74 HTTP_SERVICE_DEFINE(service_D, "2001:db8::1", &service_D_port, 7, 3, DETAIL(3), NULL);
75 HTTP_RESOURCE_DEFINE(resource_5, service_D, "/foo1.htm*", RES(0));
76 HTTP_RESOURCE_DEFINE(resource_6, service_D, "/fo*", RES(1));
77 HTTP_RESOURCE_DEFINE(resource_7, service_D, "/f[ob]o3.html", RES(1));
78 HTTP_RESOURCE_DEFINE(resource_8, service_D, "/fb?3.htm", RES(0));
79 HTTP_RESOURCE_DEFINE(resource_9, service_D, "/f*4.html", RES(3));
80 HTTP_RESOURCE_DEFINE(resource_11, service_D, "/foo/*", RES(3));
81 HTTP_RESOURCE_DEFINE(resource_12, service_D, "/foo/b?r", RES(3));
82 
83 /* Default resource in case of no match */
84 static uint16_t service_E_port = 8080;
85 HTTP_SERVICE_DEFINE(service_E, "192.0.2.1", &service_E_port, 0, 0, NULL, DETAIL(0));
86 HTTP_RESOURCE_DEFINE(resource_10, service_E, "/index.html", RES(4));
87 
ZTEST(http_service,test_HTTP_SERVICE_DEFINE)88 ZTEST(http_service, test_HTTP_SERVICE_DEFINE)
89 {
90 	zassert_ok(strcmp(service_A.host, "a.service.com"));
91 	zassert_equal(service_A.port, &service_A_port);
92 	zassert_equal(*service_A.port, 4242);
93 	zassert_equal(service_A.detail, DETAIL(0));
94 	zassert_equal(service_A.concurrent, 4);
95 	zassert_equal(service_A.backlog, 2);
96 
97 	zassert_ok(strcmp(service_B.host, "b.service.com"));
98 	zassert_equal(service_B.port, &service_B_port);
99 	zassert_equal(*service_B.port, 0);
100 	zassert_equal(service_B.detail, DETAIL(1));
101 	zassert_equal(service_B.concurrent, 7);
102 	zassert_equal(service_B.backlog, 3);
103 
104 	zassert_ok(strcmp(service_C.host, "192.168.1.1"));
105 	zassert_equal(service_C.port, &service_C_port);
106 	zassert_equal(*service_C.port, 5959);
107 	zassert_equal(service_C.detail, DETAIL(2));
108 	zassert_equal(service_C.concurrent, 5);
109 	zassert_equal(service_C.backlog, 9);
110 	zassert_equal(service_C.res_begin, NULL);
111 	zassert_equal(service_C.res_end, NULL);
112 }
113 
ZTEST(http_service,test_HTTP_SERVICE_COUNT)114 ZTEST(http_service, test_HTTP_SERVICE_COUNT)
115 {
116 	size_t n_svc;
117 
118 	n_svc = 4273;
119 	HTTP_SERVICE_COUNT(&n_svc);
120 	zassert_equal(n_svc, 5);
121 }
122 
ZTEST(http_service,test_HTTP_SERVICE_RESOURCE_COUNT)123 ZTEST(http_service, test_HTTP_SERVICE_RESOURCE_COUNT)
124 {
125 	zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_A), 3);
126 	zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_B), 2);
127 	zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_C), 0);
128 }
129 
ZTEST(http_service,test_HTTP_SERVICE_FOREACH)130 ZTEST(http_service, test_HTTP_SERVICE_FOREACH)
131 {
132 	size_t n_svc = 0;
133 	size_t have_service_A = 0;
134 	size_t have_service_B = 0;
135 	size_t have_service_C = 0;
136 	size_t have_service_D = 0;
137 	size_t have_service_E = 0;
138 
139 	HTTP_SERVICE_FOREACH(svc) {
140 		if (svc == &service_A) {
141 			have_service_A = 1;
142 		} else if (svc == &service_B) {
143 			have_service_B = 1;
144 		} else if (svc == &service_C) {
145 			have_service_C = 1;
146 		} else if (svc == &service_D) {
147 			have_service_D = 1;
148 		} else if (svc == &service_E) {
149 			have_service_E = 1;
150 		} else {
151 			zassert_unreachable("svc (%p) not equal to any defined service", svc);
152 		}
153 
154 		n_svc++;
155 	}
156 
157 	zassert_equal(n_svc, 5);
158 	zassert_equal(have_service_A, 1);
159 	zassert_equal(have_service_B, 1);
160 	zassert_equal(have_service_C, 1);
161 	zassert_equal(have_service_D, 1);
162 	zassert_equal(have_service_E, 1);
163 }
164 
ZTEST(http_service,test_HTTP_RESOURCE_FOREACH)165 ZTEST(http_service, test_HTTP_RESOURCE_FOREACH)
166 {
167 	size_t first_res, second_res, third_res, n_res;
168 
169 	n_res = 0;
170 	first_res = 0;
171 	second_res = 0;
172 	third_res = 0;
173 	HTTP_RESOURCE_FOREACH(service_A, res) {
174 		if (res == &resource_0) {
175 			first_res = 1;
176 		} else if (res == &resource_1) {
177 			second_res = 1;
178 		} else if (res == &resource_2) {
179 			third_res = 1;
180 		} else {
181 			zassert_unreachable("res (%p) not equal to &resource_0 (%p), &resource_1 "
182 					    "(%p) or &resource_2 (%p)",
183 					    res, &resource_0, &resource_1, &resource_2);
184 		}
185 
186 		n_res++;
187 	}
188 
189 	zassert_equal(n_res, 3);
190 	zassert_equal(first_res + second_res + third_res, n_res);
191 
192 	n_res = 0;
193 	first_res = 0;
194 	second_res = 0;
195 	HTTP_RESOURCE_FOREACH(service_B, res) {
196 		if (res == &resource_3) {
197 			first_res = 1;
198 		} else if (res == &resource_4) {
199 			second_res = 1;
200 		} else {
201 			zassert_unreachable(
202 				"res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
203 				&resource_3, &resource_4);
204 		}
205 
206 		n_res++;
207 	}
208 
209 	zassert_equal(n_res, 2);
210 	zassert_equal(first_res + second_res, n_res);
211 
212 	n_res = 0;
213 	HTTP_SERVICE_FOREACH_RESOURCE(&service_C, res) {
214 		zassert_unreachable("service_C does not have any resources");
215 		n_res++;
216 	}
217 
218 	zassert_equal(n_res, 0);
219 }
220 
ZTEST(http_service,test_HTTP_SERVICE_FOREACH_RESOURCE)221 ZTEST(http_service, test_HTTP_SERVICE_FOREACH_RESOURCE)
222 {
223 	size_t first_res, second_res, third_res, n_res;
224 
225 	n_res = 0;
226 	first_res = 0;
227 	second_res = 0;
228 	third_res = 0;
229 	HTTP_SERVICE_FOREACH_RESOURCE(&service_A, res) {
230 		if (res == &resource_0) {
231 			first_res = 1;
232 		} else if (res == &resource_1) {
233 			second_res = 1;
234 		} else if (res == &resource_2) {
235 			third_res = 1;
236 		} else {
237 			zassert_unreachable("res (%p) not equal to &resource_0 (%p), &resource_1 "
238 					    "(%p) or &resource_2 (%p)",
239 					    res, &resource_0, &resource_1, &resource_2);
240 		}
241 
242 		n_res++;
243 	}
244 
245 	zassert_equal(n_res, 3);
246 	zassert_equal(first_res + second_res + third_res, n_res);
247 
248 	n_res = 0;
249 	first_res = 0;
250 	second_res = 0;
251 	HTTP_SERVICE_FOREACH_RESOURCE(&service_B, res) {
252 		if (res == &resource_3) {
253 			first_res = 1;
254 		} else if (res == &resource_4) {
255 			second_res = 1;
256 		} else {
257 			zassert_unreachable(
258 				"res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
259 				&resource_3, &resource_4);
260 		}
261 
262 		n_res++;
263 	}
264 
265 	zassert_equal(n_res, 2);
266 	zassert_equal(first_res + second_res, n_res);
267 
268 	n_res = 0;
269 	HTTP_SERVICE_FOREACH_RESOURCE(&service_C, res) {
270 		zassert_unreachable("service_C does not have any resources");
271 		n_res++;
272 	}
273 
274 	zassert_equal(n_res, 0);
275 }
276 
ZTEST(http_service,test_HTTP_RESOURCE_DEFINE)277 ZTEST(http_service, test_HTTP_RESOURCE_DEFINE)
278 {
279 	HTTP_SERVICE_FOREACH_RESOURCE(&service_A, res) {
280 		if (res == &resource_0) {
281 			zassert_ok(strcmp(res->resource, "/"));
282 			zassert_equal(res->detail, RES(0));
283 		} else if (res == &resource_1) {
284 			zassert_ok(strcmp(res->resource, "/index.html"));
285 			zassert_equal(res->detail, RES(1));
286 		} else if (res == &resource_2) {
287 			zassert_ok(strcmp(res->resource, "/fs/*"));
288 			zassert_equal(res->detail, RES(5));
289 		} else {
290 			zassert_unreachable(
291 				"res (%p) not equal to &resource_0 (%p) or &resource_1 (%p)", res,
292 				&resource_0, &resource_1);
293 		}
294 	}
295 
296 	HTTP_SERVICE_FOREACH_RESOURCE(&service_B, res) {
297 		if (res == &resource_3) {
298 			zassert_ok(strcmp(res->resource, "/foo.htm"));
299 			zassert_equal(res->detail, RES(2));
300 		} else if (res == &resource_4) {
301 			zassert_ok(strcmp(res->resource, "/bar/baz.php"));
302 			zassert_equal(res->detail, RES(3));
303 		} else {
304 			zassert_unreachable(
305 				"res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
306 				&resource_3, &resource_4);
307 		}
308 	}
309 }
310 
311 extern struct http_resource_detail *get_resource_detail(const struct http_service_desc *service,
312 							const char *path,
313 							int *path_len,
314 							bool is_websocket);
315 
316 #define CHECK_PATH(svc, path, len) ({ *len = 0; get_resource_detail(&svc, path, len, false); })
317 
ZTEST(http_service,test_HTTP_RESOURCE_WILDCARD)318 ZTEST(http_service, test_HTTP_RESOURCE_WILDCARD)
319 {
320 	struct http_resource_detail *res;
321 	int len;
322 
323 	res = CHECK_PATH(service_A, "/", &len);
324 	zassert_not_null(res, "Cannot find resource");
325 	zassert_true(len > 0, "Length not set");
326 	zassert_equal(res, RES(0), "Resource mismatch");
327 
328 	res = CHECK_PATH(service_D, "/f", &len);
329 	zassert_is_null(res, "Resource found");
330 	zassert_equal(len, 0, "Length set");
331 
332 	res = CHECK_PATH(service_D, "/foo1.html", &len);
333 	zassert_not_null(res, "Cannot find resource");
334 	zassert_true(len > 0, "Length not set");
335 	zassert_equal(res, RES(0), "Resource mismatch");
336 
337 	res = CHECK_PATH(service_D, "/foo2222.html", &len);
338 	zassert_not_null(res, "Cannot find resource");
339 	zassert_true(len > 0, "Length not set");
340 	zassert_equal(res, RES(1), "Resource mismatch");
341 
342 	res = CHECK_PATH(service_D, "/fbo3.html", &len);
343 	zassert_not_null(res, "Cannot find resource");
344 	zassert_true(len > 0, "Length not set");
345 	zassert_equal(res, RES(1), "Resource mismatch");
346 
347 	res = CHECK_PATH(service_D, "/fbo3.htm", &len);
348 	zassert_not_null(res, "Cannot find resource");
349 	zassert_true(len > 0, "Length not set");
350 	zassert_equal(res, RES(0), "Resource mismatch");
351 
352 	res = CHECK_PATH(service_D, "/fbo4.html", &len);
353 	zassert_not_null(res, "Cannot find resource");
354 	zassert_true(len > 0, "Length not set");
355 	zassert_equal(res, RES(3), "Resource mismatch");
356 
357 	res = CHECK_PATH(service_D, "/fb", &len);
358 	zassert_is_null(res, "Resource found");
359 	zassert_equal(len, 0, "Length set");
360 
361 	res = CHECK_PATH(service_A, "/fs/index.html", &len);
362 	zassert_not_null(res, "Cannot find resource");
363 	zassert_true(len > 0, "Length not set");
364 	zassert_equal(res, RES(5), "Resource mismatch");
365 
366 	/* Resources that only exist on one service should not be found on another */
367 	res = CHECK_PATH(service_A, "/foo1.htm", &len);
368 	zassert_is_null(res, "Resource found");
369 	zassert_equal(len, 0, "Length set");
370 
371 	res = CHECK_PATH(service_A, "/foo2222.html", &len);
372 	zassert_is_null(res, "Resource found");
373 	zassert_equal(len, 0, "Length set");
374 
375 	res = CHECK_PATH(service_A, "/fbo3.htm", &len);
376 	zassert_is_null(res, "Resource found");
377 	zassert_equal(len, 0, "Length set");
378 
379 	res = CHECK_PATH(service_D, "/foo/bar", &len);
380 	zassert_not_null(res, "Resource not found");
381 	zassert_true(len == (sizeof("/foo/bar") - 1), "Length not set correctly");
382 	zassert_equal(res, RES(3), "Resource mismatch");
383 
384 	res = CHECK_PATH(service_D, "/foo/bar?param=value", &len);
385 	zassert_not_null(res, "Resource not found");
386 	zassert_true(len == (sizeof("/foo/bar") - 1), "Length not set correctly");
387 	zassert_equal(res, RES(3), "Resource mismatch");
388 
389 	res = CHECK_PATH(service_D, "/bar?foo=value", &len);
390 	zassert_is_null(res, "Resource found");
391 	zassert_equal(len, 0, "Length set");
392 
393 	res = CHECK_PATH(service_D, "/foo/bar?param=value", &len);
394 	zassert_not_null(res, "Resource not found");
395 	zassert_true(len == (sizeof("/foo/bar") - 1), "Length not set correctly");
396 	zassert_equal(res, RES(3), "Resource mismatch");
397 }
398 
ZTEST(http_service,test_HTTP_RESOURCE_DEFAULT)399 ZTEST(http_service, test_HTTP_RESOURCE_DEFAULT)
400 {
401 #define NON_EXISTING_PATH "/this_path_is_not_registered"
402 	struct http_resource_detail *res;
403 	int len;
404 
405 	/* For a path that does exist, the correct resource should be returned */
406 	res = CHECK_PATH(service_E, "/index.html", &len);
407 	zassert_not_null(res, "Cannot find resource");
408 	zassert_true(len > 0, "Length not set");
409 	zassert_equal(res, RES(4), "Resource mismatch");
410 
411 	/* For a path that does not exist, the default resource should be returned */
412 	res = CHECK_PATH(service_E, NON_EXISTING_PATH, &len);
413 	zassert_not_null(res, "Cannot find resource");
414 	zassert_equal(len, strlen(NON_EXISTING_PATH), "Length incorrect");
415 	zassert_equal(res, RES(0), "Resource mismatch");
416 
417 	/* If query params are present, length should not include them */
418 	res = CHECK_PATH(service_E, NON_EXISTING_PATH "?param=value", &len);
419 	zassert_not_null(res, "Cannot find resource");
420 	zassert_equal(len, strlen(NON_EXISTING_PATH), "Length incorrect");
421 	zassert_equal(res, RES(0), "Resource mismatch");
422 }
423 
424 extern void http_server_get_content_type_from_extension(char *url, char *content_type,
425 							size_t content_type_size);
426 
427 /* add another content type */
428 HTTP_SERVER_CONTENT_TYPE(mpg, "video/mpeg")
429 
ZTEST(http_service,test_HTTP_SERVER_CONTENT_TYPE)430 ZTEST(http_service, test_HTTP_SERVER_CONTENT_TYPE)
431 {
432 	size_t n_content_types = 0;
433 	size_t have_html = 0;
434 	size_t have_css = 0;
435 	size_t have_js = 0;
436 	size_t have_jpg = 0;
437 	size_t have_png = 0;
438 	size_t have_svg = 0;
439 	size_t have_mpg = 0;
440 
441 	HTTP_SERVER_CONTENT_TYPE_FOREACH(ct)
442 	{
443 		if (strncmp(ct->extension, "html", ct->extension_len) == 0) {
444 			have_html = 1;
445 		} else if (strncmp(ct->extension, "css", ct->extension_len) == 0) {
446 			have_css = 1;
447 		} else if (strncmp(ct->extension, "js", ct->extension_len) == 0) {
448 			have_js = 1;
449 		} else if (strncmp(ct->extension, "jpg", ct->extension_len) == 0) {
450 			have_jpg = 1;
451 		} else if (strncmp(ct->extension, "png", ct->extension_len) == 0) {
452 			have_png = 1;
453 		} else if (strncmp(ct->extension, "svg", ct->extension_len) == 0) {
454 			have_svg = 1;
455 		} else if (strncmp(ct->extension, "mpg", ct->extension_len) == 0) {
456 			have_mpg = 1;
457 		} else {
458 			zassert_unreachable("unknown extension (%s)", ct->extension);
459 		}
460 
461 		n_content_types++;
462 	}
463 
464 	zassert_equal(n_content_types, 7);
465 	zassert_equal(have_html & have_css & have_js & have_jpg & have_png & have_svg & have_mpg,
466 		      1);
467 
468 	char *mp3 = "song.mp3";
469 	char *html = "page.html";
470 	char *mpg = "video.mpg";
471 	char content_type[64] = "unknown";
472 
473 	http_server_get_content_type_from_extension(mp3, content_type, sizeof(content_type));
474 	zassert_str_equal(content_type, "unknown");
475 
476 	http_server_get_content_type_from_extension(html, content_type, sizeof(content_type));
477 	zassert_str_equal(content_type, "text/html");
478 
479 	http_server_get_content_type_from_extension(mpg, content_type, sizeof(content_type));
480 	zassert_str_equal(content_type, "video/mpeg");
481 }
482 
483 ZTEST_SUITE(http_service, NULL, NULL, NULL, NULL, NULL);
484