| /* |
| * 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->eof = 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); |
| } |