_i32 _ReadFileHeaders(_i16 fileSockId, _u8 *domian_name, _u8 *file_name) { _i32 status=0; _i32 len; _u8 *send_buf = http_send_buf(); Report("_ReadFileHeaders: domain=%s, file=%s\r\n", domian_name, file_name); http_build_request (send_buf, "GET ", domian_name, NULL, file_name, NULL, NULL); len = sl_Send(fileSockId, send_buf, (_i16)strlen((const char *)send_buf), 0); if (len <= 0) { Report("_ReadFileHeaders: ERROR, sl_Send, status=%ld\r\n", len); return OTA_STATUS_ERROR; } Report("_ReadFileHeaders: skip http headers\r\n"); status = http_skip_headers(fileSockId); if (status < 0) { Report("_ReadFileHeaders: ERROR, http_skip_headers, status=%ld\r\n", status); return OTA_STATUS_ERROR; } return OTA_STATUS_OK; }
enum host_status host_recv_file(struct host *h, struct http_info *req, FILE *file) { boolean mid_inflate = false, mid_chunk = false, deflated = false; unsigned int content_length = 0; const char *host_name = h->name; unsigned long len = 0, pos = 0; char line[LINE_BUF_LEN]; z_stream stream; ssize_t line_len; enum { NONE, NORMAL, CHUNKED, } transfer_type = NONE; // Tell the server that we support pipelining snprintf(line, LINE_BUF_LEN, "GET %s HTTP/1.1", req->url); line[LINE_BUF_LEN - 1] = 0; if(http_send_line(h, line) < 0) return -HOST_SEND_FAILED; // For vhost resolution if (h->proxied) host_name = h->endpoint; snprintf(line, LINE_BUF_LEN, "Host: %s", host_name); line[LINE_BUF_LEN - 1] = 0; if(http_send_line(h, line) < 0) return -HOST_SEND_FAILED; // We support DEFLATE/GZIP payloads if(http_send_line(h, "Accept-Encoding: gzip") < 0) return -HOST_SEND_FAILED; // Black line tells server we are done if(http_send_line(h, "") < 0) return -HOST_SEND_FAILED; // Read in the HTTP status line line_len = http_recv_line(h, line, LINE_BUF_LEN); if(line_len < 0) { warn("No response for url '%s': %d!\n", req->url, (int)line_len); return line_len; } if(!http_read_status(req, line, line_len)) { warn("Invalid status: %s\nFailed for url '%s'\n", line, req->url); return -HOST_HTTP_INVALID_STATUS; } // Unhandled status categories switch(req->status_type) { case 1: return -HOST_HTTP_INFO; case 3: return -HOST_HTTP_REDIRECT; case 4: return -HOST_HTTP_CLIENT_ERROR; case 5: return -HOST_HTTP_SERVER_ERROR; } // Now parse the HTTP headers, extracting only the pertinent fields while(true) { int len = http_recv_line(h, line, LINE_BUF_LEN); char *key, *value, *buf = line; if(len < 0) return -HOST_HTTP_INVALID_HEADER; else if(len == 0) break; key = strsep(&buf, ":"); value = strsep(&buf, ":"); if(!key || !value) return -HOST_HTTP_INVALID_HEADER; // Skip common prefix space if present if(value[0] == ' ') value++; /* Parse pertinent headers. These are: * * Content-Length Necessary to determine payload length * Transfer-Encoding Instead of Content-Length, can only be "chunked" * Content-Type Text or binary; also used for sanity checks * Content-Encoding Present and set to 'gzip' if deflated */ if(strcmp(key, "Content-Length") == 0) { char *endptr; content_length = (unsigned int)strtoul(value, &endptr, 10); if(endptr[0]) return -HOST_HTTP_INVALID_CONTENT_LENGTH; transfer_type = NORMAL; } else if(strcmp(key, "Transfer-Encoding") == 0) { if(strcmp(value, "chunked") != 0) return -HOST_HTTP_INVALID_TRANSFER_ENCODING; transfer_type = CHUNKED; } else if(strcmp(key, "Content-Type") == 0) { strncpy(req->content_type, value, 63); if(strcmp(value, req->expected_type) != 0) return -HOST_HTTP_INVALID_CONTENT_TYPE; } else if(strcmp(key, "Content-Encoding") == 0) { if(strcmp(value, "gzip") != 0) return -HOST_HTTP_INVALID_CONTENT_ENCODING; deflated = true; } } if(transfer_type != NORMAL && transfer_type != CHUNKED) return -HOST_HTTP_INVALID_TRANSFER_ENCODING; while(true) { unsigned long block_size; char block[BLOCK_SIZE]; /* Both transfer mechanisms need preambles. For NORMAL, this will * happen only once, because we have a predetermined length for * transfer. However, for CHUNKED we don't know the total payload * size, so this will be invoked each time we exhaust a chunk. * * The CHUNKED handling basically involves chopping away the * headers and determining the next chunk size. */ if(!mid_chunk) { if(transfer_type == NORMAL) len = content_length; else if(transfer_type == CHUNKED) { char *endptr, *length, *buf = line; // Get a chunk_length;parameters formatted line (CRLF terminated) if(http_recv_line(h, line, LINE_BUF_LEN) <= 0) return -HOST_HTTP_INVALID_CHUNK_LENGTH; // HTTP 1.1 says we can ignore trailing parameters length = strsep(&buf, ";"); if(!length) return -HOST_HTTP_INVALID_CHUNK_LENGTH; // Convert hex length to unsigned long; check for conversion errors len = strtoul(length, &endptr, 16); if(endptr[0]) return -HOST_HTTP_INVALID_CHUNK_LENGTH; } mid_chunk = true; pos = 0; } /* For NORMAL transfers, this indicates that there was a zero byte * payload. This is unusual but we can handle it safely by aborting. * * For CHUNKED transfers, zero indicates that there are no more chunks * to process, and that final footer handling should occur. We then * abort as with NORMAL. */ if(len == 0) { if(transfer_type == CHUNKED) if(!http_skip_headers(h)) return -HOST_HTTP_INVALID_HEADER; break; } /* For a NORMAL transfer, the block_size computation should yield * BLOCK_SIZE until the final block, which will be len % BLOCK_SIZE. * * For CHUNKED, this block_size can be more volatile. In most cases it * will be BLOCK_SIZE if chunk size > BLOCK_SIZE, until the final block. * * However for very small chunks (which are unlikely) this will always * be shorter than BLOCK_SIZE. */ block_size = MIN(BLOCK_SIZE, len - pos); /* In either case, all headers and block computation has now been done, * and the buffer can be streamed to disk. */ if(!__recv(h, block, block_size)) return -HOST_RECV_FAILED; if(deflated) { /* This is the first block requiring inflation. In this case, we must * parse the GZIP header in order to compute an offset to the DEFLATE * formatted data. */ if(!mid_inflate) { ssize_t deflate_offset = 0; /* Compute the offset within this block to begin the inflation * process. For all but the first block, deflate_offset will be * zero. */ deflate_offset = zlib_skip_gzip_header(block, block_size); if(deflate_offset < 0) return deflate_offset; /* Now we can initialize the decompressor. Pass along the block * without the GZIP header (and for a GZIP, this is also without * the DEFLATE header too, which is what the -MAX_WBITS trick is for). */ stream.avail_in = block_size - (unsigned long)deflate_offset; stream.next_in = (Bytef *)&block[deflate_offset]; stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; if(inflateInit2(&stream, -MAX_WBITS) != Z_OK) return -HOST_ZLIB_INFLATE_FAILED; mid_inflate = true; } else { stream.avail_in = block_size; stream.next_in = (Bytef *)block; } while(true) { char outbuf[BLOCK_SIZE]; int ret; // Each pass, only decompress a maximum of BLOCK_SIZE stream.avail_out = BLOCK_SIZE; stream.next_out = (Bytef *)outbuf; /* Perform the inflation (this will modify avail_in and * next_in automatically. */ ret = inflate(&stream, Z_NO_FLUSH); if(ret != Z_OK && ret != Z_STREAM_END) return -HOST_ZLIB_INFLATE_FAILED; // Push the block to disk if(fwrite(outbuf, BLOCK_SIZE - stream.avail_out, 1, file) != 1) return -HOST_FWRITE_FAILED; // If the stream has terminated, flag it and break out if(ret == Z_STREAM_END) { mid_inflate = false; break; } /* The stream hasn't terminated but we've exhausted input * data for this pass. */ if(stream.avail_in == 0) break; } // The stream terminated, so we should free associated data-structures if(!mid_inflate) inflateEnd(&stream); } else { /* If the transfer is not deflated, we can simply write out * block_size bytes to the file now. */ if(fwrite(block, block_size, 1, file) != 1) return -HOST_FWRITE_FAILED; } pos += block_size; if(h->recv_cb) h->recv_cb(ftell(file)); /* For NORMAL transfers we can now abort since we have reached the end * of our payload. * * For CHUNKED transfers, we remove the trailing newline and flag that * a new set of chunk headers should be read. */ if(len == pos) { if(transfer_type == NORMAL) break; else if(transfer_type == CHUNKED) { if(http_recv_line(h, line, LINE_BUF_LEN) != 0) return -HOST_HTTP_INVALID_HEADER; mid_chunk = false; } } } return HOST_SUCCESS; }
boolean host_handle_http_request(struct host *h) { const char *mime_type = "application/octet-stream"; char buffer[LINE_BUF_LEN], *buf = buffer; char *cmd_type, *path, *proto; enum host_status ret; size_t path_len; FILE *f; if(http_recv_line(h, buffer, LINE_BUF_LEN) < 0) { warn("Failed to receive HTTP request\n"); return false; } cmd_type = strsep(&buf, " "); if(!cmd_type) return false; if(strcmp(cmd_type, "GET") != 0) return false; path = strsep(&buf, " "); if(!path) return false; proto = strsep(&buf, " "); if(!proto) return false; if(strncmp("HTTP/1.1", proto, 8) != 0) { warn("Client must support HTTP 1.1, rejecting\n"); return false; } if(!http_skip_headers(h)) { warn("Failed to skip HTTP headers\n"); return false; } path++; debug("Received request for '%s'\n", path); f = fopen(path, "rb"); if(!f) { warn("Failed to open file '%s', sending 404\n", path); snprintf(buffer, LINE_BUF_LEN, "HTTP/1.1 404 Not Found\r\n" "Content-Length: %zd\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n\r\n", strlen(resp_404)); if(__send(h, buffer, strlen(buffer))) { if(!__send(h, resp_404, strlen(resp_404))) warn("Failed to send 404 payload\n"); } else warn("Failed to send 404 status code\n"); return false; } path_len = strlen(path); if(path_len >= 4 && strcasecmp(&path[path_len - 4], ".txt") == 0) mime_type = "text/plain"; ret = host_send_file(h, f, mime_type); if(ret != HOST_SUCCESS) { warn("Failed to send file '%s' over HTTP (error %d)\n", path, ret); return false; } return true; }