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-27 21:23
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 222 : char* HTTPHeader_getname(HTTPHeader_t* header) {
66 222 : assert(header);
67 220 : assert(header->name);
68 220 : assert(header->value);
69 :
70 220 : 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 153 : char* HTTPHeader_getvalue(HTTPHeader_t* header) {
91 153 : assert(header);
92 151 : assert(header->name);
93 151 : assert(header->value);
94 :
95 151 : return header->value;
96 : }
97 :
98 195 : static HTTPHeader_t* HTTPHeader_new(const char* name, const char* value) {
99 : HTTPHeader_t* header;
100 :
101 195 : assert(name);
102 195 : assert(value);
103 :
104 195 : if (NULL==(header=malloc(sizeof(struct HTTPHeader_s)))) {
105 0 : perror("ERROR: HTTPHeader_new");
106 0 : return NULL;
107 : }
108 195 : if (NULL==(header->name=strdup(name))) {
109 0 : perror("ERROR: HTTPHeader_new name");
110 0 : free(header);
111 0 : return NULL;
112 : }
113 195 : 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 195 : header->self = NULL;
120 195 : return header;
121 : }
122 :
123 180 : static void HTTPHeader_del(HTTPHeader_t* header) {
124 180 : assert(header);
125 180 : assert(header->name);
126 180 : assert(header->value);
127 :
128 180 : free(header->name);
129 180 : free(header->value);
130 180 : free(header);
131 180 : }
132 :
133 142 : HTTP_t* HTTP_new() {
134 : HTTP_t* http;
135 :
136 142 : if (NULL==(http=malloc(sizeof(struct HTTP_s)))) {
137 0 : perror("ERROR: HTTP_new HTTP_s");
138 0 : return NULL;
139 : }
140 :
141 142 : if (NULL==(http->body=strdup(""))) {
142 0 : perror("ERROR: HTTP_new body");
143 0 : free(http);
144 0 : return NULL;
145 : };
146 :
147 142 : 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 142 : return http;
155 : }
156 :
157 119 : void HTTP_del(HTTP_t* http) {
158 : sclistrecord_t* headerlst_entry;
159 :
160 119 : assert(http);
161 117 : assert(http->headers);
162 117 : assert(http->body);
163 :
164 117 : headerlst_entry = sclist_firstrecord(http->headers);
165 285 : while (headerlst_entry) {
166 : HTTPHeader_t* headerlst_value;
167 :
168 : /* Free payload */
169 168 : headerlst_value=sclist_getvalue(headerlst_entry);
170 168 : HTTPHeader_del(headerlst_value);
171 :
172 : /* Remember the entry to delete before moving to the next one */
173 168 : sclistrecord_t* tmp = headerlst_entry;
174 168 : headerlst_entry = sclist_nextrecord(headerlst_entry);
175 168 : sclist_remrecord(http->headers,tmp);
176 : }
177 117 : sclist_del(http->headers);
178 :
179 117 : free(http->body);
180 117 : free(http);
181 117 : }
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 204 : HTTPHeader_t* HTTP_addheader(HTTP_t* http, const char* name, const char* value) {
210 : HTTPHeader_t* header;
211 :
212 204 : assert(http);
213 202 : assert(http->headers);
214 202 : assert(http->body);
215 202 : assert(name);
216 200 : assert(value);
217 :
218 198 : if (name[0]==0) {
219 3 : fprintf(stderr,"ERROR: HTTP_addheader empty name\n");
220 3 : return NULL;
221 : }
222 :
223 195 : if (NULL==(header = HTTPHeader_new(name,value))) {
224 0 : fprintf(stderr,"ERROR: HTTP_addheader HTTPHeader_new\n");
225 0 : return NULL;
226 : }
227 :
228 195 : 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 195 : 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 84 : HTTPHeader_t* HTTP_firstheader(const HTTP_t* http) {
277 : sclistrecord_t* headerlst_entry;
278 :
279 84 : assert(http);
280 82 : assert(http->headers);
281 82 : assert(http->body);
282 :
283 82 : if (NULL==(headerlst_entry=sclist_firstrecord(http->headers)))
284 11 : return NULL;
285 71 : 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 87 : static char* strconcat(char* dst, const char* src) {
357 : char* newdst;
358 :
359 87 : assert(dst);
360 87 : assert(src);
361 :
362 87 : if (NULL==(newdst=realloc(dst,strlen(dst)+strlen(src)+1))) {
363 0 : perror("ERROR: strconcat");
364 0 : return NULL;
365 : }
366 87 : dst = newdst;
367 87 : strcat(dst,src);
368 87 : return dst;
369 : }
370 :
371 14 : char* HTTP_buildheaders(const HTTP_t* http) {
372 14 : char* headers_str=NULL;
373 : sclistrecord_t* headerlst_entry;
374 :
375 14 : assert(http);
376 12 : assert(http->headers);
377 12 : assert(http->body);
378 :
379 12 : if (NULL==(headers_str=strdup(""))) {
380 0 : perror("ERROR: HTTP_buildheaders headers_str");
381 0 : return NULL;
382 : }
383 12 : for (
384 12 : headerlst_entry=sclist_firstrecord(http->headers);
385 27 : headerlst_entry;
386 15 : headerlst_entry = sclist_nextrecord(headerlst_entry)
387 : ) {
388 : HTTPHeader_t* headerlst_value;
389 :
390 15 : headerlst_value = sclist_getvalue(headerlst_entry);
391 15 : assert(headerlst_value);
392 : /** @bug possible memory leak, check strconcat return values */
393 15 : headers_str=strconcat(headers_str,HTTPHeader_getname(headerlst_value));
394 15 : headers_str=strconcat(headers_str,": ");
395 15 : headers_str=strconcat(headers_str,HTTPHeader_getvalue(headerlst_value));
396 15 : headers_str=strconcat(headers_str,"\r\n");
397 : }
398 :
399 12 : return headers_str;
400 : }
401 :
402 :
403 :
404 :
405 :
406 :
407 :
408 :
409 :
410 :
411 :
412 :
413 : /* @todo redesign headers and implementation to identify different errors */
414 3 : char* HTTP_buildrequest(const HTTP_t* http, const HTTPMethod_t method, const char* uri, const HTTPVersion_t version) {
415 : char* request;
416 : char* newrequest;
417 : char* headers;
418 :
419 3 : assert(method<=HTTPMETHOD_INVALID);
420 3 : assert(uri);
421 3 : assert(version<=HTTPVERSION_INVALID);
422 :
423 3 : if (NULL==(request=strdup(""))) {
424 0 : perror("ERROR: HTTP_getrequest strdup");
425 0 : return NULL;
426 : }
427 :
428 3 : switch(method) {
429 3 : case HTTPMETHOD_GET:
430 3 : if (NULL==(newrequest=strconcat(request, "GET"))) {
431 0 : fprintf(stderr,"ERROR: HTTP_buildrequest method\n");
432 0 : free(request);
433 0 : return NULL;
434 : }
435 3 : break;;
436 0 : case HTTPMETHOD_HEAD:
437 : case HTTPMETHOD_POST:
438 : case HTTPMETHOD_PUT:
439 : case HTTPMETHOD_DELETE:
440 : case HTTPMETHOD_CONNECT:
441 : case HTTPMETHOD_OPTIONS:
442 : case HTTPMETHOD_TRACE:
443 : case HTTPMETHOD_PATCH:
444 : case HTTPMETHOD_INVALID:
445 : default:
446 0 : fprintf(stderr,"ERROR: HTTP method not supported\n");
447 0 : free(request);
448 0 : return NULL;
449 : break;;
450 : }
451 :
452 3 : if (uri[0]) {
453 3 : if (NULL==(newrequest=strconcat(request, " "))) {
454 0 : fprintf(stderr,"ERROR: HTTP_buildrequest uri\n");
455 0 : free(request);
456 0 : return NULL;
457 : }
458 3 : request = newrequest;
459 3 : if (NULL==(newrequest=strconcat(request, uri))) {
460 0 : fprintf(stderr,"ERROR: HTTP_buildrequest uri\n");
461 0 : free(request);
462 0 : return NULL;
463 : }
464 3 : request = newrequest;
465 : }
466 :
467 3 : if (NULL==(newrequest=strconcat(request, " HTTP/"))) {
468 0 : fprintf(stderr,"ERROR: HTTP_buildrequest HTTP\n");
469 0 : free(request);
470 0 : return NULL;
471 : }
472 3 : request = newrequest;
473 3 : switch(version) {
474 3 : case HTTPVERSION_HTTP11:
475 : case HTTPVERSION_HTTP11b:
476 3 : if (NULL==(newrequest=strconcat(request, "1.1"))) {
477 0 : fprintf(stderr,"ERROR: HTTP_buildrequest Version\n");
478 0 : free(request);
479 0 : return NULL;
480 : }
481 3 : request = newrequest;
482 3 : break;;
483 0 : case HTTPVERSION_HTTP09:
484 : case HTTPVERSION_HTTP10:
485 : case HTTPVERSION_HTTP2:
486 : case HTTPVERSION_HTTP3:
487 : case HTTPVERSION_INVALID:
488 : default:
489 0 : fprintf(stderr,"PANIC: HTTP version not supported\n");
490 0 : free(request);
491 0 : return NULL;
492 : break;;
493 : }
494 3 : if (NULL==(newrequest=strconcat(request, "\r\n"))) {
495 0 : fprintf(stderr,"ERROR: HTTP_buildrequest Version\n");
496 0 : free(request);
497 0 : return NULL;
498 : }
499 3 : request = newrequest;
500 :
501 3 : if (NULL==(headers=HTTP_buildheaders(http))) {
502 0 : fprintf(stderr,"ERROR: HTTP_buildrequest buildheaders\n");
503 0 : free(request);
504 0 : return NULL;
505 : }
506 :
507 3 : if (NULL==(newrequest=strconcat(request, headers))) {
508 0 : fprintf(stderr,"ERROR: HTTP_buildrequest header separator\n");
509 0 : free(request);
510 0 : return NULL;
511 : }
512 3 : request = newrequest;
513 3 : free(headers);
514 :
515 3 : if (NULL==(newrequest=strconcat(request, "\r\n"))) {
516 0 : fprintf(stderr,"ERROR: HTTP_buildrequest header separator\n");
517 0 : free(request);
518 0 : return NULL;
519 : }
520 3 : request = newrequest;
521 :
522 3 : if (NULL==(newrequest=strconcat(request, http->body))) {
523 0 : fprintf(stderr,"ERROR: HTTP_buildrequest body\n");
524 0 : free(request);
525 0 : return NULL;
526 : }
527 3 : request = newrequest;
528 :
529 3 : return request;
530 : }
531 :
|