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