Diff for /parser3/src/main/pa_http.C between versions 1.89 and 1.112

version 1.89, 2020/10/12 21:15:07 version 1.112, 2020/12/17 11:50:44
Line 1 Line 1
 /** @file  /** @file
         Parser: http support functions.          Parser: http support functions.
   
         Copyright (c) 2001-2017 Art. Lebedev Studio (http://www.artlebedev.com)          Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
  */   */
   
Line 59  bool HTTP_Headers::add_header(const char Line 59  bool HTTP_Headers::add_header(const char
                         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 105  public: Line 105  public:
         size_t body_offset;          size_t body_offset;
   
         HTTP_Headers headers;          HTTP_Headers headers;
         const String &url;  
   
         HTTP_response(const 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 115  public: Line 114  public:
         }          }
   
         bool read(int sock, size_t size){          bool read(int sock, size_t size){
                 if(length+size>buf_size)                  if(length + size > buf_size)
                         resize(buf_size*2 + size);                          resize(buf_size * 2 + size);
                 ssize_t received_size=recv(sock, buf + length, size, 0);                  ssize_t received_size=recv(sock, buf + length, size, 0);
                 if(received_size==0)                  if(received_size == 0)
                         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: %s (%d)", pa_socks_strerr(no), no);
                         return false;                          return false;
                 }                  }
                 length+=received_size;                  length+=received_size;
Line 153  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=pa_atoui(result_str, 10);                  ALTER_EXCEPTION_COMMENT(result=pa_atoui(result_str), " for HTTP status");
                 return result_str;                  return result_str;
         }          }
   
Line 186  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);
                 }                  }
         }          }
   
