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