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 
is_tspecial(int ch)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 
is_ctl(int ch)25 static bool is_ctl(int ch)
26 {
27     return ch < 0x20;
28 }
29 
is_token(int ch)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 
append_ch(char * str,size_t size,size_t * pos,int ch)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 
http_do_bake_cookies(char * q)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 
http_bake_cookies(void)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 
http_open(struct url_info * url,int flags,struct inode * inode,const char ** redir)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