Diff for /parser3/src/main/pa_common.C between versions 1.165 and 1.204.6.4

version 1.165, 2003/11/19 08:20:02 version 1.204.6.4, 2005/11/18 09:17:09
Line 1 Line 1
 /** @file  /** @file
         Parser: commonly functions.          Parser: commonly functions.
   
         Copyright(c) 2001-2003 ArtLebedev Group (http://www.artlebedev.com)          Copyright(c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
 */  */
   
 static const char* IDENT_COMMON_C="$Date$";   static const char * const IDENT_COMMON_C="$Date$"; 
   
 #include "pa_common.h"  #include "pa_common.h"
 #include "pa_exception.h"  #include "pa_exception.h"
Line 21  static const char* IDENT_COMMON_C="$Date Line 21  static const char* IDENT_COMMON_C="$Date
 #include "pa_vint.h"  #include "pa_vint.h"
 #include "pa_vhash.h"  #include "pa_vhash.h"
 #include "pa_vtable.h"  #include "pa_vtable.h"
   #include "pa_socks.h"
   
 #ifdef CYGWIN  #ifdef CYGWIN
 #define _GNU_H_WINDOWS32_SOCKETS  #define _GNU_H_WINDOWS32_SOCKETS
Line 64  int PASCAL closesocket(SOCKET); Line 65  int PASCAL closesocket(SOCKET);
 #       endif  #       endif
 #endif  #endif
   
   #       ifndef INADDR_NONE
   #               define INADDR_NONE ((ulong) -1)
   #       endif
   
 // defines for globals  // defines for globals
   
 #define FILE_STATUS_NAME  "status"  #define FILE_STATUS_NAME  "status"
Line 72  int PASCAL closesocket(SOCKET); Line 77  int PASCAL closesocket(SOCKET);
   
 const String file_status_name(FILE_STATUS_NAME);  const String file_status_name(FILE_STATUS_NAME);
   
 // defines for statics  // defines
   
 #define HTTP_METHOD_NAME  "method"  #define HTTP_METHOD_NAME  "method"
   #define HTTP_FORM_NAME  "form"
   #define HTTP_BODY_NAME  "body"
 #define HTTP_TIMEOUT_NAME    "timeout"  #define HTTP_TIMEOUT_NAME    "timeout"
 #define HTTP_HEADERS_NAME "headers"  #define HTTP_HEADERS_NAME "headers"
 #define HTTP_ANY_STATUS_NAME "any-status"  #define HTTP_ANY_STATUS_NAME "any-status"
 #define HTTP_CHARSET_NAME "charset"  #define HTTP_CHARSET_NAME "charset"
 #define HTTP_TABLES_NAME "tables"  #define HTTP_TABLES_NAME "tables"
   #define HTTP_USER "user"
 // statics  #define HTTP_PASSWORD "password"
   
 static const String http_method_name(HTTP_METHOD_NAME);  
 static const String http_timeout_name(HTTP_TIMEOUT_NAME);  
 static const String http_headers_name(HTTP_HEADERS_NAME);  
 static const String http_any_status_name(HTTP_ANY_STATUS_NAME);  
 static const String http_charset_name(HTTP_CHARSET_NAME);  
 static const String http_tables_name(HTTP_TABLES_NAME);  
   
 // defines  // defines
   
Line 131  char* file_read_text(Request_charsets& c Line 131  char* file_read_text(Request_charsets& c
         return file.success?file.str:0;          return file.success?file.str:0;
 }  }
   
 #ifdef PA_HTTP  /// 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  //http request stuff
   #ifdef PA_HTTP
   
 #undef CRLF  #undef CRLF
 #define CRLF "\r\n"  #define CRLF "\r\n"
Line 142  static bool set_addr(struct sockaddr_in Line 157  static bool set_addr(struct sockaddr_in
     addr->sin_family=AF_INET;      addr->sin_family=AF_INET;
     addr->sin_port=htons(port);       addr->sin_port=htons(port); 
     if(host) {      if(host) {
                 if(struct hostent *hostIP=gethostbyname(host))                   ulong packed_ip=inet_addr(host);
                         memcpy(&addr->sin_addr, hostIP->h_addr, hostIP->h_length);                   if(packed_ip!=INADDR_NONE)
                 else                          memcpy(&addr->sin_addr, &packed_ip, sizeof(packed_ip)); 
                         return false;                  else {
                           struct hostent *hostIP=gethostbyname(host);
                           if(hostIP) 
                                   memcpy(&addr->sin_addr, hostIP->h_addr, hostIP->h_length); 
                           else
                                   return false;
                   } 
     } else       } else 
                 addr->sin_addr.s_addr=INADDR_ANY;                  addr->sin_addr.s_addr=INADDR_ANY;
     return true;      return true;
 }  }
   
 static int http_read_response(String& response, int sock, bool fail_on_status_ne_200){  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;          int result=0;
         size_t EOLat=0;          // fetching some to local buffer, guessing on possible content-length   
         while(true) {          response_size=0x400*20; // initial size if content-length could not be determined       
                 char *buf=new(PointerFreeGC) char[MAX_STRING];           const size_t preview_size=0x400*20;
                 ssize_t size=recv(sock, buf, MAX_STRING, 0);           char preview_buf[preview_size+1/*terminator*/];  // 20K buffer to preview headers
                 if(size<=0)          ssize_t received_size=recv(sock, preview_buf, preview_size, 0); 
                         break;          if(received_size==0)
                 response.append_strdup(buf, size, String::L_TAINTED);                   goto done;
                 if(!result && (EOLat=response.pos(CRLF, 2))!=STRING_NOT_FOUND) { // checking status in first response          if(received_size<0) {
                         const String& status_line=response.mid(0, (size_t)EOLat);                  if(int no=pa_socks_errno())
                         ArrayString astatus;                           throw Exception("http.timeout", 
                         size_t pos_after=0;                                  0, 
                         status_line.split(astatus, pos_after, " ");                                   "error receiving response header: %s (%d)", pa_socks_strerr(no), no); 
                         const String& status_code=*astatus.get(1);                  goto done;
                         result=status_code.as_int();           }
           // detecting response_size
                         if(fail_on_status_ne_200 && result!=200)          {
                                 throw Exception("http.status",                  preview_buf[received_size]=0; // terminator
                                         &status_code,                  if(size_t content_length=guess_content_length(preview_buf))
                                         "invalid HTTP response status");                          response_size=preview_size+content_length; // a little more than needed, will adjust response_size by actual received size later
           }
   
           // [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 <todo_size check
                           ptr+=received_size;
                           todo_size-=received_size;
   
                           char* EOLat=0;
                           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");
                           }
                 }                  }
         }          }
   done:
         if(result)          if(result)
           {
                   response[response_size]=0;
                 return result;                  return result;
           }
         else          else
                 throw Exception("http.response",                  throw Exception("http.response",
                         0,                          0,
                         "bad response from host - no status found (size=%lu)", response.length());                           "bad response from host - no status found (size=%u)", response_size); 
 }  }
   
 /* ********************** request *************************** */  /* ********************** request *************************** */
Line 190  static int http_read_response(String& re Line 299  static int http_read_response(String& re
   
 #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); 
 }  }
 #endif  #endif
   
 static int http_request(String& response,  static int http_request(char*& response, size_t& response_size,
                         const char* host, int port,                           const char* host, short port, 
                         const char* request,                           const char* request, 
                         int timeout,                          int timeout_secs,
                         bool fail_on_status_ne_200) {                          bool fail_on_status_ne_200) {
         if(!host)          if(!host)
                 throw Exception("http.host",                   throw Exception("http.host", 
                         0,                           0, 
                         "zero hostname");  //never                          "zero hostname");  //never
   
           volatile // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning]
                   int sock=-1;
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
         signal(SIGALRM, timeout_handler);           signal(SIGALRM, timeout_handler); 
 #endif  #endif
         int sock=-1;  
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
         if(sigsetjmp(timeout_env, 1)) {          if(sigsetjmp(timeout_env, 1)) {
                 // stupid gcc [2.95.4] generated bad code                  // stupid gcc [2.95.4] generated bad code
                 // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code.                  // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code.
                 // rewritten simplier [though duplicating closesocket code]                  // rewritten simplier [athough duplicating closesocket code]
                 if(sock>=0)                   if(sock>=0) 
                         closesocket(sock);                           closesocket(sock); 
                 throw Exception("http.timeout",                   throw Exception("http.timeout", 
                         origin_string,                           0, 
                         "timeout occured while retrieving document");                           "timeout occured while retrieving document"); 
                 return 0; // never                  return 0; // never
         } else {          } else {
                 alarm(timeout);                   alarm(timeout_secs); 
 #endif  #endif
                 try {                  try {
                         int result;                          int result;
Line 232  static int http_request(String& response Line 342  static int http_request(String& response
                                         0,                                           0, 
                                         "can not resolve hostname \"%s\"", host);                                           "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",                                   throw Exception("http.connect", 
                                         0,                                           0, 
                                         "can not make socket: %s (%d)", strerror(errno), errno);                                           "can not make socket: %s (%d)", pa_socks_strerr(no), no); 
                         if(connect(sock, (struct sockaddr *)&dest, sizeof(dest)))                          }
   
                           // 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",                                   throw Exception("http.connect", 
                                         0,                                           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);                          size_t request_size=strlen(request);
                         if(send(sock, request, request_size, 0)!=(ssize_t)request_size)                          if(send(sock, request, request_size, 0)!=(ssize_t)request_size) {
                                 throw Exception("http.connect",                                   int no=pa_socks_errno();
                                   throw Exception("http.timeout", 
                                         0,                                           0, 
                                         "error sending request: %s (%d)", strerror(errno), errno);                                           "error sending request: %s (%d)", pa_socks_strerr(no), no); 
                           }
   
                         result=http_read_response(response, sock, fail_on_status_ne_200);                           result=http_read_response(response, response_size, sock, fail_on_status_ne_200); 
                         closesocket(sock);                           closesocket(sock); 
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
                         alarm(0)                          alarm(0)
Line 316  static Charset* detect_charset(Charset& Line 447  static Charset* detect_charset(Charset&
         return 0;          return 0;
 }  }
   
   static const String* basic_authorization_field(const char* user, const char* pass) {
           if(!user&& !pass)
                   return 0;
   
           String combined;  
           if(user)
                   combined<<user;
           combined<<":";
           if(pass)
                   combined<<pass;
           
           String* result=new String("Basic "); *result<<pa_base64(combined.cstr(), combined.length());
           return result;
   }
   
   static void form_string_value2string(
                                                                     HashStringValue::key_type key, 
                                                                     const String& value, 
                                                                     String& result) 
   {
           result << String(key, String::L_TAINTED) << "=";
           result.append(value, String::L_URI, true);
           result<< "&";
   }
   #ifndef DOXYGEN
   struct Form_table_value2string_info {
           HashStringValue::key_type key;
           String& result;
   
           Form_table_value2string_info(HashStringValue::key_type akey, String& aresult): 
                   key(akey), result(aresult) {}
   };
   #endif
   static void form_table_value2string(Table::element_type row, Form_table_value2string_info* info) {
           form_string_value2string(info->key, *row->get(0), info->result);
   }
   static void form_value2string(
                                                                     HashStringValue::key_type key, 
                                                                     HashStringValue::value_type value, 
                                                                     String* result) 
   {
           if(const String* svalue=value->get_string())
                   form_string_value2string(key, *svalue, *result);
           else if(Table* tvalue=value->get_table()) {
                   Form_table_value2string_info info(key, *result);
                   tvalue->for_each(form_table_value2string, &info);
           } else
                   throw Exception(0,
                           new String(key, String::L_TAINTED),
                           "is %s, "HTTP_FORM_NAME" option value must either string or table", value->type());
   }
   static const char* form2string(HashStringValue& form) {
           String string;
           form.for_each(form_value2string, &string);
           return string.cstr(String::L_UNSPECIFIED);
   }
 #ifndef DOXYGEN  #ifndef DOXYGEN
 struct File_read_http_result {  struct File_read_http_result {
         char *str; size_t length;          char *str; size_t length;
         HashStringValue* headers;          HashStringValue* headers;
 };   }; 
 #endif  #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  /// @todo build .cookies field. use ^file.tables.SET-COOKIES.menu{ for now
 static File_read_http_result file_read_http(Request_charsets& charsets,   static File_read_http_result file_read_http(Request_charsets& charsets, 
                                             const String& file_spec,                                               const String& file_spec, 
                                             HashStringValue *options=0) {                                              bool as_text,
                                                   HashStringValue *options=0) {
         File_read_http_result result;          File_read_http_result result;
         char host[MAX_STRING];           char host[MAX_STRING]; 
         const char* uri;           const char* uri; 
         int port;          short port;
         const char* method=0;          const char* method="GET"; bool method_is_get;
         int timeout=2;          HashStringValue* form=0;
           const char* body_cstr=0;
           int timeout_secs=2;
         bool fail_on_status_ne_200=true;          bool fail_on_status_ne_200=true;
         Value* vheaders=0;          Value* vheaders=0;
         Charset *asked_remote_charset=0;          Charset *asked_remote_charset=0;
           const char* user_cstr=0;
           const char* password_cstr=0;
   
         if(options) {          if(options) {
                 int valid_options=0;                  int valid_options=get_valid_file_options_count(*options);
                 if(Value* vmethod=options->get(http_method_name)) {  
                   if(Value* vmethod=options->get(HTTP_METHOD_NAME)) {
                         valid_options++;                          valid_options++;
                         method=vmethod->as_string().cstr();                          method=vmethod->as_string().cstr();
                 }                  }
                 if(Value* vtimeout=options->get(http_timeout_name)) {                  if(Value* vform=options->get(HTTP_FORM_NAME)) {
                         valid_options++;                          valid_options++;
                         timeout=vtimeout->as_int();                           form=vform->get_hash(); 
                 }                  } 
                 if(vheaders=options->get(http_headers_name)) {                  if(Value* vbody=options->get(HTTP_BODY_NAME)) {
                         valid_options++;                          valid_options++;
                 }                          body_cstr=vbody->as_string().cstr(String::L_UNSPECIFIED);
                 if(Value* vany_status=options->get(http_any_status_name)) {                  } 
                   if(Value* vtimeout=options->get(HTTP_TIMEOUT_NAME)) {
                           valid_options++;
                           timeout_secs=vtimeout->as_int(); 
                   } 
                   if((vheaders=options->get(HTTP_HEADERS_NAME))) {
                           valid_options++;
                   } 
                   if(Value* vany_status=options->get(HTTP_ANY_STATUS_NAME)) {
                         valid_options++;                          valid_options++;
                         fail_on_status_ne_200=!vany_status->as_bool();                           fail_on_status_ne_200=!vany_status->as_bool(); 
                 }                  } 
                 if(Value* vcharset_name=options->get(http_charset_name)) {                  if(Value* vcharset_name=options->get(HTTP_CHARSET_NAME)) {
                         valid_options++;                          valid_options++;
                         asked_remote_charset=&::charsets.get(vcharset_name->as_string().                          asked_remote_charset=&::charsets.get(vcharset_name->as_string().
                                 change_case(charsets.source(), String::CC_UPPER));                                  change_case(charsets.source(), String::CC_UPPER));
                   } 
                   if(Value* vuser=options->get(HTTP_USER)) {
                           valid_options++;
                           user_cstr=vuser->as_string().cstr();
                   } 
                   if(Value* vpassword=options->get(HTTP_PASSWORD)) {
                           valid_options++;
                           password_cstr=vpassword->as_string().cstr();
                 }                  }
   
                 if(valid_options!=options->count())                  if(valid_options!=options->count())
Line 368  static File_read_http_result file_read_h Line 595  static File_read_http_result file_read_h
         if(!asked_remote_charset) // defaulting to $request:charset          if(!asked_remote_charset) // defaulting to $request:charset
                 asked_remote_charset=&charsets.source();                  asked_remote_charset=&charsets.source();
   
           method_is_get=strcmp(method, "GET")==0;
           if(method_is_get && body_cstr)
                   throw Exception("parser.runtime",
                           0,
                           "you can not use $."HTTP_BODY_NAME" option with method GET");
   
         //preparing request          //preparing request
         String& connect_string=*new String;          String& connect_string=*new String;
         // not in ^sql{... L_SQL ...} spirit, but closer to ^file::load one          // not in ^sql{... L_SQL ...} spirit, but closer to ^file::load one
         connect_string.append(file_spec, String::L_URI); // tainted pieces -> URI pieces          connect_string.append(file_spec, String::L_URI); // tainted pieces -> URI pieces
   
         const char* request_cstr;          String request_head_and_body;
         {          {
                 // influence URLencoding of tainted pieces to String::L_URI lang                  // influence URLencoding of tainted pieces to String::L_URI lang
                 Temp_client_charset temp(charsets, *asked_remote_charset);                  Temp_client_charset temp(charsets, *asked_remote_charset);
Line 392  static File_read_http_result file_read_h Line 625  static File_read_http_result file_read_h
                 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, ':'); 
                 char* error_pos=0;                  char* error_pos=0;
                 port=port_cstr?strtol(port_cstr, &error_pos, 0):80;                  port=port_cstr?(short)strtol(port_cstr, &error_pos, 0):80;
   
                   if(strchr(uri, '?') && form)
                           throw Exception("parser.runtime",
                                   0,
                                   "use either uri with ?params or $."HTTP_FORM_NAME" option");
   
                 //making request                  //making request head
                 String request;                  String head;
                 if(method)                  head << method;
                         request<<method;                  head << " " << uri;
                 else                  if(form)
                         request<<"GET";                          if(method_is_get)
                 request<< " "<< uri <<" HTTP/1.0" CRLF                                  head << "?" << form2string(*form);
                   head <<" HTTP/1.0" CRLF
                         "host: "<< host << CRLF;                           "host: "<< host << CRLF; 
                   if(form && !method_is_get) {
                           head << "content-type: application/x-www-form-urlencoded" CRLF;
                           body_cstr = form2string(*form);
                   }
   
                   // http://www.ietf.org/rfc/rfc2617.txt
                   if(const String* authorization_field_value=basic_authorization_field(user_cstr, password_cstr))
                           head<<"authorization: "<<*authorization_field_value<<CRLF;
   
                 bool user_agent_specified=false;                  bool user_agent_specified=false;
                 if(vheaders && !vheaders->is_string()) { // allow empty                  if(vheaders && !vheaders->is_string()) { // allow empty
                         if(HashStringValue *headers=vheaders->get_hash()) {                          if(HashStringValue *headers=vheaders->get_hash()) {
                                 Http_pass_header_info info={&charsets, &request};                                  Http_pass_header_info info={&charsets, &head, false};
                                 headers->for_each(http_pass_header, &info);                                   headers->for_each(http_pass_header, &info); 
                                 user_agent_specified=info.user_agent_specified;                                  user_agent_specified=info.user_agent_specified;
                         } else                          } else
Line 415  static File_read_http_result file_read_h Line 662  static File_read_http_result file_read_h
                                         "headers param must be hash");                                           "headers param must be hash"); 
                 };                  };
                 if(!user_agent_specified) // defaulting                  if(!user_agent_specified) // defaulting
                         request << "user-agent: " DEFAULT_USER_AGENT CRLF;                          head << "user-agent: " DEFAULT_USER_AGENT CRLF;
                 request << CRLF;   
                   if(body_cstr) {
                           // recode those pieces which are not in String::L_URI lang 
                           // [those violating HTTP standard, but widly used]
                           body_cstr=Charset::transcode(
                                   String::C(body_cstr, strlen(body_cstr)),
                                   charsets.source(),
                                   *asked_remote_charset);
   
                           head << "content-length: " << format(strlen(body_cstr), "%u") << CRLF;
                   }
   
                   const char* head_cstr=head.cstr(String::L_UNSPECIFIED);
   
                 request_cstr=request.cstr(String::L_UNSPECIFIED);                  // recode those pieces which are not in String::L_URI lang 
                   // [those violating HTTP standard, but widly used]
                   head_cstr=Charset::transcode(
                           String::C(head_cstr, strlen(head_cstr)),
                           charsets.source(),
                           *asked_remote_charset);
   
                   // head + end of header
                   request_head_and_body << head_cstr << CRLF;
                   // body
                   if(body_cstr)
                           request_head_and_body << body_cstr;
         }          }
         // recode those pieces which are not in String::L_URI lang   
         // [those violating HTTP standard, but widly used]  
         request_cstr=Charset::transcode(  
                 String::C(request_cstr, strlen(request_cstr)),  
                 charsets.source(),  
                 *asked_remote_charset);  
                   
         //sending request          //sending request
         String response;          char* response;
         int status_code=http_request(response,          size_t response_size;
                 host, port, request_cstr,           int status_code=http_request(response, response_size,
                 timeout, fail_on_status_ne_200);                   host, port, request_head_and_body.cstr(), 
                   timeout_secs, fail_on_status_ne_200); 
                   
         //processing results              //processing results    
         size_t pos=response.pos(CRLF CRLF, 4);           char* raw_body; size_t raw_body_size;
         if(pos==STRING_NOT_FOUND || pos<1) {          char* headers_end_at;
                 throw Exception("http.response",           find_headers_end(response, 
                         &connect_string,                  headers_end_at,
                         "bad response from host - no headers found");                   raw_body);
         }          raw_body_size=response_size-(raw_body-response);
         const String& header_block=response.mid(0, pos);   
         const String& raw_body=response.mid(pos+4, response.length());   
                   
         ArrayString aheaders;  
         result.headers=new HashStringValue;          result.headers=new HashStringValue;
         VHash* vtables=new VHash;          VHash* vtables=new VHash;
         result.headers->put(http_tables_name, vtables);          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          Charset* real_remote_charset=0; // undetected, yet
         for(size_t i=1; i<aheaders_count; i++) {  
                 const String& line=*aheaders.get(i);          if(headers_end_at) {
                 pos=line.pos(": ", 2);                   *headers_end_at=0;
                 if(pos==STRING_NOT_FOUND || pos<1)                  const String header_block(String::C(response, headers_end_at-response), true);
                         throw Exception("http.response",                   
                                 &connect_string,                  ArrayString aheaders;
                                 "bad response from host - bad header \"%s\"", line.cstr());                  HashStringValue& tables=vtables->hash();
                 const String::Body HEADER_NAME=  
                         line.mid(0, pos).change_case(charsets.source(), String::CC_UPPER);                  size_t pos_after=0;
                 const String& header_value=line.mid(pos+2, line.length());                  header_block.split(aheaders, pos_after, "\n"); 
                 if(HEADER_NAME=="CONTENT-TYPE")                  
                         real_remote_charset=detect_charset(charsets.source(), header_value);                  //processing headers
                   size_t aheaders_count=aheaders.count();
                 // tables                  for(size_t i=1; i<aheaders_count; i++) {
                 {                          const String& line=*aheaders.get(i);
                         Value *valready=(Value *)tables.get(HEADER_NAME);                          size_t pos=line.pos(':'); 
                         bool existed=valready!=0;                          if(pos==STRING_NOT_FOUND || pos<1)
                         Table *table;                                  throw Exception("http.response", 
                         if(existed) {                                          &connect_string,
                                 // second+ appearence                                          "bad response from host - bad header \"%s\"", line.cstr());
                                 table=valready->get_table();                          const String::Body HEADER_NAME=
                         } else {                                  line.mid(0, pos).change_case(charsets.source(), String::CC_UPPER);
                                 // first appearence                          const String& header_value=line.mid(pos+1, line.length()).trim(String::TRIM_BOTH, " \t\r");
                                 Table::columns_type columns =new ArrayString(1);                          if(as_text && HEADER_NAME=="CONTENT-TYPE")
                                 *columns+=new String("value");                                  real_remote_charset=detect_charset(charsets.source(), header_value);
                                 table=new Table(columns);  
                           // tables
                           {
                                   Value *valready=(Value *)tables.get(HEADER_NAME);
                                   bool existed=valready!=0;
                                   Table *table;
                                   if(existed) {
                                           // second+ appearence
                                           table=valready->get_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));  
         }  
         // defaulting to used-asked charset [it's never empty!]  
         if(!real_remote_charset)  
                 real_remote_charset=asked_remote_charset;  
   
         // todo: do that only if ^file::load[text                          result.headers->put(HEADER_NAME, new VString(header_value));
                   }
           }
   
         // output response          // output response
         String::C real_body=Charset::transcode(          String::C real_body=String::C(raw_body, raw_body_size);
                 String::C(raw_body.cstrm()/*must be modifiable*/, raw_body.length()),          if(as_text && raw_body_size) { // must be checked because transcode returns CONST string in case length==0, which contradicts hacking few lines below
                 *real_remote_charset,                  // defaulting to used-asked charset [it's never empty!]
                 charsets.source());                  if(!real_remote_charset)
                           real_remote_charset=asked_remote_charset;
                   real_body=Charset::transcode(real_body, *real_remote_charset, charsets.source());
           }
   
         result.str=const_cast<char *>(real_body.str); // hacking a little          result.str=const_cast<char *>(real_body.str); // hacking a little
         result.length=real_body.length;          result.length=real_body.length;
Line 515  static File_read_http_result file_read_h Line 783  static File_read_http_result file_read_h
 #ifndef DOXYGEN  #ifndef DOXYGEN
 struct File_read_action_info {  struct File_read_action_info {
         char **data; size_t *data_size;          char **data; size_t *data_size;
           char* buf; size_t offset; size_t count;
 };   }; 
 #endif  #endif
 static void file_read_action(  static void file_read_action(
                              struct stat& finfo,                                struct stat& finfo, 
                              int f,                                int f, 
                              const String& file_spec, const char* fname, bool as_text,                                const String& file_spec, const char* /*fname*/, bool as_text, 
                              void *context) {                               void *context) {
         File_read_action_info& info=*static_cast<File_read_action_info *>(context);           File_read_action_info& info=*static_cast<File_read_action_info *>(context); 
         if(size_t to_read_size=(size_t)finfo.st_size) {           size_t to_read_size=info.count;
                 *info.data=new(PointerFreeGC) char[to_read_size+(as_text?1:0)];           if(!to_read_size)
                   to_read_size=(size_t)finfo.st_size;
           assert( !(info.buf && as_text) );
           if(to_read_size) { 
                   if(info.offset)
                           lseek(f, info.offset, SEEK_SET);
                   *info.data=info.buf
                           ? info.buf
                           : new(PointerFreeGC) char[to_read_size+(as_text?1:0)]; 
                 *info.data_size=(size_t)read(f, *info.data, to_read_size);                   *info.data_size=(size_t)read(f, *info.data, to_read_size); 
   
                 if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size)                  if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size)
                         throw Exception(0,                           throw Exception(0, 
                                 &file_spec,                                   &file_spec, 
                                 "read failed: actually read %l bytes count not in [0..%lu] valid range",                                   "read failed: actually read %u bytes count not in [0..%u] valid range", 
                                         *info.data_size, to_read_size);                                           *info.data_size, to_read_size); 
         } else { // empty file          } else { // empty file
                 if(as_text) {                  if(as_text) {
Line 544  static void file_read_action( Line 821  static void file_read_action(
 }  }
 File_read_result file_read(Request_charsets& charsets, const String& file_spec,   File_read_result file_read(Request_charsets& charsets, const String& file_spec, 
                            bool as_text, HashStringValue *params,                             bool as_text, HashStringValue *params,
                            bool fail_on_read_problem) {                             bool fail_on_read_problem,
         File_read_result result={false};                             char* buf, size_t offset, size_t count) {
           File_read_result result={false, 0, 0, 0};
 #ifdef PA_HTTP  #ifdef PA_HTTP
         if(file_spec.starts_with("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                  // fail on read problem
                 File_read_http_result http=file_read_http(charsets, file_spec, params);                  File_read_http_result http=file_read_http(charsets, file_spec, as_text, params);
                 result.success=true;                  result.success=true;
                 result.str=http.str;                  result.str=http.str;
                 result.length=http.length;                  result.length=http.length;
                 result.headers=http.headers;                   result.headers=http.headers; 
         } else {          } else {
 #endif  #endif
                 if(params && params->count())                  if(params) {
                         throw Exception("parser.runtime",                          int valid_options=get_valid_file_options_count(*params);
                                 0,                          if(valid_options!=params->count())
                                 "invalid option passed");                                  throw Exception("parser.runtime",
                                           0,
                                           "invalid option passed");
                   }
   
                 File_read_action_info info={&result.str, &result.length};                   File_read_action_info info={&result.str, &result.length,
                           buf, offset, count}; 
                 result.success=file_read_action_under_lock(file_spec,                   result.success=file_read_action_under_lock(file_spec, 
                         "read", file_read_action, &info,                           "read", file_read_action, &info, 
                         as_text, fail_on_read_problem);                           as_text, fail_on_read_problem); 
Line 579  File_read_result file_read(Request_chars Line 866  File_read_result file_read(Request_chars
                 }                  }
   
                 fix_line_breaks((char *)(result.str), result.length);                   fix_line_breaks((char *)(result.str), result.length); 
                 // note: after fixing  
                 ((char*&)(result.str))[result.length]=0;  
         }          }
   
         return result;          return result;
Line 658  bool file_read_action_under_lock(const S Line 943  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_after=1;
         size_t pos_before;          size_t pos_before;
         while((pos_before=file_spec.pos('/', pos_after))!=STRING_NOT_FOUND) {          while((pos_before=file_spec.pos('/', pos_after))!=STRING_NOT_FOUND) {
Line 918  const char* format(double value, char* f Line 1203  const char* format(double value, char* f
   
 size_t stdout_write(const void *buf, size_t size) {  size_t stdout_write(const void *buf, size_t size) {
 #ifdef WIN32  #ifdef WIN32
           size_t to_write = size;
         do{          do{
                 int chunk_written=fwrite(buf, 1, min((size_t)8*0x400, size), stdout);                   int chunk_written=fwrite(buf, 1, min((size_t)8*0x400, size), stdout); 
                 if(chunk_written<=0)                  if(chunk_written<=0)
Line 926  size_t stdout_write(const void *buf, siz Line 1212  size_t stdout_write(const void *buf, siz
                 buf=((const char*)buf)+chunk_written;                  buf=((const char*)buf)+chunk_written;
         } while(size>0);           } while(size>0); 
   
         return size;          return to_write-size;
 #else  #else
         return fwrite(buf, 1, size, stdout);           return fwrite(buf, 1, size, stdout); 
 #endif  #endif
Line 939  char* unescape_chars(const char* cp, int Line 1225  char* unescape_chars(const char* cp, int
                 EscapeFirst,                   EscapeFirst, 
                 EscapeSecond                  EscapeSecond
         } escapeState=EscapeRest;          } escapeState=EscapeRest;
         int escapedValue=0;          uchar escapedValue=0;
         int srcPos=0;          int srcPos=0;
         int dstPos=0;          int dstPos=0;
         while(srcPos < len) {          while(srcPos < len) {
                 int ch=cp[srcPos];                   uchar ch=(uchar)cp[srcPos]; 
                 switch(escapeState) {                  switch(escapeState) {
                         case EscapeRest:                          case EscapeRest:
                         if(ch=='%') {                          if(ch=='%') {
Line 955  char* unescape_chars(const char* cp, int Line 1241  char* unescape_chars(const char* cp, int
                         }                          }
                         break;                          break;
                         case EscapeFirst:                          case EscapeFirst:
                         escapedValue=hex_value[ch] << 4;                                  escapedValue=(uchar)(hex_value[ch] << 4);
                         escapeState=EscapeSecond;                          escapeState=EscapeSecond;
                         break;                          break;
                         case EscapeSecond:                          case EscapeSecond:
Line 996  bool StrEqNc(const char* s1, const char* Line 1282  bool StrEqNc(const char* s1, const char*
                                 return !strict;                                  return !strict;
                 } else if(!(*s2))                  } else if(!(*s2))
                         return !strict;                          return !strict;
                 if(isalpha(*s1)) {                  if(isalpha((unsigned char)*s1)) {
                         if(tolower(*s1) !=tolower(*s2))                          if(tolower((unsigned char)*s1) !=tolower((unsigned char)*s2))
                                 return false;                                  return false;
                 } else if((*s1) !=(*s2))                  } else if((*s1) !=(*s2))
                         return false;                          return false;
Line 1049  int __vsnprintf(char* b, size_t s, const Line 1335  int __vsnprintf(char* b, size_t s, const
         // note: on win32& maybe somewhere else          // note: on win32& maybe somewhere else
         // vsnprintf do not writes terminating 0 in 'buffer full' case, reducing          // vsnprintf do not writes terminating 0 in 'buffer full' case, reducing
         --s;          --s;
   
           // clients do not check for negative 's', feature: ignore such prints
           if((ssize_t)s<0)
                   return 0;
   
 #if _MSC_VER  #if _MSC_VER
         /*          /*
         win32:           win32: 
Line 1074  int __vsnprintf(char* b, size_t s, const Line 1365  int __vsnprintf(char* b, size_t s, const
   
         if(r<0)          if(r<0)
                 r=0;                  r=0;
         else if(r>s)          else if((size_t)r>s)
                 r=s;                  r=s;
 #endif  #endif
         b[r]=0;          b[r]=0;
Line 1088  int __snprintf(char* b, size_t s, const Line 1379  int __snprintf(char* b, size_t s, const
     va_end(l);       va_end(l); 
         return r;          return r;
 }  }
   
   /* mime64 functions are from libgmime[http://spruce.sourceforge.net/gmime/] lib */
   /*
    *  Authors: Michael Zucchi <notzed@helixcode.com>
    *           Jeffrey Stedfast <fejj@helixcode.com>
    *
    *  Copyright 2000 Helix Code, Inc. (www.helixcode.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 char *base64_alphabet =
           "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   
   /**
    * g_mime_utils_base64_encode_step:
    * @in: input stream
    * @inlen: length of the input
    * @out: output string
    * @state: holds the number of bits that are stored in @save
    * @save: leftover bits that have not yet been encoded
    *
    * Base64 encodes a chunk of data. Performs an 'encode step', only
    * encodes blocks of 3 characters to the output at a time, saves
    * left-over state in state and save (initialise to 0 on first
    * invocation).
    *
    * Returns the number of bytes encoded.
    **/
   static size_t
   g_mime_utils_base64_encode_step (const unsigned char *in, size_t inlen, unsigned char *out, int *state, int *save)
   {
           register const unsigned char *inptr;
           register unsigned char *outptr;
           
           if (inlen <= 0)
                   return 0;
           
           inptr = in;
           outptr = out;
           
           if (inlen + ((unsigned char *)save)[0] > 2) {
                   const unsigned char *inend = in + inlen - 2;
                   register int c1 = 0, c2 = 0, c3 = 0;
                   register int already;
                   
                   already = *state;
                   
                   switch (((char *)save)[0]) {
                   case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
                   case 2: c1 = ((unsigned char *)save)[1];
                           c2 = ((unsigned char *)save)[2]; goto skip2;
                   }
                   
                   /* yes, we jump into the loop, no i'm not going to change it, its beautiful! */
                   while (inptr < inend) {
                           c1 = *inptr++;
                   skip1:
                           c2 = *inptr++;
                   skip2:
                           c3 = *inptr++;
                           *outptr++ = base64_alphabet [c1 >> 2];
                           *outptr++ = base64_alphabet [(c2 >> 4) | ((c1 & 0x3) << 4)];
                           *outptr++ = base64_alphabet [((c2 & 0x0f) << 2) | (c3 >> 6)];
                           *outptr++ = base64_alphabet [c3 & 0x3f];
                           /* this is a bit ugly ... */
                           if ((++already) >= 19) {
                                   *outptr++ = '\n';
                                   already = 0;
                           }
                   }
                   
                   ((unsigned char *)save)[0] = 0;
                   inlen = 2 - (inptr - inend);
                   *state = already;
           }
           
           //d(printf ("state = %d, inlen = %d\n", (int)((char *)save)[0], inlen));
           
           if (inlen > 0) {
                   register char *saveout;
                   
                   /* points to the slot for the next char to save */
                   saveout = & (((char *)save)[1]) + ((char *)save)[0];
                   
                   /* inlen can only be 0 1 or 2 */
                   switch (inlen) {
                   case 2: *saveout++ = *inptr++;
                   case 1: *saveout++ = *inptr++;
                   }
                   ((char *)save)[0] += inlen;
           }
           
           /*d(printf ("mode = %d\nc1 = %c\nc2 = %c\n",
                     (int)((char *)save)[0],
                     (int)((char *)save)[1],
                     (int)((char *)save)[2]));*/
           
           return (outptr - out);
   }
   
   /**
    * g_mime_utils_base64_encode_close:
    * @in: input stream
    * @inlen: length of the input
    * @out: output string
    * @state: holds the number of bits that are stored in @save
    * @save: leftover bits that have not yet been encoded
    *
    * Base64 encodes the input stream to the output stream. Call this
    * when finished encoding data with g_mime_utils_base64_encode_step to
    * flush off the last little bit.
    *
    * Returns the number of bytes encoded.
    **/
   static size_t
   g_mime_utils_base64_encode_close (const unsigned char *in, size_t inlen, unsigned char *out, int *state, int *save)
   {
           unsigned char *outptr = out;
           int c1, c2;
           
           if (inlen > 0)
                   outptr += g_mime_utils_base64_encode_step (in, inlen, outptr, state, save);
           
           c1 = ((unsigned char *)save)[1];
           c2 = ((unsigned char *)save)[2];
           
           switch (((unsigned char *)save)[0]) {
           case 2:
                   outptr[2] = base64_alphabet [(c2 & 0x0f) << 2];
                   goto skip;
           case 1:
                   outptr[2] = '=';
           skip:
                   outptr[0] = base64_alphabet [c1 >> 2];
                   outptr[1] = base64_alphabet [c2 >> 4 | ((c1 & 0x3) << 4)];
                   outptr[3] = '=';
                   outptr += 4;
                   break;
           }
           
           *outptr++ = 0;
           
           *save = 0;
           *state = 0;
           
           return (outptr - out);
   }
   
   char* pa_base64(const char *in, size_t len)
   {
           /* wont go to more than 2x size (overly conservative) */
           char* result=new(PointerFreeGC) char[len * 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, 
                   (unsigned char*)result, &state, &save);
           assert(filled <= len * 2 + 6);
   
           return result;
   }

Removed from v.1.165  
changed lines
  Added in v.1.204.6.4


E-mail: