Diff for /parser3/src/main/pa_http.C between versions 1.103 and 1.121

version 1.103, 2020/11/13 16:55:48 version 1.121, 2023/09/26 20:49:10
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-2023 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
  */   */
   
 #include "pa_http.h"  #include "pa_http.h"
Line 16 Line 16
   
 volatile const char * IDENT_PA_HTTP_C="$Id$" IDENT_PA_HTTP_H;   volatile const char * IDENT_PA_HTTP_C="$Id$" IDENT_PA_HTTP_H; 
   
 #ifdef _MSC_VER  
 #include <windows.h>  
 #define socklen_t int  
 #else  
 #define closesocket close  
 #endif  
   
 // defines  // defines
   
 #define HTTP_METHOD_NAME        "method"  #define HTTP_METHOD_NAME        "method"
Line 113  public: Line 106  public:
                 buf=(char *)pa_realloc(buf, size + 1);                  buf=(char *)pa_realloc(buf, size + 1);
         }          }
   
         bool read(int sock, size_t size){          bool read(SOCKET 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);
Line 189  public: Line 182  public:
                 }                  }
         }          }
   
         int read_response(int sock, bool fail_on_status_ne_200);          int read_response(SOCKET sock, bool fail_on_status_ne_200);
 };  };
   
 enum HTTP_response_state {  enum HTTP_response_state {
Line 198  enum HTTP_response_state { Line 191  enum HTTP_response_state {
         HTTP_BODY          HTTP_BODY
 };  };
   
 int HTTP_response::read_response(int sock, bool fail_on_status_ne_200) {  int HTTP_response::read_response(SOCKET sock, bool fail_on_status_ne_200) {
         HTTP_response_state state=HTTP_STATUS_CODE;          HTTP_response_state state=HTTP_STATUS_CODE;
         int result=0;          int result=0;
   
Line 264  static sigjmp_buf timeout_env; Line 257  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)  
   #define PA_NO_THREADS (HTTPD_Server::mode != HTTPD_Server::MULTITHREADED)
   
   #define ALARM(value) if(PA_NO_THREADS) alarm(value)
 #else  #else
 #define ALARM(value)  #define ALARM(value)
 #endif  #endif
Line 273  static int http_request(HTTP_response& r Line 269  static int http_request(HTTP_response& r
         if(!host)          if(!host)
                 throw Exception("http.host", 0, "zero hostname");  //never                  throw Exception("http.host", 0, "zero hostname");  //never
   
         volatile int sock=-1; // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning]          volatile SOCKET sock=INVALID_SOCKET; // to prevent makeing it register variable, because it will be clobbered by longjmp [thanks gcc warning]
                                   
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
         signal(SIGALRM, timeout_handler);          if(PA_NO_THREADS) signal(SIGALRM, timeout_handler);
         if(sigsetjmp(timeout_env, 1)) {          if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) {
                 // duplicating closesocket to make code more simple for old compilers                  // duplicating closesocket to make code more simple for old compilers
                 if(sock>=0)                  if(sock != INVALID_SOCKET)
                         closesocket(sock);                          closesocket(sock);
                 throw Exception("http.timeout", 0, "timeout occurred while retrieving document");                  throw Exception("http.timeout", 0, "timeout occurred while retrieving document");
                 return 0; // never                  return 0; // never
Line 294  static int http_request(HTTP_response& r Line 290  static int http_request(HTTP_response& r
                         if(!set_addr(&dest, host, port))                          if(!set_addr(&dest, host, port))
                                 throw Exception("http.host", 0, "can not resolve hostname \"%s\"", host);                                   throw Exception("http.host", 0, "can not resolve hostname \"%s\"", host); 
                                                   
                         if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/))<0) {                          if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/)) == INVALID_SOCKET) {
                                 int no=pa_socks_errno();                                  int no=pa_socks_errno();
                                 throw Exception("http.connect", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no);                                   throw Exception("http.connect", 0, "can not make socket: %s (%d)", pa_socks_strerr(no), no); 
                         }                          }
Line 329  static int http_request(HTTP_response& r Line 325  static int http_request(HTTP_response& r
                         return result;                          return result;
                 } catch(...) {                  } catch(...) {
                         ALARM(0);                          ALARM(0);
                         if(sock>=0)                          if(sock != INVALID_SOCKET)
                                 closesocket(sock);                                  closesocket(sock);
                         rethrow;                          rethrow;
                 }                  }
Line 739  File_read_http_result pa_internal_file_r Line 735  File_read_http_result pa_internal_file_r
                         throw Exception(PARSER_RUNTIME, &connect_string, "does not start with http://"); //never                          throw Exception(PARSER_RUNTIME, &connect_string, "does not start with http://"); //never
                 current+=7;                  current+=7;
   
                 strncpy(host, current, sizeof(host)-1);  host[sizeof(host)-1]=0;                  pa_strncpy(host, current, sizeof(host));
                 char* host_uri=lsplit(host, '/');                  char* host_uri=lsplit(host, '/');
                 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, ':');
Line 913  File_read_http_result pa_internal_file_r Line 909  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 {  enum EscapeState {
         Initial,          Initial,
         Default,          Default,
Line 922  enum EscapeState { Line 925  enum EscapeState {
   
 static bool check_uri(const char *uri){  static bool check_uri(const char *uri){
         EscapeState state=Initial;          EscapeState state=Initial;
         uint escapedValue;          uint escapedValue=0;
   
         const char *pattern="/../";          const char *pattern="/../";
         const char *pos=pattern;          const char *pos=pattern;
Line 953  static bool check_uri(const char *uri){ Line 956  static bool check_uri(const char *uri){
                         case EscapeSecond:                          case EscapeSecond:
                                 if(isxdigit(c)){                                  if(isxdigit(c)){
                                         state=Default;                                          state=Default;
                                         c=escapedValue + hex_value[c];                                          c=(uchar)(escapedValue + hex_value[c]);
   
                                         // implementing Apache AllowEncodedSlashes Off just in case                                          // implementing Apache AllowEncodedSlashes Off just in case
                                         if(c=='/' || c=='\\')                                          if(c=='/' || c=='\\')
Line 981  public: Line 984  public:
   
         HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){};          HTTPD_request() : HTTP_response(), method(NULL), uri(NULL){};
   
         ssize_t pa_recv(int sockfd, void *buf, size_t len);          ssize_t pa_recv(SOCKET sockfd, char *buf, size_t len);
   
         bool read(int sock, size_t size){          bool read(SOCKET 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=pa_recv(sock, buf + length, size);                  ssize_t received_size=pa_recv(sock, buf + length, size);
Line 991  public: Line 994  public:
                         return false;                          return false;
                 if(received_size < 0) {                  if(received_size < 0) {
                         if(int no = pa_socks_errno())                          if(int no = pa_socks_errno())
                                 throw Exception("httpd.timeout", 0, "error receiving request: %s (%d)", pa_socks_strerr(no), no);                                  throw Exception("httpd.read", 0, "error receiving request: %s (%d)", pa_socks_strerr(no), no);
                         return false;                          return false;
                 }                  }
                 length+=received_size;                  length+=received_size;
Line 1018  public: Line 1021  public:
         }          }
   
   
         void read_header(int);          bool read_header(SOCKET);
         size_t read_post(int, char *, size_t);          size_t read_post(SOCKET, char *, size_t);
 };  };
   
 enum HTTPD_request_state {  enum HTTPD_request_state {
Line 1027  enum HTTPD_request_state { Line 1030  enum HTTPD_request_state {
         HTTPD_HEADERS          HTTPD_HEADERS
 };  };
   
 ssize_t HTTPD_request::pa_recv(int sockfd, void *buf, size_t len){  ssize_t HTTPD_request::pa_recv(SOCKET sockfd, char *buffer, size_t len){
           LOG(pa_log("httpd [%d] recv %d appending to %d ...", sockfd, len, length));
   
 #ifdef PA_USE_ALARM  #ifdef PA_USE_ALARM
         signal(SIGALRM, timeout_handler);          if(PA_NO_THREADS) signal(SIGALRM, timeout_handler);
         if(sigsetjmp(timeout_env, 1)) {          if(PA_NO_THREADS && sigsetjmp(timeout_env, 1)) {
                 throw Exception("httpd.timeout", 0, "timeout occurred while receiving request");                  LOG(pa_log("httpd [%d] recv got %d sec timeout", sockfd, pa_httpd_timeout));
                 return 0; // never                  if(length) // timeout on "void" connection is normal
                           throw Exception("httpd.timeout", 0, "timeout occurred while receiving request");
                   return 0;
         } else          } else
 #endif  #endif
         {          {
                 ALARM(pa_httpd_timeout);                  ALARM(pa_httpd_timeout);
                 ssize_t result=recv(sockfd, buf, len, 0);                  ssize_t result=recv(sockfd, buffer, len, 0);
                 ALARM(0);                  ALARM(0);
                   LOG(pa_log("httpd [%d] recv got %d bytes", sockfd, result));
                   LOG(pa_log("httpd [%d] %s", sockfd, buffer));
                 return result;                  return result;
         }          }
 }  }
   
 void HTTPD_request::read_header(int sock) {  static bool valid_http_method(const char * method){
           return method && (
                   !strcmp(method, "GET") ||
                   !strcmp(method, "HEAD") ||
                   !strcmp(method, "POST") ||
                   !strcmp(method, "PUT") ||
                   !strcmp(method, "DELETE") ||
                   !strcmp(method, "CONNECT") ||
                   !strcmp(method, "OPTIONS") ||
                   !strcmp(method, "TRACE") ||
                   !strcmp(method, "PATCH")
           );
   }
   
   bool HTTPD_request::read_header(SOCKET 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 1059  void HTTPD_request::read_header(int sock Line 1082  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 ||                                  if(!valid_http_method(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 1076  void HTTPD_request::read_header(int sock Line 1092  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 1088  void HTTPD_request::read_header(int sock Line 1109  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(SOCKET 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 + body_offset, total_read);          memcpy(body, buf + body_offset, total_read);
   
Line 1100  size_t HTTPD_request::read_post(int sock Line 1123  size_t HTTPD_request::read_post(int sock
                         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", new String(uri), "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 1130  uint64_t HTTPD_Connection::content_lengt Line 1153  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) {
Line 1140  size_t HTTPD_Connection::read_post(char Line 1167  size_t HTTPD_Connection::read_post(char
 }  }
   
 size_t HTTPD_Connection::send_body(const void *buf, size_t size) {  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) {          if(send(sock, (const char*)buf, size, 0) != (ssize_t)size) {
                 int no=pa_socks_errno();                  int no=pa_socks_errno();
                 throw Exception("httpd.timeout", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no);                  throw Exception("httpd.write", 0, "error sending response: %s (%d)", pa_socks_strerr(no), no);
         }          }
         return size;          return size;
 }  }
   
 HTTPD_Connection::~HTTPD_Connection(){  HTTPD_Connection::~HTTPD_Connection(){
         if(sock != -1)          if(sock != INVALID_SOCKET){
                   LOG(pa_log("httpd [%d] closed", sock));
                 closesocket(sock);                  closesocket(sock);
           }
 }  }
   
 static int sock_ready(int fd,int operation,int timeout_value){  static int sock_ready(SOCKET fd, int operation, int timeout_value){
         struct timeval timeout = {0, timeout_value * 1000};          struct timeval timeout = {0, timeout_value * 1000};
         fd_set fds;          fd_set fds;
         FD_ZERO(&fds);          FD_ZERO(&fds);
         FD_SET(fd, &fds);          FD_SET(fd, &fds);
           int nfds = (int)fd + 1; /* typecast as nfds is ignored in MSVC anyway */
         switch (operation){          switch (operation){
                 case 0: return select(fd + 1, &fds, NULL, NULL, &timeout)>0;  /* read */                  case 0: return select(nfds, &fds, NULL, NULL, &timeout)>0;  /* read */
                 case 1: return select(fd + 1, NULL, &fds, NULL, &timeout)>0;  /* write */                  case 1: return select(nfds, NULL, &fds, NULL, &timeout)>0;  /* write */
                 default: return select(fd + 1, &fds, &fds, NULL, &timeout)>0;  /* both */                  default: return select(nfds, &fds, &fds, NULL, &timeout)>0;  /* both */
         }          }
 }  }
   
 bool HTTPD_Connection::accept(int server_sock, int timeout_value) {  bool HTTPD_Connection::accept(SOCKET server_sock, int timeout_value) {
         int ready = sock_ready(server_sock, 0, timeout_value);          int ready = sock_ready(server_sock, 0, timeout_value);
         if (ready < 0) {          if (ready < 0) {
                 int no=pa_socks_errno();                  int no=pa_socks_errno();
Line 1180  bool HTTPD_Connection::accept(int server Line 1212  bool HTTPD_Connection::accept(int server
         memset(&addr, 0, sock_addr_len);          memset(&addr, 0, sock_addr_len);
   
         sock = ::accept(server_sock, (struct sockaddr *)&addr, &sock_addr_len);          sock = ::accept(server_sock, (struct sockaddr *)&addr, &sock_addr_len);
         if(server_sock == -1){          if(sock == INVALID_SOCKET){
                 int no=pa_socks_errno();                  int no=pa_socks_errno();
                 throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no);                  throw Exception("httpd.accept", 0, "error accepting connection: %s (%d)", pa_socks_strerr(no), no);
         }          }
   
         remote_addr = pa_strdup(inet_ntoa(addr.sin_addr));          remote_addr = pa_strdup(inet_ntoa(addr.sin_addr));
           LOG(pa_log("httpd [%d] accepted from %s", sock, remote_addr));
         return true;          return true;
 }  }
   
 int HTTPD_Server::bind(const char *host_port){  HTTPD_Server::HTTPD_MODE HTTPD_Server::mode = HTTPD_Server::SEQUENTIAL;
   const char *HTTPD_Server::port=NULL;
   
   void HTTPD_Server::set_mode(const String &value){
                   if(value == "sequental") mode = SEQUENTIAL;
   #ifdef HAVE_TLS
                   else if (value == "threaded") mode = MULTITHREADED;
   #endif
   #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
   }
   
   SOCKET HTTPD_Server::bind(const char *host_port){
         struct sockaddr_in me;          struct sockaddr_in me;
   
         const char *port = strchr(host_port, ':');          port = strchr(host_port, ':');
         const char *host = NULL;          const char *host = NULL;
         if(port && port > host_port){          if(port){
                 host = pa_strdup(host_port, port - host_port);                  if(port > host_port)
                           host = pa_strdup(host_port, port - host_port);
                 port += 1;                  port += 1;
         } else {          } else {
                 port = host_port;                  port = host_port;
         }          }
   
         if(!set_addr(&me, host, pa_atoui(port))){          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;
         }          }
   
         int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/);          SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/);
   
         if(sock < 0){          if(sock == INVALID_SOCKET){
                 int no=pa_socks_errno();                  int no=pa_socks_errno();
                 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);
         }          }

Removed from v.1.103  
changed lines
  Added in v.1.121


E-mail: