Line data Source code
1 : /**
2 : * @file libhttp.c
3 : * @brief HTTP parsing and building library
4 : *
5 : * @author François Cerbelle (Fanfan), francois@cerbelle.net
6 : *
7 : * @internal
8 : * Created: 15/11/2024
9 : * Revision: none
10 : * Last modified: 2024-11-30 22:15
11 : * Compiler: gcc
12 : * Organization: Cerbelle.net
13 : * Copyright: Copyright (c) 2024, François Cerbelle
14 : *
15 : * This source code is released for free distribution under the terms of the
16 : * GNU General Public License as published by the Free Software Foundation.
17 : */
18 :
19 : #ifdef HAVE_CONFIG_H
20 : #include "config.h"
21 : #endif
22 :
23 : #include "libhttp.h"
24 : #include "sclist.h"
25 : #include "base64.h" /* Base64 encoder and decoder */
26 :
27 : #include <stdlib.h>
28 : #include <stdio.h>
29 : #include <string.h>
30 : #include <assert.h>
31 :
32 : typedef struct HTTP_s {
33 : sclist_t* headers;
34 : char* body;
35 : } HTTP_t;
36 :
37 : typedef struct HTTPHeader_s {
38 : sclistrecord_t* self;
39 : char* name;
40 : char* value;
41 : } HTTPHeader_t;
42 :
43 10 : HTTPHeader_t* HTTPHeader_setname(HTTPHeader_t* header, const char* name) {
44 : char* newname;
45 :
46 10 : assert(header);
47 8 : assert(header->name);
48 8 : assert(header->value);
49 8 : assert(name);
50 :
51 6 : if (name[0]==0) {
52 3 : fprintf(stderr,"ERROR: HTTP_setname empty name\n");
53 3 : return NULL;
54 : }
55 :
56 3 : if (NULL==(newname=realloc(header->name,strlen(name)+1))) {
57 0 : perror("ERROR: HTTPHeader_setname realloc");
58 0 : return NULL;
59 : }
60 3 : header->name = newname;
61 3 : strcpy(header->name,name);
62 3 : return header;
63 : }
64 :
65 240 : char* HTTPHeader_getname(HTTPHeader_t* header) {
66 240 : assert(header);
67 238 : assert(header->name);
68 238 : assert(header->value);
69 :
70 238 : return header->name;
71 : }
72 :
73 10 : HTTPHeader_t* HTTPHeader_setvalue(HTTPHeader_t* header, const char* value) {
74 : char* newvalue;
75 :
76 10 : assert(header);
77 8 : assert(header->name);
78 8 : assert(header->value);
79 8 : assert(value);
80 :
81 6 : if (NULL==(newvalue=realloc(header->value,strlen(value)+1))) {
82 0 : perror("ERROR: HTTPHeader_setvalue realloc");
83 0 : return NULL;
84 : }
85 6 : header->value = newvalue;
86 6 : strcpy(header->value,value);
87 6 : return header;
88 : }
89 :
90 171 : char* HTTPHeader_getvalue(HTTPHeader_t* header) {
91 171 : assert(header);
92 169 : assert(header->name);
93 169 : assert(header->value);
94 :
95 169 : return header->value;
96 : }
97 :
98 213 : static HTTPHeader_t* HTTPHeader_new(const char* name, const char* value) {
99 : HTTPHeader_t* header;
100 :
101 213 : assert(name);
102 213 : assert(value);
103 :
104 213 : if (NULL==(header=malloc(sizeof(struct HTTPHeader_s)))) {
105 0 : perror("ERROR: HTTPHeader_new");
106 0 : return NULL;
107 : }
108 213 : if (NULL==(header->name=strdup(name))) {
109 0 : perror("ERROR: HTTPHeader_new name");
110 0 : free(header);
111 0 : return NULL;
112 : }
113 213 : if (NULL==(header->value=strdup(value))) {
114 0 : perror("ERROR: HTTPHeader_new value");
115 0 : free(header->name);
116 0 : free(header);
117 0 : return NULL;
118 : }
119 213 : header->self = NULL;
120 213 : return header;
121 : }
122 :
123 201 : static void HTTPHeader_del(HTTPHeader_t* header) {
124 201 : assert(header);
125 201 : assert(header->name);
126 201 : assert(header->value);
127 :
128 201 : free(header->name);
129 201 : free(header->value);
130 201 : free(header);
131 201 : }
132 :
133 174 : HTTP_t* HTTP_new() {
134 : HTTP_t* http;
135 :
136 174 : if (NULL==(http=malloc(sizeof(struct HTTP_s)))) {
137 0 : perror("ERROR: HTTP_new HTTP_s");
138 0 : return NULL;
139 : }
140 :
141 174 : if (NULL==(http->body=strdup(""))) {
142 0 : perror("ERROR: HTTP_new body");
143 0 : free(http);
144 0 : return NULL;
145 : };
146 :
147 174 : if (NULL==(http->headers = sclist_new())) {
148 0 : fprintf(stderr,"ERROR: HTTP_new headers\n");
149 0 : free(http->body);
150 0 : free(http);
151 0 : return NULL;
152 : }
153 :
154 174 : return http;
155 : }
156 :
157 152 : void HTTP_del(HTTP_t* http) {
158 : sclistrecord_t* headerlst_entry;
159 :
160 152 : assert(http);
161 150 : assert(http->headers);
162 150 : assert(http->body);
163 :
164 150 : headerlst_entry = sclist_firstrecord(http->headers);
165 339 : while (headerlst_entry) {
166 : HTTPHeader_t* headerlst_value;
167 :
168 : /* Free payload */
169 189 : headerlst_value=sclist_getvalue(headerlst_entry);
170 189 : HTTPHeader_del(headerlst_value);
171 :
172 : /* Remember the entry to delete before moving to the next one */
173 189 : sclistrecord_t* tmp = headerlst_entry;
174 189 : headerlst_entry = sclist_nextrecord(headerlst_entry);
175 189 : sclist_remrecord(http->headers,tmp);
176 : }
177 150 : sclist_del(http->headers);
178 :
179 150 : free(http->body);
180 150 : free(http);
181 150 : }
182 :
183 19 : HTTP_t* HTTP_setbody(HTTP_t* http, const char* body) {
184 : char* newbody;
185 :
186 19 : assert(http);
187 17 : assert(http->headers);
188 17 : assert(http->body);
189 17 : assert(body);
190 :
191 15 : if (NULL==(newbody=realloc(http->body,strlen(body)+1))) {
192 0 : perror("ERROR: HTTP_setbody realloc");
193 0 : return NULL;
194 : }
195 :
196 15 : http->body = newbody;
197 15 : strcpy(http->body, body);
198 15 : return http;
199 : }
200 :
201 17 : char* HTTP_getbody(HTTP_t* http) {
202 17 : assert(http);
203 15 : assert(http->headers);
204 15 : assert(http->body);
205 :
206 15 : return http->body;
207 : }
208 :
209 222 : HTTPHeader_t* HTTP_addheader(HTTP_t* http, const char* name, const char* value) {
210 : HTTPHeader_t* header;
211 :
212 222 : assert(http);
213 220 : assert(http->headers);
214 220 : assert(http->body);
215 220 : assert(name);
216 218 : assert(value);
217 :
218 216 : if (name[0]==0) {
219 3 : fprintf(stderr,"ERROR: HTTP_addheader empty name\n");
220 3 : return NULL;
221 : }
222 :
223 213 : if (NULL==(header = HTTPHeader_new(name,value))) {
224 0 : fprintf(stderr,"ERROR: HTTP_addheader HTTPHeader_new\n");
225 0 : return NULL;
226 : }
227 :
228 213 : if (NULL==(header->self=sclist_addrecord(http->headers,header))) {
229 0 : fprintf(stderr,"ERROR: HTTP_addheader\n");
230 0 : HTTPHeader_del(header);
231 0 : return NULL;
232 : }
233 213 : return header;
234 : }
235 :
236 15 : HTTPHeader_t* HTTP_addbasicauth(HTTP_t* http, const char* login, const char* pass) {
237 : char* auth_encoded;
238 : char* auth;
239 : HTTPHeader_t* header;
240 :
241 15 : assert(http);
242 13 : assert(http->headers);
243 13 : assert(http->body);
244 13 : assert(login);
245 11 : assert(pass);
246 :
247 9 : if (NULL==(auth=malloc(strlen(login)+1+strlen(pass)+1))) {
248 0 : perror("ERROR: HTTP_addbasicauth credentials");
249 0 : return NULL;
250 : }
251 : /* strcpy/strcat expected to be faster than sprintf */
252 9 : strcpy(auth,login);
253 9 : strcat(auth,":");
254 9 : strcat(auth,pass);
255 9 : auth_encoded=base64_encode(auth);
256 9 : free(auth);
257 :
258 9 : if (NULL==(auth=malloc(strlen("Basic ")+strlen(auth_encoded)+1))) {
259 0 : perror("ERROR: HTTP_addbasicauth value");
260 0 : free(auth_encoded);
261 0 : return NULL;
262 : }
263 9 : strcpy(auth,"Basic ");
264 9 : strcat(auth, auth_encoded);
265 9 : free(auth_encoded);
266 :
267 9 : if (NULL==(header=HTTP_addheader(http,"Authorization",auth))) {
268 0 : fprintf(stderr,"ERROR: HTTP_addbasicauth addheader\n");
269 0 : free(auth);
270 0 : return NULL;
271 : }
272 9 : free(auth);
273 9 : return header;
274 : }
275 :
276 81 : HTTPHeader_t* HTTP_firstheader(const HTTP_t* http) {
277 : sclistrecord_t* headerlst_entry;
278 :
279 81 : assert(http);
280 79 : assert(http->headers);
281 79 : assert(http->body);
282 :
283 79 : if (NULL==(headerlst_entry=sclist_firstrecord(http->headers)))
284 11 : return NULL;
285 68 : return sclist_getvalue(headerlst_entry);
286 : }
287 :
288 52 : HTTPHeader_t* HTTP_nextheader(const HTTPHeader_t* header) {
289 : sclistrecord_t* headerlst_entry;
290 :
291 52 : assert(header);
292 48 : assert(header->self);
293 48 : assert(header->name);
294 48 : assert(header->value);
295 :
296 48 : headerlst_entry = header->self;
297 :
298 : /* EOL reached */
299 48 : if (NULL==(headerlst_entry = sclist_nextrecord(headerlst_entry)))
300 21 : return NULL;
301 : else
302 27 : return sclist_getvalue(headerlst_entry);
303 : }
304 :
305 37 : HTTPHeader_t* HTTP_findheader(const HTTPHeader_t* start, const char* name) {
306 : sclistrecord_t* headerlst_entry;
307 :
308 37 : assert(start);
309 35 : assert(start->self);
310 35 : assert(start->name);
311 35 : assert(start->value);
312 35 : assert(name);
313 :
314 33 : headerlst_entry = start->self;
315 :
316 75 : while (headerlst_entry) {
317 : HTTPHeader_t* headerlst_value;
318 : char* header_name;
319 :
320 69 : headerlst_value = sclist_getvalue(headerlst_entry);
321 69 : assert(headerlst_value);
322 69 : header_name = HTTPHeader_getname(headerlst_value);
323 69 : if (0==strcmp(header_name,name))
324 27 : break;
325 42 : headerlst_entry=sclist_nextrecord(headerlst_entry);
326 : }
327 33 : if (!headerlst_entry)
328 : /* Not found */
329 6 : return NULL;
330 : else
331 : /* Found */
332 27 : return sclist_getvalue(headerlst_entry);
333 : }
334 :
335 :
336 19 : HTTP_t* HTTP_remheader(HTTP_t* http, HTTPHeader_t* header) {
337 19 : assert(http);
338 17 : assert(http->headers);
339 17 : assert(http->body);
340 17 : assert(header);
341 15 : assert(header->self);
342 15 : assert(header->name);
343 15 : assert(header->value);
344 :
345 : /* Remove from the header list */
346 15 : if (NULL==sclist_remrecord(http->headers, header->self)) {
347 3 : fprintf(stderr,"ERROR: HTTP_remheader not found\n");
348 3 : return NULL;
349 : }
350 12 : header->self = NULL;
351 12 : HTTPHeader_del(header);
352 :
353 12 : return http;
354 : }
355 :
356 367 : static char* strconcat(char* dst, const char* src) {
357 : char* newdst;
358 :
359 367 : assert(dst);
360 367 : assert(src);
361 :
362 367 : if (NULL==(newdst=realloc(dst,strlen(dst)+strlen(src)+1))) {
363 0 : perror("ERROR: strconcat");
364 0 : return NULL;
365 : }
366 367 : dst = newdst;
367 367 : strcat(dst,src);
368 367 : return dst;
369 : }
370 :
371 39 : char* HTTP_buildheaders(const HTTP_t* http) {
372 39 : char* headers_str=NULL;
373 : sclistrecord_t* headerlst_entry;
374 :
375 39 : assert(http);
376 33 : assert(http->headers);
377 33 : assert(http->body);
378 :
379 33 : if (NULL==(headers_str=strdup(""))) {
380 0 : perror("ERROR: HTTP_buildheaders headers_str");
381 0 : return NULL;
382 : }
383 33 : for (
384 33 : headerlst_entry=sclist_firstrecord(http->headers);
385 66 : headerlst_entry;
386 33 : headerlst_entry = sclist_nextrecord(headerlst_entry)
387 : ) {
388 : HTTPHeader_t* headerlst_value;
389 :
390 33 : headerlst_value = sclist_getvalue(headerlst_entry);
391 33 : assert(headerlst_value);
392 : /** @bug possible memory leak, check strconcat return values */
393 33 : headers_str=strconcat(headers_str,HTTPHeader_getname(headerlst_value));
394 33 : headers_str=strconcat(headers_str,": ");
395 33 : headers_str=strconcat(headers_str,HTTPHeader_getvalue(headerlst_value));
396 33 : headers_str=strconcat(headers_str,"\r\n");
397 : }
398 :
399 33 : return headers_str;
400 : }
401 :
402 : /* @todo redesign headers and implementation to identify different errors */
403 22 : char* HTTP_buildrequest(const HTTP_t* http, const HTTPMethod_t method, const char* uri, const HTTPVersion_t version) {
404 : char* request;
405 : char* newrequest;
406 : char* headers;
407 :
408 22 : assert(uri);
409 :
410 20 : if (NULL==(request=strdup(""))) {
411 0 : perror("ERROR: HTTP_getrequest strdup");
412 0 : return NULL;
413 : }
414 :
415 20 : switch(method) {
416 17 : case HTTPMETHOD_GET:
417 17 : if (NULL==(newrequest=strconcat(request, "GET"))) {
418 0 : fprintf(stderr,"ERROR: HTTP_buildrequest method\n");
419 0 : free(request);
420 0 : return NULL;
421 : }
422 17 : request = newrequest;
423 17 : break;;
424 3 : case HTTPMETHOD_HEAD:
425 : case HTTPMETHOD_POST:
426 : case HTTPMETHOD_PUT:
427 : case HTTPMETHOD_DELETE:
428 : case HTTPMETHOD_CONNECT:
429 : case HTTPMETHOD_OPTIONS:
430 : case HTTPMETHOD_TRACE:
431 : case HTTPMETHOD_PATCH:
432 : case HTTPMETHOD_INVALID:
433 : default:
434 3 : fprintf(stderr,"ERROR: HTTP method not supported\n");
435 3 : free(request);
436 3 : return NULL;
437 : break;;
438 : }
439 :
440 17 : if (uri[0]) {
441 14 : if (NULL==(newrequest=strconcat(request, " "))) {
442 0 : fprintf(stderr,"ERROR: HTTP_buildrequest uri separator\n");
443 0 : free(request);
444 0 : return NULL;
445 : }
446 14 : request = newrequest;
447 14 : if (NULL==(newrequest=strconcat(request, uri))) {
448 0 : fprintf(stderr,"ERROR: HTTP_buildrequest uri\n");
449 0 : free(request);
450 0 : return NULL;
451 : }
452 14 : request = newrequest;
453 : }
454 :
455 17 : if (NULL==(newrequest=strconcat(request, " HTTP/"))) {
456 0 : fprintf(stderr,"ERROR: HTTP_buildrequest HTTP\n");
457 0 : free(request);
458 0 : return NULL;
459 : }
460 17 : request = newrequest;
461 17 : switch(version) {
462 14 : case HTTPVERSION_HTTP11:
463 : case HTTPVERSION_HTTP11b:
464 14 : if (NULL==(newrequest=strconcat(request, "1.1"))) {
465 0 : fprintf(stderr,"ERROR: HTTP_buildrequest Version\n");
466 0 : free(request);
467 0 : return NULL;
468 : }
469 14 : request = newrequest;
470 14 : break;;
471 3 : case HTTPVERSION_HTTP09:
472 : case HTTPVERSION_HTTP10:
473 : case HTTPVERSION_HTTP2:
474 : case HTTPVERSION_HTTP3:
475 : case HTTPVERSION_INVALID:
476 : default:
477 3 : fprintf(stderr,"ERROR: HTTP version not supported\n");
478 3 : free(request);
479 3 : return NULL;
480 : break;;
481 : }
482 14 : if (NULL==(newrequest=strconcat(request, "\r\n"))) {
483 0 : fprintf(stderr,"ERROR: HTTP_buildrequest version separator\n");
484 0 : free(request);
485 0 : return NULL;
486 : }
487 14 : request = newrequest;
488 :
489 14 : if (NULL==(headers=HTTP_buildheaders(http))) {
490 0 : fprintf(stderr,"ERROR: HTTP_buildrequest buildheaders\n");
491 0 : free(request);
492 0 : return NULL;
493 : }
494 :
495 12 : if (NULL==(newrequest=strconcat(request, headers))) {
496 0 : fprintf(stderr,"ERROR: HTTP_buildrequest headers\n");
497 0 : free(request);
498 0 : return NULL;
499 : }
500 12 : request = newrequest;
501 12 : free(headers);
502 :
503 12 : if (NULL==(newrequest=strconcat(request, "\r\n"))) {
504 0 : fprintf(stderr,"ERROR: HTTP_buildrequest header separator\n");
505 0 : free(request);
506 0 : return NULL;
507 : }
508 12 : request = newrequest;
509 :
510 12 : if (NULL==(newrequest=strconcat(request, http->body))) {
511 0 : fprintf(stderr,"ERROR: HTTP_buildrequest body\n");
512 0 : free(request);
513 0 : return NULL;
514 : }
515 12 : request = newrequest;
516 :
517 12 : return request;
518 : }
519 :
520 17 : char* HTTP_buildreply(const HTTP_t* http, const HTTPVersion_t version, const HTTPStatus_t status) {
521 : char* reply;
522 : char* newreply;
523 : char* headers;
524 : char status_str[6];
525 :
526 17 : if (NULL==(reply=strdup(""))) {
527 0 : fprintf(stderr,"ERROR: HTTP_buildreply strdup\n");
528 0 : return NULL;
529 : }
530 :
531 17 : if (NULL==(newreply=strconcat(reply, "HTTP/"))) {
532 0 : fprintf(stderr,"ERROR: HTTP_buildreply HTTP\n");
533 0 : free(reply);
534 0 : return NULL;
535 : }
536 17 : reply = newreply;
537 17 : switch(version) {
538 14 : case HTTPVERSION_HTTP11:
539 : case HTTPVERSION_HTTP11b:
540 14 : if (NULL==(newreply=strconcat(reply, "1.1"))) {
541 0 : fprintf(stderr,"ERROR: HTTP_buildreply Version\n");
542 0 : free(reply);
543 0 : return NULL;
544 : }
545 14 : reply = newreply;
546 14 : break;;
547 3 : case HTTPVERSION_HTTP09:
548 : case HTTPVERSION_HTTP10:
549 : case HTTPVERSION_HTTP2:
550 : case HTTPVERSION_HTTP3:
551 : case HTTPVERSION_INVALID:
552 : default:
553 3 : fprintf(stderr,"ERROR: HTTP version not supported\n");
554 3 : free(reply);
555 3 : return NULL;
556 : break;;
557 : }
558 :
559 14 : snprintf(status_str,6," %d ",status);
560 14 : if (NULL==(newreply=strconcat(reply, status_str))) {
561 0 : fprintf(stderr,"ERROR: HTTP_buildreply status code\n");
562 0 : free(reply);
563 0 : return NULL;
564 : }
565 14 : reply = newreply;
566 :
567 14 : if (NULL==(newreply=strconcat(reply, "Dummy Reason"))) {
568 0 : fprintf(stderr,"ERROR: HTTP_buildreply reason phrase\n");
569 0 : free(reply);
570 0 : return NULL;
571 : }
572 14 : reply = newreply;
573 :
574 14 : if (NULL==(newreply=strconcat(reply, "\r\n"))) {
575 0 : fprintf(stderr,"ERROR: HTTP_buildreply reason separator\n");
576 0 : free(reply);
577 0 : return NULL;
578 : }
579 14 : reply = newreply;
580 :
581 14 : if (NULL==(headers=HTTP_buildheaders(http))) {
582 0 : fprintf(stderr,"ERROR: HTTP_buildreply buildheaders\n");
583 0 : free(reply);
584 0 : return NULL;
585 : }
586 :
587 12 : if (NULL==(newreply=strconcat(reply, headers))) {
588 0 : fprintf(stderr,"ERROR: HTTP_buildreply headers\n");
589 0 : free(reply);
590 0 : return NULL;
591 : }
592 12 : reply = newreply;
593 12 : free(headers);
594 :
595 12 : if (NULL==(newreply=strconcat(reply, "\r\n"))) {
596 0 : fprintf(stderr,"ERROR: HTTP_buildreply header separator\n");
597 0 : free(reply);
598 0 : return NULL;
599 : }
600 12 : reply = newreply;
601 :
602 12 : if (NULL==(newreply=strconcat(reply, http->body))) {
603 0 : fprintf(stderr,"ERROR: HTTP_buildreply body\n");
604 0 : free(reply);
605 0 : return NULL;
606 : }
607 12 : reply = newreply;
608 :
609 12 : return reply;
610 : }
611 :
612 0 : HTTP_t* HTTP_parseheaders(const char* text) {
613 : (void) text;
614 0 : fprintf(stderr,"Not yet implemented\n");
615 0 : abort();
616 : return NULL;
617 : }
618 :
619 0 : void HTTP_parserequest(const char* request, HTTP_t** http, HTTPMethod_t method, char* uri, HTTPVersion_t version) {
620 : (void) request;
621 : (void) http;
622 : (void) method;
623 : (void) uri;
624 : (void) version;
625 0 : fprintf(stderr,"Not yet implemented\n");
626 0 : abort();
627 : }
628 :
629 0 : void HTTP_parsereply(const char* reply, HTTP_t** http, HTTPVersion_t* version, HTTPStatus_t* status) {
630 : (void) reply;
631 : (void) http;
632 : (void) version;
633 : (void) status;
634 0 : fprintf(stderr,"Not yet implemented\n");
635 0 : abort();
636 : }
|