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