/* * KTCPVS An implementation of the TCP Virtual Server daemon inside * kernel for the LINUX operating system. KTCPVS can be used * to build a moderately scalable and highly available server * based on a cluster of servers, with more flexibility. * * tcp_vs_http_parser.c: KTCPVS HTTP parsing engine * * Version: $Id$ * * Authors: Wensong Zhang, * Hai Long, * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include "tcp_vs.h" typedef struct methods { const short number; const char *const name; int len; } methods_t; #define HTTP_VERSION_HEADER_LEN 5 #define HTTP_VERSION_NUMBER_LEN 3 #define HTTP_VERSION_MAX 3 const char *http_version_header = "HTTP/"; const char *http_version_number[HTTP_VERSION_MAX] = { "0.9", /* The position is crucial & magic! do not insert, only add */ "1.0", "1.1", }; const methods_t http_methods[HTTP_M_MAX] = { {HTTP_M_UNKNOWN, "UNKNOWN", 7}, {HTTP_M_OPTIONS, "OPTIONS", 7}, {HTTP_M_GET, "GET", 3}, {HTTP_M_HEAD, "HEAD", 4}, {HTTP_M_POST, "POST", 4}, {HTTP_M_PUT, "PUT", 3}, {HTTP_M_DELETE, "DELETE", 6}, {HTTP_M_TRACE, "TRACE", 5} }; #define DEFAULT_MAX_COOKIE_AGE 1800 #define MAX_MIME_HEADER_STRING_LEN 64 typedef void (*HTTP_MIME_PARSER) (http_mime_header_t * mime, char *buffer); typedef struct { HTTP_MIME_PARSER parser; char mime_header_string[MAX_MIME_HEADER_STRING_LEN]; int mime_header_string_len; int enable; } http_mime_parse_t; static http_mime_parse_t http_mime_parse_table[MAX_PARSER_ID]; /**************************************************************************** * skip whitespace */ static inline char * skip_lws(const char *buffer) { char *s = (char *) buffer; while ((*s == ' ') || (*s == '\t') || (*s == '\n') || (*s == '\r')) { s++; } return s; } /**************************************************************************** * search the seperator in a string */ static char * search_sep(const char *s, int len, const char *sep) { int l, ll; l = strlen(sep); if (!l) return (char *) s; ll = len; while (ll >= l) { ll--; if (!memcmp(s, sep, l)) return (char *) s; s++; } return NULL; } /**************************************************************************** * extract the attribute-value pair * The input string has the form: A = "V", and it will also accept * A = V too. * return: * 0 -- OK, and parse end. * \; -- OK, end with a ';' * \, -- OK, end with a ',' * 1 -- parse error. * Note: * The input buffer will be modified by this routine. be care! * */ static int extract_av(char **buffer, char **attribute, char **value) { char *begin, *end, *pos, *a, *v; char c; int ret = 1; int flag = 0; a = v = end = NULL; /* get attribute */ pos = a = begin = skip_lws(*buffer); for (;;) { c = *pos; switch (c) { case ' ': case '\t': case '\r': case '\n': end = pos; *end = 0; break; case '=': if (end == NULL) { end = pos; *end = 0; } goto get_value; case ';': case ',': case 0: if (end == NULL) { end = pos; *end = 0; } ret = c; goto exit; } pos++; } get_value: pos++; /* get value */ pos = v = begin = skip_lws(pos); end = NULL; if (*pos == '"') { flag = 1; pos++; v++; } for (;;) { c = *pos; switch (c) { case ' ': case '\t': case '\r': case '\n': if ((flag == 0) && (end == NULL)) { end = pos; *end = 0; } break; case '"': if (flag == 1) { end = pos; *end = 0; flag = 0; } break; case ';': case ',': if (flag == 0) { if (end == NULL) { end = pos; *end = 0; } ret = c; goto exit; } break; case 0: if (end == NULL) { end = pos; *end = 0; } ret = c; goto exit; } pos++; } exit: if (*a == 0) { a = NULL; } if ((v != NULL) && (*v == 0)) { v = NULL; } if (ret > 1) { *buffer = (pos + 1); } *attribute = a; *value = v; return ret; } /**************************************************************************** * This doesn't accept 0x if the radix is 16. The overflow code assumes * a 2's complement architecture */ #ifndef strtol static long strtol(char *string, char **endptr, int radix) { char *s; long value; long new_value; int sign; int increment; value = 0; sign = 1; s = string; if ((radix == 1) || (radix > 36) || (radix < 0)) { goto done; } /* skip whitespace */ while ((*s == ' ') || (*s == '\t') || (*s == '\n') || (*s == '\r')) { s++; } if (*s == '-') { sign = -1; s++; } else if (*s == '+') { s++; } if (radix == 0) { if (*s == '0') { s++; if ((*s == 'x') || (*s == 'X')) { s++; radix = 16; } else radix = 8; } else radix = 10; } /* read number */ while (1) { if ((*s >= '0') && (*s <= '9')) increment = *s - '0'; else if ((*s >= 'a') && (*s <= 'z')) increment = *s - 'a' + 10; else if ((*s >= 'A') && (*s <= 'Z')) increment = *s - 'A' + 10; else break; if (increment >= radix) break; new_value = value * radix + increment; /* detect overflow */ if ((new_value - increment) / radix != value) { s = string; value = -1 >> 1; if (sign < 0) value += 1; goto done; } value = new_value; s++; } done: if (endptr) *endptr = s; return value * sign; } #endif /**************************************************************************** * Parse a chunk extension, detect overflow. * There are two error cases: * 1) If the conversion would require too many bits, a -1 is returned. * 2) If the conversion used the correct number of bits, but an overflow * caused only the sign bit to flip, then that negative number is * returned. * In general, any negative number can be considered an overflow error. */ static long get_chunk_size(char *b) { long chunksize = 0; size_t chunkbits = sizeof(long) * 8; /* skip whitespace */ while ((*b == ' ') || (*b == '\t') || (*b == '\n') || (*b == '\r')) { b++; } /* Skip leading zeros */ while (*b == '0') { ++b; } while (isxdigit(*b) && (chunkbits > 0)) { int xvalue = 0; if (*b >= '0' && *b <= '9') { xvalue = *b - '0'; } else if (*b >= 'A' && *b <= 'F') { xvalue = *b - 'A' + 0xa; } else if (*b >= 'a' && *b <= 'f') { xvalue = *b - 'a' + 0xa; } chunksize = (chunksize << 4) | xvalue; chunkbits -= 4; ++b; } if (isxdigit(*b) && (chunkbits <= 0)) { /* overflow */ return -1; } return chunksize; } /**************************************************************************** * Parse http request line. (request line is terminated by CRLF) * * RFC 2616, 19.3 * Clients SHOULD be tolerant in parsing the Status-Line and servers * tolerant when parsing the Request-Line. In particular, they SHOULD * accept any amount of SP or HT characters between fields, even though * only a single SP is required. * */ int parse_http_request_line(char *buffer, size_t len, http_request_t * req) { char *pos, c; int ret = PARSE_ERROR; int i; EnterFunction(5); /* terminate string */ c = buffer[len]; buffer[len] = 0; TCP_VS_DBG(5, "parsing request:\n"); TCP_VS_DBG(5, "--------------------\n"); TCP_VS_DBG(5, "%s\n", buffer); TCP_VS_DBG(5, "--------------------\n"); req->message = buffer; req->message_len = len; /* * RFC 2616, 5.1: * Request-Line = Method SP Request-URI SP HTTP-Version CRLF */ /* try to get method */ pos = skip_lws(buffer); req->method = HTTP_M_UNKNOWN; /* Default :) */ for (i = 1; i < HTTP_M_MAX; i++) { if (strnicmp (pos, http_methods[i].name, http_methods[i].len) == 0) { req->method = i; break; } } if (req->method == HTTP_M_UNKNOWN) { goto exit; } TCP_VS_DBG(6, "HTTP METHOD: %s\n", http_methods[i].name); pos += http_methods[i].len; /* get URI string */ req->uri_str = skip_lws(pos + 1); TCP_VS_DBG(6, "URI: %s\n", req->uri_str); if ((pos = strchr((char *) req->uri_str, SP)) == NULL) { goto exit; } req->uri_len = pos - req->uri_str; /* get http version */ req->version_str = skip_lws(pos + 1); if (strnicmp(req->version_str, http_version_header, HTTP_VERSION_HEADER_LEN) != 0) { goto exit; } req->version = HTTP_V_UNKNOWN; req->version_str += HTTP_VERSION_HEADER_LEN; for (i = HTTP_VERSION_MAX - 1; i >= 0; i--) { if (strncmp(req->version_str, http_version_number[i], HTTP_VERSION_NUMBER_LEN) == 0) { req->version = i + HTTP_V_0_9; break; } } if (req->version == HTTP_V_UNKNOWN) { goto exit; } TCP_VS_DBG(6, "HTTP VERSION: %s\n", http_version_number[i]); ret = PARSE_OK; exit: buffer[len] = c; /* restore string */ LeaveFunction(5); return ret; } /**************************************************************************** * parse_http_status_line - parse the http status line. * * RFC 2616, 19.3 * Clients SHOULD be tolerant in parsing the Status-Line and servers * tolerant when parsing the Request-Line. In particular, they SHOULD * accept any amount of SP or HT characters between fields, even though * only a single SP is required. * */ int parse_http_status_line(char *buffer, size_t len, http_response_t * resp) { char *pos, c; int i, ret = PARSE_ERROR; EnterFunction(5); assert(buffer != NULL); /* terminate string */ c = buffer[len]; buffer[len] = '\0'; TCP_VS_DBG(5, "parsing response:\n"); TCP_VS_DBG(5, "--------------------\n"); TCP_VS_DBG(5, "%s\n", buffer); TCP_VS_DBG(5, "--------------------\n"); /* * RFC 2616, 6.1: * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ pos = skip_lws(buffer); if (strnicmp(pos, http_version_header, HTTP_VERSION_HEADER_LEN) != 0) { return ret; } pos += HTTP_VERSION_HEADER_LEN; resp->version = HTTP_V_UNKNOWN; for (i = HTTP_VERSION_MAX - 1; i >= 0; i--) { if (strnicmp(pos, http_version_number[i], HTTP_VERSION_NUMBER_LEN) == 0) { resp->version = i + HTTP_V_0_9; break; } } pos += HTTP_VERSION_NUMBER_LEN; if (resp->version != HTTP_V_UNKNOWN) { /* get the status code */ resp->status_code = strtol(pos, NULL, 10); assert(resp->status_code >= 100); TCP_VS_DBG(6, "Status Code: %d\n", resp->status_code); ret = PARSE_OK; } buffer[len] = c; /* restore string */ LeaveFunction(5); return ret; } /**************************************************************************** * http_line_unescape - convert escaped characters in buffer to ASCII * * This routine can be used to convert an "escaped" form of a URL or * parameter (appended to a URL) to standard ASCII format. * The escaping is done by the browser on the client side, for * transferring characters not allowed by the HTTP protocol. * For example, a whitespace character is not allowed in an URL. * It must be substituted by an escape sequence to be transferred. * * ESCAPING * When you want to include any character, not part of the standard * set allowed in URLs, you can do this by specifying its hex value * in the format %xx, where xx is the hex representation. * In addition, every '+' character will be substituted by a space. * */ #if 0 static void http_line_unescape(char *string, /* escaped string to unescape */ int len /* length of the string */ ) { int i = 0; char buffer[3]; char c; EnterFunction(5); assert(string != NULL); while (i < len) { if (string[i] == '+') { string[i] = ' '; /* replace '+' by spaces */ } if ((string[i] == '%') && (i < len - 2)) { if (isxdigit(string[i + 1]) && isxdigit(string[i + 2])) { strncpy(buffer, &(string[i + 1]), 2); buffer[2] = 0; c = (char) strtol(buffer, NULL, 16); if (c != 0) { memmove(&(string[i]), /* move string 2 chars */ &(string[i + 2]), 2); string[i] = c; /* replace % by new char */ len -= 2; } } } i++; } LeaveFunction(5); return; } #endif /**************************************************************************** * * register_mime_parser - register a http mime header parser * */ static void register_mime_parser (int parser_id, HTTP_MIME_PARSER mime_parser, char *mime_header_string) { assert(parser_id < MAX_PARSER_ID); assert(mime_parser != NULL); assert(mime_header_string != NULL); strncpy(http_mime_parse_table[parser_id].mime_header_string, mime_header_string, MAX_MIME_HEADER_STRING_LEN); http_mime_parse_table[parser_id].mime_header_string_len = strlen(mime_header_string); http_mime_parse_table[parser_id].parser = mime_parser; http_mime_parse_table[parser_id].enable = 1; } /**************************************************************************** * * http_mime_parse_enable - enable or disable a specified http mime header parser * */ void http_mime_parse_enable(int parser_id, int enable) { http_mime_parse_table[parser_id].enable = enable; } /**************************************************************************** * * transfer_encoding_parser - http mime header parser for "Transfer-Encoding" * */ static void transfer_encoding_parser(http_mime_header_t * mime, char *buffer) { EnterFunction(6); if (strnicmp(buffer, "identity", 8) != 0) { mime->transfer_encoding = 1; TCP_VS_DBG(6, "Transfer-Encoding: chunked\n"); } LeaveFunction(6); return; } /**************************************************************************** * * content_length_parser - http mime header parser for "Content-Length" * */ static void content_length_parser(http_mime_header_t * mime, char *buffer) { EnterFunction(6); mime->content_length = strtol(buffer, NULL, 10); TCP_VS_DBG(6, "Content-Length: %d\n", mime->content_length); LeaveFunction(6); return; } /**************************************************************************** * * connection_parser - http mime header parser for "Connection" * */ static void connection_parser(http_mime_header_t * mime, char *buffer) { EnterFunction(6); if (strnicmp(buffer, "close", 5) == 0) { mime->connection_close = 1; TCP_VS_DBG(5, "Connection: close\n"); } LeaveFunction(6); return; } /**************************************************************************** * * content_type_parser - http mime header parser for "Content-type" * * Note: buffer should be a NULL terminated string. */ static void content_type_parser(http_mime_header_t * mime, char *buffer) { int sep_len; char *pos; EnterFunction(6); if (strnicmp(buffer, "multipart/byteranges", 20) == 0) { TCP_VS_DBG(6, "multipart/byteranges\n"); pos = buffer + 20 + 1; /* skip ';' */ pos = skip_lws(pos + 1); if (strnicmp(pos, "boundary=", 9) != 0) { goto exit; } /* the rest of this line is THIS_STRING_SEPARATES */ pos += 9; sep_len = strlen(pos); if ((mime->sep = kmalloc(sep_len + 1, GFP_KERNEL)) == NULL) { goto exit; } /* RFC 2046 [40] permits the boundary string to be quoted */ if (pos[0] == '"' || pos[0] == '\'') { pos++; sep_len--; } strncpy(mime->sep, pos, sep_len); mime->sep[sep_len] = 0; TCP_VS_DBG(5, "THIS_STRING_SEPARATES : %s\n", mime->sep); } exit: LeaveFunction(6); return; } /**************************************************************************** * * set_cookie_parser - http mime header parser for "Set-Cookie" * * set-cookie = "Set-Cookie:" cookies * cookies = 1#cookie * cookie = NAME "=" VALUE *(";" cookie-av) * NAME = attr * VALUE = value * cookie-av = "Comment" "=" value * | "Domain" "=" value * | "Max-Age" "=" value * | "Path" "=" value * | "Secure" * | "Version" "=" 1*DIGIT* * * Note: the input buffer will be modified indirectly. */ static void set_cookie_parser(http_mime_header_t * mime, char *buffer) { http_cookie_t *ck; char *attribute, *value, *s; int r; EnterFunction(6); TCP_VS_DBG(5, "Set-Cookie:%s", buffer); mime->set_cookie2 = 0; if (mime->cookie == 0) { INIT_LIST_HEAD(&mime->cookie_list); } s = skip_lws(buffer); for (;;) { parse_again: r = extract_av(&s, &attribute, &value); if (r == 1) { TCP_VS_ERR("Error while get Name & Value\n"); goto out; } ck = (http_cookie_t *) kmalloc(sizeof(http_cookie_t), GFP_KERNEL); if (ck == NULL) { goto out; } memset(ck, 0, sizeof(http_cookie_t)); ck->key = (cookie_key_t *) kmalloc(sizeof(cookie_key_t), GFP_KERNEL); if (ck->key == NULL) { kfree(ck); goto out; } memset(ck->key, 0, sizeof(cookie_key_t)); ck->max_age = DEFAULT_MAX_COOKIE_AGE; mime->cookie++; list_add_tail(&ck->c_list, &mime->cookie_list); ck->key->name = strdup(attribute); ck->key->value = strdup(value); if (r == 0) { goto out; } while (1) { r = extract_av(&s, &attribute, &value); if (r == 1) { TCP_VS_ERR("Error while get other AV\n"); goto out; } if (strcmp(attribute, "Max-Age") == 0) { ck->max_age = strtol(value, NULL, 10); } switch (r) { case 0: case 1: goto out; break; case ';': continue; case ',': goto parse_again; } } /* end while (1) */ } /* end for */ out: LeaveFunction(6); return; } /**************************************************************************** * * set_cookie2_parser - http mime header parser for "Set-Cookie2" * * set-cookie = "Set-Cookie2:" cookies * cookies = 1#cookie * cookie = NAME "=" VALUE *(";" set-cookie-av) * NAME = attr * VALUE = value * set-cookie-av = "Comment" "=" value * | "CommentURL" "=" <"> http_URL <"> * | "Discard" * | "Domain" "=" value * | "Max-Age" "=" value * | "Path" "=" value * | "Port" [ "=" <"> portlist <"> ] * | "Secure" * | "Version" "=" 1*DIGIT * portlist = 1#portnum * portnum = 1*DIGIT * * Note: the input buffer will be modified indirectly. */ static void set_cookie2_parser(http_mime_header_t * mime, char *buffer) { http_cookie_t *ck; char *attribute, *value, *s; int r; EnterFunction(6); TCP_VS_DBG(5, "Set-Cookie2:%s", buffer); mime->set_cookie2 = 1; if (mime->cookie == 0) { INIT_LIST_HEAD(&mime->cookie_list); } s = skip_lws(buffer); for (;;) { parse_again: r = extract_av(&s, &attribute, &value); if (r == 1) { TCP_VS_ERR("Error while extract NAME & VALUE\n"); goto out; } ck = (http_cookie_t *) kmalloc(sizeof(http_cookie_t), GFP_KERNEL); if (ck == NULL) { goto out; } memset(ck, 0, sizeof(http_cookie_t)); ck->key = (cookie_key_t *) kmalloc(sizeof(cookie_key_t), GFP_KERNEL); if (ck->key == NULL) { kfree(ck); goto out; } memset(ck->key, 0, sizeof(cookie_key_t)); ck->max_age = DEFAULT_MAX_COOKIE_AGE; mime->cookie++; list_add_tail(&ck->c_list, &mime->cookie_list); ck->key->name = strdup(attribute); ck->key->value = strdup(value); if (r == 0) { goto out; } while (1) { r = extract_av(&s, &attribute, &value); if (r == 1) { TCP_VS_ERR ("Error while extract other AV.\n"); goto out; } if (strcmp(attribute, "Max-Age") == 0) { ck->max_age = strtol(value, NULL, 10); } else if (strcmp(attribute, "Discard") == 0) { ck->discard = 1; } switch (r) { case 0: case 1: goto out; break; case ';': continue; case ',': goto parse_again; } } /* end while (1) */ } /* end for */ out: LeaveFunction(6); return; } /**************************************************************************** * * cookie_parser - http mime header parser for "Cookie" * * cookie = "Cookie:" cookie-version 1*((";" | ",") cookie-value) * cookie-value = NAME "=" VALUE [";" path] [";" domain] [";" port] * cookie-version = "$Version" "=" value * NAME = attr * VALUE = value * path = "$Path" "=" value * domain = "$Domain" "=" value * port = "$Port" [ "=" <"> value <"> ] * * Note: We only have interest in the KTCPVS_SID cookie, other cookie will be * omitted. */ static void cookie_parser(http_mime_header_t * mime, char *buffer) { char *pos, *attribute, *value; int r = 2; EnterFunction(6); TCP_VS_DBG(5, "\nCookie:%s ", buffer); pos = skip_lws(buffer); while (r > 1) { r = extract_av(&pos, &attribute, &value); if (r == 1) { TCP_VS_ERR("Error while parse cookie header.\n"); break; } if (strcmp(attribute, "KTCPVS_SID") == 0) { mime->session_id = strtol(value, NULL, 10); break; } } LeaveFunction(6); return; } void http_mime_parser_init(void) { memset(http_mime_parse_table, 0, sizeof(http_mime_parse_table)); register_mime_parser(TRANSFER_ENCODING, transfer_encoding_parser, "Transfer-Encoding"); register_mime_parser(CONTENT_LENGTH, content_length_parser, "Content-Length"); register_mime_parser(CONNECTION, connection_parser, "Connection"); register_mime_parser(CONTENT_TYPE, content_type_parser, "Content-type"); register_mime_parser(SET_COOKIE, set_cookie_parser, "Set-Cookie"); register_mime_parser(SET_COOKIE2, set_cookie2_parser, "Set-Cookie2"); register_mime_parser(COOKIE, cookie_parser, "Cookie"); } /****************************************************************************** * http_mime_parse - parse MIME line in a buffer * * This routine parses the MIME line in a buffer. * * NOTE: Some MIME headers (host, Referer) need be considered again, tbd. * */ int http_mime_parse(char *buffer, int len, http_mime_header_t * mime) { char *pos, c; int i, l, ret = PARSE_ERROR; EnterFunction(5); assert(buffer != NULL); /* terminate string */ c = buffer[len]; buffer[len] = 0; TCP_VS_DBG(5, "MIME Header: %s\n", buffer); buffer = skip_lws(buffer); if ((pos = strchr(buffer, ':')) == NULL) { return PARSE_ERROR; } l = pos - buffer; pos = skip_lws(pos + 1); for (i = 0; i < MAX_PARSER_ID; i++) { if (http_mime_parse_table[i].enable && (l == http_mime_parse_table[i].mime_header_string_len) && (strnicmp (http_mime_parse_table[i].mime_header_string, buffer, l) == 0)) { http_mime_parse_table[i].parser(mime, pos); break; } } buffer[len] = c; /* restore string */ LeaveFunction(5); return ret; } /**************************************************************************** * relay data between source socket and destination socket */ static int relay_http_data(struct socket *dsock, /* destination socket */ http_read_ctl_block_t * ctl_blk, /* read control block with source socket */ int len /* relay data length */ ) { int nbytes, reads, w = 0; int ret = -1; DECLARE_WAITQUEUE(wait, current); EnterFunction(5); assert(ctl_blk->remaining <= (ctl_blk->len - ctl_blk->offset)); assert(len > 0); /* if there is enough data in read buffer */ nbytes = len - ctl_blk->remaining; if (nbytes <= 0) { if (tcp_vs_xmit(dsock, ctl_blk->buffer + ctl_blk->offset, len, MSG_MORE) < 0) { TCP_VS_ERR("Error in xmitting message body\n"); goto exit; } ctl_blk->offset += len; ctl_blk->remaining -= len; goto done; } /* xmit the remaining bytes */ if (ctl_blk->remaining > 0) { if (tcp_vs_xmit(dsock, ctl_blk->buffer + ctl_blk->offset, ctl_blk->remaining, MSG_MORE) < 0) { TCP_VS_ERR("Error in xmitting remaining bytes\n"); goto exit; } } do { reads = tcp_vs_recvbuffer(ctl_blk->sock, ctl_blk->buffer, ctl_blk->len, ctl_blk->flag); if (reads == 0) { TCP_VS_DBG(5, "Reads 0 bytes while relay\n"); add_wait_queue(ctl_blk->sock->sk->sleep, &wait); __set_current_state(TASK_INTERRUPTIBLE); schedule(); __set_current_state(TASK_RUNNING); remove_wait_queue(ctl_blk->sock->sk->sleep, &wait); continue; } if (reads < 0) { TCP_VS_ERR("Error in reading while relaying\n"); goto exit; } w = MIN(nbytes, reads); if (tcp_vs_xmit(dsock, ctl_blk->buffer, w, MSG_MORE) < 0) { TCP_VS_ERR("Error in relaying bytes\n"); goto exit; } nbytes -= w; } while (nbytes > 0); ctl_blk->offset = w; ctl_blk->remaining = reads - w; assert(ctl_blk->remaining >= 0); assert(ctl_blk->offset < ctl_blk->len); done: ret = 0; exit: LeaveFunction(5); return ret; } /**************************************************************************** * * http_read_line - read a line from socket. * * At first, get the line from the remaining bytes. then read bytes without * move the buffer. finally, move the memory and read a full buffer to get a * line. * Return the len of the line (not including CRLF), or -1 if failed. * * Note: * 1, http_read_line does not terminate the line with '\0', it still end * with CRLF. * 2, If the line length is larger than the size of the buffer, it will * failed. * */ int http_read_line(http_read_ctl_block_t * ctl_blk) { char *buf; int nbytes, i, offset, reads, move; int len = -1; DECLARE_WAITQUEUE(wait, current); EnterFunction(5); assert(ctl_blk->remaining <= (ctl_blk->len - ctl_blk->offset)); ctl_blk->info = NULL; if (ctl_blk->remaining == 0) { ctl_blk->offset = 0; } offset = ctl_blk->offset; buf = ctl_blk->buffer + offset; /* try to get a line from the remaining bytes */ for (i = 0; i < ctl_blk->remaining - 1; i++) { if ((buf[i] == CR) && (buf[i + 1] == LF)) { len = i; goto done; } } move = 0; get_a_line: nbytes = ctl_blk->len - ctl_blk->offset - ctl_blk->remaining; /* try to read a line from the socket */ while ((nbytes > 0) && (len < 0)) { /* go out if the connection is closed */ if (ctl_blk->sock->sk->state != TCP_ESTABLISHED && ctl_blk->sock->sk->state != TCP_CLOSE_WAIT) { if (len > 0) goto done; else goto exit; } assert(ctl_blk->remaining <= (ctl_blk->len - ctl_blk->offset)); reads = tcp_vs_recvbuffer(ctl_blk->sock, ctl_blk->buffer + ctl_blk->offset + ctl_blk->remaining, ctl_blk->len - ctl_blk->offset - ctl_blk->remaining, ctl_blk->flag); if (reads == 0) { TCP_VS_DBG(5, "Read 0 bytes while reading a line\n"); add_wait_queue(ctl_blk->sock->sk->sleep, &wait); __set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ); __set_current_state(TASK_RUNNING); remove_wait_queue(ctl_blk->sock->sk->sleep, &wait); continue; } if (reads < 0) { TCP_VS_ERR("Error in reading a line\n"); goto exit; } ctl_blk->remaining += reads; /* try to get a line from the remaing bytes */ for (; i < ctl_blk->remaining - 1; i++) { if ((buf[i] == CR) && (buf[i + 1] == LF)) { len = i; goto done; } } nbytes -= reads; } /* memmove and read again */ if ((len < 0) && (move == 0)) { memmove(ctl_blk->buffer, buf, ctl_blk->remaining); ctl_blk->offset = 0; buf = ctl_blk->buffer; move = 1; goto get_a_line; } else { assert((len < 0) && (move == 1)); TCP_VS_ERR("Buffer is too small while reading a line.\n"); goto exit; } done: ctl_blk->info = buf; ctl_blk->offset += len + 2; ctl_blk->remaining -= len + 2; assert(ctl_blk->remaining >= 0); assert(ctl_blk->offset < ctl_blk->len); exit: LeaveFunction(5); return len; } /**************************************************************************** * relay_multiparts: relay multipart/byteranges body * * relay all data until "CRLF--THIS_STRING_SEPARATES--CRLF" is found. * * Note: there may be a endless loop if the separate string is not found, tbd. * */ static int relay_multiparts (struct socket *dsock, http_read_ctl_block_t * ctl_blk, http_mime_header_t * mime) { int len, sep_len, l, reads; int ret = -1; char *buf, *pos; char *sep = NULL; DECLARE_WAITQUEUE(wait, current); EnterFunction(5); sep_len = strlen(mime->sep) + 8; if ((sep = kmalloc(sep_len + 1, GFP_KERNEL)) == NULL) { goto exit; } snprintf(sep, sep_len + 1, "\r\n--%s--\r\n", mime->sep); /* deal with the remaining bytes */ buf = ctl_blk->buffer + ctl_blk->offset; len = ctl_blk->remaining; if ((len > 0) && (tcp_vs_xmit(dsock, buf, len, MSG_MORE) < 0)) { TCP_VS_ERR("Error in xmitting multiparts (remaining)\n"); goto exit; } pos = search_sep(buf, len, sep); if (pos != NULL) { goto done; } l = MIN(len, sep_len); memmove(ctl_blk->buffer, buf + len - l, l); /* search for CRLF--THIS_STRING_SEPARATES--CRLF */ while (1) { reads = tcp_vs_recvbuffer(ctl_blk->sock, ctl_blk->buffer + l, ctl_blk->len - l, 0); if (reads == 0) { TCP_VS_DBG(5, "Reads 0 bytes while relaying multiparts\n"); add_wait_queue(ctl_blk->sock->sk->sleep, &wait); __set_current_state(TASK_INTERRUPTIBLE); schedule(); __set_current_state(TASK_RUNNING); remove_wait_queue(ctl_blk->sock->sk->sleep, &wait); continue; } if (reads < 0) { TCP_VS_ERR("Error in receiving multiparts\n"); goto exit; } if (tcp_vs_xmit (dsock, ctl_blk->buffer + l, reads, MSG_MORE) < 0) { TCP_VS_ERR("Error in xmitting multiparts\n"); goto exit; } len = l + reads; pos = search_sep(ctl_blk->buffer, len, sep); if (pos != NULL) { goto done; } l = MIN(len, sep_len); memmove(ctl_blk->buffer, ctl_blk->buffer + len - l, l); } done: ret = 0; exit: if (sep) { kfree(sep); } LeaveFunction(5); return ret; } /**************************************************************************** * transfer http message body. * * When a message-body is included with a message, the transfer-length of that * body is determined by one of the following (in order of precedence): * 1. Any response message which "MUST NOT" include a message-body (such as * the 1xx, 204, and 304 responses and any response to a HEAD request) is always * terminated by the first empty line after the header fields, regardless of the * entity-header fields present in the message. * 2. If a Transfer-Encoding header field (section 14.41) is present and has * any value other than "identity", then the transfer-length is defined by use of * the "chunked" transfer-coding (section 3.6), unless the message is terminated * by closing the connection. * 3. If a Content-Length header field (section 14.13) is present, its decimal * value in OCTETs represents both the entity-length and the transfer-length. The * Content-Length header field MUST NOT be sent if these two lengths are different * (i.e., if a Transfer-Encoding header field is present). If a message is received * with both a Transfer-Encoding header field and a Content-Length header field, * the latter MUST be ignored. * 4. If the message uses the media type "multipart/byteranges", and the transfer- * length is not otherwise specified, then this self-delimiting media type defines * the transfer-length. This media type MUST NOT be used unless the sender knows * that the recipient can arse it; the presence in a request of a Range header with * multiple byte-range specifiers from a 1.1 client implies that the client can parse * multipart/byteranges responses. * A range header might be forwarded by a 1.0 proxy that does not understand multipart/byteranges; in this case the server MUST delimit the message using methods defined in items 1,3 or 5 of this section. * 5. By the server closing the connection. (Closing the connection cannot be * used to indicate the end of a request body, since that would leave no possibility * for the server to send back a response.) * */ int relay_http_message_body(struct socket *dsock, /* destination socket */ http_read_ctl_block_t * ctl_blk, /* read control block */ http_mime_header_t * mime) { int ret = -1; EnterFunction(5); if (mime->transfer_encoding) { /* * 19.4.6 Introduction of Transfer-Encoding * HTTP/1.1 introduces the Transfer-Encoding header field (section * 14.41). Proxies/gateways MUST remove any transfer-coding prior to * forwarding a message via a MIME-compliant protocol. * A process for decoding the "chunked" transfer-coding (section 3.6) can be * represented in pseudo-code as: * length := 0 * read chunk-size, chunk-extension (if any) and CRLF * while (chunk-size > 0) { * read chunk-data and CRLF * append chunk-data to entity-body * length := length + chunk-size * read chunk-size and CRLF * } * read entity-header * while (entity-header not empty) { * append entity-header to existing header fields * read entity-header * } * Content-Length := length * Remove "chunked" from Transfer-Encoding */ int len, chunk_size; do { len = http_read_line(ctl_blk); if (len < 0) { TCP_VS_ERR ("Error in reading chunk size from client\n"); goto exit; } if (tcp_vs_xmit (dsock, ctl_blk->info, len + 2, MSG_MORE) < 0) { TCP_VS_ERR ("Error in xmitting chunk size & extension\n"); goto exit; } ctl_blk->info[len] = 0; chunk_size = get_chunk_size(ctl_blk->info); TCP_VS_DBG(5, "Chunked line: %s\n", ctl_blk->info); if (chunk_size > 0) { if (relay_http_data (dsock, ctl_blk, chunk_size + 2) < 0) { TCP_VS_ERR ("Error in xmitting chunk data\n"); goto exit; } } } while (chunk_size > 0); /* relay the trailer */ do { len = http_read_line(ctl_blk); if (len < 0) { TCP_VS_ERR("Error in reading trailer.\n"); goto exit; } if (tcp_vs_xmit (dsock, ctl_blk->info, len + 2, MSG_MORE) < 0) { TCP_VS_ERR("Error in xmitting trailer\n"); goto exit; } } while (len != 0); ret = 0; } else if (mime->content_length) { ret = relay_http_data(dsock, ctl_blk, mime->content_length); } else if (mime->sep) { ret = relay_multiparts(dsock, ctl_blk, mime); } else { ret = 0; /* ? */ } exit: LeaveFunction(5); return ret; } /**************************************************************************** * Is there any data in socket? * * return: * -1, Socket error * 0, No data can read from socket * 1, Data available */ int data_available(http_read_ctl_block_t * ctl_blk) { int ret; EnterFunction(12); if (ctl_blk->remaining == 0) { /* check if the connection is closed */ if (ctl_blk->sock->sk->state != TCP_ESTABLISHED && ctl_blk->sock->sk->state != TCP_CLOSE_WAIT) { ret = -1; goto out; } /* Do we have data ? */ if (skb_queue_empty(&(ctl_blk->sock->sk->receive_queue))) ret = 0; else ret = 1; goto out; } else ret = 1; out: LeaveFunction(12); return ret; }