Home | History | Annotate | Download | only in pxe
      1 #include <syslinux/sysappend.h>
      2 #include <ctype.h>
      3 #include <lwip/api.h>
      4 #include "pxe.h"
      5 #include "version.h"
      6 #include "url.h"
      7 #include "net.h"
      8 
      9 #define HTTP_PORT	80
     10 
     11 static bool is_tspecial(int ch)
     12 {
     13     bool tspecial = false;
     14     switch(ch) {
     15     case '(':  case ')':  case '<':  case '>':  case '@':
     16     case ',':  case ';':  case ':':  case '\\': case '"':
     17     case '/':  case '[':  case ']':  case '?':  case '=':
     18     case '{':  case '}':  case ' ':  case '\t':
     19 	tspecial = true;
     20 	break;
     21     }
     22     return tspecial;
     23 }
     24 
     25 static bool is_ctl(int ch)
     26 {
     27     return ch < 0x20;
     28 }
     29 
     30 static bool is_token(int ch)
     31 {
     32     /* Can by antying except a ctl character or a tspecial */
     33     return !is_ctl(ch) && !is_tspecial(ch);
     34 }
     35 
     36 static bool append_ch(char *str, size_t size, size_t *pos, int ch)
     37 {
     38     bool success = true;
     39     if ((*pos + 1) >= size) {
     40 	*pos = 0;
     41 	success = false;
     42     } else {
     43 	str[*pos] = ch;
     44 	str[*pos + 1] = '\0';
     45 	*pos += 1;
     46     }
     47     return success;
     48 }
     49 
     50 static size_t cookie_len, header_len;
     51 static char *cookie_buf, *header_buf;
     52 
     53 __export uint32_t SendCookies = -1UL; /* Send all cookies */
     54 
     55 static size_t http_do_bake_cookies(char *q)
     56 {
     57     static const char uchexchar[16] = "0123456789ABCDEF";
     58     int i;
     59     size_t n = 0;
     60     const char *p;
     61     char c;
     62     bool first = true;
     63     uint32_t mask = SendCookies;
     64 
     65     for (i = 0; i < SYSAPPEND_MAX; i++) {
     66 	if ((mask & 1) && (p = sysappend_strings[i])) {
     67 	    if (first) {
     68 		if (q) {
     69 		    strcpy(q, "Cookie: ");
     70 		    q += 8;
     71 		}
     72 		n += 8;
     73 		first = false;
     74 	    }
     75 	    if (q) {
     76 		strcpy(q, "_Syslinux_");
     77 		q += 10;
     78 	    }
     79 	    n += 10;
     80 	    /* Copy string up to and including '=' */
     81 	    do {
     82 		c = *p++;
     83 		if (q)
     84 		    *q++ = c;
     85 		n++;
     86 	    } while (c != '=');
     87 	    while ((c = *p++)) {
     88 		if (c == ' ') {
     89 		    if (q)
     90 			*q++ = '+';
     91 		    n++;
     92 		} else if (is_token(c)) {
     93 		    if (q)
     94 			*q++ = c;
     95 		    n++;
     96 		} else {
     97 		    if (q) {
     98 			*q++ = '%';
     99 			*q++ = uchexchar[c >> 4];
    100 			*q++ = uchexchar[c & 15];
    101 		    }
    102 		    n += 3;
    103 		}
    104 	    }
    105 	    if (q)
    106 		*q++ = ';';
    107 	    n++;
    108 	}
    109 	mask >>= 1;
    110     }
    111     if (!first) {
    112 	if (q) {
    113 	    *q++ = '\r';
    114 	    *q++ = '\n';
    115 	}
    116 	n += 2;
    117     }
    118     if (q)
    119 	*q = '\0';
    120 
    121     return n;
    122 }
    123 
    124 __export void http_bake_cookies(void)
    125 {
    126     if (cookie_buf)
    127 	free(cookie_buf);
    128 
    129     cookie_len = http_do_bake_cookies(NULL);
    130     cookie_buf = malloc(cookie_len+1);
    131     if (!cookie_buf) {
    132 	cookie_len = 0;
    133 	return;
    134     }
    135 
    136     if (header_buf)
    137 	free(header_buf);
    138 
    139     header_len = cookie_len + 6*FILENAME_MAX + 256;
    140     header_buf = malloc(header_len);
    141     if (!header_buf) {
    142 	header_len = 0;
    143 	return;			/* Uh-oh... */
    144     }
    145 
    146     http_do_bake_cookies(cookie_buf);
    147 }
    148 
    149 static const struct pxe_conn_ops http_conn_ops = {
    150     .fill_buffer	= core_tcp_fill_buffer,
    151     .close		= core_tcp_close_file,
    152     .readdir		= http_readdir,
    153 };
    154 
    155 void http_open(struct url_info *url, int flags, struct inode *inode,
    156 	       const char **redir)
    157 {
    158     struct pxe_pvt_inode *socket = PVT(inode);
    159     int header_bytes;
    160     const char *next;
    161     char field_name[20];
    162     char field_value[1024];
    163     size_t field_name_len, field_value_len;
    164     enum state {
    165 	st_httpver,
    166 	st_stcode,
    167 	st_skipline,
    168 	st_fieldfirst,
    169 	st_fieldname,
    170 	st_fieldvalue,
    171 	st_skip_fieldname,
    172 	st_skip_fieldvalue,
    173 	st_eoh,
    174     } state;
    175     static char location[FILENAME_MAX];
    176     uint32_t content_length; /* same as inode->size */
    177     size_t response_size;
    178     int status;
    179     int pos;
    180     int err;
    181 
    182     (void)flags;
    183 
    184     if (!header_buf)
    185 	return;			/* http is broken... */
    186 
    187     /* This is a straightforward TCP connection after headers */
    188     socket->ops = &http_conn_ops;
    189 
    190     /* Reset all of the variables */
    191     inode->size = content_length = -1;
    192 
    193     /* Start the http connection */
    194     err = core_tcp_open(socket);
    195     if (err)
    196         return;
    197 
    198     if (!url->port)
    199 	url->port = HTTP_PORT;
    200 
    201     err = core_tcp_connect(socket, url->ip, url->port);
    202     if (err)
    203 	goto fail;
    204 
    205     strcpy(header_buf, "GET /");
    206     header_bytes = 5;
    207     header_bytes += url_escape_unsafe(header_buf+5, url->path,
    208 				      header_len - 5);
    209     if (header_bytes >= header_len)
    210 	goto fail;		/* Buffer overflow */
    211     header_bytes += snprintf(header_buf + header_bytes,
    212 			     header_len - header_bytes,
    213 			     " HTTP/1.0\r\n"
    214 			     "Host: %s\r\n"
    215 			     "User-Agent: Syslinux/" VERSION_STR "\r\n"
    216 			     "Connection: close\r\n"
    217 			     "%s"
    218 			     "\r\n",
    219 			     url->host, cookie_buf ? cookie_buf : "");
    220     if (header_bytes >= header_len)
    221 	goto fail;		/* Buffer overflow */
    222 
    223     err = core_tcp_write(socket, header_buf, header_bytes, false);
    224     if (err)
    225 	goto fail;
    226 
    227     /* Parse the HTTP header */
    228     state = st_httpver;
    229     pos = 0;
    230     status = 0;
    231     response_size = 0;
    232     field_value_len = 0;
    233     field_name_len = 0;
    234 
    235     while (state != st_eoh) {
    236 	int ch = pxe_getc(inode);
    237 	/* Eof before I finish paring the header */
    238 	if (ch == -1)
    239 	    goto fail;
    240 #if 0
    241         printf("%c", ch);
    242 #endif
    243 	response_size++;
    244 	if (ch == '\r' || ch == '\0')
    245 	    continue;
    246 	switch (state) {
    247 	case st_httpver:
    248 	    if (ch == ' ') {
    249 		state = st_stcode;
    250 		pos = 0;
    251 	    }
    252 	    break;
    253 
    254 	case st_stcode:
    255 	    if (ch < '0' || ch > '9')
    256 	       goto fail;
    257 	    status = (status*10) + (ch - '0');
    258 	    if (++pos == 3)
    259 		state = st_skipline;
    260 	    break;
    261 
    262 	case st_skipline:
    263 	    if (ch == '\n')
    264 		state = st_fieldfirst;
    265 	    break;
    266 
    267 	case st_fieldfirst:
    268 	    if (ch == '\n')
    269 		state = st_eoh;
    270 	    else if (isspace(ch)) {
    271 		/* A continuation line */
    272 		state = st_fieldvalue;
    273 		goto fieldvalue;
    274 	    }
    275 	    else if (is_token(ch)) {
    276 		/* Process the previous field before starting on the next one */
    277 		if (strcasecmp(field_name, "Content-Length") == 0) {
    278 		    next = field_value;
    279 		    /* Skip leading whitespace */
    280 		    while (isspace(*next))
    281 			next++;
    282 		    content_length = 0;
    283 		    for (;(*next >= '0' && *next <= '9'); next++) {
    284 			if ((content_length * 10) < content_length)
    285 			    break;
    286 			content_length = (content_length * 10) + (*next - '0');
    287 		    }
    288 		    /* In the case of overflow or other error ignore
    289 		     * Content-Length.
    290 		     */
    291 		    if (*next)
    292 			content_length = -1;
    293 		}
    294 		else if (strcasecmp(field_name, "Location") == 0) {
    295 		    next = field_value;
    296 		    /* Skip leading whitespace */
    297 		    while (isspace(*next))
    298 			next++;
    299 		    strlcpy(location, next, sizeof location);
    300 		}
    301 		/* Start the field name and field value afress */
    302 		field_name_len = 1;
    303 		field_name[0] = ch;
    304 		field_name[1] = '\0';
    305 		field_value_len = 0;
    306 		field_value[0] = '\0';
    307 		state = st_fieldname;
    308 	    }
    309 	    else /* Bogus try to recover */
    310 		state = st_skipline;
    311 	    break;
    312 
    313 	case st_fieldname:
    314 	    if (ch == ':' ) {
    315 		state = st_fieldvalue;
    316 	    }
    317 	    else if (is_token(ch)) {
    318 		if (!append_ch(field_name, sizeof field_name, &field_name_len, ch))
    319 		    state = st_skip_fieldname;
    320 	    }
    321 	    /* Bogus cases try to recover */
    322 	    else if (ch == '\n')
    323 		state = st_fieldfirst;
    324 	    else
    325 		state = st_skipline;
    326 	    break;
    327 
    328 	 case st_fieldvalue:
    329 	    if (ch == '\n')
    330 		state = st_fieldfirst;
    331 	    else {
    332 	    fieldvalue:
    333 		if (!append_ch(field_value, sizeof field_value, &field_value_len, ch))
    334 		    state = st_skip_fieldvalue;
    335 	    }
    336 	    break;
    337 
    338 	/* For valid fields whose names are longer than I choose to support. */
    339 	case st_skip_fieldname:
    340 	    if (ch == ':')
    341 		state = st_skip_fieldvalue;
    342 	    else if (is_token(ch))
    343 		state = st_skip_fieldname;
    344 	    /* Bogus cases try to recover */
    345 	    else if (ch == '\n')
    346 		state = st_fieldfirst;
    347 	    else
    348 		state = st_skipline;
    349 	    break;
    350 
    351 	/* For valid fields whose bodies are longer than I choose to support. */
    352 	case st_skip_fieldvalue:
    353 	    if (ch == '\n')
    354 		state = st_fieldfirst;
    355 	    break;
    356 
    357 	case st_eoh:
    358 	   break; /* Should never happen */
    359 	}
    360     }
    361 
    362     if (state != st_eoh)
    363 	status = 0;
    364 
    365     switch (status) {
    366     case 200:
    367 	/*
    368 	 * All OK, need to mark header data consumed and set up a file
    369 	 * structure...
    370 	 */
    371 	/* Treat the remainder of the bytes as data */
    372 	socket->tftp_filepos -= response_size;
    373 	break;
    374     case 301:
    375     case 302:
    376     case 303:
    377     case 307:
    378 	/* A redirect */
    379 	if (!location[0])
    380 	    goto fail;
    381 	*redir = location;
    382 	goto fail;
    383     default:
    384 	goto fail;
    385 	break;
    386     }
    387     return;
    388 fail:
    389     inode->size = 0;
    390     core_tcp_close_file(inode);
    391     return;
    392 }
    393