--- parser3/src/main/pa_common.C 2004/08/30 09:36:41 1.193 +++ parser3/src/main/pa_common.C 2005/11/18 09:11:21 1.204.6.3 @@ -1,11 +1,11 @@ /** @file Parser: commonly functions. - Copyright(c) 2001-2004 ArtLebedev Group (http://www.artlebedev.com) + Copyright(c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char * const IDENT_COMMON_C="$Date: 2004/08/30 09:36:41 $"; +static const char * const IDENT_COMMON_C="$Date: 2005/11/18 09:11:21 $"; #include "pa_common.h" #include "pa_exception.h" @@ -21,6 +21,7 @@ static const char * const IDENT_COMMON_C #include "pa_vint.h" #include "pa_vhash.h" #include "pa_vtable.h" +#include "pa_socks.h" #ifdef CYGWIN #define _GNU_H_WINDOWS32_SOCKETS @@ -130,6 +131,21 @@ char* file_read_text(Request_charsets& c return file.success?file.str:0; } +/// these options were handled but not checked elsewhere, now check them +static int get_valid_file_options_count(HashStringValue& options) +{ + int result=0; + if(options.get(PA_SQL_LIMIT_NAME)) + result++; + if(options.get(PA_SQL_OFFSET_NAME)) + result++; + if(options.get(PA_COLUMN_SEPARATOR_NAME)) + result++; + if(options.get(PA_COLUMN_ENCLOSER_NAME)) + result++; + return result; +} + //http request stuff #ifdef PA_HTTP @@ -156,21 +172,96 @@ static bool set_addr(struct sockaddr_in return true; } -static int http_read_response(char*& response, size_t& response_size, int sock, bool fail_on_status_ne_200){ - response=(char*)pa_malloc_atomic(1/*terminator*/); // setting memory block type - response[(response_size=0)]=0; +size_t guess_content_length(char* buf) { + char* ptr; + if((ptr=strstr(buf, "Content-Length:"))) // Apache + goto found; + if((ptr=strstr(buf, "content-length:"))) // Parser 3 + goto found; + if((ptr=strstr(buf, "Content-length:"))) // maybe 1 + goto found; + if((ptr=strstr(buf, "CONTENT-LENGTH:"))) // maybe 2 + goto found; + return 0; +found: + char *error_pos; + size_t result=(size_t)strtol(ptr+15/*strlen("CONTENT-LENGTH:")*/, &error_pos, 0); + + const size_t reasonable_initial_max=0x400*0x400*10 /*10M*/; + if(result>reasonable_initial_max) // sanity check + return reasonable_initial_max; + return 0;//result; +} + +static int http_read_response(char*& response, size_t& response_size, int sock, bool fail_on_status_ne_200) { int result=0; - char* EOLat=0; + // fetching some to local buffer, guessing on possible content-length + response_size=0x400*20; // initial size if content-length could not be determined + const size_t preview_size=0x400*20; + char preview_buf[preview_size+1/*terminator*/]; // 20K buffer to preview headers + ssize_t received_size=recv(sock, preview_buf, preview_size, 0); + if(received_size==0) + goto done; + if(received_size<0) { + if(int no=pa_socks_errno()) + throw Exception("http.timeout", + 0, + "error receiving response header: %s (%d)", pa_socks_strerr(no), no); + goto done; + } + // detecting response_size + { + preview_buf[received_size]=0; // terminator + if(size_t content_length=guess_content_length(preview_buf)) + response_size=preview_size+content_length; // a little more than needed, will adjust response_size by actual received size later + } + + // allocating initial buf + response=(char*)pa_malloc_atomic(response_size+1/*terminator*/); // just setting memory block type + char* ptr=response; + size_t todo_size=response_size; + // coping part of already received body + memcpy(ptr, preview_buf, received_size); + ptr+=received_size; + todo_size-=received_size; + + // we use terminator byte for two purposes here: + // 1. we return there zero always, not knowing: maybe they would want to create String form $file.body? + // invariant: all Strings should have zero-terminated buffers + // 2. we use that out-of-size byte to detect if our content-length guess was wrong + // when recv gets more than we expected + // a) we know that the content-length guess was wrong + // b) we have space to put the first byte of extra data + // c) we use less code to detect normal situation: on last while-cycle recv expected to just return 0 while(true) { - char buf[MAX_STRING*10]; - ssize_t received_size=recv(sock, buf, sizeof(buf), 0); - if(received_size<=0) + received_size=recv(sock, ptr, todo_size+1/*there is always a place for terminator*/, 0); + if(received_size==0) { + response_size-=todo_size; // in case we received less than expected, cut down the reported size break; - response=(char*)pa_realloc(response, response_size+received_size+1/*terminator*/); - memcpy(response+response_size, buf, received_size); - response_size+=received_size; - response[response_size]=0; + } + if(received_size<0) { + if(int no=pa_socks_errno()) + throw Exception("http.timeout", + 0, + "error receiving response body: %s (%d)", pa_socks_strerr(no), no); + break; + } + // they've touched the terminator? + if((size_t)received_size>todo_size) + { + // that means that our guessed response_size was not big enough + const size_t grow_chunk_size=0x400*0x400; // 1M + response_size+=grow_chunk_size; + size_t ptr_offset=ptr-response; + response=(char*)pa_realloc(response, response_size+1/*terminator*/); + ptr=response+ptr_offset; + todo_size+=grow_chunk_size; + } + // can't do this before realloc: we need =0) closesocket(sock); throw Exception("http.timeout", - origin_string, + 0, "timeout occured while retrieving document"); return 0; // never } else { - alarm(timeout); + alarm(timeout_secs); #endif try { int result; @@ -247,19 +339,40 @@ static int http_request(char*& response, 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*/))<0) { + int no=pa_socks_errno(); throw Exception("http.connect", 0, - "can not make socket: %s (%d)", strerror(errno), errno); - if(connect(sock, (struct sockaddr *)&dest, sizeof(dest))) + "can not make socket: %s (%d)", pa_socks_strerr(no), no); + } + + // To enable SO_DONTLINGER (that is, disable SO_LINGER) + // l_onoff should be set to zero and setsockopt should be called + linger dont_linger={0,0}; + setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char *)&dont_linger, sizeof(dont_linger)); + +#ifdef WIN32 +// SO_*TIMEO can be defined in .h but not implemlemented in protocol, +// failing subsequently with Option not supported by protocol (99) message +// could not suppress that, so leaving this only for win32 + int timeout_ms=timeout_secs*1000; + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms)); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms)); +#endif + + if(connect(sock, (struct sockaddr *)&dest, sizeof(dest))) { + int no=pa_socks_errno(); throw Exception("http.connect", 0, - "can not connect to host \"%s\": %s (%d)", host, strerror(errno), errno); + "can not connect to host \"%s\": %s (%d)", host, pa_socks_strerr(no), no); + } size_t request_size=strlen(request); - if(send(sock, request, request_size, 0)!=(ssize_t)request_size) - throw Exception("http.connect", + if(send(sock, request, request_size, 0)!=(ssize_t)request_size) { + int no=pa_socks_errno(); + throw Exception("http.timeout", 0, - "error sending request: %s (%d)", strerror(errno), errno); + "error sending request: %s (%d)", pa_socks_strerr(no), no); + } result=http_read_response(response, response_size, sock, fail_on_status_ne_200); closesocket(sock); @@ -393,6 +506,25 @@ struct File_read_http_result { HashStringValue* headers; }; #endif +static void find_headers_end(char* p, + char*& headers_end_at, + char*& raw_body) +{ + raw_body=p; + // \n\n + // \r\n\r\n + while((p=strchr(p, '\n'))) { + headers_end_at=++p; // \n>.< + if(*p=='\r') // \r\n>\r?<\n + p++; + if(*p=='\n') { // \r\n\r>\n?< + raw_body=p+1; + return; + } + } + headers_end_at=0; +} + /// @todo build .cookies field. use ^file.tables.SET-COOKIES.menu{ for now static File_read_http_result file_read_http(Request_charsets& charsets, const String& file_spec, @@ -405,7 +537,7 @@ static File_read_http_result file_read_h const char* method="GET"; bool method_is_get; HashStringValue* form=0; const char* body_cstr=0; - int timeout=2; + int timeout_secs=2; bool fail_on_status_ne_200=true; Value* vheaders=0; Charset *asked_remote_charset=0; @@ -413,7 +545,8 @@ static File_read_http_result file_read_h const char* password_cstr=0; if(options) { - int valid_options=0; + int valid_options=get_valid_file_options_count(*options); + if(Value* vmethod=options->get(HTTP_METHOD_NAME)) { valid_options++; method=vmethod->as_string().cstr(); @@ -428,7 +561,7 @@ static File_read_http_result file_read_h } if(Value* vtimeout=options->get(HTTP_TIMEOUT_NAME)) { valid_options++; - timeout=vtimeout->as_int(); + timeout_secs=vtimeout->as_int(); } if((vheaders=options->get(HTTP_HEADERS_NAME))) { valid_options++; @@ -560,83 +693,71 @@ static File_read_http_result file_read_h size_t response_size; int status_code=http_request(response, response_size, host, port, request_head_and_body.cstr(), - timeout, fail_on_status_ne_200); + timeout_secs, fail_on_status_ne_200); //processing results char* raw_body; size_t raw_body_size; - char* headers_end_at=strstr(response, "\n\n" /*change '2' below along!*/); - if(headers_end_at) - raw_body=headers_end_at+2; - else { - headers_end_at=strstr(response, CRLF CRLF /*change '4' below along!*/); - if(headers_end_at) - raw_body=headers_end_at+4; - else { - // yandex web server (http://localhost:17000) - // returns "\n\r\n" - headers_end_at=strstr(response, "\n\r\n" /*change '3' below along!*/); - if(!headers_end_at) - throw Exception("http.response", - &connect_string, - "bad response from host - no headers found"); - - raw_body=headers_end_at+3; - } - } + char* headers_end_at; + find_headers_end(response, + headers_end_at, + raw_body); raw_body_size=response_size-(raw_body-response); - *headers_end_at=0; - const String header_block(response, headers_end_at-response, true); - - ArrayString aheaders; result.headers=new HashStringValue; VHash* vtables=new VHash; result.headers->put(HTTP_TABLES_NAME, vtables); - HashStringValue& tables=vtables->hash(); - - size_t pos_after=0; - header_block.split(aheaders, pos_after, CRLF); - - //processing headers - size_t aheaders_count=aheaders.count(); Charset* real_remote_charset=0; // undetected, yet - for(size_t i=1; iget_table(); - } else { - // first appearence - Table::columns_type columns =new ArrayString(1); - *columns+=new String("value"); - table=new Table(columns); + if(headers_end_at) { + *headers_end_at=0; + const String header_block(String::C(response, headers_end_at-response), true); + + ArrayString aheaders; + HashStringValue& tables=vtables->hash(); + + size_t pos_after=0; + header_block.split(aheaders, pos_after, "\n"); + + //processing headers + size_t aheaders_count=aheaders.count(); + for(size_t i=1; iget_table(); + } else { + // first appearence + Table::columns_type columns =new ArrayString(1); + *columns+=new String("value"); + table=new Table(columns); + } + // this string becomes next row + ArrayString& row=*new ArrayString(1); + row+=&header_value; + *table+=&row; + // not existed before? add it + if(!existed) + tables.put(HEADER_NAME, new VTable(table)); } - // this string becomes next row - ArrayString& row=*new ArrayString(1); - row+=&header_value; - *table+=&row; - // not existed before? add it - if(!existed) - tables.put(HEADER_NAME, new VTable(table)); - } - result.headers->put(HEADER_NAME, new VString(header_value)); + result.headers->put(HEADER_NAME, new VString(header_value)); + } } // output response @@ -702,6 +823,11 @@ File_read_result file_read(Request_chars File_read_result result={false, 0, 0, 0}; #ifdef PA_HTTP if(file_spec.starts_with("http://")) { + if(offset || count) + throw Exception("parser.runtime", + 0, + "offset and load options are not supported for HTTP:// file load"); + // fail on read problem File_read_http_result http=file_read_http(charsets, file_spec, as_text, params); result.success=true; @@ -710,10 +836,13 @@ File_read_result file_read(Request_chars result.headers=http.headers; } else { #endif - if(params && params->count()) - throw Exception("parser.runtime", - 0, - "invalid option passed"); + if(params) { + int valid_options=get_valid_file_options_count(*params); + if(valid_options!=params->count()) + throw Exception("parser.runtime", + 0, + "invalid option passed"); + } File_read_action_info info={&result.str, &result.length, buf, offset, count}; @@ -811,7 +940,7 @@ bool file_read_action_under_lock(const S } } -static void create_dir_for_file(const String& file_spec) { +void create_dir_for_file(const String& file_spec) { size_t pos_after=1; size_t pos_before; while((pos_before=file_spec.pos('/', pos_after))!=STRING_NOT_FOUND) {