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