|
|
| version 1.112, 2020/12/17 11:50:44 | version 1.121, 2023/09/26 20:49:10 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: http support functions. | Parser: http support functions. |
| Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2023 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 113 public: | Line 106 public: |
| 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); |
| Line 189 public: | Line 182 public: |
| } | } |
| } | } |
| 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 198 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 264 static sigjmp_buf timeout_env; | Line 257 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 ALARM(value) alarm(value) | |
| #define PA_NO_THREADS (HTTPD_Server::mode != HTTPD_Server::MULTITHREADED) | |
| #define ALARM(value) if(PA_NO_THREADS) alarm(value) | |
| #else | #else |
| #define ALARM(value) | #define ALARM(value) |
| #endif | #endif |
| Line 273 static int http_request(HTTP_response& r | Line 269 static int http_request(HTTP_response& r |
| if(!host) | if(!host) |
| throw Exception("http.host", 0, "zero hostname"); //never | throw Exception("http.host", 0, "zero hostname"); //never |
| volatile int sock=-1; // 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] |
| #ifdef PA_USE_ALARM | #ifdef PA_USE_ALARM |
| signal(SIGALRM, timeout_handler); | if(PA_NO_THREADS) signal(SIGALRM, timeout_handler); |
| if(sigsetjmp(timeout_env, 1)) { | if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) { |
| // duplicating closesocket to make code more simple for old compilers | // duplicating closesocket to make code more simple for old compilers |
| if(sock>=0) | if(sock != INVALID_SOCKET) |
| 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 |
| Line 294 static int http_request(HTTP_response& r | Line 290 static int http_request(HTTP_response& r |
| 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, "can not 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, "can not make socket: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| Line 329 static int http_request(HTTP_response& r | Line 325 static int http_request(HTTP_response& r |
| return result; | return result; |
| } catch(...) { | } catch(...) { |
| ALARM(0); | ALARM(0); |
| if(sock>=0) | if(sock != INVALID_SOCKET) |
| closesocket(sock); | closesocket(sock); |
| rethrow; | rethrow; |
| } | } |
| Line 739 File_read_http_result pa_internal_file_r | Line 735 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 929 enum EscapeState { | Line 925 enum EscapeState { |
| static bool check_uri(const char *uri){ | static bool check_uri(const char *uri){ |
| EscapeState state=Initial; | EscapeState state=Initial; |
| uint escapedValue; | uint escapedValue=0; |
| const char *pattern="/../"; | const char *pattern="/../"; |
| const char *pos=pattern; | const char *pos=pattern; |
| Line 988 public: | Line 984 public: |
| HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){}; | HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){}; |
| ssize_t pa_recv(int sockfd, char *buf, size_t len); | ssize_t pa_recv(SOCKET sockfd, char *buf, size_t len); |
| 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=pa_recv(sock, buf + length, size); | ssize_t received_size=pa_recv(sock, buf + length, size); |
| Line 1025 public: | Line 1021 public: |
| } | } |
| bool read_header(int); | bool read_header(SOCKET); |
| size_t read_post(int, char *, size_t); | size_t read_post(SOCKET, char *, size_t); |
| }; | }; |
| enum HTTPD_request_state { | enum HTTPD_request_state { |
| Line 1034 enum HTTPD_request_state { | Line 1030 enum HTTPD_request_state { |
| HTTPD_HEADERS | HTTPD_HEADERS |
| }; | }; |
| ssize_t HTTPD_request::pa_recv(int sockfd, char *buffer, size_t len){ | 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)); | LOG(pa_log("httpd [%d] recv %d appending to %d ...", sockfd, len, length)); |
| if(HTTPD_Server::mode == HTTPD_Server::MULTITHREADED){ | |
| ssize_t result=recv(sockfd, buffer, len, 0); | |
| LOG(pa_log("httpd [%d] recv got %d bytes", sockfd, result)); | |
| return result; | |
| } | |
| #ifdef PA_USE_ALARM | #ifdef PA_USE_ALARM |
| signal(SIGALRM, timeout_handler); | if(PA_NO_THREADS) signal(SIGALRM, timeout_handler); |
| if(sigsetjmp(timeout_env, 1)) { | if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) { |
| LOG(pa_log("httpd [%d] recv got %d sec timeout", sockfd, pa_httpd_timeout)); | LOG(pa_log("httpd [%d] recv got %d sec timeout", sockfd, pa_httpd_timeout)); |
| if(length) // timeout on "void" connection is normal | if(length) // timeout on "void" connection is normal |
| throw Exception("httpd.timeout", 0, "timeout occurred while receiving request"); | throw Exception("httpd.timeout", 0, "timeout occurred while receiving request"); |
| Line 1061 ssize_t HTTPD_request::pa_recv(int sockf | Line 1052 ssize_t HTTPD_request::pa_recv(int sockf |
| } | } |
| } | } |
| bool HTTPD_request::read_header(int sock) { | 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 1077 bool HTTPD_request::read_header(int sock | Line 1082 bool 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 1115 bool HTTPD_request::read_header(int sock | Line 1113 bool HTTPD_request::read_header(int sock |
| return true; | 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 + body_offset, total_read); | memcpy(body, buf + body_offset, total_read); |
| Line 1179 size_t HTTPD_Connection::send_body(const | Line 1177 size_t HTTPD_Connection::send_body(const |
| } | } |
| HTTPD_Connection::~HTTPD_Connection(){ | HTTPD_Connection::~HTTPD_Connection(){ |
| if(sock != -1){ | if(sock != INVALID_SOCKET){ |
| LOG(pa_log("httpd [%d] closed", sock)); | 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 1213 bool HTTPD_Connection::accept(int server | Line 1212 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); |
| } | } |
| Line 1224 bool HTTPD_Connection::accept(int server | Line 1223 bool HTTPD_Connection::accept(int server |
| } | } |
| HTTPD_Server::HTTPD_MODE HTTPD_Server::mode = HTTPD_Server::SEQUENTIAL; | HTTPD_Server::HTTPD_MODE HTTPD_Server::mode = HTTPD_Server::SEQUENTIAL; |
| const char *HTTPD_Server::port=NULL; | |
| void HTTPD_Server::set_mode(const String &value){ | void HTTPD_Server::set_mode(const String &value){ |
| if(value == "sequental") mode = SEQUENTIAL; | if(value == "sequental") mode = SEQUENTIAL; |
| #ifdef HAVE_TLS | |
| else if (value == "threaded") mode = MULTITHREADED; | else if (value == "threaded") mode = MULTITHREADED; |
| #endif | |
| #ifdef _MSC_VER | #ifdef _MSC_VER |
| else throw Exception("httpd.mode", &value, "$main:HTTPD.mode must be 'sequental' or 'threaded'"); | else throw Exception("httpd.mode", &value, "$MAIN:HTTPD.mode must be 'sequental' or 'threaded'"); |
| #else | #else |
| else if (value == "parallel") mode = PARALLEL; | else if (value == "parallel") mode = PARALLEL; |
| else throw Exception("httpd.mode", &value, "$main:HTTPD.mode must be 'sequental', 'parallel' or 'threaded'"); | else throw Exception("httpd.mode", &value, "$MAIN:HTTPD.mode must be 'sequental', 'parallel' or 'threaded'"); |
| #endif | #endif |
| } | } |
| int HTTPD_Server::bind(const char *host_port){ | 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; |
| Line 1254 int HTTPD_Server::bind(const char *host_ | Line 1257 int HTTPD_Server::bind(const char *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, "can not make socket: %s (%d)", pa_socks_strerr(no), no); |
| } | } |