source: mainline/uspace/lib/http/http.c@ 19d21728

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 19d21728 was d7b7f5e, checked in by Martin Sucha <sucha14@…>, 12 years ago

Add a basic HTTP download tool

  • Property mode set to 100644
File size: 11.3 KB
Line 
1/*
2 * Copyright (c) 2013 Martin Sucha
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup http
30 * @{
31 */
32/**
33 * @file
34 */
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <str.h>
39#include <macros.h>
40
41#include <net/socket.h>
42#include <inet/dnsr.h>
43
44#include "http.h"
45
46#define HTTP_METHOD_LINE "%s %s HTTP/1.1\r\n"
47#define HTTP_HEADER_LINE "%s: %s\r\n"
48#define HTTP_REQUEST_LINE "\r\n"
49
50static char *cut_str(const char *start, const char *end)
51{
52 size_t size = end - start;
53 return str_ndup(start, size);
54}
55
56static void recv_reset(http_t *http)
57{
58 http->recv_buffer_in = 0;
59 http->recv_buffer_out = 0;
60}
61
62/** Receive one character (with buffering) */
63static int recv_char(http_t *http, char *c, bool consume)
64{
65 if (http->recv_buffer_out == http->recv_buffer_in) {
66 recv_reset(http);
67
68 ssize_t rc = recv(http->conn_sd, http->recv_buffer, http->buffer_size, 0);
69 if (rc <= 0)
70 return rc;
71
72 http->recv_buffer_in = rc;
73 }
74
75 *c = http->recv_buffer[http->recv_buffer_out];
76 if (consume)
77 http->recv_buffer_out++;
78 return EOK;
79}
80
81static ssize_t recv_buffer(http_t *http, char *buf, size_t buf_size)
82{
83 /* Flush any buffered data*/
84 if (http->recv_buffer_out != http->recv_buffer_in) {
85 size_t size = min(http->recv_buffer_in - http->recv_buffer_out, buf_size);
86 memcpy(buf, http->recv_buffer + http->recv_buffer_out, size);
87 http->recv_buffer_out += size;
88 return size;
89 }
90
91 return recv(http->conn_sd, buf, buf_size, 0);
92}
93
94/** Receive a character and if it is c, discard it from input buffer */
95static int recv_discard(http_t *http, char discard)
96{
97 char c = 0;
98 int rc = recv_char(http, &c, false);
99 if (rc != EOK)
100 return rc;
101 if (c != discard)
102 return EOK;
103 return recv_char(http, &c, true);
104}
105
106/* Receive a single line */
107static ssize_t recv_line(http_t *http, char *line, size_t size)
108{
109 size_t written = 0;
110
111 while (written < size) {
112 char c = 0;
113 int rc = recv_char(http, &c, true);
114 if (rc != EOK)
115 return rc;
116 if (c == '\n') {
117 recv_discard(http, '\r');
118 line[written++] = 0;
119 return written;
120 }
121 else if (c == '\r') {
122 recv_discard(http, '\n');
123 line[written++] = 0;
124 return written;
125 }
126 line[written++] = c;
127 }
128
129 return ELIMIT;
130}
131
132http_t *http_create(const char *host, uint16_t port)
133{
134 http_t *http = malloc(sizeof(http_t));
135 if (http == NULL)
136 return NULL;
137
138 http->host = str_dup(host);
139 if (http->host == NULL) {
140 free(http);
141 return NULL;
142 }
143 http->port = port;
144
145 http->buffer_size = 4096;
146 http->recv_buffer = malloc(http->buffer_size);
147 if (http->recv_buffer == NULL) {
148 free(http->host);
149 free(http);
150 return NULL;
151 }
152
153 return http;
154}
155
156int http_connect(http_t *http)
157{
158 if (http->connected)
159 return EBUSY;
160
161 /* Interpret as address */
162 int rc = inet_addr_parse(http->host, &http->addr);
163
164 if (rc != EOK) {
165 /* Interpret as a host name */
166 dnsr_hostinfo_t *hinfo = NULL;
167 rc = dnsr_name2host(http->host, &hinfo, AF_NONE);
168
169 if (rc != EOK)
170 return rc;
171
172 http->addr = hinfo->addr;
173 dnsr_hostinfo_destroy(hinfo);
174 }
175
176 struct sockaddr_in addr;
177 struct sockaddr_in6 addr6;
178 uint16_t af = inet_addr_sockaddr_in(&http->addr, &addr, &addr6);
179
180 http->conn_sd = socket(PF_INET, SOCK_STREAM, 0);
181 if (http->conn_sd < 0)
182 return http->conn_sd;
183
184 switch (af) {
185 case AF_INET:
186 addr.sin_port = htons(http->port);
187 rc = connect(http->conn_sd, (struct sockaddr *) &addr, sizeof(addr));
188 break;
189 case AF_INET6:
190 addr6.sin6_port = htons(http->port);
191 rc = connect(http->conn_sd, (struct sockaddr *) &addr6, sizeof(addr6));
192 break;
193 default:
194 return ENOTSUP;
195 }
196
197 return rc;
198}
199
200http_header_t *http_header_create(const char *name, const char *value)
201{
202 char *dname = str_dup(name);
203 if (dname == NULL)
204 return NULL;
205
206 char *dvalue = str_dup(value);
207 if (dvalue == NULL) {
208 free(dname);
209 return NULL;
210 }
211
212 return http_header_create_no_copy(dname, dvalue);
213}
214
215http_header_t *http_header_create_no_copy(char *name, char *value)
216{
217 http_header_t *header = malloc(sizeof(http_header_t));
218 if (header == NULL)
219 return NULL;
220
221 link_initialize(&header->link);
222 header->name = name;
223 header->value = value;
224
225 return header;
226}
227
228void http_header_destroy(http_header_t *header)
229{
230 free(header->name);
231 free(header->value);
232 free(header);
233}
234
235http_request_t *http_request_create(const char *method, const char *path)
236{
237 http_request_t *req = malloc(sizeof(http_request_t));
238 if (req == NULL)
239 return NULL;
240
241 req->method = str_dup(method);
242 if (req->method == NULL) {
243 free(req);
244 return NULL;
245 }
246
247 req->path = str_dup(path);
248 if (req->path == NULL) {
249 free(req->method);
250 free(req);
251 return NULL;
252 }
253
254 list_initialize(&req->headers);
255
256 return req;
257}
258
259void http_request_destroy(http_request_t *req)
260{
261 free(req->method);
262 free(req->path);
263 link_t *link = req->headers.head.next;
264 while (link != &req->headers.head) {
265 link_t *next = link->next;
266 http_header_t *header = list_get_instance(link, http_header_t, link);
267 http_header_destroy(header);
268 link = next;
269 }
270 free(req);
271}
272
273static ssize_t http_encode_method(char *buf, size_t buf_size,
274 const char *method, const char *path)
275{
276 if (buf == NULL) {
277 return printf_size(HTTP_METHOD_LINE, method, path);
278 }
279 else {
280 return snprintf(buf, buf_size, HTTP_METHOD_LINE, method, path);
281 }
282}
283
284static ssize_t http_encode_header(char *buf, size_t buf_size,
285 http_header_t *header)
286{
287 /* TODO properly split long header values */
288 if (buf == NULL) {
289 return printf_size(HTTP_HEADER_LINE, header->name, header->value);
290 }
291 else {
292 return snprintf(buf, buf_size,
293 HTTP_HEADER_LINE, header->name, header->value);
294 }
295}
296
297int http_request_format(http_request_t *req, char **out_buf,
298 size_t *out_buf_size)
299{
300 /* Compute the size of the request */
301 ssize_t meth_size = http_encode_method(NULL, 0, req->method, req->path);
302 if (meth_size < 0)
303 return meth_size;
304 size_t size = meth_size;
305
306 list_foreach(req->headers, link, http_header_t, header) {
307 ssize_t header_size = http_encode_header(NULL, 0, header);
308 if (header_size < 0)
309 return header_size;
310 size += header_size;
311 }
312 size += str_length(HTTP_REQUEST_LINE);
313
314 char *buf = malloc(size);
315 if (buf == NULL)
316 return ENOMEM;
317
318 char *pos = buf;
319 size_t pos_size = size;
320 ssize_t written = http_encode_method(pos, pos_size, req->method, req->path);
321 if (written < 0) {
322 free(buf);
323 return written;
324 }
325 pos += written;
326 pos_size -= written;
327
328 list_foreach(req->headers, link, http_header_t, header) {
329 written = http_encode_header(pos, pos_size, header);
330 if (written < 0) {
331 free(buf);
332 return written;
333 }
334 pos += written;
335 pos_size -= written;
336 }
337
338 size_t rlsize = str_size(HTTP_REQUEST_LINE);
339 memcpy(pos, HTTP_REQUEST_LINE, rlsize);
340 pos_size -= rlsize;
341 assert(pos_size == 0);
342
343 *out_buf = buf;
344 *out_buf_size = size;
345 return EOK;
346}
347
348int http_send_request(http_t *http, http_request_t *req)
349{
350 char *buf;
351 size_t buf_size;
352
353 int rc = http_request_format(req, &buf, &buf_size);
354 if (rc != EOK)
355 return rc;
356
357 rc = send(http->conn_sd, buf, buf_size, 0);
358 free(buf);
359
360 return rc;
361}
362
363int http_parse_status(const char *line, http_version_t *out_version,
364 uint16_t *out_status, char **out_message)
365{
366 http_version_t version;
367 uint16_t status;
368 char *message = NULL;
369
370 if (!str_test_prefix(line, "HTTP/"))
371 return EINVAL;
372
373 const char *pos_version = line + 5;
374 const char *pos = pos_version;
375
376 int rc = str_uint8_t(pos_version, &pos, 10, false, &version.major);
377 if (rc != EOK)
378 return rc;
379 if (*pos != '.')
380 return EINVAL;
381 pos++;
382
383 pos_version = pos;
384 rc = str_uint8_t(pos_version, &pos, 10, false, &version.minor);
385 if (rc != EOK)
386 return rc;
387 if (*pos != ' ')
388 return EINVAL;
389 pos++;
390
391 const char *pos_status = pos;
392 rc = str_uint16_t(pos_status, &pos, 10, false, &status);
393 if (rc != EOK)
394 return rc;
395 if (*pos != ' ')
396 return EINVAL;
397 pos++;
398
399 if (out_message) {
400 message = str_dup(pos);
401 if (message == NULL)
402 return ENOMEM;
403 }
404
405 if (out_version)
406 *out_version = version;
407 if (out_status)
408 *out_status = status;
409 if (out_message)
410 *out_message = message;
411 return EOK;
412}
413
414int http_parse_header(const char *line, char **out_name, char **out_value)
415{
416 const char *pos = line;
417 while (*pos != 0 && *pos != ':') pos++;
418 if (*pos != ':')
419 return EINVAL;
420
421 char *name = cut_str(line, pos);
422 if (name == NULL)
423 return ENOMEM;
424
425 pos++;
426
427 while (*pos == ' ') pos++;
428
429 char *value = str_dup(pos);
430 if (value == NULL) {
431 free(name);
432 return ENOMEM;
433 }
434
435 *out_name = name;
436 *out_value = value;
437
438 return EOK;
439}
440
441int http_receive_response(http_t *http, http_response_t **out_response)
442{
443 http_response_t *resp = malloc(sizeof(http_response_t));
444 if (resp == NULL)
445 return ENOMEM;
446 memset(resp, 0, sizeof(http_response_t));
447 list_initialize(&resp->headers);
448
449 char *line = malloc(http->buffer_size);
450 if (line == NULL) {
451 free(resp);
452 return ENOMEM;
453 }
454
455 int rc = recv_line(http, line, http->buffer_size);
456 if (rc < 0)
457 goto error;
458
459 rc = http_parse_status(line, &resp->version, &resp->status,
460 &resp->message);
461 if (rc != EOK)
462 goto error;
463
464 while (true) {
465 rc = recv_line(http, line, http->buffer_size);
466 if (rc < 0)
467 goto error;
468 if (*line == 0)
469 break;
470
471 char *name = NULL;
472 char *value = NULL;
473 rc = http_parse_header(line, &name, &value);
474 if (rc != EOK)
475 goto error;
476
477 http_header_t *header = http_header_create_no_copy(name, value);
478 if (header == NULL) {
479 free(name);
480 free(value);
481 rc = ENOMEM;
482 goto error;
483 }
484
485 list_append(&header->link, &resp->headers);
486 }
487
488 *out_response = resp;
489
490 return EOK;
491error:
492 free(line);
493 http_response_destroy(resp);
494 return rc;
495}
496
497int http_receive_body(http_t *http, void *buf, size_t buf_size)
498{
499 return recv_buffer(http, buf, buf_size);
500}
501
502void http_response_destroy(http_response_t *resp)
503{
504 free(resp->message);
505 link_t *link = resp->headers.head.next;
506 while (link != &resp->headers.head) {
507 link_t *next = link->next;
508 http_header_t *header = list_get_instance(link, http_header_t, link);
509 http_header_destroy(header);
510 link = next;
511 }
512 free(resp);
513}
514
515int http_close(http_t *http)
516{
517 if (!http->connected)
518 return EINVAL;
519
520 return closesocket(http->conn_sd);
521}
522
523void http_destroy(http_t *http)
524{
525 free(http->recv_buffer);
526 free(http);
527}
528
529/** @}
530 */
Note: See TracBrowser for help on using the repository browser.