--- parser3/src/targets/cgi/parser3.C 2019/12/27 20:46:26 1.285 +++ parser3/src/targets/cgi/parser3.C 2020/10/14 00:13:01 1.301 @@ -5,7 +5,7 @@ Author: Alexandr Petrosian (http://paf.design.ru) */ -volatile const char * IDENT_PARSER3_C="$Id: parser3.C,v 1.285 2019/12/27 20:46:26 moko Exp $"; +volatile const char * IDENT_PARSER3_C="$Id: parser3.C,v 1.301 2020/10/14 00:13:01 moko Exp $"; #include "pa_config_includes.h" @@ -15,6 +15,7 @@ volatile const char * IDENT_PARSER3_C="$ #include "pa_request.h" #include "pa_version.h" #include "pa_vconsole.h" +#include "pa_sapi_info.h" #ifdef _MSC_VER #include @@ -25,7 +26,7 @@ volatile const char * IDENT_PARSER3_C="$ // defines // comment remove me after debugging -//#define PA_DEBUG_CGI_ENTRY_EXIT "parser3-debug.log" +//#define PA_DEBUG_CGI_ENTRY_EXIT "parser3-debug.log" #if defined(_MSC_VER) && !defined(_DEBUG) # define PA_SUPPRESS_SYSTEM_EXCEPTION @@ -37,31 +38,23 @@ volatile const char * IDENT_PARSER3_C="$ #define PARSER_CONFIG_ENV_NAME "CGI_PARSER_CONFIG" #define PARSER_LOG_ENV_NAME "CGI_PARSER_LOG" -/// IIS refuses to read bigger chunks -const size_t READ_POST_CHUNK_SIZE=0x400*0x400; // 1M - -static const char* argv0; -static const char* config_filespec_cstr=0; -static bool fail_on_config_read_problem=true; +static const char* config_filespec_cstr=0; // -f option +static const char* httpd_host_port=0; // -p option +static bool mail_received=false; // -m option? [asked to parse incoming message to $mail:received] static int args_skip=1; static char** argv_all = NULL; static bool cgi; ///< we were started as CGI? -static bool mail_received=false; ///< we were started with -m option? [asked to parse incoming message to $mail:received] // for signal handlers Request *request=0; -Request_info *request_info=0; bool execution_canceled=false; +// for die error logging +Request_info request_info; // SAPI -class SAPI_Info { -public: - int http_response_code; -} SAPI_info = { 0 }; - static void log(const char* fmt, va_list args) { bool opened=false; FILE *f=0; @@ -111,16 +104,10 @@ static void log(const char* fmt, va_list size=remove_crlf(buf, buf+size); fwrite(buf, size, 1, f); - if(request_info) - fprintf(f, " [uri=%s, method=%s, cl=%lu]", - request_info->uri? request_info->uri: "", - request_info->method? request_info->method: "", - request_info->content_length); - else - fputs(" [no request info]", f); - - // newline - fputs("\n", f); + if(request_info.method) { + fprintf(f, " [uri=%s, method=%s, cl=%lu]\n", request_info.uri ? request_info.uri : "", request_info.method, request_info.content_length); + } else + fputs(" [no request info]\n", f); if(opened) fclose(f); @@ -145,110 +132,46 @@ void SAPI::log(SAPI_Info&, const char* f va_end(args); } -static void die_or_abort(const char* fmt, va_list args, bool write_core) { - // inform user - - char body[MAX_STRING]; - int content_length=vsnprintf(body, MAX_STRING, fmt, args); - - // prepare header - // let's be honest, that's bad we couldn't produce valid output - // capitalized headers passed for preventing malloc during capitalization - SAPI::add_header_attribute(SAPI_info, HTTP_STATUS_CAPITALIZED, "500"); - SAPI::add_header_attribute(SAPI_info, HTTP_CONTENT_TYPE_CAPITALIZED, "text/plain"); - // don't use 'format' function because it calls malloc - char content_length_cstr[MAX_NUMBER]; - snprintf(content_length_cstr, sizeof(content_length_cstr), "%u", content_length); - SAPI::add_header_attribute(SAPI_info, HTTP_CONTENT_LENGTH_CAPITALIZED, content_length_cstr); - - // send header - SAPI::send_header(SAPI_info); - - // body - SAPI::send_body(SAPI_info, body, content_length); - - // exit & try to produce core dump[unix] or invoke debugger[Win32 Debug version] - if(write_core) { -#ifdef WIN32 - // IIS with abort failes to show STDOUT, it just barks "abnormal program termination" - exit(1); -#else - abort(); -#endif - } else - exit(1); -} - void SAPI::die(const char* fmt, ...) { va_list args; - // logging first, can't log inside die_or_abort due to vsnprintf (bug #106) + // logging first, first vsnprintf va_start(args,fmt); ::log(fmt, args); va_end(args); + // inform user, second vsnprintf va_start(args, fmt); - die_or_abort(fmt, args, false /*write core?*/); -// va_end(args); -} - -void SAPI::abort(const char* fmt, ...) { - va_list args; - - // logging first, can't log inside die_or_abort due to vsnprintf (bug #106) - va_start(args,fmt); - ::log(fmt, args); - va_end(args); + char message[MAX_STRING]; + vsnprintf(message, MAX_STRING, fmt, args); - va_start(args, fmt); - die_or_abort(fmt, args, true /*write core?*/); + SAPI::send_error(*sapiInfo, message); + exit(1); // va_end(args); } -char* SAPI::Env::get(SAPI_Info& , const char* name) { - if(char *local=getenv(name)) - return pa_strdup(local); - else - return 0; +char* SAPI::Env::get(SAPI_Info& info, const char* name) { + return info.get_env(name); } -const char* const *SAPI::Env::get(SAPI_Info&) { -#ifdef _MSC_VER - extern char **_environ; - return _environ; -#else - extern char **environ; - return environ; -#endif +const char* const *SAPI::Env::get(SAPI_Info& info) { + return info.get_env(); } -size_t SAPI::read_post(SAPI_Info& , char *buf, size_t max_bytes) { - size_t read_size=0; - do { - ssize_t chunk_size=read(fileno(stdin), buf+read_size, min(READ_POST_CHUNK_SIZE, max_bytes-read_size)); - if(chunk_size<=0) - break; - read_size+=chunk_size; - } while(read_sizeconsole.was_used()) ) - printf("%s: %s\n", capitalize(dont_store_key), dont_store_value); +void SAPI::add_header_attribute(SAPI_Info& info, const char* dont_store_key, const char* dont_store_value) { + info.add_header_attribute(dont_store_key, dont_store_value); } -void SAPI::send_header(SAPI_Info& ) { - if(cgi) { - puts(""); - } +void SAPI::send_header(SAPI_Info& info) { + info.send_header(); } -size_t SAPI::send_body(SAPI_Info& , const void *buf, size_t size) { - return stdout_write(buf, size); +size_t SAPI::send_body(SAPI_Info& info, const void *buf, size_t size) { + return info.send_body(buf, size); } static void full_file_spec(const char* file_name, char *buf, size_t buf_size) { @@ -271,16 +194,7 @@ static void full_file_spec(const char* f } static void log_signal(const char* signal_name) { - if(request_info) - SAPI::log(SAPI_info, "%s received while %s. uri=%s, method=%s, cl=%u", - signal_name, - request ? "executing code" : "reading data", - request_info->uri, - request_info->method, - request_info->content_length); - else - SAPI::log(SAPI_info, "%s received before or after processing request", - signal_name); + SAPI::log(*sapiInfo, "%s received %s processing request", signal_name, request ? "while" : "before or after"); } #ifdef SIGUSR1 @@ -306,8 +220,7 @@ static void SIGPIPE_handler(int /*sig*/) #endif #ifdef WIN32 -const char* maybe_reconstruct_IIS_status_in_qs(const char* original) -{ +const char* maybe_reconstruct_IIS_status_in_qs(const char* original) { // 404;http://servername/page[?param=value...] // ';' should be urlencoded by HTTP standard, so we shouldn't get it from browser // and can consider that as an indication that this is IIS way to report errors @@ -355,27 +268,99 @@ public: } }; +static bool locate_config(){ + if(!config_filespec_cstr) { + config_filespec_cstr=getenv(PARSER_CONFIG_ENV_NAME); + if(!config_filespec_cstr) + config_filespec_cstr=getenv(REDIRECT_PREFIX PARSER_CONFIG_ENV_NAME); + if(!config_filespec_cstr){ + // beside by binary + char beside_binary_path[MAX_STRING]; + strncpy(beside_binary_path, argv_all[0], MAX_STRING-1); beside_binary_path[MAX_STRING-1]=0; // filespec of my binary + if(!(rsplit(beside_binary_path, '/') || rsplit(beside_binary_path, '\\'))) { // strip filename + // no path, just filename + // @todo full path, not ./! + beside_binary_path[0]='.'; beside_binary_path[1]=0; + } + char config_filespec_buf[MAX_STRING]; + snprintf(config_filespec_buf, MAX_STRING, "%s/%s", beside_binary_path, AUTO_FILE_NAME); + config_filespec_cstr=pa_strdup(config_filespec_buf); + return entry_exists(config_filespec_cstr); + } + } + return true; +} + +static void connection_handler(SAPI_Info_HTTPD &info, HTTPD_Connection &connection, const char* filespec_to_process){ + connection.read_header(); + info.populate_env(); -/** -main workhorse + // connection request info, still global for correct log() reporting + memset(&request_info, 0, sizeof(request_info)); - @todo - IIS: remove trailing default-document[index.html] from $request.uri. - to do that we need to consult metabase, - wich is tested but seems slow. -*/ -static void real_parser_handler(const char* filespec_to_process, const char* request_method, bool header_only) { + char document_root_buf[MAX_STRING]; + full_file_spec("", document_root_buf, sizeof(document_root_buf)); + request_info.document_root = document_root_buf; + request_info.path_translated = filespec_to_process; + request_info.method = connection.method(); + request_info.query_string = connection.query(); + request_info.uri = request_info.strip_absolute_uri(connection.uri()); + request_info.content_type = connection.content_type(); + request_info.content_length = connection.content_length(); + request_info.cookie = info.get_env("HTTP_COOKIE"); + request_info.mail_received = false; + request_info.argv = argv_all + args_skip; + + // prepare to process request + Request request(info, request_info, String::Language(String::L_HTML|String::L_OPTIMIZE_BIT)); + { + // initing ::request ptr for signal handlers + RequestController rc(&request); + bool fail_on_config_read_problem=locate_config(); + // process the request + request.core(config_filespec_cstr, fail_on_config_read_problem, strcasecmp(request_info.method, "HEAD")==0); + // clearing ::request in RequestController desctructor to prevent signal handlers from accessing invalid memory + } +} +static void httpd_mode(const char* filespec_to_process){ + int sock = HTTPD_Server::bind(httpd_host_port); + + while(1){ + HTTPD_Connection *connection = HTTPD_Server::accept(sock, 5); + if(!connection) + continue; + + SAPI_Info_HTTPD info(*connection); + + try { // connection try + connection_handler(info, *connection, filespec_to_process); + } catch(const Exception& e) { // exception in connection handling or unhandled exception + SAPI::log(info, "%s", e.comment()); + SAPI::send_error(info, e.comment(), info.exception_http_status(e.type())); + } + connection->close(); + } +} + +/** main workhorse */ + +static void real_parser_handler(const char* filespec_to_process) { // init libraries pa_globals_init(); - + + if(httpd_host_port){ + httpd_mode(filespec_to_process); + } + + const char* request_method=getenv("REQUEST_METHOD"); + if(!filespec_to_process || !*filespec_to_process) SAPI::die("Parser/%s", PARSER_VERSION); - // Request info - Request_info request_info; memset(&request_info, 0, sizeof(request_info)); char document_root_buf[MAX_STRING]; + // global request info request_info.path_translated = filespec_to_process; request_info.method = request_method ? request_method : "GET"; request_info.query_string = MAYBE_RECONSTRUCT_IIS_STATUS_IN_QS(getenv("QUERY_STRING")); @@ -423,7 +408,7 @@ static void real_parser_handler(const ch SAPI::die("CGI: illegal call (1)"); } else { // fcgiwrap minimalistic setup - if(request_info.query_string) { + if(request_info.query_string && *request_info.query_string) { char* reconstructed_uri = new(PointerFreeGC) char[strlen(path_info) + 1/*'?'*/+ strlen(request_info.query_string) + 1/*0*/]; strcpy(reconstructed_uri, path_info); strcat(reconstructed_uri, "?"); @@ -439,21 +424,17 @@ static void real_parser_handler(const ch } request_info.content_type = getenv("CONTENT_TYPE"); - const char* content_length = getenv("CONTENT_LENGTH"); - request_info.content_length = content_length ? atoi(content_length) : 0; + request_info.content_length = pa_atoui(getenv("CONTENT_LENGTH"), 10); request_info.cookie = getenv("HTTP_COOKIE"); request_info.mail_received = mail_received; - request_info.argv = argv_all; - request_info.args_skip = args_skip; + request_info.argv = argv_all + args_skip; - // get request_info ptr for signal handlers - ::request_info = &request_info; if(execution_canceled) SAPI::die("Execution canceled"); #ifdef PA_DEBUG_CGI_ENTRY_EXIT - log("request_info: method=%s, uri=%s, q=%s, dr=%s, pt=%s, cookies=%s, cl=%u", + log("request_info: method=%s, uri=%s, q=%s, dr=%s, pt=%s, cookies=%s, cl=%u", request_info.method, request_info.uri, request_info.query_string, @@ -464,38 +445,14 @@ static void real_parser_handler(const ch #endif // prepare to process request - Request request(SAPI_info, request_info, cgi ? String::Language(String::L_HTML|String::L_OPTIMIZE_BIT) : String::L_AS_IS); - + Request request(*sapiInfo, request_info, cgi ? String::Language(String::L_HTML|String::L_OPTIMIZE_BIT) : String::L_AS_IS); { - // get ::request ptr for signal handlers + // initing ::request ptr for signal handlers RequestController rc(&request); - - char config_filespec_buf[MAX_STRING]; - if(!config_filespec_cstr) { - const char* config_by_env=getenv(PARSER_CONFIG_ENV_NAME); - if(!config_by_env) - config_by_env=getenv(REDIRECT_PREFIX PARSER_CONFIG_ENV_NAME); - if(config_by_env) - config_filespec_cstr=config_by_env; - else { - // beside by binary - char beside_binary_path[MAX_STRING]; - strncpy(beside_binary_path, argv0, MAX_STRING-1); beside_binary_path[MAX_STRING-1]=0; // filespec of my binary - if(!(rsplit(beside_binary_path, '/') || rsplit(beside_binary_path, '\\'))) { // strip filename - // no path, just filename - // @todo full path, not ./! - beside_binary_path[0]='.'; beside_binary_path[1]=0; - } - snprintf(config_filespec_buf, MAX_STRING, "%s/%s", beside_binary_path, AUTO_FILE_NAME); - config_filespec_cstr=config_filespec_buf; - fail_on_config_read_problem=entry_exists(config_filespec_cstr); - } - } - + bool fail_on_config_read_problem=locate_config(); // process the request - request.core(config_filespec_cstr, fail_on_config_read_problem, header_only); - - // ::request cleared in RequestController desctructor to prevent signal handlers from accessing invalid memory + request.core(config_filespec_cstr, fail_on_config_read_problem, strcasecmp(request_info.method, "HEAD")==0); + // clearing ::request in RequestController desctructor to prevent signal handlers from accessing invalid memory } // finalize libraries @@ -503,9 +460,9 @@ static void real_parser_handler(const ch } #ifdef PA_SUPPRESS_SYSTEM_EXCEPTION -static const Exception call_real_parser_handler__do_PEH_return_it(const char* filespec_to_process, const char* request_method, bool header_only){ +static const Exception call_real_parser_handler__do_PEH_return_it(const char* filespec_to_process) { try { - real_parser_handler(filespec_to_process, request_method, header_only); + real_parser_handler(filespec_to_process); } catch(const Exception& e) { return e; } @@ -513,17 +470,13 @@ static const Exception call_real_parser_ return Exception(); } -static void call_real_parser_handler__supress_system_exception(const char* filespec_to_process, const char* request_method, bool header_only){ +static void call_real_parser_handler__supress_system_exception(const char* filespec_to_process) { Exception parser_exception; LPEXCEPTION_POINTERS system_exception=0; __try { - parser_exception=call_real_parser_handler__do_PEH_return_it(filespec_to_process, request_method, header_only); - } __except ( - (system_exception=GetExceptionInformation()), - EXCEPTION_EXECUTE_HANDLER) - { - + parser_exception=call_real_parser_handler__do_PEH_return_it(filespec_to_process); + } __except ( (system_exception=GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) { if(system_exception) if(_EXCEPTION_RECORD *er=system_exception->ExceptionRecord) throw Exception("system", 0, "0x%08X at 0x%08X", er->ExceptionCode, er->ExceptionAddress); @@ -554,17 +507,26 @@ static void usage(const char* program) { " -m Parse mail, put received letter to $mail:received\n" #endif " -f config_file Use this config file (/path/to/auto.p)\n" + " -p [host:]port Start web server on this port\n" " -h Display usage information (this message)\n", PARSER_VERSION, program); exit(EINVAL); } + int main(int argc, char *argv[]) { #ifdef PA_DEBUG_CGI_ENTRY_EXIT log("main: entry"); #endif + argv_all=argv; + umask(2); + + // were we started as CGI? + cgi=(getenv("SERVER_SOFTWARE") || getenv("SERVER_NAME") || getenv("GATEWAY_INTERFACE") || getenv("REQUEST_METHOD")) && !getenv("PARSER_VERSION"); + sapiInfo = cgi ? new SAPI_Info_CGI() : new SAPI_Info(); + #ifdef SIGUSR1 if(signal(SIGUSR1, SIGUSR1_handler)==SIG_ERR) SAPI::die("Can not set handler for SIGUSR1"); @@ -574,15 +536,7 @@ int main(int argc, char *argv[]) { SAPI::die("Can not set handler for SIGPIPE"); #endif - argv_all=argv; - argv0=argv[0]; - - umask(2); - - // were we started as CGI? - cgi=(getenv("SERVER_SOFTWARE") || getenv("SERVER_NAME") || getenv("GATEWAY_INTERFACE") || getenv("REQUEST_METHOD")) && !getenv("PARSER_VERSION"); - - char *raw_filespec_to_process; + char *raw_filespec_to_process = NULL; if(cgi) { raw_filespec_to_process=getenv("PATH_TRANSLATED"); if(raw_filespec_to_process && !*raw_filespec_to_process) @@ -606,6 +560,12 @@ int main(int argc, char *argv[]) { config_filespec_cstr=argv[optind]; } break; + case 'p': + if(optind < argc - 1){ + optind++; + httpd_host_port=argv[optind]; + } + break; #ifdef WITH_MAILRECEIVE case 'm': mail_received=true; @@ -621,11 +581,19 @@ int main(int argc, char *argv[]) { } if (optind > argc - 1) { - fprintf(stderr, "%s: file not specified\n", argv[0]); - usage(argv[0]); + if(!httpd_host_port) { + fprintf(stderr, "%s: file not specified\n", argv[0]); + usage(argv[0]); + } + } else { + raw_filespec_to_process=argv[optind]; } - raw_filespec_to_process=argv[optind]; args_skip=optind; + + if (httpd_host_port && mail_received) { + fprintf(stderr, "%s: -p and -m options should not be used together\n", argv[0]); + usage(argv[0]); + } } #ifdef _MSC_VER @@ -651,43 +619,14 @@ int main(int argc, char *argv[]) { char filespec_to_process[MAX_STRING]; full_file_spec(raw_filespec_to_process, filespec_to_process, sizeof(filespec_to_process)); - const char* request_method=getenv("REQUEST_METHOD"); - bool header_only=request_method && strcasecmp(request_method, "HEAD")==0; - try { // global try - REAL_PARSER_HANDLER(filespec_to_process, request_method, header_only); - } catch(const Exception& e) { // global problem - // don't allocate anything on pool here: - // possible pool' exception not catch-ed now - // and there could be out-of-memory exception - char buf[MAX_STRING]; - snprintf(buf, MAX_STRING, "Unhandled exception %s", e.comment()); - // log it - SAPI::log(SAPI_info, "%s", buf); - - // - int content_length=strlen(buf); - - // prepare header - // capitalized headers are used for preventing malloc during capitalization - SAPI::add_header_attribute(SAPI_info, HTTP_CONTENT_TYPE_CAPITALIZED, "text/plain"); - // don't use 'format' function because it calls malloc - char content_length_cstr[MAX_NUMBER]; - snprintf(content_length_cstr, MAX_NUMBER, "%u", content_length); - SAPI::add_header_attribute(SAPI_info, HTTP_CONTENT_LENGTH_CAPITALIZED, content_length_cstr); - - // send header - SAPI::send_header(SAPI_info); - - // send body - if(!header_only) - SAPI::send_body(SAPI_info, buf, content_length); - - // unsuccessful finish + REAL_PARSER_HANDLER(filespec_to_process); + } catch(const Exception& e) { // exception in unhandled exception + SAPI::die("%s", e.comment()); } #ifdef PA_DEBUG_CGI_ENTRY_EXIT log("main: successful return"); #endif - return SAPI_info.http_response_code < 100 ? SAPI_info.http_response_code : 0; + return sapiInfo->http_response_code < 100 ? sapiInfo->http_response_code : 0; }