--- parser3/src/main/pa_common.C 2005/08/09 08:14:51 1.205 +++ parser3/src/main/pa_common.C 2005/11/22 11:12:40 1.210 @@ -3,9 +3,30 @@ 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: 2005/08/09 08:14:51 $"; + * BASE64 part + * Authors: Michael Zucchi + * Jeffrey Stedfast + * + * Copyright 2000-2004 Ximian, Inc. (www.ximian.com) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. + * + */ + +static const char * const IDENT_COMMON_C="$Date: 2005/11/22 11:12:40 $"; #include "pa_common.h" #include "pa_exception.h" @@ -131,6 +152,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 @@ -157,44 +193,119 @@ 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; - while(true) { - char buf[MAX_STRING*10]; - ssize_t received_size=recv(sock, buf, sizeof(buf), 0); - if(received_size==0) - break; - if(received_size<0) { - if(int no=pa_socks_errno()) - throw Exception("http.timeout", - 0, - "error receiving response: %s (%d)", pa_socks_strerr(no), no); - 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; + // 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 + } - if(!result && (EOLat=strstr(response, "\n"))) { // checking status in first response - const String status_line(pa_strdup(response, EOLat-response)); - ArrayString astatus; - size_t pos_after=0; - status_line.split(astatus, pos_after, " "); - const String& status_code=*astatus.get(astatus.count()>1?1:0); - result=status_code.as_int(); - - if(fail_on_status_ne_200 && result!=200) - throw Exception("http.status", - &status_code, - "invalid HTTP response status"); + // [gcc is happier this way, see goto above] + { + // 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) { + 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; + } + 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 1?1:0); + result=status_code.as_int(); + + if(fail_on_status_ne_200 && result!=200) + throw Exception("http.status", + &status_code, + "invalid HTTP response status"); + } } } +done: if(result) + { + response[response_size]=0; return result; + } else throw Exception("http.response", 0, @@ -368,7 +479,7 @@ static const String* basic_authorization if(pass) combined<get(HTTP_METHOD_NAME)) { valid_options++; method=vmethod->as_string().cstr(); @@ -719,11 +831,9 @@ static void file_read_action( "read failed: actually read %u bytes count not in [0..%u] valid range", *info.data_size, to_read_size); } else { // empty file - if(as_text) { - *info.data=new(PointerFreeGC) char[1]; - *(char*)(*info.data)=0; - } else - *info.data=0; + // for both, text and binary: for text we need that terminator, for binary we need nonzero pointer to be able to save such files + *info.data=new(PointerFreeGC) char[1]; + *(char*)(*info.data)=0; *info.data_size=0; return; } @@ -748,10 +858,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}; @@ -1446,18 +1559,113 @@ g_mime_utils_base64_encode_close (const return (outptr - out); } -char* pa_base64(const char *in, size_t len) +static unsigned char gmime_base64_rank[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +}; + +/** + * g_mime_utils_base64_decode_step: + * @in: input stream + * @inlen: max length of data to decode + * @out: output stream + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been decoded + * + * Decodes a chunk of base64 encoded data. + * + * Returns the number of bytes decoded (which have been dumped in @out). + **/ +size_t +g_mime_utils_base64_decode_step (const unsigned char *in, size_t inlen, unsigned char *out, int *state, int *save) +{ + const register unsigned char *inptr; + register unsigned char *outptr; + const unsigned char *inend; + register guint32 saved; + unsigned char c; + int i; + + inend = in + inlen; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + saved = *save; + i = *state; + inptr = in; + while (inptr < inend) { + c = gmime_base64_rank[*inptr++]; + if (c != 0xff) { + saved = (saved << 6) | c; + i++; + if (i == 4) { + *outptr++ = saved >> 16; + *outptr++ = saved >> 8; + *outptr++ = saved; + i = 0; + } + } + } + + *save = saved; + *state = i; + + /* quick scan back for '=' on the end somewhere */ + /* fortunately we can drop 1 output char for each trailing = (upto 2) */ + i = 2; + while (inptr > in && i) { + inptr--; + if (gmime_base64_rank[*inptr] != 0xff) { + if (*inptr == '=' && outptr > out) + outptr--; + i--; + } + } + + /* if i != 0 then there is a truncation error! */ + return (outptr - out); +} + + +char* pa_base64_encode(const char *in, size_t in_size) { /* wont go to more than 2x size (overly conservative) */ - char* result=new(PointerFreeGC) char[len * 2 + 6]; + char* result=new(PointerFreeGC) char[in_size * 2 + 6]; int state=0; int save=0; #ifndef NDEBUG size_t filled= #endif - g_mime_utils_base64_encode_close ((const unsigned char*)in, len, + g_mime_utils_base64_encode_close ((const unsigned char*)in, in_size, (unsigned char*)result, &state, &save); - assert(filled <= len * 2 + 6); + assert(filled <= in_size * 2 + 6); return result; } + +void pa_base64_decode(const char *in, size_t in_size, void*& result, size_t& result_size) +{ + /* wont go to more than had (overly conservative) */ + result=static_cast(new(PointerFreeGC) char[in_size+1/*terminator*/]); + int state=0; + int save=0; + result_size= + g_mime_utils_base64_decode_step ((const unsigned char*)in, in_size, + (unsigned char*)result, &state, &save); + assert(result_size <= in_size); + static_cast(result)[result_size]=0; // for text files +}