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