393 lines
8.0 KiB
C
393 lines
8.0 KiB
C
#include <syslinux/sysappend.h>
|
|
#include <ctype.h>
|
|
#include <lwip/api.h>
|
|
#include "pxe.h"
|
|
#include "version.h"
|
|
#include "url.h"
|
|
#include "net.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;
|
|
}
|
|
|
|
__export 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 = core_tcp_fill_buffer,
|
|
.close = core_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;
|
|
enum state {
|
|
st_httpver,
|
|
st_stcode,
|
|
st_skipline,
|
|
st_fieldfirst,
|
|
st_fieldname,
|
|
st_fieldvalue,
|
|
st_skip_fieldname,
|
|
st_skip_fieldvalue,
|
|
st_eoh,
|
|
} state;
|
|
static char location[FILENAME_MAX];
|
|
uint32_t content_length; /* same as inode->size */
|
|
size_t response_size;
|
|
int status;
|
|
int pos;
|
|
int err;
|
|
|
|
(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 */
|
|
err = core_tcp_open(socket);
|
|
if (err)
|
|
return;
|
|
|
|
if (!url->port)
|
|
url->port = HTTP_PORT;
|
|
|
|
err = core_tcp_connect(socket, url->ip, url->port);
|
|
if (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 = core_tcp_write(socket, header_buf, header_bytes, false);
|
|
if (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;
|
|
core_tcp_close_file(inode);
|
|
return;
|
|
}
|