genode/repos/gems/src/server/http_block/http.cc

277 lines
5.2 KiB
C++

/*
* \brief HTTP protocol parts
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
* \date 2010-08-19
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <base/child.h>
#include <base/log.h>
#include <base/sleep.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include "http.h"
using namespace Genode;
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<Scanner_policy_file> 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) {
error("cmd_head: write error");
throw Http::Socket_error();
}
}
void Http::connect()
{
_fd = socket(AF_INET, SOCK_STREAM, 0);
if (_fd < 0) {
error("connect: no socket avaiable");
throw Http::Socket_error();
}
if (::connect(_fd, _info->ai_addr, sizeof(*(_info->ai_addr))) < 0) {
error("connect: 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)) {
error("host ", Cstring(_host), " not found");
throw Http::Uri_error();
}
_heap.alloc(sizeof(struct addrinfo), (void**)&_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();
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) {
error("read_header: 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);
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) {
error("could not read data (", errno, ")");
throw Http::Socket_error();
}
buf_fill += part;
}
}
Http::Http(Genode::Heap &heap, ::String const &uri)
: _heap(heap), _port((char *)"80")
{
_heap.alloc(HTTP_BUF, (void**)&_http_buf);
/* parse URI */
parse_uri(uri);
/* search for host */
resolve_uri();
/* connect to host */
connect();
/* retrieve file info */
get_capacity();
}
Http::~Http()
{
_heap.free(_host, Genode::strlen(_host) + 1);
_heap.free(_path, Genode::strlen(_path) + 2);
_heap.free(_http_buf, HTTP_BUF);
_heap.free(_info, sizeof(struct addrinfo));
}
void Http::parse_uri(::String const &u)
{
/* strip possible http prefix */
const char *http = "http://";
char * uri = const_cast<char*>(u.string());
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++) ;
_heap.alloc(i + 1, (void**)&_host);
copy_cstring(_host, uri, i + 1);
_heap.alloc(length - i + 1, (void**)&_path);
copy_cstring(_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';
}
}
void Http::cmd_get(size_t file_offset, size_t size, addr_t 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) {
error("cmd_get: server returned ", _http_ret);
throw Http::Server_error();
}
do_read((void *)(buffer), size);
return;
}
}