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 18: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 : 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 60 : char* HTTPHeader_getname(HTTPHeader_t* header) {
66 60 : assert(header);
67 58 : assert(header->name);
68 58 : assert(header->value);
69 :
70 58 : 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 60 : char* HTTPHeader_getvalue(HTTPHeader_t* header) {
91 60 : assert(header);
92 58 : assert(header->name);
93 58 : assert(header->value);
94 :
95 58 : return header->value;
96 : }
97 :
98 52 : static HTTPHeader_t* HTTPHeader_new(const char* name, const char* value) {
99 : HTTPHeader_t* header;
100 :
101 52 : assert(name);
102 52 : assert(value);
103 :
104 52 : if (NULL==(header=malloc(sizeof(struct HTTPHeader_s)))) {
105 0 : perror("HTTPHeader_new");
106 0 : return NULL;
107 : }
108 52 : if (NULL==(header->name=strdup(name))) {
109 0 : perror("HTTPHeader_new name");
110 0 : free(header);
111 0 : return NULL;
112 : }
113 52 : 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 52 : header->self = NULL;
120 52 : return header;
121 : }
122 :
123 48 : static void HTTPHeader_del(HTTPHeader_t* header) {
124 48 : assert(header);
125 48 : assert(header->name);
126 48 : assert(header->value);
127 :
128 48 : free(header->name);
129 48 : free(header->value);
130 48 : free(header);
131 48 : }
132 :
133 71 : HTTP_t* HTTP_new() {
134 : HTTP_t* http;
135 :
136 71 : if (NULL==(http=malloc(sizeof(struct HTTP_s)))) {
137 0 : perror("HTTP_new HTTP_s");
138 0 : return NULL;
139 : }
140 :
141 71 : if (NULL==(http->body=strdup(""))) {
142 0 : perror("HTTP_new body");
143 0 : free(http);
144 0 : return NULL;
145 : };
146 :
147 71 : 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 71 : return http;
155 : }
156 :
157 59 : void HTTP_del(HTTP_t* http) {
158 : sclistrecord_t* headerlst_entry;
159 :
160 59 : assert(http);
161 57 : assert(http->headers);
162 57 : assert(http->body);
163 :
164 57 : headerlst_entry = sclist_firstrecord(http->headers);
165 105 : while (headerlst_entry) {
166 : HTTPHeader_t* headerlst_value;
167 :
168 : /* Free payload */
169 48 : headerlst_value=sclist_getvalue(headerlst_entry);
170 48 : HTTPHeader_del(headerlst_value);
171 :
172 : /* Remember the entry to delete before moving to the next one */
173 48 : sclistrecord_t* tmp = headerlst_entry;
174 48 : headerlst_entry = sclist_nextrecord(headerlst_entry);
175 48 : sclist_remrecord(http->headers,tmp);
176 : }
177 :
178 57 : free(http->body);
179 57 : free(http);
180 57 : }
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 61 : HTTPHeader_t* HTTP_addheader(HTTP_t* http, const char* name, const char* value) {
209 : HTTPHeader_t* header;
210 :
211 61 : assert(http);
212 59 : assert(http->headers);
213 59 : assert(http->body);
214 59 : assert(name);
215 57 : assert(value);
216 :
217 55 : if (name[0]==0) {
218 3 : perror("HTTP_addheader empty name");
219 3 : return NULL;
220 : }
221 :
222 52 : if (NULL==(header = HTTPHeader_new(name,value))) {
223 0 : perror("HTTP_addheader HTTPHeader_new");
224 0 : return NULL;
225 : }
226 :
227 52 : 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 52 : 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 6 : HTTPHeader_t* HTTP_firstheader(const HTTP_t* http) {
276 : sclistrecord_t* headerlst_entry;
277 :
278 6 : assert(http);
279 6 : assert(http->headers);
280 6 : assert(http->body);
281 :
282 6 : if (NULL==(headerlst_entry=sclist_firstrecord(http->headers)))
283 3 : return NULL;
284 3 : return sclist_getvalue(headerlst_entry);
285 : }
286 :
287 0 : HTTPHeader_t* HTTP_nextheader(const HTTPHeader_t* header) {
288 : sclistrecord_t* headerlst_entry;
289 :
290 0 : assert(header);
291 0 : assert(header->self);
292 0 : assert(header->name);
293 0 : assert(header->value);
294 :
295 0 : headerlst_entry = header->self;
296 :
297 : /* EOL reached */
298 0 : if (NULL==(headerlst_entry = sclist_nextrecord(headerlst_entry)))
299 0 : return NULL;
300 : else
301 0 : return sclist_getvalue(headerlst_entry);
302 : }
303 :
304 0 : HTTPHeader_t* HTTP_findheader(const HTTPHeader_t* start, const char* name) {
305 : sclistrecord_t* headerlst_entry;
306 :
307 0 : assert(start);
308 0 : assert(start->self);
309 0 : assert(start->name);
310 0 : assert(start->value);
311 0 : assert(name);
312 :
313 0 : headerlst_entry = start->self;
314 :
315 0 : while (headerlst_entry) {
316 : HTTPHeader_t* headerlst_value;
317 : char* header_name;
318 :
319 0 : headerlst_value = sclist_getvalue(headerlst_entry);
320 0 : assert(headerlst_value);
321 0 : header_name = HTTPHeader_getname(headerlst_value);
322 0 : if (0==strcmp(header_name,name))
323 0 : break;
324 0 : headerlst_entry=sclist_nextrecord(headerlst_entry);
325 : }
326 0 : if (!headerlst_entry)
327 : /* Not found */
328 0 : return NULL;
329 : else
330 : /* Found */
331 0 : return sclist_getvalue(headerlst_entry);
332 : }
333 :
334 :
335 0 : HTTP_t* HTTP_remheader(HTTP_t* http, HTTPHeader_t* header) {
336 0 : assert(http);
337 0 : assert(http->headers);
338 0 : assert(http->body);
339 0 : assert(header);
340 0 : assert(header->self);
341 0 : assert(header->name);
342 0 : assert(header->value);
343 :
344 : /* Remove from the header list */
345 0 : 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 0 : header->self = NULL;
354 0 : HTTPHeader_del(header);
355 :
356 0 : return http;
357 : }
358 :
359 36 : static char* strconcat(char* dst, char* src) {
360 : char* newdst;
361 :
362 36 : assert(dst);
363 36 : assert(src);
364 :
365 36 : if (NULL==(newdst=realloc(dst,strlen(dst)+strlen(src)+1))) {
366 0 : perror("strconcat");
367 0 : return NULL;
368 : }
369 36 : dst = newdst;
370 36 : strcat(dst,src);
371 36 : return dst;
372 : }
373 :
374 3 : char* HTTP_buildheaders(const HTTP_t* http) {
375 3 : char* headers_str=NULL;
376 : sclistrecord_t* headerlst_entry;
377 :
378 3 : assert(http);
379 3 : assert(http->headers);
380 3 : assert(http->body);
381 :
382 3 : if (NULL==(headers_str=strdup(""))) {
383 0 : perror("HTTP_buildheaders headers_str");
384 0 : return NULL;
385 : }
386 12 : for (headerlst_entry=sclist_firstrecord(http->headers); headerlst_entry; headerlst_entry = sclist_nextrecord(headerlst_entry)) {
387 : HTTPHeader_t* headerlst_value;
388 :
389 9 : headerlst_value = sclist_getvalue(headerlst_entry);
390 9 : assert(headerlst_value);
391 : /** @todo: OOM tests */
392 9 : headers_str=strconcat(headers_str,HTTPHeader_getname(headerlst_value));
393 9 : headers_str=strconcat(headers_str,": ");
394 9 : headers_str=strconcat(headers_str,HTTPHeader_getvalue(headerlst_value));
395 9 : headers_str=strconcat(headers_str,"\r\n");
396 : }
397 :
398 3 : return headers_str;
399 : }
400 :
401 :
402 :
403 :
404 :
405 :
406 :
407 :
408 :
409 :
410 :
411 :
412 :
413 3 : char* HTTP_buildrequest(const HTTPMethod_t method, const char* uri, const HTTPVersion_t version) {
414 : char* retval;
415 :
416 3 : assert(method<=HTTPMETHOD_INVALID);
417 3 : assert(uri);
418 3 : assert(version<=HTTPVERSION_INVALID);
419 :
420 3 : if (NULL==(retval=malloc(7+1+strlen(uri)+1+8+1))) {
421 0 : perror("HTTP_getrequest");
422 0 : exit(EXIT_FAILURE);
423 : }
424 3 : retval[0]=0;
425 :
426 3 : switch(method) {
427 3 : case HTTPMETHOD_GET:
428 3 : strcat(retval, "GET");
429 3 : break;;
430 0 : case HTTPMETHOD_HEAD:
431 : case HTTPMETHOD_POST:
432 : case HTTPMETHOD_PUT:
433 : case HTTPMETHOD_DELETE:
434 : case HTTPMETHOD_CONNECT:
435 : case HTTPMETHOD_OPTIONS:
436 : case HTTPMETHOD_TRACE:
437 : case HTTPMETHOD_PATCH:
438 : case HTTPMETHOD_INVALID:
439 : default:
440 0 : fprintf(stderr,"HTTP method not supported\n");
441 0 : exit(EXIT_FAILURE);
442 : break;;
443 : }
444 :
445 3 : if (uri[0]) {
446 3 : strcat(retval," ");
447 3 : strcat(retval, uri);
448 : }
449 :
450 3 : strcat(retval," HTTP/");
451 3 : switch(version) {
452 3 : case HTTPVERSION_HTTP11:
453 : case HTTPVERSION_HTTP11b:
454 3 : strcat(retval, "1.1");
455 3 : break;;
456 0 : case HTTPVERSION_HTTP09:
457 : case HTTPVERSION_HTTP10:
458 : case HTTPVERSION_HTTP2:
459 : case HTTPVERSION_HTTP3:
460 : case HTTPVERSION_INVALID:
461 : default:
462 0 : fprintf(stderr,"HTTP version not supported\n");
463 0 : exit(EXIT_FAILURE);
464 : break;;
465 : }
466 3 : strcat(retval, "\r\n");
467 3 : return retval;
468 : }
469 :
|