diff options
author | Matt Fleming <matt.fleming@intel.com> | 2013-03-20 17:14:21 +0000 |
---|---|---|
committer | Matt Fleming <matt.fleming@intel.com> | 2013-03-22 13:57:44 +0000 |
commit | 37d43cf9dd5dd2d2cef1e86aa651097473fd0b48 (patch) | |
tree | 07a7df546f178c4e1576f4cbcc0bb4db3a8e18b4 /core/fs/pxe/http.c | |
parent | bf20364b582c383b4927f898de213b1cc0981a80 (diff) | |
parent | a107cb3b6fa219cf5f65bef366c9b00b108e9a3a (diff) | |
download | syslinux-37d43cf9dd5dd2d2cef1e86aa651097473fd0b48.tar.gz |
Merge tag 'syslinux-5.10-pre2' into for-hpa/elflink/firmware
syslinux-5.10-pre2
Conflicts:
NEWS
com32/include/netinet/in.h
com32/include/sys/cpu.h
com32/lib/Makefile
core/Makefile
core/fs/diskio.c
core/fs/pxe/pxe.h
core/init.c
core/mem/free.c
core/mem/malloc.c
mk/devel.mk
version
Diffstat (limited to 'core/fs/pxe/http.c')
-rw-r--r-- | core/fs/pxe/http.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/core/fs/pxe/http.c b/core/fs/pxe/http.c new file mode 100644 index 00000000..cc139b68 --- /dev/null +++ b/core/fs/pxe/http.c @@ -0,0 +1,400 @@ +#include <syslinux/sysappend.h> +#include <ctype.h> +#include <lwip/api.h> +#include "pxe.h" +#include "version.h" +#include "url.h" + +#define HTTP_PORT 80 + +static bool is_tspecial(int ch) +{ + bool tspecial = false; + switch(ch) { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + tspecial = true; + break; + } + return tspecial; +} + +static bool is_ctl(int ch) +{ + return ch < 0x20; +} + +static bool is_token(int ch) +{ + /* Can by antying except a ctl character or a tspecial */ + return !is_ctl(ch) && !is_tspecial(ch); +} + +static bool append_ch(char *str, size_t size, size_t *pos, int ch) +{ + bool success = true; + if ((*pos + 1) >= size) { + *pos = 0; + success = false; + } else { + str[*pos] = ch; + str[*pos + 1] = '\0'; + *pos += 1; + } + return success; +} + +static size_t cookie_len, header_len; +static char *cookie_buf, *header_buf; + +__export uint32_t SendCookies = -1UL; /* Send all cookies */ + +static size_t http_do_bake_cookies(char *q) +{ + static const char uchexchar[16] = "0123456789ABCDEF"; + int i; + size_t n = 0; + const char *p; + char c; + bool first = true; + uint32_t mask = SendCookies; + + for (i = 0; i < SYSAPPEND_MAX; i++) { + if ((mask & 1) && (p = sysappend_strings[i])) { + if (first) { + if (q) { + strcpy(q, "Cookie: "); + q += 8; + } + n += 8; + first = false; + } + if (q) { + strcpy(q, "_Syslinux_"); + q += 10; + } + n += 10; + /* Copy string up to and including '=' */ + do { + c = *p++; + if (q) + *q++ = c; + n++; + } while (c != '='); + while ((c = *p++)) { + if (c == ' ') { + if (q) + *q++ = '+'; + n++; + } else if (is_token(c)) { + if (q) + *q++ = c; + n++; + } else { + if (q) { + *q++ = '%'; + *q++ = uchexchar[c >> 4]; + *q++ = uchexchar[c & 15]; + } + n += 3; + } + } + if (q) + *q++ = ';'; + n++; + } + mask >>= 1; + } + if (!first) { + if (q) { + *q++ = '\r'; + *q++ = '\n'; + } + n += 2; + } + if (q) + *q = '\0'; + + return n; +} + +void http_bake_cookies(void) +{ + if (cookie_buf) + free(cookie_buf); + + cookie_len = http_do_bake_cookies(NULL); + cookie_buf = malloc(cookie_len+1); + if (!cookie_buf) { + cookie_len = 0; + return; + } + + if (header_buf) + free(header_buf); + + header_len = cookie_len + 6*FILENAME_MAX + 256; + header_buf = malloc(header_len); + if (!header_buf) { + header_len = 0; + return; /* Uh-oh... */ + } + + http_do_bake_cookies(cookie_buf); +} + +static const struct pxe_conn_ops http_conn_ops = { + .fill_buffer = tcp_fill_buffer, + .close = tcp_close_file, + .readdir = http_readdir, +}; + +void http_open(struct url_info *url, int flags, struct inode *inode, + const char **redir) +{ + struct pxe_pvt_inode *socket = PVT(inode); + int header_bytes; + const char *next; + char field_name[20]; + char field_value[1024]; + size_t field_name_len, field_value_len; + err_t err; + enum state { + st_httpver, + st_stcode, + st_skipline, + st_fieldfirst, + st_fieldname, + st_fieldvalue, + st_skip_fieldname, + st_skip_fieldvalue, + st_eoh, + } state; + struct ip_addr addr; + static char location[FILENAME_MAX]; + uint32_t content_length; /* same as inode->size */ + size_t response_size; + int status; + int pos; + + (void)flags; + + if (!header_buf) + return; /* http is broken... */ + + /* This is a straightforward TCP connection after headers */ + socket->ops = &http_conn_ops; + + /* Reset all of the variables */ + inode->size = content_length = -1; + + /* Start the http connection */ + socket->private.conn = netconn_new(NETCONN_TCP); + if (!socket->private.conn) { + printf("netconn_new failed\n"); + return; + } + + addr.addr = url->ip; + if (!url->port) + url->port = HTTP_PORT; + + err = netconn_connect(socket->private.conn, &addr, url->port); + if (err) { + printf("netconn_connect error %d\n", err); + goto fail; + } + + strcpy(header_buf, "GET /"); + header_bytes = 5; + header_bytes += url_escape_unsafe(header_buf+5, url->path, + header_len - 5); + if (header_bytes >= header_len) + goto fail; /* Buffer overflow */ + header_bytes += snprintf(header_buf + header_bytes, + header_len - header_bytes, + " HTTP/1.0\r\n" + "Host: %s\r\n" + "User-Agent: Syslinux/" VERSION_STR "\r\n" + "Connection: close\r\n" + "%s" + "\r\n", + url->host, cookie_buf ? cookie_buf : ""); + if (header_bytes >= header_len) + goto fail; /* Buffer overflow */ + + err = netconn_write(socket->private.conn, header_buf, + header_bytes, NETCONN_NOCOPY); + if (err) { + printf("netconn_write error %d\n", err); + goto fail; + } + + /* Parse the HTTP header */ + state = st_httpver; + pos = 0; + status = 0; + response_size = 0; + field_value_len = 0; + field_name_len = 0; + + while (state != st_eoh) { + int ch = pxe_getc(inode); + /* Eof before I finish paring the header */ + if (ch == -1) + goto fail; +#if 0 + printf("%c", ch); +#endif + response_size++; + if (ch == '\r' || ch == '\0') + continue; + switch (state) { + case st_httpver: + if (ch == ' ') { + state = st_stcode; + pos = 0; + } + break; + + case st_stcode: + if (ch < '0' || ch > '9') + goto fail; + status = (status*10) + (ch - '0'); + if (++pos == 3) + state = st_skipline; + break; + + case st_skipline: + if (ch == '\n') + state = st_fieldfirst; + break; + + case st_fieldfirst: + if (ch == '\n') + state = st_eoh; + else if (isspace(ch)) { + /* A continuation line */ + state = st_fieldvalue; + goto fieldvalue; + } + else if (is_token(ch)) { + /* Process the previous field before starting on the next one */ + if (strcasecmp(field_name, "Content-Length") == 0) { + next = field_value; + /* Skip leading whitespace */ + while (isspace(*next)) + next++; + content_length = 0; + for (;(*next >= '0' && *next <= '9'); next++) { + if ((content_length * 10) < content_length) + break; + content_length = (content_length * 10) + (*next - '0'); + } + /* In the case of overflow or other error ignore + * Content-Length. + */ + if (*next) + content_length = -1; + } + else if (strcasecmp(field_name, "Location") == 0) { + next = field_value; + /* Skip leading whitespace */ + while (isspace(*next)) + next++; + strlcpy(location, next, sizeof location); + } + /* Start the field name and field value afress */ + field_name_len = 1; + field_name[0] = ch; + field_name[1] = '\0'; + field_value_len = 0; + field_value[0] = '\0'; + state = st_fieldname; + } + else /* Bogus try to recover */ + state = st_skipline; + break; + + case st_fieldname: + if (ch == ':' ) { + state = st_fieldvalue; + } + else if (is_token(ch)) { + if (!append_ch(field_name, sizeof field_name, &field_name_len, ch)) + state = st_skip_fieldname; + } + /* Bogus cases try to recover */ + else if (ch == '\n') + state = st_fieldfirst; + else + state = st_skipline; + break; + + case st_fieldvalue: + if (ch == '\n') + state = st_fieldfirst; + else { + fieldvalue: + if (!append_ch(field_value, sizeof field_value, &field_value_len, ch)) + state = st_skip_fieldvalue; + } + break; + + /* For valid fields whose names are longer than I choose to support. */ + case st_skip_fieldname: + if (ch == ':') + state = st_skip_fieldvalue; + else if (is_token(ch)) + state = st_skip_fieldname; + /* Bogus cases try to recover */ + else if (ch == '\n') + state = st_fieldfirst; + else + state = st_skipline; + break; + + /* For valid fields whose bodies are longer than I choose to support. */ + case st_skip_fieldvalue: + if (ch == '\n') + state = st_fieldfirst; + break; + + case st_eoh: + break; /* Should never happen */ + } + } + + if (state != st_eoh) + status = 0; + + switch (status) { + case 200: + /* + * All OK, need to mark header data consumed and set up a file + * structure... + */ + /* Treat the remainder of the bytes as data */ + socket->tftp_filepos -= response_size; + break; + case 301: + case 302: + case 303: + case 307: + /* A redirect */ + if (!location[0]) + goto fail; + *redir = location; + goto fail; + default: + goto fail; + break; + } + return; +fail: + inode->size = 0; + tcp_close_file(inode); + return; +} |