Line 227  int HTTP_response::read_response(int soc Line 226  int HTTP_response::read_response(int soc
   
                                 parse_headers();                                  parse_headers();
   
                                 size_t content_length=check_file_size(headers.content_length, url);                                  size_t content_length=check_file_size(headers.content_length, 0);
                                 if(content_length>0 && (content_length + body_offset) > length){                                  if(content_length>0 && (content_length + body_offset) > length){
                                         resize(content_length + body_offset + 0x400*64);                                          resize(content_length + body_offset + 0x400*64);
                                 }                                  }
Line 244  int HTTP_response::read_response(int soc Line 243  int HTTP_response::read_response(int soc
         }          }
   
         if(state==HTTP_STATUS_CODE)          if(state==HTTP_STATUS_CODE)
                 throw Exception("http.response", &url, "bad response from host - no status found (size=%u)", length);                  throw Exception("http.response", 0, "bad response from host - no status found (size=%u)", length);
   
         if(state==HTTP_HEADERS){          if(state==HTTP_HEADERS){
                 parse_headers();                  parse_headers();
Line 263  int HTTP_response::read_response(int soc Line 262  int HTTP_response::read_response(int soc
 #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);
 }  }
   #define ALARM(value) alarm(value)
   #else
   #define ALARM(value)
 #endif  #endif
   
 static int http_request(HTTP_response& response, const char* host, short port, const char* request, size_t request_size, int timeout_secs, bool fail_on_status_ne_200) {  static int http_request(HTTP_response& response, const char* host, short port, const char* request, size_t request_size, int timeout_secs, bool fail_on_status_ne_200) {
         if(!host)          if(!host)
                 throw Exception("http.host", 0, "zero hostname");  //never                  throw Exception("http.host", 0, "zero hostname");  //never
   
         volatile // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning]          volatile int sock=-1; // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning]
                 int sock=-1;                  
 #ifdef PA_USE_ALARM  
         signal(SIGALRM, timeout_handler);   
 #endif  
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
           signal(SIGALRM, timeout_handler);
         if(sigsetjmp(timeout_env, 1)) {          if(sigsetjmp(timeout_env, 1)) {
                 // stupid gcc [2.95.4] generated bad code                  // duplicating closesocket to make code more simple for old compilers
                 // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code.                  if(sock>=0)
                 // rewritten simplier [athough duplicating closesocket code]                          closesocket(sock);
                 if(sock>=0)                   throw Exception("http.timeout", 0, "timeout occurred while retrieving document");
                         closesocket(sock);   
                 throw Exception("http.timeout", 0, "timeout occurred while retrieving document");   
                 return 0; // never                  return 0; // never
         } else {          } else
                 alarm(timeout_secs);   
 #endif  #endif
           {
                   ALARM(timeout_secs);
                 try {                  try {
                         int result;                          int result;
                         struct sockaddr_in dest;                          struct sockaddr_in dest;
Line 326  static int http_request(HTTP_response& r Line 325  static int http_request(HTTP_response& r
   
                         result=response.read_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                          ALARM(0);
                         alarm(0);  
 #endif  
                         return result;                          return result;
                 } catch(...) {                  } catch(...) {
 #ifdef PA_USE_ALARM                          ALARM(0);
                         alarm(0);  
 #endif  
                         if(sock>=0)                          if(sock>=0)
                                 closesocket(sock);                                  closesocket(sock);
                         rethrow;                          rethrow;
                 }                  }
 #ifdef PA_USE_ALARM  
         }          }
 #endif  
 }  }
   
 #ifndef DOXYGEN  #ifndef DOXYGEN
Line 365  char *pa_http_safe_header_name(const cha Line 358  char *pa_http_safe_header_name(const cha
         return result;          return result;
 }  }
   
 static void http_pass_header(HashStringValue::key_type aname,   static void http_pass_header(HashStringValue::key_type aname, HashStringValue::value_type avalue, Http_pass_header_info *info) {
                                 HashStringValue::value_type avalue,   
                                 Http_pass_header_info *info) {  
   
         const char* name_cstr=aname.cstr();          const char* name_cstr=aname.cstr();
   
Line 864  File_read_http_result pa_internal_file_r Line 855  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 921  File_read_http_result pa_internal_file_r Line 913  File_read_http_result pa_internal_file_r
   
 /* ********************** httpd *************************** */  /* ********************** httpd *************************** */
   
   #ifdef HTTPD_DEBUG
   void pa_log(const char* fmt, ...);
   #define LOG(action) action
   #else
   #define LOG(action)
   #endif
   
   enum EscapeState {
           Initial,
           Default,
           EscapeFirst,
           EscapeSecond
   };
   
   static bool check_uri(const char *uri){
           EscapeState state=Initial;
           uint escapedValue;
   
           const char *pattern="/../";
           const char *pos=pattern;
   
           while(*uri){
                   uchar c=(uchar)*(uri++);
                   switch(state) {
                           case Initial:
                                   if(c!='/')
                                           return false;
                                   state=Default;
                                   break;
                           case Default:
                                   if(c=='%'){
                                           state=EscapeFirst;
                                           continue;
                                   }
                                   if(c=='?')
                                           return true;
                                   break;
                           case EscapeFirst:
                                   if(isxdigit(c)){
                                           state=EscapeSecond;
                                           escapedValue=hex_value[c] << 4;
                                           continue;
                                   }
                                   return false;
                           case EscapeSecond:
                                   if(isxdigit(c)){
                                           state=Default;
                                           c=(uchar)(escapedValue + hex_value[c]);
   
                                           // implementing Apache AllowEncodedSlashes Off just in case
                                           if(c=='/' || c=='\\')
                                                   return false;
   
                                           break;
                                   }
                                   return false;
                   }
   
                   if(c==*pos || c=='\\' && *pos=='/'){
                           if(!*(++pos))
                                   return false;
                   } else {
                           pos=pattern;
                   }
           }
           return true;
   }
   
 class HTTPD_request : public HTTP_response {  class HTTPD_request : public HTTP_response {
 public:  public:
         const char *method;          const char *method;
         const char *uri;          const char *uri;
   
         HTTPD_request() : HTTP_response(String::Empty), method(NULL), uri(NULL){};          HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){};
   
           ssize_t pa_recv(int sockfd, char *buf, size_t len);
   
           bool read(int sock, size_t size){
                   if(length + size > buf_size)
                           resize(buf_size * 2 + size);
                   ssize_t received_size=pa_recv(sock, buf + length, size);
                   if(received_size == 0)
                           return false;
                   if(received_size < 0) {
                           if(int no = pa_socks_errno())
                                   throw Exception("httpd.read", 0, "error receiving request: %s (%d)", pa_socks_strerr(no), no);
                           return false;
                   }
                   length+=received_size;
                   buf[length]='\0';
                   return true;
           }
   
         const char *extract_method(char *method_line){          const char *extract_method(char *method_line){
                 char* uri_start = strchr(method_line, ' ');                  char* uri_start = strchr(method_line, ' ');
Line 940  public: Line 1018  public:
                         return NULL;                          return NULL;
   
                 uri=pa_strdup(uri_start+1, uri_end-uri_start-1);                  uri=pa_strdup(uri_start+1, uri_end-uri_start-1);
                   if(!check_uri(uri))
                           throw Exception("httpd.request", 0, "invalid uri '%s'", uri);
   
                 return str_upper(method_line, uri_start-method_line);                  return str_upper(method_line, uri_start-method_line);
         }          }
   
         void read_header(int);  
           bool read_header(int);
         size_t read_post(int, char *, size_t);          size_t read_post(int, char *, size_t);
 };  };
   
Line 952  enum HTTPD_request_state { Line 1034  enum HTTPD_request_state {
         HTTPD_HEADERS          HTTPD_HEADERS
 };  };
   
 void HTTPD_request::read_header(int sock) {  ssize_t HTTPD_request::pa_recv(int sockfd, char *buffer, size_t len){
           LOG(pa_log("httpd [%d] recv %d appending to %d ...", sockfd, len, length));
           if(HTTPD_Server::mode == HTTPD_Server::MULTITHREADED){
                   ssize_t result=recv(sockfd, buffer, len, 0);
                   LOG(pa_log("httpd [%d] recv got %d bytes", sockfd, result));
                   return result;
           }
   
   #ifdef PA_USE_ALARM
           signal(SIGALRM, timeout_handler);
           if(sigsetjmp(timeout_env, 1)) {
                   LOG(pa_log("httpd [%d] recv got %d sec timeout", sockfd, pa_httpd_timeout));
                   if(length) // timeout on "void" connection is normal
                           throw Exception("httpd.timeout", 0, "timeout occurred while receiving request");
                   return 0;
           } else
   #endif
           {
                   ALARM(pa_httpd_timeout);
                   ssize_t result=recv(sockfd, buffer, len, 0);
                   ALARM(0);
                   LOG(pa_log("httpd [%d] recv got %d bytes", sockfd, result));
                   LOG(pa_log("httpd [%d] %s", sockfd, buffer));
                   return result;
           }
   }
   
   bool HTTPD_request::read_header(int sock) {
         enum HTTPD_request_state state = HTTPD_METHOD;          enum HTTPD_request_state state = HTTPD_METHOD;
   
         size_t chunk_size = 0x400*4;          size_t chunk_size = 0x400*4;
Line 968  void HTTPD_request::read_header(int sock Line 1077  void HTTPD_request::read_header(int sock
                                 char *method_line = pa_strdup(buf, method_size);                                  char *method_line = pa_strdup(buf, method_size);
                                 method = extract_method(method_line);                                  method = extract_method(method_line);
   
                                 if(!method || strcmp(method, "GET") && strcmp(method, "HEAD") && strcmp(method, "POST") && strcmp(method, "PUT") && strcmp(method, "DELETE"))                                  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");                                          throw Exception("httpd.method", new String(method ? method : method_line), "invalid request method");
                                 state = HTTPD_HEADERS;                                  state = HTTPD_HEADERS;
                         }                          }
Line 978  void HTTPD_request::read_header(int sock Line 1094  void HTTPD_request::read_header(int sock
                                         break;                                          break;
   
                                 parse_headers();                                  parse_headers();
                                 return;                                  return true;
                         }                          }
                 }                  }
         }          }
   
           if(!length){ // browsers open connections in advance and they will be empty unless user requests more pages
                   LOG(pa_log("httpd [%d] void request", sock));
                   return false;
           }
   
         if(state == HTTPD_METHOD)          if(state == HTTPD_METHOD)
                 throw Exception("httpd.request", 0, "bad request from host - no method found (size=%u)", length);                  throw Exception("httpd.request", 0, "bad request from host - no method found (size=%u)", length);
   
Line 990  void HTTPD_request::read_header(int sock Line 1111  void HTTPD_request::read_header(int sock
                 parse_headers();                  parse_headers();
                 body_offset=length;                  body_offset=length;
         }          }
   
           return true;
 }  }
   
 size_t HTTPD_request::read_post(int sock, char *body, size_t max_bytes) {  size_t HTTPD_request::read_post(int sock, char *body, size_t max_bytes) {
         size_t total_read = min(length - body_offset, max_bytes);          size_t total_read = min(length - body_offset, max_bytes);
         memcpy(body, buf, total_read);          memcpy(body, buf + body_offset, total_read);
   
         while (total_read < max_bytes){          while (total_read < max_bytes){
                 ssize_t received_size = recv(sock, buf + total_read, max_bytes - total_read, 0);                  ssize_t received_size = pa_recv(sock, body + total_read, max_bytes - total_read);
                 if(received_size == 0)                  if(received_size == 0)
                         return total_read;                          return total_read;
                 if(received_size < 0) {                  if(received_size < 0) {
                         if(int no = pa_socks_errno())                          if(int no = pa_socks_errno())
                                 throw Exception("httpd.timeout", &url, "error receiving request body: %s (%d)", pa_socks_strerr(no), no);                                  throw Exception("httpd.read", new String(uri), "error receiving request body: %s (%d)", pa_socks_strerr(no), no);
                         return total_read;                          return total_read;
                 }                  }
                 total_read += received_size;                  total_read += received_size;
Line 1032  uint64_t HTTPD_Connection::content_lengt Line 1155  uint64_t HTTPD_Connection::content_lengt
         return request->headers.content_length;          return request->headers.content_length;
 }  }
   
 void HTTPD_Connection::read_header(){  bool HTTPD_Connection::read_header(){
         request = new HTTPD_request();          request = new HTTPD_request();
         request->read_header(sock);          bool result = request->read_header(sock);
           LOG(if(result){
                   pa_log("httpd [%d] got %s \"%s\"", sock, method(), uri());
           })
           return result;
 }  }
   
 size_t HTTPD_Connection::read_post(char *body, size_t max_bytes) {  size_t HTTPD_Connection::read_post(char *body, size_t max_bytes) {
         return request->read_post(sock, body, max_bytes);          return request->read_post(sock, body, max_bytes);
 }  }
   
 static int sock_on = 1;  size_t HTTPD_Connection::send_body(const void *buf, size_t size) {
           LOG(pa_log("httpd [%d] response %d bytes", sock, size));
           LOG(pa_log("httpd [%d] %s", sock, buf));
           if(send(sock, (const char*)buf, size, 0) != (ssize_t)size) {
                   int no=pa_socks_errno();
                   throw Exception("httpd.write", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no);
           }
           return size;
   }
   
   HTTPD_Connection::~HTTPD_Connection(){
           if(sock != -1){
                   LOG(pa_log("httpd [%d] closed", sock));
                   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));
           LOG(pa_log("httpd [%d] accepted from %s", sock, remote_addr));
           return true;
   }
   
   HTTPD_Server::HTTPD_MODE HTTPD_Server::mode = HTTPD_Server::SEQUENTIAL;
   
   void HTTPD_Server::set_mode(const String &value){
                   if(value == "sequental") mode = SEQUENTIAL;
                   else if (value == "threaded") mode = MULTITHREADED;
   #ifdef _MSC_VER
                   else throw Exception("httpd.mode", &value, "$main:HTTPD.mode must be 'sequental' or 'threaded'");
   #else
                   else if (value == "parallel") mode = PARALLEL;
                   else throw Exception("httpd.mode", &value, "$main:HTTPD.mode must be 'sequental', 'parallel' or 'threaded'");
   #endif
   }
   
 int HTTPD_Server::bind(const char *host_port){  int HTTPD_Server::bind(const char *host_port){
         struct sockaddr_in me;          struct sockaddr_in me;
Line 1055  int HTTPD_Server::bind(const char *host_ Line 1248  int HTTPD_Server::bind(const char *host_
                 port = host_port;                  port = host_port;
         }          }
   
         if(!set_addr(&me, host, pa_atoui(port, 10))){          if(!set_addr(&me, host, (short)pa_atoui(port))){
                 if (host)                  if (host)
                         throw Exception("httpd.bind", 0, "can not resolve hostname \"%s\"", host);                          throw Exception("httpd.bind", 0, "can not resolve hostname \"%s\"", host);
                 me.sin_addr.s_addr=INADDR_ANY;                  me.sin_addr.s_addr=INADDR_ANY;
Line 1068  int HTTPD_Server::bind(const char *host_ Line 1261  int HTTPD_Server::bind(const char *host_
                 throw Exception("httpd.bind", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no);                  throw Exception("httpd.bind", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no);
         }          }
   
           static int sock_on = 1;
   
         if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_on, sizeof(sock_on)) ||          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)) ||              setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&sock_on, sizeof(sock_on)) ||
             ::bind(sock, (struct sockaddr*)&me, sizeof(me)) ||              ::bind(sock, (struct sockaddr*)&me, sizeof(me)) ||
Line 1078  int HTTPD_Server::bind(const char *host_ Line 1273  int HTTPD_Server::bind(const char *host_
         }          }
         return sock;          return sock;
 }  }
   
 static int 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 */  
         }  
 }  
   
 HTTPD_Connection *HTTPD_Server::accept(int sock, int timeout_value) {  
         int ready = ::ready(sock, 0, timeout_value);  
         if (ready < 0) {  
                 int no=pa_socks_errno();  
                 if(no == EINTR)  
                         return NULL;  
                 throw Exception("httpd.accept", 0, "error waiting for connection: %s (%d)", pa_socks_strerr(no), no);  
         }  
         if (ready == 0) {  
                 /* Timeout */  
                 return NULL;  
         }  
   
         struct sockaddr_in addr;  
         socklen_t sock_addr_len = sizeof(struct sockaddr_in);  
         memset(&addr, 0, sock_addr_len);  
   
         int csock = ::accept(sock, (struct sockaddr *)&addr, &sock_addr_len);  
         if(csock == -1){  
                 int no=pa_socks_errno();  
                 throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no);  
         }  
   
         return new HTTPD_Connection(csock, pa_strdup(inet_ntoa(addr.sin_addr)));  
 }  
   

Removed from v.1.89  
changed lines
  Added in v.1.112


E-mail: