1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the copyright holder nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27
28#include <stdlib.h>
29#include <string.h>
30#include <stdio.h>
31#include <ctype.h>
32
33#include "containers/containers.h"
34#include "containers/core/containers_common.h"
35#include "containers/core/containers_io.h"
36#include "containers/core/containers_uri.h"
37#include "containers/core/containers_logging.h"
38#include "containers/core/containers_list.h"
39#include "containers/core/containers_utils.h"
40#include "containers/net/net_sockets.h"
41
42/* Set to 1 if you want to log all HTTP requests */
43#define ENABLE_HTTP_EXTRA_LOGGING 0
44
45/******************************************************************************
46Defines and constants.
47******************************************************************************/
48
49#define IO_HTTP_DEFAULT_PORT "80"
50
51/** Space for sending requests and receiving responses */
52#define COMMS_BUFFER_SIZE 4000
53
54/** Largest allowed HTTP URI. Must be substantially smaller than COMMS_BUFFER_SIZE
55 * to allow for the headers that may be sent. */
56#define HTTP_URI_LENGTH_MAX 1024
57
58/** Initial capacity of header list */
59#define HEADER_LIST_INITIAL_CAPACITY 16
60
61/** Format of the first line of an HTTP request */
62#define HTTP_REQUEST_LINE_FORMAT "%s %s HTTP/1.1\r\nHost: %s\r\n"
63
64/** Format of a range request */
65#define HTTP_RANGE_REQUEST "Range: bytes=%"PRId64"-%"PRId64"\r\n"
66
67/** Format string for common headers used with all request methods.
68 * Note: includes double new line to terminate headers */
69#define TRAILING_HEADERS_FORMAT "User-Agent: Broadcom/1.0\r\n\r\n"
70
71/** \name HTTP methods, used as the first item in the request line
72 * @{ */
73#define GET_METHOD "GET"
74#define HEAD_METHOD "HEAD"
75/* @} */
76
77/** \name Names of headers used by the code
78 * @{ */
79#define CONTENT_LENGTH_NAME "Content-Length"
80#define CONTENT_BASE_NAME "Content-Base"
81#define CONTENT_LOCATION_NAME "Content-Location"
82#define ACCEPT_RANGES_NAME "Accept-Ranges"
83#define CONNECTION_NAME "Connection"
84/* @} */
85
86/** Supported HTTP major version number */
87#define HTTP_MAJOR_VERSION 1
88/** Supported HTTP minor version number */
89#define HTTP_MINOR_VERSION 1
90
91/** Lowest successful status code value */
92#define HTTP_STATUS_OK 200
93#define HTTP_STATUS_PARTIAL_CONTENT 206
94
95typedef struct http_header_tag {
96 const char *name;
97 char *value;
98} HTTP_HEADER_T;
99
100
101/******************************************************************************
102Type definitions
103******************************************************************************/
104typedef struct VC_CONTAINER_IO_MODULE_T
105{
106 VC_CONTAINER_NET_T *sock;
107 VC_CONTAINERS_LIST_T *header_list; /**< Parsed response headers, pointing into comms buffer */
108
109 bool persistent;
110 int64_t cur_offset;
111 bool reconnecting;
112
113 /* Buffer used for sending and receiving HTTP messages */
114 char comms_buffer[COMMS_BUFFER_SIZE];
115} VC_CONTAINER_IO_MODULE_T;
116
117/******************************************************************************
118Function prototypes
119******************************************************************************/
120
121static int io_http_header_comparator(const HTTP_HEADER_T *first, const HTTP_HEADER_T *second);
122static VC_CONTAINER_STATUS_T io_http_send(VC_CONTAINER_IO_T *p_ctx);
123
124VC_CONTAINER_STATUS_T vc_container_io_http_open(VC_CONTAINER_IO_T *, const char *,
125 VC_CONTAINER_IO_MODE_T);
126
127/******************************************************************************
128Local Functions
129******************************************************************************/
130
131/**************************************************************************//**
132 * Trim whitespace from the end and start of the string
133 *
134 * \param str String to be trimmed
135 * \return Trimmed string
136 */
137static char *io_http_trim(char *str)
138{
139 char *s = str + strlen(str);
140
141 /* Search backwards for first non-whitespace */
142 while (--s >= str &&(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
143 ; /* Everything done in the while */
144 s[1] = '\0';
145
146 /* Now move start of string forwards to first non-whitespace */
147 s = str;
148 while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
149 s++;
150
151 return s;
152}
153
154/**************************************************************************//**
155 * Header comparison function.
156 * Compare two header structures and return whether the first is less than,
157 * equal to or greater than the second.
158 *
159 * @param first The first structure to be compared.
160 * @param second The second structure to be compared.
161 * @return Negative if first is less than second, positive if first is greater
162 * and zero if they are equal.
163 */
164static int io_http_header_comparator(const HTTP_HEADER_T *first, const HTTP_HEADER_T *second)
165{
166 return strcasecmp(first->name, second->name);
167}
168
169/**************************************************************************//**
170 * Check a response status line to see if the response is usable or not.
171 * Reasons for invalidity include:
172 * - Incorrectly formatted
173 * - Unsupported version
174 * - Status code is not in the 2xx range
175 *
176 * @param status_line The response status line.
177 * @return The resulting status of the function.
178 */
179static bool io_http_successful_response_status(const char *status_line)
180{
181 unsigned int major_version, minor_version, status_code;
182
183 /* coverity[secure_coding] String is null-terminated */
184 if (sscanf(status_line, "HTTP/%u.%u %u", &major_version, &minor_version, &status_code) != 3)
185 {
186 LOG_ERROR(NULL, "HTTP: Invalid response status line:\n%s", status_line);
187 return false;
188 }
189
190 if (major_version != HTTP_MAJOR_VERSION || minor_version != HTTP_MINOR_VERSION)
191 {
192 LOG_ERROR(NULL, "HTTP: Unexpected response HTTP version: %u.%u", major_version, minor_version);
193 return false;
194 }
195
196 if (status_code != HTTP_STATUS_OK && status_code != HTTP_STATUS_PARTIAL_CONTENT)
197 {
198 LOG_ERROR(NULL, "HTTP: Response status unsuccessful:\n%s", status_line);
199 return false;
200 }
201
202 return true;
203}
204
205/**************************************************************************//**
206 * Get the content length header from the response headers as an unsigned
207 * 64-bit integer.
208 * If the content length header is not found or badly formatted, zero is
209 * returned.
210 *
211 * @param header_list The response headers.
212 * @return The content length.
213 */
214static uint64_t io_http_get_content_length(VC_CONTAINERS_LIST_T *header_list)
215{
216 uint64_t content_length = 0;
217 HTTP_HEADER_T header;
218
219 header.name = CONTENT_LENGTH_NAME;
220 if (header_list && vc_containers_list_find_entry(header_list, &header))
221 /* coverity[secure_coding] String is null-terminated */
222 sscanf(header.value, "%"PRIu64, &content_length);
223
224 return content_length;
225}
226
227/**************************************************************************//**
228 * Get the accept ranges header from the response headers and verify that
229 * the server accepts byte ranges..
230 * If the accept ranges header is not found false is returned.
231 *
232 * @param header_list The response headers.
233 * @return The resulting status of the function.
234 */
235static bool io_http_check_accept_range(VC_CONTAINERS_LIST_T *header_list)
236{
237 HTTP_HEADER_T header;
238
239 header.name = ACCEPT_RANGES_NAME;
240 if (header_list && vc_containers_list_find_entry(header_list, &header))
241 {
242 /* coverity[secure_coding] String is null-terminated */
243 if (!strcasecmp(header.value, "bytes"))
244 return true;
245 }
246
247 return false;
248}
249
250/**************************************************************************//**
251 * Check whether the server supports persistent connections.
252 *
253 * @param header_list The response headers.
254 * @return The resulting status of the function.
255 */
256static bool io_http_check_persistent_connection(VC_CONTAINERS_LIST_T *header_list)
257{
258 HTTP_HEADER_T header;
259
260 header.name = CONNECTION_NAME;
261 if (header_list && vc_containers_list_find_entry(header_list, &header))
262 {
263 /* coverity[secure_coding] String is null-terminated */
264 if (!strcasecmp(header.value, "close"))
265 return false;
266 }
267
268 return true;
269}
270
271/*****************************************************************************/
272static VC_CONTAINER_STATUS_T translate_net_status_to_container_status(vc_container_net_status_t net_status)
273{
274 switch (net_status)
275 {
276 case VC_CONTAINER_NET_SUCCESS: return VC_CONTAINER_SUCCESS;
277 case VC_CONTAINER_NET_ERROR_INVALID_SOCKET: return VC_CONTAINER_ERROR_INVALID_ARGUMENT;
278 case VC_CONTAINER_NET_ERROR_NOT_ALLOWED: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;
279 case VC_CONTAINER_NET_ERROR_INVALID_PARAMETER: return VC_CONTAINER_ERROR_INVALID_ARGUMENT;
280 case VC_CONTAINER_NET_ERROR_NO_MEMORY: return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
281 case VC_CONTAINER_NET_ERROR_IN_USE: return VC_CONTAINER_ERROR_URI_OPEN_FAILED;
282 case VC_CONTAINER_NET_ERROR_NETWORK: return VC_CONTAINER_ERROR_EOS;
283 case VC_CONTAINER_NET_ERROR_CONNECTION_LOST: return VC_CONTAINER_ERROR_EOS;
284 case VC_CONTAINER_NET_ERROR_NOT_CONNECTED: return VC_CONTAINER_ERROR_INVALID_ARGUMENT;
285 case VC_CONTAINER_NET_ERROR_TIMED_OUT: return VC_CONTAINER_ERROR_ABORTED;
286 case VC_CONTAINER_NET_ERROR_CONNECTION_REFUSED: return VC_CONTAINER_ERROR_NOT_FOUND;
287 case VC_CONTAINER_NET_ERROR_HOST_NOT_FOUND: return VC_CONTAINER_ERROR_NOT_FOUND;
288 case VC_CONTAINER_NET_ERROR_TRY_AGAIN: return VC_CONTAINER_ERROR_CONTINUE;
289 default: return VC_CONTAINER_ERROR_FAILED;
290 }
291}
292
293/*****************************************************************************/
294static VC_CONTAINER_STATUS_T io_http_open_socket(VC_CONTAINER_IO_T *ctx)
295{
296 VC_CONTAINER_IO_MODULE_T *module = ctx->module;
297 VC_CONTAINER_STATUS_T status;
298 const char *host, *port;
299
300 /* Treat empty host or port strings as not defined */
301 port = vc_uri_port(ctx->uri_parts);
302 if (port && !*port)
303 port = NULL;
304
305 /* Require the port to be defined */
306 if (!port)
307 {
308 status = VC_CONTAINER_ERROR_URI_OPEN_FAILED;
309 goto error;
310 }
311
312 host = vc_uri_host(ctx->uri_parts);
313 if (host && !*host)
314 host = NULL;
315
316 if (!host)
317 {
318 status = VC_CONTAINER_ERROR_URI_OPEN_FAILED;
319 goto error;
320 }
321
322 module->sock = vc_container_net_open(host, port, VC_CONTAINER_NET_OPEN_FLAG_STREAM, NULL);
323 if (!module->sock)
324 {
325 status = VC_CONTAINER_ERROR_URI_NOT_FOUND;
326 goto error;
327 }
328
329 return VC_CONTAINER_SUCCESS;
330
331error:
332 return status;
333}
334
335/*****************************************************************************/
336static VC_CONTAINER_STATUS_T io_http_close_socket(VC_CONTAINER_IO_MODULE_T *module)
337{
338 if (module->sock)
339 {
340 vc_container_net_close(module->sock);
341 module->sock = NULL;
342 }
343
344 return VC_CONTAINER_SUCCESS;
345}
346
347/*****************************************************************************/
348static size_t io_http_read_from_net(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size)
349{
350 size_t ret;
351 vc_container_net_status_t net_status;
352
353 ret = vc_container_net_read(p_ctx->module->sock, buffer, size);
354 net_status = vc_container_net_status(p_ctx->module->sock);
355 p_ctx->status = translate_net_status_to_container_status(net_status);
356
357 return ret;
358}
359
360/**************************************************************************//**
361 * Reads an HTTP response and parses it into headers and content.
362 * The headers and content remain stored in the comms buffer, but referenced
363 * by the module's header list. Content uses a special header name that cannot
364 * occur in the real headers.
365 *
366 * @param p_ctx The HTTP reader context.
367 * @return The resulting status of the function.
368 */
369static VC_CONTAINER_STATUS_T io_http_read_response(VC_CONTAINER_IO_T *p_ctx)
370{
371 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
372 char *next_read = module->comms_buffer;
373 size_t space_available = sizeof(module->comms_buffer) - 1; /* Allow for a NUL */
374 char *ptr = next_read;
375 bool end_response = false;
376 HTTP_HEADER_T header;
377 const char endstr[] = "\r\n\r\n";
378 int endcount = sizeof(endstr) - 1;
379 int endchk = 0;
380
381 vc_containers_list_reset(module->header_list);
382
383 /* Response status line doesn't need to be stored, just checked */
384 header.name = NULL;
385 header.value = next_read;
386
387 /*
388 * We need to read just a byte at a time to make sure that we just read the HTTP response and
389 * no more. For example, if a GET operation was requested the file being fetched will also
390 * be waiting to be read on the socket.
391 */
392
393 while (space_available)
394 {
395 if (io_http_read_from_net(p_ctx, next_read, 1) != 1)
396 break;
397
398 next_read++;
399 space_available--;
400
401 if (next_read[-1] == endstr[endchk])
402 {
403 if (++endchk == endcount)
404 break;
405 }
406 else
407 endchk = 0;
408 }
409 if (!space_available)
410 {
411 LOG_ERROR(NULL, "comms buffer too small for complete HTTP message (%d)",
412 sizeof(module->comms_buffer));
413 return VC_CONTAINER_ERROR_CORRUPTED;
414 }
415
416 *next_read = '\0';
417
418 if (endchk == endcount)
419 {
420 if (ENABLE_HTTP_EXTRA_LOGGING)
421 LOG_DEBUG(NULL, "READ FROM SERVER: %d bytes\n%s\n-----------------------------------------",
422 sizeof(module->comms_buffer) - 1 - space_available, module->comms_buffer);
423
424 while (!end_response && ptr < next_read)
425 {
426 switch (*ptr)
427 {
428 case ':':
429 if (header.value)
430 {
431 /* Just another character in the value */
432 ptr++;
433 } else {
434 /* End of name, expect value next */
435 *ptr++ = '\0';
436 header.value = ptr;
437 }
438 break;
439
440 case '\n':
441 if (header.value)
442 {
443 /* End of line while parsing the value part of the header, add name/value pair to list */
444 *ptr++ = '\0';
445 header.value = io_http_trim(header.value);
446 if (header.name)
447 {
448 if (!vc_containers_list_insert(module->header_list, &header, false))
449 {
450 LOG_ERROR(NULL, "HTTP: Failed to add <%s> header to list", header.name);
451 return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
452 }
453 } else {
454 /* Check response status line */
455 if (!io_http_successful_response_status(header.value))
456 return VC_CONTAINER_ERROR_FORMAT_INVALID;
457 }
458 /* Ready for next header */
459 header.name = ptr;
460 header.value = NULL;
461 } else {
462 /* End of line while parsing the name of a header */
463 *ptr++ = '\0';
464 if (*header.name && *header.name != '\r')
465 {
466 /* A non-empty name is invalid, so fail */
467 LOG_ERROR(NULL, "HTTP: Invalid name in header - no colon:\n%s", header.name);
468 return VC_CONTAINER_ERROR_FORMAT_INVALID;
469 }
470
471 /* An empty name signifies the end of the HTTP response */
472 end_response = true;
473 }
474 break;
475
476 default:
477 /* Just another character in either the name or the value */
478 ptr++;
479 }
480 }
481 }
482
483 if (!space_available && !end_response)
484 {
485 /* Ran out of buffer space */
486 LOG_ERROR(NULL, "HTTP: Response header section too big");
487 return VC_CONTAINER_ERROR_FORMAT_INVALID;
488 }
489
490 return p_ctx->status;
491}
492
493/**************************************************************************//**
494 * Send a GET request to the HTTP server.
495 *
496 * @param p_ctx The reader context.
497 * @return The resulting status of the function.
498 */
499static VC_CONTAINER_STATUS_T io_http_send_get_request(VC_CONTAINER_IO_T *p_ctx, size_t size)
500{
501 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
502 char *ptr = module->comms_buffer, *end = ptr + sizeof(module->comms_buffer);
503 int64_t end_offset;
504
505 ptr += snprintf(ptr, end - ptr, HTTP_REQUEST_LINE_FORMAT, GET_METHOD,
506 vc_uri_path(p_ctx->uri_parts), vc_uri_host(p_ctx->uri_parts));
507
508 end_offset = module->cur_offset + size - 1;
509 if (end_offset >= p_ctx->size)
510 end_offset = p_ctx->size - 1;
511
512 if (ptr < end)
513 ptr += snprintf(ptr, end - ptr, HTTP_RANGE_REQUEST, module->cur_offset, end_offset);
514
515 if (ptr < end)
516 ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT);
517
518 if (ptr >= end)
519 {
520 LOG_ERROR(0, "comms buffer too small (%i/%u)", (int)(end - ptr),
521 sizeof(module->comms_buffer));
522 return VC_CONTAINER_ERROR_OUT_OF_RESOURCES;
523 }
524
525 if (ENABLE_HTTP_EXTRA_LOGGING)
526 LOG_DEBUG(NULL, "Sending server read request:\n%s\n---------------------\n", module->comms_buffer);
527 return io_http_send(p_ctx);
528}
529
530/*****************************************************************************/
531static VC_CONTAINER_STATUS_T io_http_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset)
532{
533 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
534
535 /*
536 * No seeking past the end of the file.
537 */
538
539 if (offset < 0 || offset > p_ctx->size)
540 {
541 p_ctx->status = VC_CONTAINER_ERROR_EOS;
542 return VC_CONTAINER_ERROR_EOS;
543 }
544
545 module->cur_offset = offset;
546 p_ctx->status = VC_CONTAINER_SUCCESS;
547
548 return VC_CONTAINER_SUCCESS;
549}
550
551/*****************************************************************************/
552static VC_CONTAINER_STATUS_T io_http_close(VC_CONTAINER_IO_T *p_ctx)
553{
554 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
555
556 if (!module)
557 return VC_CONTAINER_ERROR_INVALID_ARGUMENT;
558
559 io_http_close_socket(module);
560 if (module->header_list)
561 vc_containers_list_destroy(module->header_list);
562
563 free(module);
564 p_ctx->module = NULL;
565
566 return VC_CONTAINER_SUCCESS;
567}
568
569/*****************************************************************************/
570static size_t io_http_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size)
571{
572 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
573 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
574 size_t content_length;
575 size_t bytes_read;
576 size_t ret = 0;
577 char *ptr = buffer;
578
579 /*
580 * Are we at the end of the file?
581 */
582
583 if (module->cur_offset >= p_ctx->size)
584 {
585 p_ctx->status = VC_CONTAINER_ERROR_EOS;
586 return 0;
587 }
588
589 if (!module->persistent)
590 {
591 status = io_http_open_socket(p_ctx);
592 if (status != VC_CONTAINER_SUCCESS)
593 {
594 LOG_ERROR(NULL, "Error opening socket for GET request");
595 return status;
596 }
597 }
598
599 /* Send GET request and get response */
600 status = io_http_send_get_request(p_ctx, size);
601 if (status != VC_CONTAINER_SUCCESS)
602 {
603 LOG_ERROR(NULL, "Error sending GET request");
604 goto error;
605 }
606
607 status = io_http_read_response(p_ctx);
608 if (status == VC_CONTAINER_ERROR_EOS && !module->reconnecting)
609 {
610 LOG_DEBUG(NULL, "reconnecting");
611 io_http_close_socket(module);
612 status = io_http_open_socket(p_ctx);
613 if (status == VC_CONTAINER_SUCCESS)
614 {
615 module->reconnecting = true;
616 status = io_http_read(p_ctx, buffer, size);
617 module->reconnecting = false;
618 return status;
619 }
620 }
621 if (status != VC_CONTAINER_SUCCESS)
622 {
623 LOG_ERROR(NULL, "Error reading GET response");
624 goto error;
625 }
626
627 /*
628 * How much data is the server offering us?
629 */
630
631 content_length = (size_t)io_http_get_content_length(module->header_list);
632 if (content_length > size)
633 {
634 LOG_ERROR(NULL, "received too much data (%i/%i)",
635 (int)content_length, (int)size);
636 status = VC_CONTAINER_ERROR_CORRUPTED;
637 goto error;
638 }
639
640 bytes_read = 0;
641 while (bytes_read < content_length && p_ctx->status == VC_CONTAINER_SUCCESS)
642 {
643 ret = io_http_read_from_net(p_ctx, ptr, content_length - bytes_read);
644 if (p_ctx->status == VC_CONTAINER_SUCCESS)
645 {
646 bytes_read += ret;
647 ptr += ret;
648 }
649 }
650
651 if (p_ctx->status == VC_CONTAINER_SUCCESS)
652 {
653 module->cur_offset += bytes_read;
654 ret = bytes_read;
655 }
656
657 if (!module->persistent)
658 io_http_close_socket(module);
659
660 return ret;
661
662error:
663 if (!module->persistent)
664 io_http_close_socket(module);
665
666 return status;
667}
668
669/*****************************************************************************/
670static size_t io_http_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size)
671{
672 size_t ret = vc_container_net_write(p_ctx->module->sock, buffer, size);
673 vc_container_net_status_t net_status;
674
675 net_status = vc_container_net_status(p_ctx->module->sock);
676 p_ctx->status = translate_net_status_to_container_status(net_status);
677
678 return ret;
679}
680
681/*****************************************************************************/
682static VC_CONTAINER_STATUS_T io_http_control(struct VC_CONTAINER_IO_T *p_ctx,
683 VC_CONTAINER_CONTROL_T operation,
684 va_list args)
685{
686 vc_container_net_status_t net_status;
687 VC_CONTAINER_STATUS_T status;
688
689 switch (operation)
690 {
691 case VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE:
692 net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_BUFFER_SIZE, args);
693 break;
694 case VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS:
695 net_status = vc_container_net_control(p_ctx->module->sock, VC_CONTAINER_NET_CONTROL_SET_READ_TIMEOUT_MS, args);
696 break;
697 default:
698 net_status = VC_CONTAINER_NET_ERROR_NOT_ALLOWED;
699 }
700
701 status = translate_net_status_to_container_status(net_status);
702 p_ctx->status = status;
703
704 return status;
705}
706
707/**************************************************************************//**
708 * Send out the data in the comms buffer.
709 *
710 * @param p_ctx The reader context.
711 * @return The resulting status of the function.
712 */
713static VC_CONTAINER_STATUS_T io_http_send(VC_CONTAINER_IO_T *p_ctx)
714{
715 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
716 size_t to_write;
717 size_t written;
718 const char *buffer = module->comms_buffer;
719
720 to_write = strlen(buffer);
721
722 while (to_write)
723 {
724 written = io_http_write(p_ctx, buffer, to_write);
725 if (p_ctx->status != VC_CONTAINER_SUCCESS)
726 break;
727
728 to_write -= written;
729 buffer += written;
730 }
731
732 return p_ctx->status;
733}
734
735/**************************************************************************//**
736 * Send a HEAD request to the HTTP server.
737 *
738 * @param p_ctx The reader context.
739 * @return The resulting status of the function.
740 */
741static VC_CONTAINER_STATUS_T io_http_send_head_request(VC_CONTAINER_IO_T *p_ctx)
742{
743 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
744 char *ptr = module->comms_buffer, *end = ptr + sizeof(module->comms_buffer);
745
746 ptr += snprintf(ptr, end - ptr, HTTP_REQUEST_LINE_FORMAT, HEAD_METHOD,
747 vc_uri_path(p_ctx->uri_parts), vc_uri_host(p_ctx->uri_parts));
748 if (ptr < end)
749 ptr += snprintf(ptr, end - ptr, TRAILING_HEADERS_FORMAT);
750
751 if (ptr >= end)
752 {
753 LOG_ERROR(0, "comms buffer too small (%i/%u)", (int)(end - ptr),
754 sizeof(module->comms_buffer));
755 return VC_CONTAINER_ERROR_OUT_OF_RESOURCES;
756 }
757
758 return io_http_send(p_ctx);
759}
760
761static VC_CONTAINER_STATUS_T io_http_head(VC_CONTAINER_IO_T *p_ctx)
762{
763 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
764 VC_CONTAINER_IO_MODULE_T *module = p_ctx->module;
765 uint64_t content_length;
766
767 /* Send HEAD request and get response */
768 status = io_http_send_head_request(p_ctx);
769 if (status != VC_CONTAINER_SUCCESS)
770 return status;
771 status = io_http_read_response(p_ctx);
772 if (status != VC_CONTAINER_SUCCESS)
773 return status;
774
775 /*
776 * Save the content length since that's our file size.
777 */
778
779 content_length = io_http_get_content_length(module->header_list);
780 if (content_length)
781 {
782 p_ctx->size = content_length;
783 LOG_DEBUG(NULL, "File size is %"PRId64, p_ctx->size);
784 }
785
786 /*
787 * Now make sure that the server supports byte range requests.
788 */
789
790 if (!io_http_check_accept_range(module->header_list))
791 {
792 LOG_ERROR(NULL, "Server doesn't support byte range requests");
793 return VC_CONTAINER_ERROR_FAILED;
794 }
795
796 /*
797 * Does it support persistent connections?
798 */
799
800 if (io_http_check_persistent_connection(module->header_list))
801 {
802 module->persistent = true;
803 }
804 else
805 {
806 LOG_DEBUG(NULL, "Server does not support persistent connections");
807 io_http_close_socket(module);
808 }
809
810 module->cur_offset = 0;
811
812 return status;
813}
814
815/*****************************************************************************
816Functions exported as part of the I/O Module API
817 *****************************************************************************/
818
819/*****************************************************************************/
820VC_CONTAINER_STATUS_T vc_container_io_http_open(VC_CONTAINER_IO_T *p_ctx,
821 const char *unused, VC_CONTAINER_IO_MODE_T mode)
822{
823 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
824 VC_CONTAINER_IO_MODULE_T *module = 0;
825 VC_CONTAINER_PARAM_UNUSED(unused);
826
827 /* Check the URI to see if we're dealing with an http stream */
828 if (!vc_uri_scheme(p_ctx->uri_parts) ||
829 strcasecmp(vc_uri_scheme(p_ctx->uri_parts), "http"))
830 return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
831
832 /*
833 * Some basic error checking.
834 */
835
836 if (mode == VC_CONTAINER_IO_MODE_WRITE)
837 {
838 status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;
839 goto error;
840 }
841
842 if (strlen(p_ctx->uri) > HTTP_URI_LENGTH_MAX)
843 {
844 status = VC_CONTAINER_ERROR_URI_OPEN_FAILED;
845 goto error;
846 }
847
848 module = calloc(1, sizeof(*module));
849 if (!module)
850 {
851 status = VC_CONTAINER_ERROR_OUT_OF_MEMORY;
852 goto error;
853 }
854 p_ctx->module = module;
855
856 /* header_list will contain pointers into the response_buffer, so take care in re-use */
857 module->header_list = vc_containers_list_create(HEADER_LIST_INITIAL_CAPACITY, sizeof(HTTP_HEADER_T),
858 (VC_CONTAINERS_LIST_COMPARATOR_T)io_http_header_comparator);
859 if (!module->header_list)
860 {
861 status = VC_CONTAINER_ERROR_OUT_OF_MEMORY;
862 goto error;
863 }
864
865 /*
866 * Make sure that we have a port number.
867 */
868
869 if (vc_uri_port(p_ctx->uri_parts) == NULL)
870 vc_uri_set_port(p_ctx->uri_parts, IO_HTTP_DEFAULT_PORT);
871
872 status = io_http_open_socket(p_ctx);
873 if (status != VC_CONTAINER_SUCCESS)
874 goto error;
875
876 /*
877 * Whoo hoo! Our socket is open. Now let's send a HEAD request.
878 */
879
880 status = io_http_head(p_ctx);
881 if (status != VC_CONTAINER_SUCCESS)
882 goto error;
883
884 p_ctx->pf_close = io_http_close;
885 p_ctx->pf_read = io_http_read;
886 p_ctx->pf_write = NULL;
887 p_ctx->pf_control = io_http_control;
888 p_ctx->pf_seek = io_http_seek;
889
890 p_ctx->capabilities = VC_CONTAINER_IO_CAPS_NO_CACHING;
891 p_ctx->capabilities |= VC_CONTAINER_IO_CAPS_SEEK_SLOW;
892
893 return VC_CONTAINER_SUCCESS;
894
895error:
896 io_http_close(p_ctx);
897 return status;
898}
899