/* * \brief HTTP protocol parts * \author Sebastian Sumpf * \date 2010-08-19 */ /* * Copyright (C) 2010-2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #include #include #include #include #include #include #include #include #include #include #include "http.h" using namespace Genode; /* * Debugging output */ static const int verbose = 0; enum { /* HTTP status codes */ HTTP_SUCC_OK = 200, HTTP_SUCC_PARTIAL = 206, /* Size of our local buffer */ HTTP_BUF = 2048, }; /* Tokenizer policy */ struct Scanner_policy_file { static bool identifier_char(char c, unsigned /* i */) { return c != ':' && c != 0 && c != ' ' && c != '\n'; } }; typedef ::Genode::Token Http_token; void Http::cmd_head() { const char *http_templ = "%s %s HTTP/1.1\r\n" "Host: %s\r\n" "\r\n"; int length = snprintf(_http_buf, HTTP_BUF, http_templ, "HEAD", _path, _host); if (write(_fd, _http_buf, length) != length) { PERR("Write error"); throw Http::Socket_error(); } } void Http::connect() { _fd = socket(AF_INET, SOCK_STREAM, 0); if (_fd < 0) { PERR("No socket avaiable"); throw Http::Socket_error(); } if (::connect(_fd, _info->ai_addr, sizeof(*(_info->ai_addr))) < 0) { PERR("Connect failed"); throw Http::Socket_error(); } } void Http::reconnect(){ close(_fd); connect(); } void Http::resolve_uri() { struct addrinfo *info; if (getaddrinfo(_host, _port, 0, &info)) { PERR("Error: Host %s not found", _host); throw Http::Uri_error(); } env()->heap()->alloc(sizeof(struct addrinfo), &_info); Genode::memcpy(_info, info, sizeof(struct addrinfo)); } Genode::size_t Http::read_header() { bool header = true; size_t i = 0; while (header) { if (!read(_fd, &_http_buf[i], 1)) throw Http::Socket_closed(); /* DEBUG: Genode::printf("%c", _http_buf[i]); */ if (i >= 3 && _http_buf[i - 3] == '\r' && _http_buf[i - 2] == '\n' && _http_buf[i - 1] == '\r' && _http_buf[i - 0] == '\n') header = false; if (++i >= HTTP_BUF) { PERR("Buffer overflow"); throw Http::Socket_error(); } } /* scan for status code */ Http_token t(_http_buf, i); for (int count = 0;; t = t.next()) { if (t.type() != Http_token::IDENT) continue; if (count) { ascii_to(t.start(), _http_ret); break; } count++; } return i; } void Http::get_capacity() { cmd_head(); size_t len = read_header(); char buf[32]; Http_token t(_http_buf, len); bool key = false; while (t) { if (t.type() != Http_token::IDENT) { t = t.next(); continue; } if (key) { ascii_to(t.start(), _size); if (verbose) PDBG("File size: %zu bytes", _size); break; } t.string(buf, 32); if (!Genode::strcmp(buf, "Content-Length", 32)) key = true; t = t.next(); } } void Http::do_read(void * buf, size_t size) { size_t buf_fill = 0; while (buf_fill < size) { int part; if ((part = read(_fd, (void *)((addr_t)buf + buf_fill), size - buf_fill)) <= 0) { PERR("Error: Reading data (%d)", errno); throw Http::Socket_error(); } buf_fill += part; } if (verbose) PDBG("Read %zu/%zu", buf_fill, size); } Http::Http(char *uri) : _port((char *)"80") { env()->heap()->alloc(HTTP_BUF, &_http_buf); /* parse URI */ parse_uri(uri); /* search for host */ resolve_uri(); /* connect to host */ connect(); /* retrieve file info */ get_capacity(); } Http::~Http() { env()->heap()->free(_host, Genode::strlen(_host) + 1); env()->heap()->free(_path, Genode::strlen(_path) + 2); env()->heap()->free(_http_buf, HTTP_BUF); env()->heap()->free(_info, sizeof(struct addrinfo)); } void Http::parse_uri(char *uri) { /* strip possible http prefix */ const char *http = "http://"; size_t length = Genode::strlen(uri); size_t http_len = Genode::strlen(http); if (!strcmp(http, uri, http_len)) { uri += http_len; length -= http_len; } /* set host and file path */ size_t i; for (i = 0; i < length && uri[i] != '/'; i++) ; env()->heap()->alloc(i + 1, &_host); Genode::strncpy(_host, uri, i + 1); env()->heap()->alloc(length - i + 1, &_path); Genode::strncpy(_path, uri + i, length - i + 1); /* look for port */ size_t len = Genode::strlen(_host); for (i = 0; i < len && _host[i] != ':'; i++) ; if (i < len) { _port = &_host[i + 1]; _host[i] = '\0'; } if (verbose) PDBG("Port: %s", _port); } void Http::cmd_get(size_t file_offset, size_t size, addr_t buffer) { if (verbose) PDBG("Read: offs %zu size: %zu I/O buffer: %lx", file_offset, size, buffer); while (true) { const char *http_templ = "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Range: bytes=%lu-%lu\r\n" "\r\n"; int length = snprintf(_http_buf, HTTP_BUF, http_templ, _path, _host, file_offset, file_offset + size - 1); if (write(_fd, _http_buf, length) < 0) { if (errno == ESHUTDOWN) reconnect(); if (write(_fd, _http_buf, length) < 0) throw Http::Socket_error(); } try { read_header(); } catch (Http::Socket_closed) { reconnect(); continue; } if (_http_ret != HTTP_SUCC_PARTIAL) { PERR("Error: Server returned %u", _http_ret); throw Http::Server_error(); } do_read((void *)(buffer), size); return; } }