grub/grub-core/net/http.c
Michael Chang abf9beb7d6 http: fix superfluous null line in range request header
At least the apache sever is very unhappy with that extra null line and will
take more than ten seconds in responding to each range request, which slows
down a lot the entire http file transfer process or even time out.
2016-04-09 07:08:13 +03:00

561 lines
13 KiB
C

/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2010,2011 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/misc.h>
#include <grub/net/tcp.h>
#include <grub/net/ip.h>
#include <grub/net/ethernet.h>
#include <grub/net/netbuff.h>
#include <grub/net.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/file.h>
#include <grub/i18n.h>
GRUB_MOD_LICENSE ("GPLv3+");
enum
{
HTTP_PORT = 80
};
typedef struct http_data
{
char *current_line;
grub_size_t current_line_len;
int headers_recv;
int first_line_recv;
int size_recv;
grub_net_tcp_socket_t sock;
char *filename;
grub_err_t err;
char *errmsg;
int chunked;
grub_size_t chunk_rem;
int in_chunk_len;
} *http_data_t;
static grub_off_t
have_ahead (struct grub_file *file)
{
grub_net_t net = file->device->net;
grub_off_t ret = net->offset;
struct grub_net_packet *pack;
for (pack = net->packs.first; pack; pack = pack->next)
ret += pack->nb->tail - pack->nb->data;
return ret;
}
static grub_err_t
parse_line (grub_file_t file, http_data_t data, char *ptr, grub_size_t len)
{
char *end = ptr + len;
while (end > ptr && *(end - 1) == '\r')
end--;
*end = 0;
/* Trailing CRLF. */
if (data->in_chunk_len == 1)
{
data->in_chunk_len = 2;
return GRUB_ERR_NONE;
}
if (data->in_chunk_len == 2)
{
data->chunk_rem = grub_strtoul (ptr, 0, 16);
grub_errno = GRUB_ERR_NONE;
if (data->chunk_rem == 0)
{
file->device->net->eof = 1;
file->device->net->stall = 1;
if (file->size == GRUB_FILE_SIZE_UNKNOWN)
file->size = have_ahead (file);
}
data->in_chunk_len = 0;
return GRUB_ERR_NONE;
}
if (ptr == end)
{
data->headers_recv = 1;
if (data->chunked)
data->in_chunk_len = 2;
return GRUB_ERR_NONE;
}
if (!data->first_line_recv)
{
int code;
if (grub_memcmp (ptr, "HTTP/1.1 ", sizeof ("HTTP/1.1 ") - 1) != 0)
{
data->errmsg = grub_strdup (_("unsupported HTTP response"));
data->first_line_recv = 1;
return GRUB_ERR_NONE;
}
ptr += sizeof ("HTTP/1.1 ") - 1;
code = grub_strtoul (ptr, &ptr, 10);
if (grub_errno)
return grub_errno;
switch (code)
{
case 200:
case 206:
break;
case 404:
data->err = GRUB_ERR_FILE_NOT_FOUND;
data->errmsg = grub_xasprintf (_("file `%s' not found"), data->filename);
return GRUB_ERR_NONE;
default:
data->err = GRUB_ERR_NET_UNKNOWN_ERROR;
/* TRANSLATORS: GRUB HTTP code is pretty young. So even perfectly
valid answers like 403 will trigger this very generic message. */
data->errmsg = grub_xasprintf (_("unsupported HTTP error %d: %s"),
code, ptr);
return GRUB_ERR_NONE;
}
data->first_line_recv = 1;
return GRUB_ERR_NONE;
}
if (grub_memcmp (ptr, "Content-Length: ", sizeof ("Content-Length: ") - 1)
== 0 && !data->size_recv)
{
ptr += sizeof ("Content-Length: ") - 1;
file->size = grub_strtoull (ptr, &ptr, 10);
data->size_recv = 1;
return GRUB_ERR_NONE;
}
if (grub_memcmp (ptr, "Transfer-Encoding: chunked",
sizeof ("Transfer-Encoding: chunked") - 1) == 0)
{
data->chunked = 1;
return GRUB_ERR_NONE;
}
return GRUB_ERR_NONE;
}
static void
http_err (grub_net_tcp_socket_t sock __attribute__ ((unused)),
void *f)
{
grub_file_t file = f;
http_data_t data = file->data;
if (data->sock)
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
data->sock = 0;
if (data->current_line)
grub_free (data->current_line);
data->current_line = 0;
file->device->net->eof = 1;
file->device->net->stall = 1;
if (file->size == GRUB_FILE_SIZE_UNKNOWN)
file->size = have_ahead (file);
}
static grub_err_t
http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
struct grub_net_buff *nb,
void *f)
{
grub_file_t file = f;
http_data_t data = file->data;
grub_err_t err;
if (!data->sock)
{
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
while (1)
{
char *ptr = (char *) nb->data;
if ((!data->headers_recv || data->in_chunk_len) && data->current_line)
{
int have_line = 1;
char *t;
ptr = grub_memchr (nb->data, '\n', nb->tail - nb->data);
if (ptr)
ptr++;
else
{
have_line = 0;
ptr = (char *) nb->tail;
}
t = grub_realloc (data->current_line,
data->current_line_len + (ptr - (char *) nb->data));
if (!t)
{
grub_netbuff_free (nb);
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
return grub_errno;
}
data->current_line = t;
grub_memcpy (data->current_line + data->current_line_len,
nb->data, ptr - (char *) nb->data);
data->current_line_len += ptr - (char *) nb->data;
if (!have_line)
{
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
err = parse_line (file, data, data->current_line,
data->current_line_len);
grub_free (data->current_line);
data->current_line = 0;
data->current_line_len = 0;
if (err)
{
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
grub_netbuff_free (nb);
return err;
}
}
while (ptr < (char *) nb->tail && (!data->headers_recv
|| data->in_chunk_len))
{
char *ptr2;
ptr2 = grub_memchr (ptr, '\n', (char *) nb->tail - ptr);
if (!ptr2)
{
data->current_line = grub_malloc ((char *) nb->tail - ptr);
if (!data->current_line)
{
grub_netbuff_free (nb);
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
return grub_errno;
}
data->current_line_len = (char *) nb->tail - ptr;
grub_memcpy (data->current_line, ptr, data->current_line_len);
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
err = parse_line (file, data, ptr, ptr2 - ptr);
if (err)
{
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
grub_netbuff_free (nb);
return err;
}
ptr = ptr2 + 1;
}
if (((char *) nb->tail - ptr) <= 0)
{
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
err = grub_netbuff_pull (nb, ptr - (char *) nb->data);
if (err)
{
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
grub_netbuff_free (nb);
return err;
}
if (!(data->chunked && (grub_ssize_t) data->chunk_rem
< nb->tail - nb->data))
{
grub_net_put_packet (&file->device->net->packs, nb);
if (file->device->net->packs.count >= 20)
file->device->net->stall = 1;
if (file->device->net->packs.count >= 100)
grub_net_tcp_stall (data->sock);
if (data->chunked)
data->chunk_rem -= nb->tail - nb->data;
return GRUB_ERR_NONE;
}
if (data->chunk_rem)
{
struct grub_net_buff *nb2;
nb2 = grub_netbuff_alloc (data->chunk_rem);
if (!nb2)
return grub_errno;
grub_netbuff_put (nb2, data->chunk_rem);
grub_memcpy (nb2->data, nb->data, data->chunk_rem);
if (file->device->net->packs.count >= 20)
{
file->device->net->stall = 1;
grub_net_tcp_stall (data->sock);
}
grub_net_put_packet (&file->device->net->packs, nb2);
grub_netbuff_pull (nb, data->chunk_rem);
}
data->in_chunk_len = 1;
}
}
static grub_err_t
http_establish (struct grub_file *file, grub_off_t offset, int initial)
{
http_data_t data = file->data;
grub_uint8_t *ptr;
int i;
struct grub_net_buff *nb;
grub_err_t err;
nb = grub_netbuff_alloc (GRUB_NET_TCP_RESERVE_SIZE
+ sizeof ("GET ") - 1
+ grub_strlen (data->filename)
+ sizeof (" HTTP/1.1\r\nHost: ") - 1
+ grub_strlen (file->device->net->server)
+ sizeof ("\r\nUser-Agent: " PACKAGE_STRING
"\r\n") - 1
+ sizeof ("Range: bytes=XXXXXXXXXXXXXXXXXXXX"
"-\r\n\r\n"));
if (!nb)
return grub_errno;
grub_netbuff_reserve (nb, GRUB_NET_TCP_RESERVE_SIZE);
ptr = nb->tail;
err = grub_netbuff_put (nb, sizeof ("GET ") - 1);
if (err)
{
grub_netbuff_free (nb);
return err;
}
grub_memcpy (ptr, "GET ", sizeof ("GET ") - 1);
ptr = nb->tail;
err = grub_netbuff_put (nb, grub_strlen (data->filename));
if (err)
{
grub_netbuff_free (nb);
return err;
}
grub_memcpy (ptr, data->filename, grub_strlen (data->filename));
ptr = nb->tail;
err = grub_netbuff_put (nb, sizeof (" HTTP/1.1\r\nHost: ") - 1);
if (err)
{
grub_netbuff_free (nb);
return err;
}
grub_memcpy (ptr, " HTTP/1.1\r\nHost: ",
sizeof (" HTTP/1.1\r\nHost: ") - 1);
ptr = nb->tail;
err = grub_netbuff_put (nb, grub_strlen (file->device->net->server));
if (err)
{
grub_netbuff_free (nb);
return err;
}
grub_memcpy (ptr, file->device->net->server,
grub_strlen (file->device->net->server));
ptr = nb->tail;
err = grub_netbuff_put (nb,
sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n")
- 1);
if (err)
{
grub_netbuff_free (nb);
return err;
}
grub_memcpy (ptr, "\r\nUser-Agent: " PACKAGE_STRING "\r\n",
sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n") - 1);
if (!initial)
{
ptr = nb->tail;
grub_snprintf ((char *) ptr,
sizeof ("Range: bytes=XXXXXXXXXXXXXXXXXXXX-"
"\r\n"),
"Range: bytes=%" PRIuGRUB_UINT64_T "-\r\n",
offset);
grub_netbuff_put (nb, grub_strlen ((char *) ptr));
}
ptr = nb->tail;
grub_netbuff_put (nb, 2);
grub_memcpy (ptr, "\r\n", 2);
data->sock = grub_net_tcp_open (file->device->net->server,
HTTP_PORT, http_receive,
http_err, http_err,
file);
if (!data->sock)
{
grub_netbuff_free (nb);
return grub_errno;
}
// grub_net_poll_cards (5000);
err = grub_net_send_tcp_packet (data->sock, nb, 1);
if (err)
{
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
return err;
}
for (i = 0; !data->headers_recv && i < 100; i++)
{
grub_net_tcp_retransmit ();
grub_net_poll_cards (300, &data->headers_recv);
}
if (!data->headers_recv)
{
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
if (data->err)
{
char *str = data->errmsg;
err = grub_error (data->err, "%s", str);
grub_free (str);
data->errmsg = 0;
return data->err;
}
return grub_error (GRUB_ERR_TIMEOUT, N_("time out opening `%s'"), data->filename);
}
return GRUB_ERR_NONE;
}
static grub_err_t
http_seek (struct grub_file *file, grub_off_t off)
{
struct http_data *old_data, *data;
grub_err_t err;
old_data = file->data;
/* FIXME: Reuse socket? */
if (old_data->sock)
grub_net_tcp_close (old_data->sock, GRUB_NET_TCP_ABORT);
old_data->sock = 0;
while (file->device->net->packs.first)
{
grub_netbuff_free (file->device->net->packs.first->nb);
grub_net_remove_packet (file->device->net->packs.first);
}
file->device->net->stall = 0;
file->device->net->offset = off;
data = grub_zalloc (sizeof (*data));
if (!data)
return grub_errno;
data->size_recv = 1;
data->filename = old_data->filename;
if (!data->filename)
{
grub_free (data);
file->data = 0;
return grub_errno;
}
grub_free (old_data);
file->data = data;
err = http_establish (file, off, 0);
if (err)
{
grub_free (data->filename);
grub_free (data);
file->data = 0;
return err;
}
return GRUB_ERR_NONE;
}
static grub_err_t
http_open (struct grub_file *file, const char *filename)
{
grub_err_t err;
struct http_data *data;
data = grub_zalloc (sizeof (*data));
if (!data)
return grub_errno;
file->size = GRUB_FILE_SIZE_UNKNOWN;
data->filename = grub_strdup (filename);
if (!data->filename)
{
grub_free (data);
return grub_errno;
}
file->not_easily_seekable = 0;
file->data = data;
err = http_establish (file, 0, 1);
if (err)
{
grub_free (data->filename);
grub_free (data);
return err;
}
return GRUB_ERR_NONE;
}
static grub_err_t
http_close (struct grub_file *file)
{
http_data_t data = file->data;
if (!data)
return GRUB_ERR_NONE;
if (data->sock)
grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
if (data->current_line)
grub_free (data->current_line);
grub_free (data->filename);
grub_free (data);
return GRUB_ERR_NONE;
}
static grub_err_t
http_packets_pulled (struct grub_file *file)
{
http_data_t data = file->data;
if (file->device->net->packs.count >= 20)
return 0;
if (!file->device->net->eof)
file->device->net->stall = 0;
if (data && data->sock)
grub_net_tcp_unstall (data->sock);
return 0;
}
static struct grub_net_app_protocol grub_http_protocol =
{
.name = "http",
.open = http_open,
.close = http_close,
.seek = http_seek,
.packets_pulled = http_packets_pulled
};
GRUB_MOD_INIT (http)
{
grub_net_app_level_register (&grub_http_protocol);
}
GRUB_MOD_FINI (http)
{
grub_net_app_level_unregister (&grub_http_protocol);
}