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