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