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-24 22:42
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 : perror("HTTP_addheader empty name");
53 3 : return NULL;
54 : }
55 :
56 3 : if (NULL==(newname=realloc(header->name,strlen(name)+1))) {
57 0 : perror("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 216 : char* HTTPHeader_getname(HTTPHeader_t* header) {
66 216 : assert(header);
67 214 : assert(header->name);
68 214 : assert(header->value);
69 :
70 214 : 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("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 147 : char* HTTPHeader_getvalue(HTTPHeader_t* header) {
91 147 : assert(header);
92 145 : assert(header->name);
93 145 : assert(header->value);
94 :
95 145 : return header->value;
96 : }
97 :
98 189 : static HTTPHeader_t* HTTPHeader_new(const char* name, const char* value) {
99 : HTTPHeader_t* header;
100 :
101 189 : assert(name);
102 189 : assert(value);
103 :
104 189 : if (NULL==(header=malloc(sizeof(struct HTTPHeader_s)))) {
105 0 : perror("HTTPHeader_new");
106 0 : return NULL;
107 : }
108 189 : if (NULL==(header->name=strdup(name))) {
109 0 : perror("HTTPHeader_new name");
110 0 : free(header);
111 0 : return NULL;
112 : }
113 189 : if (NULL==(header->value=strdup(value))) {
114 0 : perror("HTTPHeader_new value");
115 0 : free(header->name);
116 0 : free(header);
117 0 : return NULL;
118 : }
119 189 : header->self = NULL;
120 189 : return header;
121 : }
122 :
123 174 : static void HTTPHeader_del(HTTPHeader_t* header) {
124 174 : assert(header);
125 174 : assert(header->name);
126 174 : assert(header->value);
127 :
128 174 : free(header->name);
129 174 : free(header->value);
130 174 : free(header);
131 174 : }
132 :
133 133 : HTTP_t* HTTP_new() {
134 : HTTP_t* http;
135 :
136 133 : if (NULL==(http=malloc(sizeof(struct HTTP_s)))) {
137 0 : perror("HTTP_new HTTP_s");
138 0 : return NULL;
139 : }
140 :
141 133 : if (NULL==(http->body=strdup(""))) {
142 0 : perror("HTTP_new body");
143 0 : free(http);
144 0 : return NULL;
145 : };
146 :
147 133 : if (NULL==(http->headers = sclist_new())) {
148 0 : perror("HTTP_new headers");
149 0 : free(http->body);
150 0 : free(http);
151 0 : return NULL;
152 : }
153 :
154 133 : return http;
155 : }
156 :
157 110 : void HTTP_del(HTTP_t* http) {
158 : sclistrecord_t* headerlst_entry;
159 :
160 110 : assert(http);
161 108 : assert(http->headers);
162 108 : assert(http->body);
163 :
164 108 : headerlst_entry = sclist_firstrecord(http->headers);
165 270 : while (headerlst_entry) {
166 : HTTPHeader_t* headerlst_value;
167 :
168 : /* Free payload */
169 162 : headerlst_value=sclist_getvalue(headerlst_entry);
170 162 : HTTPHeader_del(headerlst_value);
171 :
172 : /* Remember the entry to delete before moving to the next one */
173 162 : sclistrecord_t* tmp = headerlst_entry;
174 162 : headerlst_entry = sclist_nextrecord(headerlst_entry);
175 162 : sclist_remrecord(http->headers,tmp);
176 : }
177 :
178 108 : free(http->body);
179 108 : free(http);
180 108 : }
181 :
182 19 : HTTP_t* HTTP_setbody(HTTP_t* http, const char* body) {
183 : char* newbody;
184 :
185 19 : assert(http);
186 17 : assert(http->headers);
187 17 : assert(http->body);
188 17 : assert(body);
189 :
190 15 : if (NULL==(newbody=realloc(http->body,strlen(body)+1))) {
191 0 : perror("HTTP_setbody realloc");
192 0 : return NULL;
193 : }
194 :
195 15 : http->body = newbody;
196 15 : strcpy(http->body, body);
197 15 : return http;
198 : }
199 :
200 20 : char* HTTP_getbody(HTTP_t* http) {
201 20 : assert(http);
202 18 : assert(http->headers);
203 18 : assert(http->body);
204 :
205 18 : return http->body;
206 : }
207 :
208 198 : HTTPHeader_t* HTTP_addheader(HTTP_t* http, const char* name, const char* value) {
209 : HTTPHeader_t* header;
210 :
211 198 : assert(http);
212 196 : assert(http->headers);
213 196 : assert(http->body);
214 196 : assert(name);
215 194 : assert(value);
216 :
217 192 : if (name[0]==0) {
218 3 : perror("HTTP_addheader empty name");
219 3 : return NULL;
220 : }
221 :
222 189 : if (NULL==(header = HTTPHeader_new(name,value))) {
223 0 : perror("HTTP_addheader HTTPHeader_new");
224 0 : return NULL;
225 : }
226 :
227 189 : if (NULL==(header->self=sclist_addrecord(http->headers,header))) {
228 0 : perror("HTTP_addheader");
229 0 : HTTPHeader_del(header);
230 0 : return NULL;
231 : }
232 189 : return header;
233 : }
234 :
235 15 : HTTPHeader_t* HTTP_addbasicauth(HTTP_t* http, const char* login, const char* pass) {
236 : char* auth_encoded;
237 : char* auth;
238 : HTTPHeader_t* header;
239 :
240 15 : assert(http);
241 13 : assert(http->headers);
242 13 : assert(http->body);
243 13 : assert(login);
244 11 : assert(pass);
245 :
246 9 : if (NULL==(auth=malloc(strlen(login)+1+strlen(pass)+1))) {
247 0 : perror("HTTP_addbasicauth credentials");
248 0 : return NULL;
249 : }
250 : /* strcpy/strcat expected to be faster than sprintf */
251 9 : strcpy(auth,login);
252 9 : strcat(auth,":");
253 9 : strcat(auth,pass);
254 9 : auth_encoded=base64_encode(auth);
255 9 : free(auth);
256 :
257 9 : if (NULL==(auth=malloc(strlen("Basic ")+strlen(auth_encoded)+1))) {
258 0 : perror("HTTP_addbasicauth value");
259 0 : free(auth_encoded);
260 0 : return NULL;
261 : }
262 9 : strcpy(auth,"Basic ");
263 9 : strcat(auth, auth_encoded);
264 9 : free(auth_encoded);
265 :
266 9 : if (NULL==(header=HTTP_addheader(http,"Authorization",auth))) {
267 0 : perror("HTTP_addbasicauth addheader");
268 0 : free(auth);
269 0 : return NULL;
270 : }
271 9 : free(auth);
272 9 : return header;
273 : }
274 :
275 84 : HTTPHeader_t* HTTP_firstheader(const HTTP_t* http) {
276 : sclistrecord_t* headerlst_entry;
277 :
278 84 : assert(http);
279 82 : assert(http->headers);
280 82 : assert(http->body);
281 :
282 82 : if (NULL==(headerlst_entry=sclist_firstrecord(http->headers)))
283 11 : return NULL;
284 71 : return sclist_getvalue(headerlst_entry);
285 : }
286 :
287 52 : HTTPHeader_t* HTTP_nextheader(const HTTPHeader_t* header) {
288 : sclistrecord_t* headerlst_entry;
289 :
290 52 : assert(header);
291 48 : assert(header->self);
292 48 : assert(header->name);
293 48 : assert(header->value);
294 :
295 48 : headerlst_entry = header->self;
296 :
297 : /* EOL reached */
298 48 : if (NULL==(headerlst_entry = sclist_nextrecord(headerlst_entry)))
299 21 : return NULL;
300 : else
301 27 : return sclist_getvalue(headerlst_entry);
302 : }
303 :
304 37 : HTTPHeader_t* HTTP_findheader(const HTTPHeader_t* start, const char* name) {
305 : sclistrecord_t* headerlst_entry;
306 :
307 37 : assert(start);
308 35 : assert(start->self);
309 35 : assert(start->name);
310 35 : assert(start->value);
311 35 : assert(name);
312 :
313 33 : headerlst_entry = start->self;
314 :
315 75 : while (headerlst_entry) {
316 : HTTPHeader_t* headerlst_value;
317 : char* header_name;
318 :
319 69 : headerlst_value = sclist_getvalue(headerlst_entry);
320 69 : assert(headerlst_value);
321 69 : header_name = HTTPHeader_getname(headerlst_value);
322 69 : if (0==strcmp(header_name,name))
323 27 : break;
324 42 : headerlst_entry=sclist_nextrecord(headerlst_entry);
325 : }
326 33 : if (!headerlst_entry)
327 : /* Not found */
328 6 : return NULL;
329 : else
330 : /* Found */
331 27 : return sclist_getvalue(headerlst_entry);
332 : }
333 :
334 :
335 16 : HTTP_t* HTTP_remheader(HTTP_t* http, HTTPHeader_t* header) {
336 16 : assert(http);
337 14 : assert(http->headers);
338 14 : assert(http->body);
339 14 : assert(header);
340 12 : assert(header->self);
341 12 : assert(header->name);
342 12 : assert(header->value);
343 :
344 : /* Remove from the header list */
345 12 : sclist_remrecord(http->headers, header->self);
346 : /** @todo: make sclist_remrecord return a status (found/notfound) and use it */
347 : #if 0
348 : if (NULL==sclist_remrecord(http->headers, header->sclistrecord)) {
349 : perror("HTTP_addheader");
350 : return NULL;
351 : }
352 : #endif
353 : /** @bug Header deleted if not found in this HTTP, but it obviously
354 : * belongs to another HTTP, will be freed, but not removed from his
355 : * header list SIGSEGV11 to expect at some point */
356 12 : header->self = NULL;
357 12 : HTTPHeader_del(header);
358 :
359 12 : return http;
360 : }
361 :
362 36 : static char* strconcat(char* dst, char* src) {
363 : char* newdst;
364 :
365 36 : assert(dst);
366 36 : assert(src);
367 :
368 36 : if (NULL==(newdst=realloc(dst,strlen(dst)+strlen(src)+1))) {
369 0 : perror("strconcat");
370 0 : return NULL;
371 : }
372 36 : dst = newdst;
373 36 : strcat(dst,src);
374 36 : return dst;
375 : }
376 :
377 3 : char* HTTP_buildheaders(const HTTP_t* http) {
378 3 : char* headers_str=NULL;
379 : sclistrecord_t* headerlst_entry;
380 :
381 3 : assert(http);
382 3 : assert(http->headers);
383 3 : assert(http->body);
384 :
385 3 : if (NULL==(headers_str=strdup(""))) {
386 0 : perror("HTTP_buildheaders headers_str");
387 0 : return NULL;
388 : }
389 12 : for (headerlst_entry=sclist_firstrecord(http->headers); headerlst_entry; headerlst_entry = sclist_nextrecord(headerlst_entry)) {
390 : HTTPHeader_t* headerlst_value;
391 :
392 9 : headerlst_value = sclist_getvalue(headerlst_entry);
393 9 : assert(headerlst_value);
394 : /** @todo: OOM tests */
395 9 : headers_str=strconcat(headers_str,HTTPHeader_getname(headerlst_value));
396 9 : headers_str=strconcat(headers_str,": ");
397 9 : headers_str=strconcat(headers_str,HTTPHeader_getvalue(headerlst_value));
398 9 : headers_str=strconcat(headers_str,"\r\n");
399 : }
400 :
401 3 : return headers_str;
402 : }
403 :
404 :
405 :
406 :
407 :
408 :
409 :
410 :
411 :
412 :
413 :
414 :
415 :
416 3 : char* HTTP_buildrequest(const HTTPMethod_t method, const char* uri, const HTTPVersion_t version) {
417 : char* retval;
418 :
419 3 : assert(method<=HTTPMETHOD_INVALID);
420 3 : assert(uri);
421 3 : assert(version<=HTTPVERSION_INVALID);
422 :
423 3 : if (NULL==(retval=malloc(7+1+strlen(uri)+1+8+1))) {
424 0 : perror("HTTP_getrequest");
425 0 : exit(EXIT_FAILURE);
426 : }
427 3 : retval[0]=0;
428 :
429 3 : switch(method) {
430 3 : case HTTPMETHOD_GET:
431 3 : strcat(retval, "GET");
432 3 : break;;
433 0 : case HTTPMETHOD_HEAD:
434 : case HTTPMETHOD_POST:
435 : case HTTPMETHOD_PUT:
436 : case HTTPMETHOD_DELETE:
437 : case HTTPMETHOD_CONNECT:
438 : case HTTPMETHOD_OPTIONS:
439 : case HTTPMETHOD_TRACE:
440 : case HTTPMETHOD_PATCH:
441 : case HTTPMETHOD_INVALID:
442 : default:
443 0 : fprintf(stderr,"HTTP method not supported\n");
444 0 : exit(EXIT_FAILURE);
445 : break;;
446 : }
447 :
448 3 : if (uri[0]) {
449 3 : strcat(retval," ");
450 3 : strcat(retval, uri);
451 : }
452 :
453 3 : strcat(retval," HTTP/");
454 3 : switch(version) {
455 3 : case HTTPVERSION_HTTP11:
456 : case HTTPVERSION_HTTP11b:
457 3 : strcat(retval, "1.1");
458 3 : break;;
459 0 : case HTTPVERSION_HTTP09:
460 : case HTTPVERSION_HTTP10:
461 : case HTTPVERSION_HTTP2:
462 : case HTTPVERSION_HTTP3:
463 : case HTTPVERSION_INVALID:
464 : default:
465 0 : fprintf(stderr,"HTTP version not supported\n");
466 0 : exit(EXIT_FAILURE);
467 : break;;
468 : }
469 3 : strcat(retval, "\r\n");
470 3 : return retval;
471 : }
472 :
|