|
|
| version 1.94, 2020/10/14 16:51:46 | version 1.129, 2024/11/04 03:53:25 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: http support functions. | Parser: http support functions. |
| Copyright (c) 2001-2017 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com) |
| Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru) | Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru> |
| */ | */ |
| #include "pa_http.h" | #include "pa_http.h" |
| Line 16 | Line 16 |
| volatile const char * IDENT_PA_HTTP_C="$Id$" IDENT_PA_HTTP_H; | volatile const char * IDENT_PA_HTTP_C="$Id$" IDENT_PA_HTTP_H; |
| #ifdef _MSC_VER | |
| #include <windows.h> | |
| #define socklen_t int | |
| #else | |
| #define closesocket close | |
| #endif | |
| // defines | // defines |
| #define HTTP_METHOD_NAME "method" | #define HTTP_METHOD_NAME "method" |
| Line 59 bool HTTP_Headers::add_header(const char | Line 52 bool HTTP_Headers::add_header(const char |
| content_type=header.value; | content_type=header.value; |
| if(header.name == String::Body("CONTENT-LENGTH") && content_length==0) | if(header.name == String::Body("CONTENT-LENGTH") && content_length==0) |
| content_length=pa_atoul(header.value.cstr()); | ALTER_EXCEPTION_COMMENT(content_length=pa_atoul(header.value.cstr()), " for content-length"); |
| headers+=header; | headers+=header; |
| Line 105 public: | Line 98 public: |
| size_t body_offset; | size_t body_offset; |
| HTTP_Headers headers; | HTTP_Headers headers; |
| const String &url; | |
| HTTP_response(const String& aurl) : buf(NULL), length(0), buf_size(0), body_offset(0), url(aurl){} | HTTP_response() : buf(NULL), length(0), buf_size(0), body_offset(0){} |
| void resize(size_t size){ | void resize(size_t size){ |
| buf_size=size; | buf_size=size; |
| buf=(char *)pa_realloc(buf, size + 1); | buf=(char *)pa_realloc(buf, size + 1); |
| } | } |
| bool read(int sock, size_t size){ | bool read(SOCKET sock, size_t size){ |
| if(length+size>buf_size) | if(length + size > buf_size) |
| resize(buf_size*2 + size); | resize(buf_size * 2 + size); |
| ssize_t received_size=recv(sock, buf + length, size, 0); | ssize_t received_size=recv(sock, buf + length, size, 0); |
| if(received_size==0) | if(received_size == 0) |
| return false; | return false; |
| if(received_size<0) { | if(received_size < 0) { |
| if(int no=pa_socks_errno()) | if(int no = pa_socks_errno()) |
| throw Exception("http.timeout", &url, "error receiving response body: %s (%d)", pa_socks_strerr(no), no); | throw Exception("http.timeout", 0, "error receiving response: %s (%d)", pa_socks_strerr(no), no); |
| return false; | return false; |
| } | } |
| length+=received_size; | length+=received_size; |
| Line 153 public: | Line 145 public: |
| return status_line; | return status_line; |
| const char *result_str=pa_strdup(status_start, status_end-status_start); | const char *result_str=pa_strdup(status_start, status_end-status_start); |
| result=pa_atoui(result_str); | ALTER_EXCEPTION_COMMENT(result=pa_atoui(result_str), " for HTTP status"); |
| return result_str; | return result_str; |
| } | } |
| Line 181 public: | Line 173 public: |
| ArrayString aheaders; | ArrayString aheaders; |
| header_block.split(aheaders, 0, "\n"); | header_block.split(aheaders, 0, "\n"); |
| Array_iterator<const String*> i(aheaders); | ArrayString::Iterator i(aheaders); |
| i.next(); // skipping status | i.next(); // skipping status |
| for(;i.has_next();){ | for(;i;){ |
| const char *line=i.next()->cstr(); | const char *line=i.next()->cstr(); |
| if(!headers.add_header(line)) | if(!headers.add_header(line)) |
| throw Exception("http.response", &url, "bad response from host - bad header \"%s\"", line); | throw Exception("http.response", 0, "bad response from host - bad header \"%s\"", line); |
| } | } |
| } | } |
| int read_response(int sock, bool fail_on_status_ne_200); | int read_response(SOCKET sock, bool fail_on_status_ne_200); |
| }; | }; |
| enum HTTP_response_state { | enum HTTP_response_state { |
| Line 199 enum HTTP_response_state { | Line 191 enum HTTP_response_state { |
| HTTP_BODY | HTTP_BODY |
| }; | }; |
| int HTTP_response::read_response(int sock, bool fail_on_status_ne_200) { | int HTTP_response::read_response(SOCKET sock, bool fail_on_status_ne_200) { |
| HTTP_response_state state=HTTP_STATUS_CODE; | HTTP_response_state state=HTTP_STATUS_CODE; |
| int result=0; | int result=0; |
| Line 227 int HTTP_response::read_response(int soc | Line 219 int HTTP_response::read_response(int soc |
| parse_headers(); | parse_headers(); |
| size_t content_length=check_file_size(headers.content_length, url); | size_t content_length=check_file_size(headers.content_length, 0); |
| if(content_length>0 && (content_length + body_offset) > length){ | if(content_length>0 && (content_length + body_offset) > length){ |
| resize(content_length + body_offset + 0x400*64); | resize(content_length + body_offset + 0x400*64); |
| } | } |
| Line 244 int HTTP_response::read_response(int soc | Line 236 int HTTP_response::read_response(int soc |
| } | } |
| if(state==HTTP_STATUS_CODE) | if(state==HTTP_STATUS_CODE) |
| throw Exception("http.response", &url, "bad response from host - no status found (size=%u)", length); | throw Exception("http.response", 0, "bad response from host - no status found (size=%u)", length); |
| if(state==HTTP_HEADERS){ | if(state==HTTP_HEADERS){ |
| parse_headers(); | parse_headers(); |
| Line 263 int HTTP_response::read_response(int soc | Line 255 int HTTP_response::read_response(int soc |
| #ifdef PA_USE_ALARM | #ifdef PA_USE_ALARM |
| static sigjmp_buf timeout_env; | static sigjmp_buf timeout_env; |
| static void timeout_handler(int /*sig*/){ | static void timeout_handler(int /*sig*/){ |
| siglongjmp(timeout_env, 1); | siglongjmp(timeout_env, 1); |
| } | } |
| #define PA_NO_THREADS (HTTPD_Server::mode != HTTPD_Server::MULTITHREADED) | |
| #define ALARM(value) if(PA_NO_THREADS) alarm(value) | |
| #else | |
| #define ALARM(value) | |
| #endif | #endif |
| static int http_request(HTTP_response& response, const char* host, short port, const char* request, size_t request_size, int timeout_secs, bool fail_on_status_ne_200) { | static int http_request(HTTP_response& response, const char* host, short port, const char* request, size_t request_size, int timeout_secs, bool fail_on_status_ne_200) { |
| if(!host) | if(!host) |
| throw Exception("http.host", 0, "zero hostname"); //never | throw Exception("http.host", 0, "zero hostname"); //never |
| volatile // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning] | volatile SOCKET sock=INVALID_SOCKET; // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning] |
| int sock=-1; | |
| #ifdef PA_USE_ALARM | |
| signal(SIGALRM, timeout_handler); | |
| #endif | |
| #ifdef PA_USE_ALARM | #ifdef PA_USE_ALARM |
| if(sigsetjmp(timeout_env, 1)) { | if(PA_NO_THREADS) signal(SIGALRM, timeout_handler); |
| // stupid gcc [2.95.4] generated bad code | if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) { |
| // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code. | // duplicating closesocket to make code more simple for old compilers |
| // rewritten simplier [athough duplicating closesocket code] | if(sock != INVALID_SOCKET) |
| if(sock>=0) | closesocket(sock); |
| closesocket(sock); | throw Exception("http.timeout", 0, "timeout occurred while retrieving document"); |
| throw Exception("http.timeout", 0, "timeout occurred while retrieving document"); | |
| return 0; // never | return 0; // never |
| } else { | } else |
| alarm(timeout_secs); | |
| #endif | #endif |
| { | |
| ALARM(timeout_secs); | |
| try { | try { |
| int result; | int result; |
| struct sockaddr_in dest; | struct sockaddr_in dest; |
| if(!set_addr(&dest, host, port)) | if(!set_addr(&dest, host, port)) |
| throw Exception("http.host", 0, "can not resolve hostname \"%s\"", host); | throw Exception("http.host", 0, "cannot resolve hostname \"%s\"", host); |
| if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/))<0) { | if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/)) == INVALID_SOCKET) { |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| throw Exception("http.connect", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no); | throw Exception("http.connect", 0, "cannot make socket: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| // To enable SO_DONTLINGER (that is, disable SO_LINGER) | // To enable SO_DONTLINGER (that is, disable SO_LINGER) |
| Line 316 static int http_request(HTTP_response& r | Line 311 static int http_request(HTTP_response& r |
| if(connect(sock, (struct sockaddr *)&dest, sizeof(dest))) { | if(connect(sock, (struct sockaddr *)&dest, sizeof(dest))) { |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| throw Exception("http.connect", 0, "can not connect to host \"%s\": %s (%d)", host, pa_socks_strerr(no), no); | throw Exception("http.connect", 0, "cannot connect to host \"%s\": %s (%d)", host, pa_socks_strerr(no), no); |
| } | } |
| if(send(sock, request, request_size, 0)!=(ssize_t)request_size) { | if(send(sock, request, request_size, 0)!=(ssize_t)request_size) { |
| Line 326 static int http_request(HTTP_response& r | Line 321 static int http_request(HTTP_response& r |
| result=response.read_response(sock, fail_on_status_ne_200); | result=response.read_response(sock, fail_on_status_ne_200); |
| closesocket(sock); | closesocket(sock); |
| #ifdef PA_USE_ALARM | ALARM(0); |
| alarm(0); | |
| #endif | |
| return result; | return result; |
| } catch(...) { | } catch(...) { |
| #ifdef PA_USE_ALARM | ALARM(0); |
| alarm(0); | if(sock != INVALID_SOCKET) |
| #endif | |
| if(sock>=0) | |
| closesocket(sock); | closesocket(sock); |
| rethrow; | rethrow; |
| } | } |
| #ifdef PA_USE_ALARM | |
| } | } |
| #endif | |
| } | } |
| #ifndef DOXYGEN | #ifndef DOXYGEN |
| Line 365 char *pa_http_safe_header_name(const cha | Line 354 char *pa_http_safe_header_name(const cha |
| return result; | return result; |
| } | } |
| static void http_pass_header(HashStringValue::key_type aname, | static void http_pass_header(HashStringValue::key_type aname, HashStringValue::value_type avalue, Http_pass_header_info *info) { |
| HashStringValue::value_type avalue, | |
| Http_pass_header_info *info) { | |
| const char* name_cstr=aname.cstr(); | const char* name_cstr=aname.cstr(); |
| Line 524 static void form_value2part(HashStringVa | Line 511 static void form_value2part(HashStringVa |
| Form_table_value2string_info info(key, *part.string); | Form_table_value2string_info info(key, *part.string); |
| part.info = &info; | part.info = &info; |
| tvalue->for_each(form_table_value2part, &part); | tvalue->for_each(form_table_value2part, &part); |
| } else if(VFile* vfile=static_cast<VFile *>(value->as("file"))){ | } else if(VFile* vfile=dynamic_cast<VFile *>(value)){ |
| form_file_value2part(key, *vfile, part); | form_file_value2part(key, *vfile, part); |
| } else | } else |
| throw Exception(PARSER_RUNTIME, new String(key, String::L_TAINTED), "is %s, " HTTP_FORM_NAME " option value can be string, table or file only", value->type()); | throw Exception(PARSER_RUNTIME, new String(key, String::L_TAINTED), "is %s, " HTTP_FORM_NAME " option value can be string, table or file only", value->type()); |
| Line 564 static ArrayString* parse_cookie(Request | Line 551 static ArrayString* parse_cookie(Request |
| if(first_pair) { | if(first_pair) { |
| // name + value | // name + value |
| name=sname; | name=sname; |
| value=smeaning; | if(smeaning) |
| value=smeaning; | |
| first_pair=false; | first_pair=false; |
| } else { | } else { |
| const String& slower=sname->change_case(r.charsets.source(), String::CC_LOWER); | const String& slower=sname->change_case(r.charsets.source(), String::CC_LOWER); |
| Line 607 static ArrayString* parse_cookie(Request | Line 595 static ArrayString* parse_cookie(Request |
| Table* parse_cookies(Request& r, Table *cookies){ | Table* parse_cookies(Request& r, Table *cookies){ |
| Table& result=*new Table(new Cookies_table_template_columns); | Table& result=*new Table(new Cookies_table_template_columns); |
| for(Array_iterator<Table::element_type> i(*cookies); i.has_next(); ) | for(Array_iterator<Table::element_type> i(*cookies); i; ) |
| if(ArrayString* row=parse_cookie(r, *i.next()->get(0))) | if(ArrayString* row=parse_cookie(r, *i.next()->get(0))) |
| result+=row; | result+=row; |
| Line 716 File_read_http_result pa_internal_file_r | Line 704 File_read_http_result pa_internal_file_r |
| if(encode){ | if(encode){ |
| if(method_is_get) | if(method_is_get) |
| throw Exception(PARSER_RUNTIME, 0, "you can not use $." HTTP_FORM_ENCTYPE_NAME " option with method GET"); | throw Exception(PARSER_RUNTIME, 0, "you cannot use $." HTTP_FORM_ENCTYPE_NAME " option with method GET"); |
| multipart=strcasecmp(encode, HTTP_CONTENT_TYPE_MULTIPART_FORMDATA)==0; | multipart=strcasecmp(encode, HTTP_CONTENT_TYPE_MULTIPART_FORMDATA)==0; |
| Line 726 File_read_http_result pa_internal_file_r | Line 714 File_read_http_result pa_internal_file_r |
| if(vbody){ | if(vbody){ |
| if(method_is_get) | if(method_is_get) |
| throw Exception(PARSER_RUNTIME, 0, "you can not use $." HTTP_BODY_NAME " option with method GET"); | throw Exception(PARSER_RUNTIME, 0, "you cannot use $." HTTP_BODY_NAME " option with method GET"); |
| if(form) | if(form) |
| throw Exception(PARSER_RUNTIME, 0, "you can not use options $." HTTP_BODY_NAME " and $." HTTP_FORM_NAME " together"); | throw Exception(PARSER_RUNTIME, 0, "you cannot use options $." HTTP_BODY_NAME " and $." HTTP_FORM_NAME " together"); |
| } | } |
| //preparing request | //preparing request |
| Line 748 File_read_http_result pa_internal_file_r | Line 736 File_read_http_result pa_internal_file_r |
| throw Exception(PARSER_RUNTIME, &connect_string, "does not start with http://"); //never | throw Exception(PARSER_RUNTIME, &connect_string, "does not start with http://"); //never |
| current+=7; | current+=7; |
| strncpy(host, current, sizeof(host)-1); host[sizeof(host)-1]=0; | pa_strncpy(host, current, sizeof(host)); |
| char* host_uri=lsplit(host, '/'); | char* host_uri=lsplit(host, '/'); |
| uri=host_uri?current+(host_uri-1-host):"/"; | uri=host_uri?current+(host_uri-1-host):"/"; |
| char* port_cstr=lsplit(host, ':'); | char* port_cstr=lsplit(host, ':'); |
| Line 844 File_read_http_result pa_internal_file_r | Line 832 File_read_http_result pa_internal_file_r |
| } | } |
| if(request_body) | if(request_body) |
| head << "Content-Length: " << format(post_size, "%u") << CRLF; | head << "Content-Length: " << pa_uitoa(post_size) << CRLF; |
| head << CRLF; | head << CRLF; |
| Line 864 File_read_http_result pa_internal_file_r | Line 852 File_read_http_result pa_internal_file_r |
| } | } |
| HTTP_response response(connect_string); | HTTP_response response; |
| // sending request | // sending request |
| int status_code=http_request(response, idna_host, port, request, request_size, timeout_secs, fail_on_status_ne_200); | int status_code; |
| ALTER_EXCEPTION_SOURCE(status_code=http_request(response, idna_host, port, request, request_size, timeout_secs, fail_on_status_ne_200), &connect_string); | |
| // processing results | // processing results |
| char* raw_body=response.buf + response.body_offset; | char* raw_body=response.buf + response.body_offset; |
| Line 886 File_read_http_result pa_internal_file_r | Line 875 File_read_http_result pa_internal_file_r |
| if (!real_remote_charset) | if (!real_remote_charset) |
| real_remote_charset=asked_remote_charset; // never null | real_remote_charset=asked_remote_charset; // never null |
| for(Array_iterator<HTTP_Headers::Header> i(response.headers.headers); i.has_next(); ){ | for(Array_iterator<HTTP_Headers::Header> i(response.headers.headers); i; ){ |
| HTTP_Headers::Header header=i.next(); | HTTP_Headers::Header header=i.next(); |
| header.transcode(*real_remote_charset, r.charsets.source()); | header.transcode(*real_remote_charset, r.charsets.source()); |
| Line 921 File_read_http_result pa_internal_file_r | Line 910 File_read_http_result pa_internal_file_r |
| /* ********************** httpd *************************** */ | /* ********************** httpd *************************** */ |
| #ifdef HTTPD_DEBUG | |
| void pa_log(const char* fmt, ...); | |
| #define LOG(action) action | |
| #else | |
| #define LOG(action) | |
| #endif | |
| enum EscapeState { | |
| Initial, | |
| Default, | |
| EscapeFirst, | |
| EscapeSecond | |
| }; | |
| static bool check_uri(const char *uri){ | |
| EscapeState state=Initial; | |
| uint escapedValue=0; | |
| const char *pattern="/../"; | |
| const char *pos=pattern; | |
| while(*uri){ | |
| uchar c=(uchar)*(uri++); | |
| switch(state) { | |
| case Initial: | |
| if(c!='/') | |
| return false; | |
| state=Default; | |
| break; | |
| case Default: | |
| if(c=='%'){ | |
| state=EscapeFirst; | |
| continue; | |
| } | |
| if(c=='?') | |
| return true; | |
| break; | |
| case EscapeFirst: | |
| if(isxdigit(c)){ | |
| state=EscapeSecond; | |
| escapedValue=hex_value[c] << 4; | |
| continue; | |
| } | |
| return false; | |
| case EscapeSecond: | |
| if(isxdigit(c)){ | |
| state=Default; | |
| c=(uchar)(escapedValue + hex_value[c]); | |
| // implementing Apache AllowEncodedSlashes Off just in case | |
| if(c=='/' || c=='\\') | |
| return false; | |
| break; | |
| } | |
| return false; | |
| } | |
| if(c==*pos || c=='\\' && *pos=='/'){ | |
| if(!*(++pos)) | |
| return false; | |
| } else { | |
| pos=pattern; | |
| } | |
| } | |
| return true; | |
| } | |
| class HTTPD_request : public HTTP_response { | class HTTPD_request : public HTTP_response { |
| public: | public: |
| const char *method; | const char *method; |
| const char *uri; | const char *uri; |
| HTTPD_request() : HTTP_response(String::Empty), method(NULL), uri(NULL){}; | HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){}; |
| ssize_t pa_recv(SOCKET sockfd, char *buf, size_t len); | |
| bool read(SOCKET sock, size_t size){ | |
| if(length + size > buf_size) | |
| resize(buf_size * 2 + size); | |
| ssize_t received_size=pa_recv(sock, buf + length, size); | |
| if(received_size == 0) | |
| return false; | |
| if(received_size < 0) { | |
| if(int no = pa_socks_errno()) | |
| throw Exception("httpd.read", 0, "error receiving request: %s (%d)", pa_socks_strerr(no), no); | |
| return false; | |
| } | |
| length+=received_size; | |
| buf[length]='\0'; | |
| return true; | |
| } | |
| const char *extract_method(char *method_line){ | const char *extract_method(char *method_line){ |
| char* uri_start = strchr(method_line, ' '); | char* uri_start = strchr(method_line, ' '); |
| Line 940 public: | Line 1015 public: |
| return NULL; | return NULL; |
| uri=pa_strdup(uri_start+1, uri_end-uri_start-1); | uri=pa_strdup(uri_start+1, uri_end-uri_start-1); |
| if(!check_uri(uri)) | |
| throw Exception("httpd.request", 0, "invalid uri '%s'", uri); | |
| return str_upper(method_line, uri_start-method_line); | return str_upper(method_line, uri_start-method_line); |
| } | } |
| void read_header(int); | |
| size_t read_post(int, char *, size_t); | bool read_header(SOCKET); |
| size_t read_post(SOCKET, char *, size_t); | |
| }; | }; |
| enum HTTPD_request_state { | enum HTTPD_request_state { |
| Line 952 enum HTTPD_request_state { | Line 1031 enum HTTPD_request_state { |
| HTTPD_HEADERS | HTTPD_HEADERS |
| }; | }; |
| void HTTPD_request::read_header(int sock) { | ssize_t HTTPD_request::pa_recv(SOCKET sockfd, char *buffer, size_t len){ |
| LOG(pa_log("httpd [%d] recv %d appending to %d ...", sockfd, len, length)); | |
| #ifdef PA_USE_ALARM | |
| if(PA_NO_THREADS) signal(SIGALRM, timeout_handler); | |
| if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) { | |
| LOG(pa_log("httpd [%d] recv got %d sec timeout", sockfd, pa_httpd_timeout)); | |
| if(length) // timeout on "void" connection is normal | |
| throw Exception("httpd.timeout", 0, "timeout occurred while receiving request"); | |
| return 0; | |
| } else | |
| #endif | |
| { | |
| ALARM(pa_httpd_timeout); | |
| ssize_t result=recv(sockfd, buffer, len, 0); | |
| ALARM(0); | |
| LOG(pa_log("httpd [%d] recv got %d bytes", sockfd, result)); | |
| LOG(pa_log("httpd [%d] %s", sockfd, buffer)); | |
| return result; | |
| } | |
| } | |
| static bool valid_http_method(const char * method){ | |
| return method && ( | |
| !strcmp(method, "GET") || | |
| !strcmp(method, "HEAD") || | |
| !strcmp(method, "POST") || | |
| !strcmp(method, "PUT") || | |
| !strcmp(method, "DELETE") || | |
| !strcmp(method, "CONNECT") || | |
| !strcmp(method, "OPTIONS") || | |
| !strcmp(method, "TRACE") || | |
| !strcmp(method, "PATCH") | |
| ); | |
| } | |
| bool HTTPD_request::read_header(SOCKET sock) { | |
| enum HTTPD_request_state state = HTTPD_METHOD; | enum HTTPD_request_state state = HTTPD_METHOD; |
| size_t chunk_size = 0x400*4; | size_t chunk_size = 0x400*4; |
| Line 968 void HTTPD_request::read_header(int sock | Line 1083 void HTTPD_request::read_header(int sock |
| char *method_line = pa_strdup(buf, method_size); | char *method_line = pa_strdup(buf, method_size); |
| method = extract_method(method_line); | method = extract_method(method_line); |
| if(!method || | if(!valid_http_method(method)) |
| strcmp(method, "GET") && | |
| strcmp(method, "HEAD") && | |
| strcmp(method, "POST") && | |
| strcmp(method, "PUT") && | |
| strcmp(method, "DELETE") && | |
| strcmp(method, "PATCH") | |
| ) | |
| throw Exception("httpd.method", new String(method ? method : method_line), "invalid request method"); | throw Exception("httpd.method", new String(method ? method : method_line), "invalid request method"); |
| state = HTTPD_HEADERS; | state = HTTPD_HEADERS; |
| } | } |
| Line 985 void HTTPD_request::read_header(int sock | Line 1093 void HTTPD_request::read_header(int sock |
| break; | break; |
| parse_headers(); | parse_headers(); |
| return; | return true; |
| } | } |
| } | } |
| } | } |
| if(!length){ // browsers open connections in advance and they will be empty unless user requests more pages | |
| LOG(pa_log("httpd [%d] void request", sock)); | |
| return false; | |
| } | |
| if(state == HTTPD_METHOD) | if(state == HTTPD_METHOD) |
| throw Exception("httpd.request", 0, "bad request from host - no method found (size=%u)", length); | throw Exception("httpd.request", 0, "bad request from host - no method found (size=%u)", length); |
| Line 997 void HTTPD_request::read_header(int sock | Line 1110 void HTTPD_request::read_header(int sock |
| parse_headers(); | parse_headers(); |
| body_offset=length; | body_offset=length; |
| } | } |
| return true; | |
| } | } |
| size_t HTTPD_request::read_post(int sock, char *body, size_t max_bytes) { | size_t HTTPD_request::read_post(SOCKET sock, char *body, size_t max_bytes) { |
| size_t total_read = min(length - body_offset, max_bytes); | size_t total_read = min(length - body_offset, max_bytes); |
| memcpy(body, buf, total_read); | memcpy(body, buf + body_offset, total_read); |
| while (total_read < max_bytes){ | while (total_read < max_bytes){ |
| ssize_t received_size = recv(sock, buf + total_read, max_bytes - total_read, 0); | ssize_t received_size = pa_recv(sock, body + total_read, max_bytes - total_read); |
| if(received_size == 0) | if(received_size == 0) |
| return total_read; | return total_read; |
| if(received_size < 0) { | if(received_size < 0) { |
| if(int no = pa_socks_errno()) | if(int no = pa_socks_errno()) |
| throw Exception("httpd.timeout", &url, "error receiving request body: %s (%d)", pa_socks_strerr(no), no); | throw Exception("httpd.read", new String(uri), "error receiving request body: %s (%d)", pa_socks_strerr(no), no); |
| return total_read; | return total_read; |
| } | } |
| total_read += received_size; | total_read += received_size; |
| Line 1039 uint64_t HTTPD_Connection::content_lengt | Line 1154 uint64_t HTTPD_Connection::content_lengt |
| return request->headers.content_length; | return request->headers.content_length; |
| } | } |
| void HTTPD_Connection::read_header(){ | bool HTTPD_Connection::read_header(){ |
| request = new HTTPD_request(); | request = new HTTPD_request(); |
| request->read_header(sock); | bool result = request->read_header(sock); |
| LOG(if(result){ | |
| pa_log("httpd [%d] got %s \"%s\"", sock, method(), uri()); | |
| }) | |
| return result; | |
| } | } |
| size_t HTTPD_Connection::read_post(char *body, size_t max_bytes) { | size_t HTTPD_Connection::read_post(char *body, size_t max_bytes) { |
| Line 1049 size_t HTTPD_Connection::read_post(char | Line 1168 size_t HTTPD_Connection::read_post(char |
| } | } |
| size_t HTTPD_Connection::send_body(const void *buf, size_t size) { | size_t HTTPD_Connection::send_body(const void *buf, size_t size) { |
| LOG(pa_log("httpd [%d] response %d bytes", sock, size)); | |
| LOG(pa_log("httpd [%d] %s", sock, buf)); | |
| if(send(sock, (const char*)buf, size, 0) != (ssize_t)size) { | if(send(sock, (const char*)buf, size, 0) != (ssize_t)size) { |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| throw Exception("httpd.timeout", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no); | throw Exception("httpd.write", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| return size; | return size; |
| } | } |
| HTTPD_Connection::~HTTPD_Connection(){ | HTTPD_Connection::~HTTPD_Connection(){ |
| if(sock != -1) | if(sock != INVALID_SOCKET){ |
| LOG(pa_log("httpd [%d] closed", sock)); | |
| closesocket(sock); | closesocket(sock); |
| } | |
| } | } |
| static int sock_ready(int fd,int operation,int timeout_value){ | static int sock_ready(SOCKET fd, int operation, int timeout_value){ |
| struct timeval timeout = {0, timeout_value * 1000}; | struct timeval timeout = {0, timeout_value * 1000}; |
| fd_set fds; | fd_set fds; |
| FD_ZERO(&fds); | FD_ZERO(&fds); |
| FD_SET(fd, &fds); | FD_SET(fd, &fds); |
| int nfds = (int)fd + 1; /* typecast as nfds is ignored in MSVC anyway */ | |
| switch (operation){ | switch (operation){ |
| case 0: return select(fd + 1, &fds, NULL, NULL, &timeout)>0; /* read */ | case 0: return select(nfds, &fds, NULL, NULL, &timeout)>0; /* read */ |
| case 1: return select(fd + 1, NULL, &fds, NULL, &timeout)>0; /* write */ | case 1: return select(nfds, NULL, &fds, NULL, &timeout)>0; /* write */ |
| default: return select(fd + 1, &fds, &fds, NULL, &timeout)>0; /* both */ | default: return select(nfds, &fds, &fds, NULL, &timeout)>0; /* both */ |
| } | } |
| } | } |
| bool HTTPD_Connection::accept(int server_sock, int timeout_value) { | bool HTTPD_Connection::accept(SOCKET server_sock, int timeout_value) { |
| int ready = sock_ready(server_sock, 0, timeout_value); | int ready = sock_ready(server_sock, 0, timeout_value); |
| if (ready < 0) { | if (ready < 0) { |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| Line 1089 bool HTTPD_Connection::accept(int server | Line 1213 bool HTTPD_Connection::accept(int server |
| memset(&addr, 0, sock_addr_len); | memset(&addr, 0, sock_addr_len); |
| sock = ::accept(server_sock, (struct sockaddr *)&addr, &sock_addr_len); | sock = ::accept(server_sock, (struct sockaddr *)&addr, &sock_addr_len); |
| if(server_sock == -1){ | if(sock == INVALID_SOCKET){ |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no); | throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| // Has no positive performance effect, requires include <netinet/tcp.h> | |
| // static int sock_on=1; | |
| // setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&sock_on, sizeof(sock_on)); | |
| remote_addr = pa_strdup(inet_ntoa(addr.sin_addr)); | remote_addr = pa_strdup(inet_ntoa(addr.sin_addr)); |
| LOG(pa_log("httpd [%d] accepted from %s", sock, remote_addr)); | |
| return true; | return true; |
| } | } |
| int HTTPD_Server::bind(const char *host_port){ | HTTPD_Server::HTTPD_MODE HTTPD_Server::mode = HTTPD_Server::SEQUENTIAL; |
| const char *HTTPD_Server::port=NULL; | |
| void HTTPD_Server::set_mode(const String &value){ | |
| if(value == "sequental") mode = SEQUENTIAL; | |
| #ifdef HAVE_TLS | |
| else if (value == "threaded") mode = MULTITHREADED; | |
| #endif | |
| #ifdef _MSC_VER | |
| else throw Exception("httpd.mode", &value, "$MAIN:HTTPD.mode must be 'sequental' or 'threaded'"); | |
| #else | |
| else if (value == "parallel") mode = PARALLEL; | |
| else throw Exception("httpd.mode", &value, "$MAIN:HTTPD.mode must be 'sequental', 'parallel' or 'threaded'"); | |
| #endif | |
| } | |
| SOCKET HTTPD_Server::bind(const char *host_port){ | |
| struct sockaddr_in me; | struct sockaddr_in me; |
| const char *port = strchr(host_port, ':'); | port = strchr(host_port, ':'); |
| const char *host = NULL; | const char *host = NULL; |
| if(port && port > host_port){ | if(port){ |
| host = pa_strdup(host_port, port - host_port); | if(port > host_port) |
| host = pa_strdup(host_port, port - host_port); | |
| port += 1; | port += 1; |
| } else { | } else { |
| port = host_port; | port = host_port; |
| } | } |
| if(!set_addr(&me, host, pa_atoui(port))){ | if(!set_addr(&me, host, (short)pa_atoui(port))){ |
| if (host) | if (host) |
| throw Exception("httpd.bind", 0, "can not resolve hostname \"%s\"", host); | throw Exception("httpd.bind", 0, "cannot resolve hostname \"%s\"", host); |
| me.sin_addr.s_addr=INADDR_ANY; | me.sin_addr.s_addr=INADDR_ANY; |
| } | } |
| int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/); | SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/); |
| if(sock < 0){ | if(sock == INVALID_SOCKET){ |
| int no=pa_socks_errno(); | int no=pa_socks_errno(); |
| throw Exception("httpd.bind", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no); | throw Exception("httpd.bind", 0, "cannot make socket: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| static int sock_on = 1; | static int sock_on = 1; |
| Line 1131 int HTTPD_Server::bind(const char *host_ | Line 1277 int HTTPD_Server::bind(const char *host_ |
| listen(sock, 16)) { | listen(sock, 16)) { |
| closesocket(sock); | closesocket(sock); |
| int no = pa_socks_errno(); | int no = pa_socks_errno(); |
| throw Exception("httpd.bind", 0, "can not bind socket: %s (%d)", pa_socks_strerr(no), no); | throw Exception("httpd.bind", 0, "cannot bind socket: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| return sock; | return sock; |
| } | } |