|
|
| version 1.83, 2020/09/30 20:01:08 | version 1.97, 2020/10/14 21:35:00 |
|---|---|
| Line 18 volatile const char * IDENT_PA_HTTP_C="$ | Line 18 volatile const char * IDENT_PA_HTTP_C="$ |
| #ifdef _MSC_VER | #ifdef _MSC_VER |
| #include <windows.h> | #include <windows.h> |
| #define socklen_t int | |
| #else | #else |
| #define closesocket close | #define closesocket close |
| #endif | #endif |
| Line 48 volatile const char * IDENT_PA_HTTP_C="$ | Line 49 volatile const char * IDENT_PA_HTTP_C="$ |
| // helpers | // helpers |
| bool ResponseHeaders::add_header(const char *line){ | bool HTTP_Headers::add_header(const char *line){ |
| const char *value=strchr(line, ':'); | const char *value=strchr(line, ':'); |
| if(value && value != line){ // we need only headers, not the response code | if(value && value != line){ // we need only headers, not the response code |
| Line 58 bool ResponseHeaders::add_header(const c | Line 59 bool ResponseHeaders::add_header(const c |
| 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(), 10); | ALTER_EXCEPTION_COMMENT(content_length=pa_atoul(header.value.cstr()), " for content-length"); |
| headers+=header; | headers+=header; |
| Line 96 static bool set_addr(struct sockaddr_in | Line 97 static bool set_addr(struct sockaddr_in |
| return false; | return false; |
| } | } |
| class HTTP_response { | class HTTP_response : public PA_Allocated { |
| public: | public: |
| char *buf; | char *buf; |
| size_t length; | size_t length; |
| size_t buf_size; | size_t buf_size; |
| size_t body_offset; | size_t body_offset; |
| ResponseHeaders headers; | HTTP_Headers headers; |
| String &url; | |
| HTTP_response(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; |
| Line 121 public: | Line 121 public: |
| 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 body: %s (%d)", pa_socks_strerr(no), no); |
| return false; | return false; |
| } | } |
| length+=received_size; | length+=received_size; |
| Line 130 public: | Line 130 public: |
| } | } |
| size_t first_line(){ | size_t first_line(){ |
| char *headers=strchr(buf, '\n'); | char *header=strchr(buf, '\n'); |
| if(!headers) | if(!header) |
| return false; | return false; |
| return headers-buf; | return header-buf; |
| } | } |
| const char *status_code(char *status_line, int &result){ | const char *status_code(char *status_line, int &result){ |
| Line 152 public: | Line 152 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=atoi(result_str); | ALTER_EXCEPTION_COMMENT(result=pa_atoui(result_str), " for HTTP status"); |
| return result_str; | return result_str; |
| } | } |
| Line 185 public: | Line 185 public: |
| for(;i.has_next();){ | for(;i.has_next();){ |
| 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); | |
| }; | }; |
| enum HTTP_response_state { | enum HTTP_response_state { |
| Line 197 enum HTTP_response_state { | Line 198 enum HTTP_response_state { |
| HTTP_BODY | HTTP_BODY |
| }; | }; |
| static int http_read_response(HTTP_response& response, int sock, bool fail_on_status_ne_200) { | int HTTP_response::read_response(int 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; |
| size_t chunk_size=0x400*16; | size_t chunk_size=0x400*16; |
| response.resize(2*chunk_size); | resize(2*chunk_size); |
| while(response.read(sock, chunk_size)){ | while(read(sock, chunk_size)){ |
| switch(state){ | switch(state){ |
| case HTTP_STATUS_CODE: { | case HTTP_STATUS_CODE: { |
| size_t status_size=response.first_line(); | size_t status_size=first_line(); |
| if(!status_size) | if(!status_size) |
| break; | break; |
| const char *status=response.status_code(pa_strdup(response.buf, status_size), result); | const char *status=status_code(pa_strdup(buf, status_size), result); |
| if(!result || fail_on_status_ne_200 && result!=200) | if(!result || fail_on_status_ne_200 && result!=200) |
| throw Exception("http.status", status ? new String(status) : &String::Empty, "invalid HTTP response status"); | throw Exception("http.status", status ? new String(status) : &String::Empty, "invalid HTTP response status"); |
| Line 220 static int http_read_response(HTTP_respo | Line 221 static int http_read_response(HTTP_respo |
| } | } |
| case HTTP_HEADERS: { | case HTTP_HEADERS: { |
| if(!response.body_start()) | if(!body_start()) |
| break; | break; |
| response.parse_headers(); | parse_headers(); |
| size_t content_length=check_file_size(response.headers.content_length, response.url); | size_t content_length=check_file_size(headers.content_length, 0); |
| if(content_length>0 && (content_length + response.body_offset) > response.length){ | if(content_length>0 && (content_length + body_offset) > length){ |
| response.resize(content_length + response.body_offset + 0x400*64); | resize(content_length + body_offset + 0x400*64); |
| } | } |
| state=HTTP_BODY; | state=HTTP_BODY; |
| Line 242 static int http_read_response(HTTP_respo | Line 243 static int http_read_response(HTTP_respo |
| } | } |
| if(state==HTTP_STATUS_CODE) | if(state==HTTP_STATUS_CODE) |
| throw Exception("http.response", &response.url, "bad response from host - no status found (size=%u)", response.length); | throw Exception("http.response", 0, "bad response from host - no status found (size=%u)", length); |
| if(state==HTTP_HEADERS){ | if(state==HTTP_HEADERS){ |
| response.parse_headers(); | parse_headers(); |
| response.body_offset=response.length; | body_offset=length; |
| } | } |
| return result; | return result; |
| Line 322 static int http_request(HTTP_response& r | Line 323 static int http_request(HTTP_response& r |
| throw Exception("http.timeout", 0, "error sending request: %s (%d)", pa_socks_strerr(no), no); | throw Exception("http.timeout", 0, "error sending request: %s (%d)", pa_socks_strerr(no), no); |
| } | } |
| result=http_read_response(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 | #ifdef PA_USE_ALARM |
| alarm(0); | alarm(0); |
| Line 862 File_read_http_result pa_internal_file_r | Line 863 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 884 File_read_http_result pa_internal_file_r | Line 886 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<ResponseHeaders::Header> i(response.headers.headers); i.has_next(); ){ | for(Array_iterator<HTTP_Headers::Header> i(response.headers.headers); i.has_next(); ){ |
| ResponseHeaders::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 896 File_read_http_result pa_internal_file_r | Line 898 File_read_http_result pa_internal_file_r |
| } | } |
| // filling $.cookies | // filling $.cookies |
| if(Value *vcookies=vtables->hash().get("SET-COOKIE")) | if(vcookies=vtables->hash().get("SET-COOKIE")) |
| result.headers->put(HTTP_COOKIES_NAME, new VTable(parse_cookies(r, vcookies->get_table()))); | result.headers->put(HTTP_COOKIES_NAME, new VTable(parse_cookies(r, vcookies->get_table()))); |
| // output response | // output response |
| Line 916 File_read_http_result pa_internal_file_r | Line 918 File_read_http_result pa_internal_file_r |
| return result; | return result; |
| } | } |
| /* ********************** httpd *************************** */ | |
| class HTTPD_request : public HTTP_response { | |
| public: | |
| const char *method; | |
| const char *uri; | |
| HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){}; | |
| const char *extract_method(char *method_line){ | |
| char* uri_start = strchr(method_line, ' '); | |
| if(!uri_start || uri_start == method_line) | |
| return NULL; | |
| char* uri_end=strchr(uri_start+1, ' '); | |
| if(!uri_end || uri_end == uri_start+1) | |
| return NULL; | |
| uri=pa_strdup(uri_start+1, uri_end-uri_start-1); | |
| return str_upper(method_line, uri_start-method_line); | |
| } | |
| void read_header(int); | |
| size_t read_post(int, char *, size_t); | |
| }; | |
| enum HTTPD_request_state { | |
| HTTPD_METHOD, | |
| HTTPD_HEADERS | |
| }; | |
| void HTTPD_request::read_header(int sock) { | |
| enum HTTPD_request_state state = HTTPD_METHOD; | |
| size_t chunk_size = 0x400*4; | |
| resize(chunk_size); | |
| while(read(sock, chunk_size)){ | |
| switch(state){ | |
| case HTTPD_METHOD: { | |
| size_t method_size = first_line(); | |
| if(!method_size) | |
| break; | |
| char *method_line = pa_strdup(buf, method_size); | |
| method = extract_method(method_line); | |
| if(!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"); | |
| state = HTTPD_HEADERS; | |
| } | |
| case HTTPD_HEADERS: { | |
| if(!body_start()) | |
| break; | |
| parse_headers(); | |
| return; | |
| } | |
| } | |
| } | |
| if(state == HTTPD_METHOD) | |
| throw Exception("httpd.request", 0, "bad request from host - no method found (size=%u)", length); | |
| if(state == HTTPD_HEADERS){ | |
| parse_headers(); | |
| body_offset=length; | |
| } | |
| } | |
| size_t HTTPD_request::read_post(int sock, char *body, size_t max_bytes) { | |
| size_t total_read = min(length - body_offset, max_bytes); | |
| memcpy(body, buf, total_read); | |
| while (total_read < max_bytes){ | |
| ssize_t received_size = recv(sock, buf + total_read, max_bytes - total_read, 0); | |
| if(received_size == 0) | |
| return total_read; | |
| if(received_size < 0) { | |
| if(int no = pa_socks_errno()) | |
| throw Exception("httpd.timeout", new String(uri), "error receiving request body: %s (%d)", pa_socks_strerr(no), no); | |
| return total_read; | |
| } | |
| total_read += received_size; | |
| } | |
| return total_read; | |
| } | |
| /* ********************************************************** */ | |
| Array<HTTP_Headers::Header> &HTTPD_Connection::headers() { | |
| return request->headers.headers; | |
| } | |
| const char *HTTPD_Connection::method() { | |
| return request->method; | |
| } | |
| const char *HTTPD_Connection::uri() { | |
| return request->uri; | |
| } | |
| const char *HTTPD_Connection::content_type() { | |
| return request->headers.content_type.cstr(); | |
| } | |
| uint64_t HTTPD_Connection::content_length(){ | |
| return request->headers.content_length; | |
| } | |
| void HTTPD_Connection::read_header(){ | |
| request = new HTTPD_request(); | |
| request->read_header(sock); | |
| } | |
| size_t HTTPD_Connection::read_post(char *body, size_t max_bytes) { | |
| return request->read_post(sock, body, max_bytes); | |
| } | |
| size_t HTTPD_Connection::send_body(const void *buf, size_t size) { | |
| if(send(sock, (const char*)buf, size, 0) != (ssize_t)size) { | |
| int no=pa_socks_errno(); | |
| throw Exception("httpd.timeout", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no); | |
| } | |
| return size; | |
| } | |
| HTTPD_Connection::~HTTPD_Connection(){ | |
| if(sock != -1) | |
| closesocket(sock); | |
| } | |
| static int sock_ready(int fd,int operation,int timeout_value){ | |
| struct timeval timeout = {0, timeout_value * 1000}; | |
| fd_set fds; | |
| FD_ZERO(&fds); | |
| FD_SET(fd, &fds); | |
| switch (operation){ | |
| case 0: return select(fd + 1, &fds, NULL, NULL, &timeout)>0; /* read */ | |
| case 1: return select(fd + 1, NULL, &fds, NULL, &timeout)>0; /* write */ | |
| default: return select(fd + 1, &fds, &fds, NULL, &timeout)>0; /* both */ | |
| } | |
| } | |
| bool HTTPD_Connection::accept(int server_sock, int timeout_value) { | |
| int ready = sock_ready(server_sock, 0, timeout_value); | |
| if (ready < 0) { | |
| int no=pa_socks_errno(); | |
| if(no == EINTR) | |
| return false; | |
| throw Exception("httpd.accept", 0, "error waiting for connection: %s (%d)", pa_socks_strerr(no), no); | |
| } | |
| if (ready == 0) | |
| return false; /* Timeout */ | |
| struct sockaddr_in addr; | |
| socklen_t sock_addr_len = sizeof(struct sockaddr_in); | |
| memset(&addr, 0, sock_addr_len); | |
| sock = ::accept(server_sock, (struct sockaddr *)&addr, &sock_addr_len); | |
| if(server_sock == -1){ | |
| int no=pa_socks_errno(); | |
| throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no); | |
| } | |
| remote_addr = pa_strdup(inet_ntoa(addr.sin_addr)); | |
| return true; | |
| } | |
| int HTTPD_Server::bind(const char *host_port){ | |
| struct sockaddr_in me; | |
| const char *port = strchr(host_port, ':'); | |
| const char *host = NULL; | |
| if(port && port > host_port){ | |
| host = pa_strdup(host_port, port - host_port); | |
| port += 1; | |
| } else { | |
| port = host_port; | |
| } | |
| if(!set_addr(&me, host, pa_atoui(port))){ | |
| if (host) | |
| throw Exception("httpd.bind", 0, "can not resolve hostname \"%s\"", host); | |
| me.sin_addr.s_addr=INADDR_ANY; | |
| } | |
| int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/); | |
| if(sock < 0){ | |
| int no=pa_socks_errno(); | |
| throw Exception("httpd.bind", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no); | |
| } | |
| static int sock_on = 1; | |
| if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_on, sizeof(sock_on)) || | |
| setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&sock_on, sizeof(sock_on)) || | |
| ::bind(sock, (struct sockaddr*)&me, sizeof(me)) || | |
| listen(sock, 16)) { | |
| closesocket(sock); | |
| int no = pa_socks_errno(); | |
| throw Exception("httpd.bind", 0, "can not bind socket: %s (%d)", pa_socks_strerr(no), no); | |
| } | |
| return sock; | |
| } |