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-22 08:26
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* sclistrecord;
35 : char* name;
36 : char* value;
37 : } HTTPHeader_t;
38 :
39 28 : HTTPHeader_t* HTTPHeader_new(const char* name, const char* value) {
40 : HTTPHeader_t* header;
41 :
42 28 : assert(name);
43 26 : assert(value);
44 :
45 26 : if (NULL==(header=malloc(sizeof(struct HTTPHeader_s)))) {
46 0 : perror("HTTPHeader_new");
47 0 : return NULL;
48 : }
49 26 : if (NULL==(header->name = strdup(name))) {
50 0 : perror("HTTPHeader_new name");
51 0 : free(header);
52 0 : return NULL;
53 : }
54 26 : if (NULL==(header->value = strdup(value))) {
55 0 : perror("HTTPHeader_new value");
56 0 : free(header->name);
57 0 : free(header);
58 0 : return NULL;
59 : }
60 26 : header->sclistrecord = NULL;
61 26 : return header;
62 : }
63 :
64 21 : void HTTPHeader_del(HTTPHeader_t* header) {
65 21 : assert(header);
66 19 : assert(header->name);
67 19 : assert(header->value);
68 :
69 19 : free(header->name);
70 19 : free(header->value);
71 19 : free(header);
72 19 : }
73 :
74 3 : HTTPHeader_t* HTTPHeader_basicauth(const char* login, const char* pass) {
75 : char* auth_encoded;
76 : char* auth;
77 :
78 3 : assert(login);
79 3 : assert(pass);
80 :
81 3 : if (NULL==(auth=malloc(strlen("Basic ")+strlen(login)+1+strlen(pass)+1))) {
82 0 : perror("HTTPHeader_basicauth");
83 0 : return NULL;
84 : }
85 : /* strcpy/strcat expected to be faster than sprintf */
86 3 : strcpy(auth,login);
87 3 : strcat(auth,":");
88 3 : strcat(auth,pass);
89 3 : auth_encoded=base64_encode(auth);
90 3 : free(auth);
91 :
92 3 : if (NULL==(auth=malloc(strlen("Basic ")+strlen(auth_encoded)+1))) {
93 0 : perror("HTTPHeader_basicauth");
94 0 : free(auth_encoded);
95 0 : return NULL;
96 : }
97 3 : strcpy(auth,"Basic ");
98 3 : strcat(auth, auth_encoded);
99 :
100 3 : return HTTPHeader_new("Authorization",auth);
101 : }
102 :
103 20 : char* HTTPHeader_getname(HTTPHeader_t* header) {
104 20 : assert(header);
105 20 : return header->name;
106 : }
107 :
108 20 : char* HTTPHeader_getvalue(HTTPHeader_t* header) {
109 20 : assert(header);
110 20 : return header->value;
111 : }
112 :
113 27 : HTTP_t* HTTP_new() {
114 : HTTP_t* retval;
115 :
116 27 : if (NULL==(retval=malloc(sizeof(struct HTTP_s)))) {
117 0 : perror("HTTP_new HTTP_s");
118 0 : return NULL;
119 : }
120 :
121 27 : if (NULL==(retval->body=malloc(1))) {
122 0 : perror("HTTP_new body");
123 0 : free(retval);
124 0 : return NULL;
125 : };
126 27 : retval->body[0]=0;
127 :
128 27 : if (NULL==(retval->headers = sclist_new())) {
129 0 : perror("HTTP_new headers");
130 0 : free(retval->body);
131 0 : free(retval);
132 0 : return NULL;
133 : }
134 :
135 27 : return retval;
136 : }
137 :
138 21 : void HTTP_del(HTTP_t* http) {
139 : sclistrecord_t* current;
140 :
141 21 : assert(http);
142 19 : assert(http->headers);
143 19 : assert(http->body);
144 :
145 19 : current = sclist_firstrecord(http->headers);
146 26 : while (current) {
147 9 : HTTPHeader_t* header=sclist_getvalue(current);
148 9 : if (header->name)
149 9 : free(header->name);
150 9 : if (header->value)
151 9 : free(header->value);
152 9 : sclistrecord_t* tmp = current;
153 9 : current = sclist_nextrecord(current);
154 9 : sclist_remrecord(http->headers,tmp);
155 : }
156 :
157 17 : free(http->body);
158 17 : free(http);
159 17 : }
160 :
161 13 : char* HTTP_setbody(HTTP_t* http, const char* body) {
162 : char* newbody;
163 :
164 13 : assert(http);
165 11 : assert(body);
166 9 : assert(http->body);
167 :
168 9 : if (NULL==(newbody=realloc(http->body,strlen(body)+1))) {
169 0 : perror("HTTP_setbody");
170 0 : return NULL;
171 : }
172 :
173 9 : http->body = newbody;
174 9 : strcpy(http->body, body);
175 9 : return http->body;
176 : }
177 :
178 12 : char* HTTP_getbody(HTTP_t* http) {
179 12 : assert(http);
180 12 : assert(http->body);
181 :
182 12 : return http->body;
183 : }
184 :
185 22 : HTTP_t* HTTP_addheader(HTTP_t* http, HTTPHeader_t* header) {
186 22 : assert(http);
187 20 : assert(http->headers);
188 20 : assert(http->body);
189 20 : assert(header);
190 :
191 : /* Header already belonging to an header list */
192 18 : if (header->sclistrecord) {
193 3 : perror("HTTP_addheader, header already in a list");
194 3 : return NULL;
195 : }
196 :
197 15 : if (NULL==(header->sclistrecord=sclist_addrecord(http->headers,header))) {
198 0 : perror("HTTP_addheader");
199 0 : return NULL;
200 : }
201 15 : return http;
202 : }
203 :
204 0 : HTTPHeader_t* HTTP_firstheader(const HTTP_t* http) {
205 0 : assert(http);
206 0 : assert(http->headers);
207 0 : sclistrecord_t* header_item=sclist_firstrecord(http->headers);
208 0 : if (!header_item)
209 0 : return NULL;
210 0 : return sclist_getvalue(header_item);
211 : }
212 :
213 0 : HTTPHeader_t* HTTP_nextheader(const HTTPHeader_t* header) {
214 : sclistrecord_t* list_it;
215 :
216 0 : assert(header);
217 :
218 0 : list_it = header->sclistrecord;
219 :
220 : /* EOL reached */
221 0 : if (NULL==(list_it = sclist_nextrecord(list_it)))
222 0 : return NULL;
223 : else
224 0 : return sclist_getvalue(list_it);
225 : }
226 :
227 0 : HTTPHeader_t* HTTP_findheader(const HTTPHeader_t* start, const char* name, const char* value) {
228 : sclistrecord_t* header_it;
229 :
230 0 : assert(start);
231 0 : assert(start->sclistrecord);
232 0 : assert(name);
233 : /* value can be null for "any" */
234 :
235 0 : header_it = start->sclistrecord;
236 :
237 : /* Find the record pointer in the list */
238 0 : for (; header_it; header_it=sclist_nextrecord(header_it)) {
239 0 : HTTPHeader_t* header_ptr = sclist_getvalue(header_it);
240 0 : assert(header_ptr);
241 0 : char* header_name = HTTPHeader_getname(header_ptr);
242 0 : if (0==strcmp(header_name,name)) {
243 0 : if (NULL==value)
244 0 : break;
245 0 : char* header_value = HTTPHeader_getvalue(header_ptr);
246 0 : assert(header_value);
247 0 : if (0==strcmp(header_value,value))
248 0 : break;
249 : }
250 : }
251 0 : if (!header_it)
252 0 : return NULL;
253 : else
254 0 : return sclist_getvalue(header_it);
255 : }
256 :
257 6 : HTTP_t* HTTP_delheader(HTTP_t* http, HTTPHeader_t* header) {
258 6 : assert(http);
259 6 : assert(http->headers);
260 6 : assert(http->body);
261 6 : assert(header);
262 :
263 : /* Calling function has the header pointer, can and need to free it */
264 6 : sclist_remrecord(http->headers, header->sclistrecord);
265 : /** @todo: make sclist_remrecord return a status and use it */
266 : #if 0
267 : if (NULL==sclist_remrecord(http->headers, header->sclistrecord)) {
268 : perror("HTTP_addheader");
269 : return NULL;
270 : }
271 : #endif
272 6 : header->sclistrecord = NULL;
273 :
274 6 : return http;
275 : }
276 :
277 3 : char* HTTP_buildrequest(const HTTPMethod_t method, const char* uri, const HTTPVersion_t version) {
278 : char* retval;
279 :
280 3 : assert(method<=HTTPMETHOD_INVALID);
281 3 : assert(version<=HTTPVERSION_INVALID);
282 :
283 : /* NULL URI allowed, as an empty string */
284 3 : if (NULL==(retval=malloc(7+1+(uri?strlen(uri)+1:0)+8+1))) {
285 0 : perror("HTTP_getrequest");
286 0 : exit(EXIT_FAILURE);
287 : }
288 3 : retval[0]=0;
289 :
290 3 : switch(method) {
291 3 : case HTTPMETHOD_GET:
292 3 : strcat(retval, "GET");
293 3 : break;;
294 0 : case HTTPMETHOD_HEAD:
295 : case HTTPMETHOD_POST:
296 : case HTTPMETHOD_PUT:
297 : case HTTPMETHOD_DELETE:
298 : case HTTPMETHOD_CONNECT:
299 : case HTTPMETHOD_OPTIONS:
300 : case HTTPMETHOD_TRACE:
301 : case HTTPMETHOD_PATCH:
302 : case HTTPMETHOD_INVALID:
303 : default:
304 0 : fprintf(stderr,"HTTP method not supported\n");
305 0 : exit(EXIT_FAILURE);
306 : break;;
307 : }
308 :
309 : /* NULL URI allowed, as an empty string */
310 3 : if (uri) {
311 3 : strcat(retval," ");
312 3 : strcat(retval, uri);
313 : }
314 :
315 3 : strcat(retval," HTTP/");
316 3 : switch(version) {
317 3 : case HTTPVERSION_HTTP11:
318 : case HTTPVERSION_HTTP11b:
319 3 : strcat(retval, "1.1");
320 3 : break;;
321 0 : case HTTPVERSION_HTTP09:
322 : case HTTPVERSION_HTTP10:
323 : case HTTPVERSION_HTTP2:
324 : case HTTPVERSION_HTTP3:
325 : case HTTPVERSION_INVALID:
326 : default:
327 0 : fprintf(stderr,"HTTP version not supported\n");
328 0 : exit(EXIT_FAILURE);
329 : break;;
330 : }
331 3 : strcat(retval, "\r\n");
332 3 : return retval;
333 : }
334 :
335 36 : static char* strconcat(char* dst, char* src) {
336 : char* newdst;
337 :
338 36 : assert(dst);
339 36 : assert(src);
340 :
341 36 : if (NULL==(newdst=realloc(dst,strlen(dst)+strlen(src)+1))) {
342 0 : perror("strconcat");
343 0 : return NULL;
344 : }
345 36 : dst = newdst;
346 36 : strcat(dst,src);
347 36 : return dst;
348 : }
349 :
350 3 : char* HTTP_buildheaders(const HTTP_t* http) {
351 3 : char* headers=NULL;
352 : sclistrecord_t* sclistrecord;
353 : HTTPHeader_t* header;
354 :
355 3 : assert(http);
356 3 : assert(http->headers);
357 3 : assert(http->body);
358 :
359 3 : if (NULL==(headers=strdup(""))) {
360 0 : perror("HTTP_buildheaders headers");
361 0 : return NULL;
362 : }
363 12 : for (sclistrecord=sclist_firstrecord(http->headers); sclistrecord; sclistrecord = sclist_nextrecord(sclistrecord)) {
364 9 : header = sclist_getvalue(sclistrecord);
365 9 : assert(header);
366 : /** @todo: OOM tests */
367 9 : headers=strconcat(headers,HTTPHeader_getname(header));
368 9 : headers=strconcat(headers,": ");
369 9 : headers=strconcat(headers,HTTPHeader_getvalue(header));
370 9 : headers=strconcat(headers,"\r\n");
371 : }
372 :
373 :
374 3 : return headers;
375 : }
|