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));
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));
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));
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));
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
ZTEST(http_service,test_HTTP_SERVICE_DEFINE)81 ZTEST(http_service, test_HTTP_SERVICE_DEFINE)
82 {
83 zassert_ok(strcmp(service_A.host, "a.service.com"));
84 zassert_equal(service_A.port, &service_A_port);
85 zassert_equal(*service_A.port, 4242);
86 zassert_equal(service_A.detail, DETAIL(0));
87 zassert_equal(service_A.concurrent, 4);
88 zassert_equal(service_A.backlog, 2);
89
90 zassert_ok(strcmp(service_B.host, "b.service.com"));
91 zassert_equal(service_B.port, &service_B_port);
92 zassert_equal(*service_B.port, 0);
93 zassert_equal(service_B.detail, DETAIL(1));
94 zassert_equal(service_B.concurrent, 7);
95 zassert_equal(service_B.backlog, 3);
96
97 zassert_ok(strcmp(service_C.host, "192.168.1.1"));
98 zassert_equal(service_C.port, &service_C_port);
99 zassert_equal(*service_C.port, 5959);
100 zassert_equal(service_C.detail, DETAIL(2));
101 zassert_equal(service_C.concurrent, 5);
102 zassert_equal(service_C.backlog, 9);
103 zassert_equal(service_C.res_begin, NULL);
104 zassert_equal(service_C.res_end, NULL);
105 }
106
ZTEST(http_service,test_HTTP_SERVICE_COUNT)107 ZTEST(http_service, test_HTTP_SERVICE_COUNT)
108 {
109 size_t n_svc;
110
111 n_svc = 4273;
112 HTTP_SERVICE_COUNT(&n_svc);
113 zassert_equal(n_svc, 4);
114 }
115
ZTEST(http_service,test_HTTP_SERVICE_RESOURCE_COUNT)116 ZTEST(http_service, test_HTTP_SERVICE_RESOURCE_COUNT)
117 {
118 zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_A), 3);
119 zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_B), 2);
120 zassert_equal(HTTP_SERVICE_RESOURCE_COUNT(&service_C), 0);
121 }
122
ZTEST(http_service,test_HTTP_SERVICE_FOREACH)123 ZTEST(http_service, test_HTTP_SERVICE_FOREACH)
124 {
125 size_t n_svc = 0;
126 size_t have_service_A = 0;
127 size_t have_service_B = 0;
128 size_t have_service_C = 0;
129 size_t have_service_D = 0;
130
131 HTTP_SERVICE_FOREACH(svc) {
132 if (svc == &service_A) {
133 have_service_A = 1;
134 } else if (svc == &service_B) {
135 have_service_B = 1;
136 } else if (svc == &service_C) {
137 have_service_C = 1;
138 } else if (svc == &service_D) {
139 have_service_D = 1;
140 } else {
141 zassert_unreachable("svc (%p) not equal to &service_A (%p), &service_B "
142 "(%p), or &service_C (%p)",
143 svc, &service_A, &service_B, &service_C);
144 }
145
146 n_svc++;
147 }
148
149 zassert_equal(n_svc, 4);
150 zassert_equal(have_service_A + have_service_B + have_service_C + have_service_D, n_svc);
151 }
152
ZTEST(http_service,test_HTTP_RESOURCE_FOREACH)153 ZTEST(http_service, test_HTTP_RESOURCE_FOREACH)
154 {
155 size_t first_res, second_res, third_res, n_res;
156
157 n_res = 0;
158 first_res = 0;
159 second_res = 0;
160 third_res = 0;
161 HTTP_RESOURCE_FOREACH(service_A, res) {
162 if (res == &resource_0) {
163 first_res = 1;
164 } else if (res == &resource_1) {
165 second_res = 1;
166 } else if (res == &resource_2) {
167 third_res = 1;
168 } else {
169 zassert_unreachable("res (%p) not equal to &resource_0 (%p), &resource_1 "
170 "(%p) or &resource_2 (%p)",
171 res, &resource_0, &resource_1, &resource_2);
172 }
173
174 n_res++;
175 }
176
177 zassert_equal(n_res, 3);
178 zassert_equal(first_res + second_res + third_res, n_res);
179
180 n_res = 0;
181 first_res = 0;
182 second_res = 0;
183 HTTP_RESOURCE_FOREACH(service_B, res) {
184 if (res == &resource_3) {
185 first_res = 1;
186 } else if (res == &resource_4) {
187 second_res = 1;
188 } else {
189 zassert_unreachable(
190 "res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
191 &resource_3, &resource_4);
192 }
193
194 n_res++;
195 }
196
197 zassert_equal(n_res, 2);
198 zassert_equal(first_res + second_res, n_res);
199
200 n_res = 0;
201 HTTP_SERVICE_FOREACH_RESOURCE(&service_C, res) {
202 zassert_unreachable("service_C does not have any resources");
203 n_res++;
204 }
205
206 zassert_equal(n_res, 0);
207 }
208
ZTEST(http_service,test_HTTP_SERVICE_FOREACH_RESOURCE)209 ZTEST(http_service, test_HTTP_SERVICE_FOREACH_RESOURCE)
210 {
211 size_t first_res, second_res, third_res, n_res;
212
213 n_res = 0;
214 first_res = 0;
215 second_res = 0;
216 third_res = 0;
217 HTTP_SERVICE_FOREACH_RESOURCE(&service_A, res) {
218 if (res == &resource_0) {
219 first_res = 1;
220 } else if (res == &resource_1) {
221 second_res = 1;
222 } else if (res == &resource_2) {
223 third_res = 1;
224 } else {
225 zassert_unreachable("res (%p) not equal to &resource_0 (%p), &resource_1 "
226 "(%p) or &resource_2 (%p)",
227 res, &resource_0, &resource_1, &resource_2);
228 }
229
230 n_res++;
231 }
232
233 zassert_equal(n_res, 3);
234 zassert_equal(first_res + second_res + third_res, n_res);
235
236 n_res = 0;
237 first_res = 0;
238 second_res = 0;
239 HTTP_SERVICE_FOREACH_RESOURCE(&service_B, res) {
240 if (res == &resource_3) {
241 first_res = 1;
242 } else if (res == &resource_4) {
243 second_res = 1;
244 } else {
245 zassert_unreachable(
246 "res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
247 &resource_3, &resource_4);
248 }
249
250 n_res++;
251 }
252
253 zassert_equal(n_res, 2);
254 zassert_equal(first_res + second_res, n_res);
255
256 n_res = 0;
257 HTTP_SERVICE_FOREACH_RESOURCE(&service_C, res) {
258 zassert_unreachable("service_C does not have any resources");
259 n_res++;
260 }
261
262 zassert_equal(n_res, 0);
263 }
264
ZTEST(http_service,test_HTTP_RESOURCE_DEFINE)265 ZTEST(http_service, test_HTTP_RESOURCE_DEFINE)
266 {
267 HTTP_SERVICE_FOREACH_RESOURCE(&service_A, res) {
268 if (res == &resource_0) {
269 zassert_ok(strcmp(res->resource, "/"));
270 zassert_equal(res->detail, RES(0));
271 } else if (res == &resource_1) {
272 zassert_ok(strcmp(res->resource, "/index.html"));
273 zassert_equal(res->detail, RES(1));
274 } else if (res == &resource_2) {
275 zassert_ok(strcmp(res->resource, "/fs/*"));
276 zassert_equal(res->detail, RES(5));
277 } else {
278 zassert_unreachable(
279 "res (%p) not equal to &resource_0 (%p) or &resource_1 (%p)", res,
280 &resource_0, &resource_1);
281 }
282 }
283
284 HTTP_SERVICE_FOREACH_RESOURCE(&service_B, res) {
285 if (res == &resource_3) {
286 zassert_ok(strcmp(res->resource, "/foo.htm"));
287 zassert_equal(res->detail, RES(2));
288 } else if (res == &resource_4) {
289 zassert_ok(strcmp(res->resource, "/bar/baz.php"));
290 zassert_equal(res->detail, RES(3));
291 } else {
292 zassert_unreachable(
293 "res (%p) not equal to &resource_3 (%p) or &resource_4 (%p)", res,
294 &resource_3, &resource_4);
295 }
296 }
297 }
298
299 extern struct http_resource_detail *get_resource_detail(const char *path,
300 int *path_len,
301 bool is_websocket);
302
303 #define CHECK_PATH(path, len) ({ *len = 0; get_resource_detail(path, len, false); })
304
ZTEST(http_service,test_HTTP_RESOURCE_WILDCARD)305 ZTEST(http_service, test_HTTP_RESOURCE_WILDCARD)
306 {
307 struct http_resource_detail *res;
308 int len;
309
310 res = CHECK_PATH("/", &len);
311 zassert_not_null(res, "Cannot find resource");
312 zassert_true(len > 0, "Length not set");
313 zassert_equal(res, RES(0), "Resource mismatch");
314
315 res = CHECK_PATH("/f", &len);
316 zassert_is_null(res, "Resource found");
317 zassert_equal(len, 0, "Length set");
318
319 res = CHECK_PATH("/foo1.html", &len);
320 zassert_not_null(res, "Cannot find resource");
321 zassert_true(len > 0, "Length not set");
322 zassert_equal(res, RES(0), "Resource mismatch");
323
324 res = CHECK_PATH("/foo2222.html", &len);
325 zassert_not_null(res, "Cannot find resource");
326 zassert_true(len > 0, "Length not set");
327 zassert_equal(res, RES(1), "Resource mismatch");
328
329 res = CHECK_PATH("/fbo3.html", &len);
330 zassert_not_null(res, "Cannot find resource");
331 zassert_true(len > 0, "Length not set");
332 zassert_equal(res, RES(1), "Resource mismatch");
333
334 res = CHECK_PATH("/fbo3.htm", &len);
335 zassert_not_null(res, "Cannot find resource");
336 zassert_true(len > 0, "Length not set");
337 zassert_equal(res, RES(0), "Resource mismatch");
338
339 res = CHECK_PATH("/fbo4.html", &len);
340 zassert_not_null(res, "Cannot find resource");
341 zassert_true(len > 0, "Length not set");
342 zassert_equal(res, RES(3), "Resource mismatch");
343
344 res = CHECK_PATH("/fs/index.html", &len);
345 zassert_not_null(res, "Cannot find resource");
346 zassert_true(len > 0, "Length not set");
347 zassert_equal(res, RES(5), "Resource mismatch");
348 }
349
350 extern void http_server_get_content_type_from_extension(char *url, char *content_type,
351 size_t content_type_size);
352
353 /* add another content type */
354 HTTP_SERVER_CONTENT_TYPE(mpg, "video/mpeg")
355
ZTEST(http_service,test_HTTP_SERVER_CONTENT_TYPE)356 ZTEST(http_service, test_HTTP_SERVER_CONTENT_TYPE)
357 {
358 size_t n_content_types = 0;
359 size_t have_html = 0;
360 size_t have_css = 0;
361 size_t have_js = 0;
362 size_t have_jpg = 0;
363 size_t have_png = 0;
364 size_t have_svg = 0;
365 size_t have_mpg = 0;
366
367 HTTP_SERVER_CONTENT_TYPE_FOREACH(ct)
368 {
369 if (strncmp(ct->extension, "html", ct->extension_len) == 0) {
370 have_html = 1;
371 } else if (strncmp(ct->extension, "css", ct->extension_len) == 0) {
372 have_css = 1;
373 } else if (strncmp(ct->extension, "js", ct->extension_len) == 0) {
374 have_js = 1;
375 } else if (strncmp(ct->extension, "jpg", ct->extension_len) == 0) {
376 have_jpg = 1;
377 } else if (strncmp(ct->extension, "png", ct->extension_len) == 0) {
378 have_png = 1;
379 } else if (strncmp(ct->extension, "svg", ct->extension_len) == 0) {
380 have_svg = 1;
381 } else if (strncmp(ct->extension, "mpg", ct->extension_len) == 0) {
382 have_mpg = 1;
383 } else {
384 zassert_unreachable("unknown extension (%s)", ct->extension);
385 }
386
387 n_content_types++;
388 }
389
390 zassert_equal(n_content_types, 7);
391 zassert_equal(have_html & have_css & have_js & have_jpg & have_png & have_svg & have_mpg,
392 1);
393
394 char *mp3 = "song.mp3";
395 char *html = "page.html";
396 char *mpg = "video.mpg";
397 char content_type[64] = "unknown";
398
399 http_server_get_content_type_from_extension(mp3, content_type, sizeof(content_type));
400 zassert_str_equal(content_type, "unknown");
401
402 http_server_get_content_type_from_extension(html, content_type, sizeof(content_type));
403 zassert_str_equal(content_type, "text/html");
404
405 http_server_get_content_type_from_extension(mpg, content_type, sizeof(content_type));
406 zassert_str_equal(content_type, "video/mpeg");
407 }
408
409 ZTEST_SUITE(http_service, NULL, NULL, NULL, NULL, NULL);
410