Annotation of parser3/src/targets/cgi/parser3.C, revision 1.387
1.27 paf 1: /** @file
2: Parser: scripting and CGI main.
3:
1.368 moko 4: Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.350 moko 5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
1.189 paf 6: */
1.27 paf 7:
1.387 ! moko 8: volatile const char * IDENT_PARSER3_C="$Id: parser3.C,v 1.386 2026/10/26 22:52:46 moko Exp $";
1.1 paf 9:
1.40 paf 10: #include "pa_config_includes.h"
1.3 paf 11:
1.37 paf 12: #include "pa_sapi.h"
1.76 paf 13: #include "classes.h"
1.24 paf 14: #include "pa_common.h"
1.2 paf 15: #include "pa_request.h"
1.68 paf 16: #include "pa_version.h"
1.333 moko 17: #include "pa_threads.h"
1.266 moko 18: #include "pa_vconsole.h"
1.294 moko 19: #include "pa_sapi_info.h"
1.375 moko 20: #include "pa_code_cache.h"
1.379 moko 21: #include "pa_websocket.h"
1.387 ! moko 22: #include "pa_os.h"
1.207 paf 23:
1.263 moko 24: #ifdef _MSC_VER
1.264 moko 25: #include <crtdbg.h>
1.263 moko 26: #include <windows.h>
27: #include <direct.h>
1.328 moko 28:
29: extern "C" HANDLE WINAPI GC_CreateThread(LPSECURITY_ATTRIBUTES, DWORD, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
30:
31: #else
32:
33: extern "C" int GC_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void * /* arg */);
34:
1.120 parser 35: #endif
36:
1.232 paf 37: // defines
1.231 paf 38:
1.233 paf 39: // comment remove me after debugging
1.362 moko 40: //#define PA_DEBUG_CGI_ENTRY_EXIT
1.233 paf 41:
1.278 moko 42: #if defined(_MSC_VER) && !defined(_DEBUG)
1.231 paf 43: # define PA_SUPPRESS_SYSTEM_EXCEPTION
44: #endif
45:
1.109 parser 46: // consts
1.84 parser 47:
1.175 paf 48: #define REDIRECT_PREFIX "REDIRECT_"
1.181 paf 49: #define PARSER_CONFIG_ENV_NAME "CGI_PARSER_CONFIG"
1.226 paf 50: #define PARSER_LOG_ENV_NAME "CGI_PARSER_LOG"
1.159 paf 51:
1.358 moko 52: SAPI_Info sapi_console;
53: SAPI_Info_CGI sapi_cgi;
54:
55: static SAPI_Info *sapi_info = &sapi_cgi;
56: static THREAD_LOCAL SAPI_Info *sapi_info_4log = NULL; // global for correct send error in die()
1.357 moko 57:
1.365 moko 58: const char* parser3_mode = "cgi"; // $status:mode
1.316 moko 59: static const char* filespec_to_process = 0; // [file]
60: static const char* httpd_host_port = 0; // -p option
1.319 moko 61: static const char* config_filespec = 0; // -f option or from env or next to the executable if exists
1.374 moko 62: static const char* log_filespec = 0; // -l option or from env, full disk path
1.316 moko 63: static bool mail_received = false; // -m option? [asked to parse incoming message to $mail:received]
1.347 moko 64: static const char* parser3_filespec = 0; // argv[0]
1.319 moko 65: static char** argv_extra = NULL;
1.245 misha 66:
1.317 moko 67: // for error logging
1.341 moko 68: static THREAD_LOCAL Request_info *request_info_4log = NULL; // global for correct log() reporting
1.374 moko 69: static const char* filespec_4log = NULL; // null only if system-wide auto.p used; full disk path, parser3.log goes next to it
1.201 paf 70:
1.366 moko 71: const char *parser3_log_filespec() { // $status:log-filename
1.363 moko 72: const char* slog=log_filespec;
1.61 paf 73:
1.366 moko 74: if(!slog) {
1.381 moko 75: static THREAD_LOCAL char log_spec[MAX_STRING + 12 /* '/parser3.log' */];
1.352 moko 76: pa_strncpy(log_spec, filespec_4log, MAX_STRING);
77:
1.374 moko 78: if(char* log_dir_pos=strrchr(log_spec, '/')){
1.352 moko 79: strcpy(log_dir_pos, "/parser3.log");
80: } else {
1.374 moko 81: // system-wide auto.p used: the current directory
1.352 moko 82: strcpy(log_spec, "./parser3.log");
1.193 paf 83: }
1.366 moko 84: slog=log_spec;
1.193 paf 85: }
1.366 moko 86: return slog;
87: }
88:
89: // SAPI
90:
1.385 moko 91: #ifndef O_CLOEXEC
92: #define O_CLOEXEC 0
93: #endif
94: #ifndef STDERR_FILENO
95: #define STDERR_FILENO 2
96: #endif
1.363 moko 97:
1.385 moko 98: static void pa_log(const char* stamp, const char* fmt, va_list args) {
99: // use no memory (to log out-of-memory error): the line is built on the stack and written at once
100: char line[64 + MAX_LOG_STRING + 1024 + 32 + 100]; // prefix + message + request info suffix: uri + method and the rest of it
101: size_t size=0;
1.61 paf 102:
103: // prefix
1.387 ! moko 104: if(!stamp)
! 105: stamp=pa_ctime(time(0));
! 106: if(size_t len=strlen(stamp)) // saw once stamp being =""
! 107: size=pa_snprintf(line, 64, "[%.*s] [%u] ", (int)len-1 /* exclude \n */, stamp, (unsigned int)pa_get_thread_id() );
1.366 moko 108:
1.61 paf 109: // message
1.385 moko 110: size+=remove_crlf(line+size, line+size+pa_vsnprintf(line+size, MAX_LOG_STRING, fmt, args));
1.61 paf 111:
1.385 moko 112: // suffix
113: if(request_info_4log && request_info_4log->method)
114: size+=pa_snprintf(line+size, sizeof(line)-size, " [uri=%.1024s, method=%.32s, cl=%lu]\n", request_info_4log->uri ? request_info_4log->uri : "<unknown>", request_info_4log->method, (unsigned long)request_info_4log->content_length);
1.85 parser 115: else
1.385 moko 116: size+=pa_snprintf(line+size, sizeof(line)-size, " [no request info]\n");
117:
118: const char* slog=parser3_log_filespec();
119: int fd=strcmp(slog,"-") ? pa_open(slog, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0666) : -1;
120: if(fd<0)
121: fd=STDERR_FILENO; // fallback to stderr
122: PA_UNUSED int ignore_result=write(fd, line, (unsigned int)size);
123: if(fd!=STDERR_FILENO)
124: close(fd);
1.124 parser 125: }
1.272 moko 126:
1.335 moko 127: void pa_log(const char* fmt, ...) {
1.272 moko 128: va_list args;
1.233 paf 129: va_start(args,fmt);
1.385 moko 130: pa_log(0, fmt, args);
1.233 paf 131: va_end(args);
132: }
1.124 parser 133:
1.387 ! moko 134: // for signal handlers, where pa_ctime can't be called: the stamp is prepared in advance
! 135: static void pa_log_at(const char* stamp, const char* fmt, ...) {
! 136: va_list args;
! 137: va_start(args,fmt);
! 138: pa_log(stamp, fmt, args);
! 139: va_end(args);
! 140: }
! 141:
1.322 moko 142: // appends to parser3.log located next to the config file if openable, to stderr otherwize
1.218 paf 143: void SAPI::log(SAPI_Info&, const char* fmt, ...) {
1.272 moko 144: va_list args;
1.124 parser 145: va_start(args,fmt);
1.385 moko 146: pa_log(0, fmt, args);
1.124 parser 147: va_end(args);
148: }
149:
1.290 moko 150: void SAPI::die(const char* fmt, ...) {
151: va_list args;
152:
153: // logging first, first vsnprintf
154: va_start(args,fmt);
1.385 moko 155: pa_log(0, fmt, args);
1.290 moko 156: va_end(args);
1.138 paf 157:
1.290 moko 158: // inform user, second vsnprintf
159: va_start(args, fmt);
1.299 moko 160: char message[MAX_STRING];
1.380 moko 161: pa_vsnprintf(message, MAX_STRING, fmt, args);
1.134 paf 162:
1.358 moko 163: SAPI::send_error(sapi_info_4log ? *sapi_info_4log : *sapi_info, message);
1.290 moko 164: exit(1);
1.258 moko 165: // va_end(args);
1.28 paf 166: }
167:
1.358 moko 168: void SAPI::send_error(SAPI_Info& info, const char *exception_cstr, const char *status){
169: info.send_error(exception_cstr, status);
170: }
171:
1.294 moko 172: char* SAPI::Env::get(SAPI_Info& info, const char* name) {
173: return info.get_env(name);
1.218 paf 174: }
175:
1.305 moko 176: bool SAPI::Env::set(SAPI_Info& info, const char* name, const char* value) {
177: return info.set_env(name, value);
178: }
179:
1.294 moko 180: const char* const *SAPI::Env::get(SAPI_Info& info) {
181: return info.get_env();
1.180 paf 182: }
183:
1.354 moko 184: size_t SAPI::read_post(SAPI_Info& info, char* buf, size_t max_bytes) {
1.294 moko 185: return info.read_post(buf, max_bytes);
1.10 paf 186: }
187:
1.294 moko 188: void SAPI::add_header_attribute(SAPI_Info& info, const char* dont_store_key, const char* dont_store_value) {
1.358 moko 189: info.add_header(dont_store_key, dont_store_value);
1.19 paf 190: }
191:
1.360 moko 192: void SAPI::send_headers(SAPI_Info& info) {
1.358 moko 193: info.send_headers();
1.30 paf 194: }
1.20 paf 195:
1.360 moko 196: void SAPI::clear_headers(SAPI_Info& info) {
1.361 moko 197: info.clear_headers();
1.360 moko 198: }
199:
1.294 moko 200: size_t SAPI::send_body(SAPI_Info& info, const void *buf, size_t size) {
201: return info.send_body(buf, size);
1.58 paf 202: }
203:
1.354 moko 204: static const char* full_disk_path(const char* file_name = "") {
1.374 moko 205: const char* path=backslashes_to_slashes(file_name);
206: if(path[0]=='/' || is_os_absolute_path(path)) // '/' includes the UNC path //server, as there are no web paths here; C: is left for is_os_absolute_path
207: return path;
1.373 moko 208: char cwd[MAX_STRING];
1.374 moko 209: return pa_strcat(backslashes_to_slashes(getcwd(cwd, MAX_STRING) ? cwd : ""), "/", path);
1.97 parser 210: }
211:
1.201 paf 212: #ifdef SIGPIPE
1.386 moko 213: // write to a closed socket or pipe fails with EPIPE. Not SIG_IGN, as the ignored signal is inherited by the executed programs
214: static void SIGPIPE_handler(int /*sig*/){}
1.201 paf 215: #endif
216:
1.322 moko 217: // requires pa_thread_request() in entry_exists() under Windows
1.354 moko 218: static const char* locate_config(const char* config_filespec_option, const char* executable_path){
1.322 moko 219: filespec_4log=config_filespec_option;
220: if(!filespec_4log)
1.321 moko 221: filespec_4log=getenv(PARSER_CONFIG_ENV_NAME);
1.322 moko 222: if(!filespec_4log)
223: filespec_4log=getenv(REDIRECT_PREFIX PARSER_CONFIG_ENV_NAME);
224: if(!filespec_4log){
1.373 moko 225: const char* exec_dir_pos = strrchr(executable_path, '/');
1.322 moko 226: #ifdef SYSTEM_CONFIG_FILE
1.353 moko 227: if(exec_dir_pos){
228: #endif
1.374 moko 229: // next to the executable; full path should be used to avoid "parser already configured"
230: filespec_4log=full_disk_path(!exec_dir_pos || (exec_dir_pos==executable_path+1 && *executable_path=='.')
231: ? AUTO_FILE_NAME // parser3 or ./parser3
232: : pa_strcat(pa_strdup(executable_path, exec_dir_pos - executable_path), "/" AUTO_FILE_NAME));
1.353 moko 233: if(entry_exists(filespec_4log))
234: return filespec_4log;
235: #ifdef SYSTEM_CONFIG_FILE
236: }
1.322 moko 237: if(entry_exists(SYSTEM_CONFIG_FILE)){
238: filespec_4log=NULL;
239: return SYSTEM_CONFIG_FILE;
240: }
241: #endif
242: return NULL;
1.321 moko 243: }
1.374 moko 244: // when -f auto.p full path should be used to avoid "parser already configured"
245: return filespec_4log=full_disk_path(filespec_4log);
1.321 moko 246: }
247:
1.227 paf 248: #ifdef WIN32
1.354 moko 249: static const char* maybe_reconstruct_IIS_status_in_qs(const char* original) {
1.228 paf 250: // 404;http://servername/page[?param=value...]
1.227 paf 251: // ';' should be urlencoded by HTTP standard, so we shouldn't get it from browser
252: // and can consider that as an indication that this is IIS way to report errors
253:
1.272 moko 254: if(original && isdigit((unsigned char)original[0]) && isdigit((unsigned char)original[1]) && isdigit((unsigned char)original[2]) && original[3]==';'){
1.227 paf 255: size_t original_len=strlen(original);
1.272 moko 256: char* reconstructed=new(PointerFreeGC) char[original_len +12/*IIS-STATUS=&*/ +14/*IIS-DOCUMENT=&*/ +1];
1.227 paf 257: char* cur=reconstructed;
258: memcpy(cur, "IIS-STATUS=", 11); cur+=11;
259: memcpy(cur, original, 3); cur+=3;
260: *cur++='&';
261:
1.228 paf 262: const char* qmark_at=strchr(original, '?');
1.227 paf 263: memcpy(cur, "IIS-DOCUMENT=", 13); cur+=13;
264: {
1.272 moko 265: size_t value_len=(qmark_at ? qmark_at-original : original_len)-4;
1.227 paf 266: memcpy(cur, original+4, value_len); cur+=value_len;
267: }
268:
269: if(qmark_at) {
270: *cur++='&';
271: strcpy(cur, qmark_at+1/*skip ? itself*/);
272: } else
273: *cur=0;
274:
275: return reconstructed;
276: }
277:
278: return original;
279: }
1.283 moko 280:
281: #define MAYBE_RECONSTRUCT_IIS_STATUS_IN_QS(s) maybe_reconstruct_IIS_status_in_qs(s)
1.354 moko 282:
1.342 moko 283: #else
1.354 moko 284:
1.283 moko 285: #define MAYBE_RECONSTRUCT_IIS_STATUS_IN_QS(s) s
1.354 moko 286:
1.227 paf 287: #endif
288:
1.256 misha 289: class RequestController {
290: public:
291: RequestController(Request* r){
1.341 moko 292: request=r;
1.256 misha 293: }
294: ~RequestController(){
1.341 moko 295: request=0;
296: }
297: };
298:
1.387 ! moko 299: #define RESPONSE_TIMEOUT_GRACE 1 // seconds to finish after the interruption, then the process is killed
! 300:
! 301: /// $response:timeout: interrupts the request that is not finished in time, the object is created before the request and destroyed after it
! 302: class Response_timeout {
! 303: public:
! 304: static THREAD_LOCAL Response_timeout *current;
! 305:
! 306: /// @a akill_process: the process will be killed if the request is still running after the grace period
! 307: Response_timeout(bool akill_process) : kill_process(akill_process), expired(0), armed(false) {
! 308: current=this;
! 309: }
! 310: ~Response_timeout() {
! 311: set(0);
! 312: current=0;
! 313: }
! 314:
! 315: /// restarts the timer for the given number of seconds, 0 cancels it
! 316: void set(int aseconds) {
! 317: if(armed){
! 318: pa_timer_stop(timer);
! 319: armed=false;
! 320: }
! 321: if(!aseconds)
! 322: return;
! 323:
! 324: seconds=aseconds;
! 325: expired=0;
! 326: if(kill_process) // pa_ctime can't be called from the handler, so preparing for the log in advance
! 327: stamp=pa_strdup(pa_ctime(time(0)+seconds+RESPONSE_TIMEOUT_GRACE));
! 328:
! 329: armed=pa_timer_start(&timer, seconds, kill_process ? RESPONSE_TIMEOUT_GRACE : 0, handler);
! 330: }
! 331:
! 332: private:
! 333: bool kill_process;
! 334: volatile sig_atomic_t expired;
! 335: bool armed;
! 336: pa_timer timer;
! 337: int seconds;
! 338: const char *stamp;
! 339:
! 340: static void handler(int) {
! 341: Response_timeout *timeout=current;
! 342: if(!timeout)
! 343: return;
! 344:
! 345: if(timeout->expired && timeout->kill_process){
! 346: // the request is still running after the grace period
! 347: pa_log_at(timeout->stamp, "response timeout (%d s) exceeded, process killed", timeout->seconds);
! 348: #ifndef _MSC_VER
! 349: kill(getpid(), SIGKILL);
! 350: #endif
! 351: return;
! 352: }
! 353:
! 354: timeout->expired=1;
! 355: if(request)
! 356: request->set_skip(Request::SKIP_INTERRUPTED);
! 357: }
! 358: };
! 359:
! 360: THREAD_LOCAL Response_timeout *Response_timeout::current=0;
! 361:
! 362: void SAPI::set_response_timeout(SAPI_Info&, int seconds) {
! 363: if(Response_timeout::current)
! 364: Response_timeout::current->set(seconds);
! 365: }
! 366:
1.341 moko 367: class RequestInfoController {
368: public:
1.358 moko 369: RequestInfoController(Request_info* rinfo, SAPI_Info* sinfo){
1.341 moko 370: request_info_4log=rinfo;
1.358 moko 371: sapi_info_4log=sinfo;
1.341 moko 372: }
373: ~RequestInfoController(){
374: request_info_4log=0;
1.358 moko 375: sapi_info_4log=0;
1.256 misha 376: }
377: };
378:
1.342 moko 379: /** httpd support */
380: static const String httpd_class_name("httpd");
381:
1.317 moko 382: static void config_handler(SAPI_Info &info) {
1.341 moko 383: Request_info request_info;
1.358 moko 384: RequestInfoController ric(&request_info, &info);
1.341 moko 385:
1.373 moko 386: request_info.set_document_root(full_disk_path());
1.317 moko 387: request_info.uri = "";
1.319 moko 388: request_info.argv = argv_extra;
1.317 moko 389:
390: // prepare to process request
1.323 moko 391: Request r(info, request_info, String::Language(String::L_HTML|String::L_OPTIMIZE_BIT));
1.330 moko 392: // only once
393: config_filespec = locate_config(config_filespec, parser3_filespec);
394: // process main auto.p only
1.375 moko 395: #ifdef CODE_CACHE
396: Code_cache_manager::start();
397: #endif
1.378 moko 398: r.core(config_filespec, String::Empty);
1.375 moko 399: #ifdef CODE_CACHE
400: Code_cache_manager::finish(r);
401: #endif
1.317 moko 402: }
403:
404: static void connection_handler(SAPI_Info_HTTPD &info, HTTPD_Connection &connection) {
1.341 moko 405: Request_info request_info;
1.358 moko 406: RequestInfoController ric(&request_info, &info);
1.341 moko 407:
1.325 moko 408: try {
1.336 moko 409: if(!connection.read_header())
1.332 moko 410: return; // ignore "void" connections
1.325 moko 411: info.populate_env();
412:
1.373 moko 413: request_info.set_document_root(full_disk_path());
1.325 moko 414: request_info.path_translated = filespec_to_process;
415: request_info.method = connection.method();
416: request_info.query_string = connection.query();
417: request_info.uri = request_info.strip_absolute_uri(connection.uri());
418: request_info.content_type = connection.content_type();
419: request_info.content_length = (size_t)connection.content_length();
420: request_info.cookie = info.get_env("HTTP_COOKIE");
421: request_info.mail_received = false;
422: request_info.argv = argv_extra;
1.294 moko 423:
1.387 ! moko 424: // process can be killed on timeout only in parallel mode
! 425: Response_timeout response_timeout(HTTPD_Server::mode == HTTPD_Server::PARALLEL);
! 426:
1.325 moko 427: // prepare to process request
428: Request r(info, request_info, String::Language(String::L_HTML|String::L_OPTIMIZE_BIT));
1.387 ! moko 429: RequestController rc(&r);
1.379 moko 430: WebSocket websocket(connection);
431: r.websocket=&websocket;
1.330 moko 432: // process the request
1.378 moko 433: r.core(config_filespec, main_method_name, &httpd_class_name);
1.361 moko 434: } catch(const Exception& e) { // exception in connection handling
1.325 moko 435: SAPI::log(info, "%s", e.comment());
1.354 moko 436: const char* status = info.exception_http_status(e.type());
1.358 moko 437: if(*status)
1.337 moko 438: SAPI::send_error(info, e.comment(), status);
1.325 moko 439: }
440: }
1.317 moko 441:
1.328 moko 442: #ifdef _MSC_VER
443: DWORD WINAPI connection_thread(void *arg){
444: #else
1.325 moko 445: static void *connection_thread(void *arg){
1.328 moko 446: #endif
1.382 moko 447: Temp_gc_references gc_references; // for pointers GC does not see, the very start of the thread
448:
1.325 moko 449: HTTPD_Connection &connection=*(HTTPD_Connection*)arg;
450: SAPI_Info_HTTPD info(connection);
1.294 moko 451:
1.325 moko 452: try {
453: connection_handler(info, connection);
454: } catch(const Exception& e) { // exception in send_error
1.358 moko 455: pa_log("%s", e.comment());
1.294 moko 456: }
1.325 moko 457:
458: delete(&connection);
1.328 moko 459: return 0;
1.294 moko 460: }
461:
1.384 moko 462: static void reject_connection(HTTPD_Connection &connection) {
463: // main thread only
464: static unsigned long total=0;
465: static time_t logged=0;
466:
467: total++;
468: time_t now=time(0);
469: if(now!=logged){ // not more than once a second
470: logged=now;
471: pa_log("max-connections limit %d reached, rejecting connection from %s (%lu rejected in total)", HTTPD_Server::max_connections, connection.remote_addr, total);
472: }
473:
474: SAPI_Info_HTTPD info(connection);
475: SAPI::send_error(info, "too many connections", "503");
476: }
477:
1.317 moko 478: static void httpd_mode() {
1.358 moko 479: config_handler(*sapi_info);
1.325 moko 480:
1.346 moko 481: SOCKET sock = HTTPD_Server::bind(httpd_host_port);
1.294 moko 482:
1.297 moko 483: while(1){
1.328 moko 484: #ifndef _MSC_VER
1.325 moko 485: pid_t pid=1;
1.328 moko 486: #endif
1.303 moko 487: try {
488: HTTPD_Connection connection;
1.383 moko 489: bool accepted=connection.accept(sock, 500);
490: #ifndef _MSC_VER
491: if(HTTPD_Server::mode == HTTPD_Server::PARALLEL)
492: while (waitpid((pid_t)(-1), 0, WNOHANG) > 0)
493: HTTPD_Server::track_connection(-1); // the child has exited, some of them while we were waiting
494: #endif
495: if(!accepted)
1.303 moko 496: continue;
497:
1.383 moko 498: if(HTTPD_Server::too_many_connections()){
1.384 moko 499: reject_connection(connection); // no thread or process is started
1.383 moko 500: continue;
501: }
502:
1.325 moko 503: switch (HTTPD_Server::mode) {
504: case HTTPD_Server::MULTITHREADED:
1.328 moko 505: #ifdef _MSC_VER
506: if (!GC_CreateThread(0, 0, connection_thread, new HTTPD_Connection(connection), 0, 0))
507: throw Exception("httpd.fork", 0, "thread creation failed");
1.383 moko 508: connection.sock=INVALID_SOCKET; // thread owns the connection now
1.328 moko 509: break;
510: #else
1.339 moko 511: #ifdef HAVE_TLS
1.325 moko 512: pthread_t thread;
513: pthread_attr_t attr;
514: pthread_attr_init(&attr);
515: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
516:
517: if(int result=GC_pthread_create(&thread, &attr, connection_thread, new HTTPD_Connection(connection)))
518: throw Exception("httpd.fork", 0, "thread creation failed (%d)", result);
1.383 moko 519: connection.sock=INVALID_SOCKET; // thread owns the connection now
1.325 moko 520: break;
1.339 moko 521: #endif
1.325 moko 522: case HTTPD_Server::PARALLEL:
523: pid=fork();
524: if(pid<0)
525: throw Exception("httpd.fork", 0, "fork failed: %s (%d)", strerror(errno), errno);
1.383 moko 526: if(pid>0){
527: closesocket(connection.sock); // parent should close connection.sock as well
528: connection.sock=INVALID_SOCKET; // child owns the connection now
529: continue;
530: }
1.325 moko 531: #endif
532: case HTTPD_Server::SEQUENTIAL: // and fork child
1.303 moko 533:
1.325 moko 534: SAPI_Info_HTTPD info(connection);
535: connection_handler(info, connection);
1.303 moko 536: }
1.383 moko 537: // SEQUENTIAL or error: closing connection socket in HTTPD_Connection destructor
1.303 moko 538: } catch(const Exception& e) { // exception in accept or send_error
1.358 moko 539: pa_log("%s", e.comment());
1.294 moko 540: }
1.325 moko 541:
1.328 moko 542: #ifndef _MSC_VER
1.325 moko 543: if(pid==0) // fork child
544: exit(0);
1.328 moko 545: #endif
1.294 moko 546: }
547: }
1.231 paf 548:
1.294 moko 549: /** main workhorse */
550:
1.316 moko 551: static void real_parser_handler(bool cgi) {
1.280 moko 552: // init libraries
1.218 paf 553: pa_globals_init();
1.294 moko 554:
1.296 moko 555: if(httpd_host_port){
1.308 moko 556: httpd_mode();
1.294 moko 557: }
558:
559: const char* request_method=getenv("REQUEST_METHOD");
560:
1.367 moko 561: if(!filespec_to_process){
1.376 moko 562: SAPI::send_error(*sapi_info, "Parser/" PARSER_VERSION_WITH_FEATURES);
1.367 moko 563: exit(1);
564: }
565:
1.297 moko 566: // global request info
1.341 moko 567: Request_info request_info;
1.358 moko 568: RequestInfoController ric(&request_info, sapi_info);
1.341 moko 569:
1.283 moko 570: request_info.path_translated = filespec_to_process;
571: request_info.method = request_method ? request_method : "GET";
572: request_info.query_string = MAYBE_RECONSTRUCT_IIS_STATUS_IN_QS(getenv("QUERY_STRING"));
573:
1.122 parser 574: if(cgi) {
1.284 moko 575: // obligatory
1.218 paf 576: const char* path_info=getenv("PATH_INFO");
1.214 paf 577: if(!path_info)
1.349 moko 578: SAPI::die("parser3: illegal CGI call (missing PATH_INFO)");
1.283 moko 579:
1.373 moko 580: const char* document_root = getenv("DOCUMENT_ROOT");
581: if(!document_root) {
1.285 moko 582: // IIS or fcgiwrap minimalistic setup
583: ssize_t prefix_len = strlen(filespec_to_process) - strlen(path_info);
584: if(prefix_len < 0 || strcmp(filespec_to_process + prefix_len, path_info) != 0)
1.349 moko 585: SAPI::die("parser3: illegal CGI call (invalid PATH_INFO in reinventing DOCUMENT_ROOT)");
1.285 moko 586:
1.373 moko 587: document_root = pa_strdup(filespec_to_process, prefix_len);
1.283 moko 588: }
1.373 moko 589: request_info.set_document_root(document_root);
1.214 paf 590:
1.285 moko 591: request_info.uri = request_info.strip_absolute_uri(getenv("REQUEST_URI"));
1.283 moko 592: if(request_info.uri) { // apache & others stuck to standards
1.284 moko 593: // another obligatory
1.285 moko 594: const char* script_name = getenv("SCRIPT_NAME");
1.284 moko 595: if(!script_name)
1.349 moko 596: SAPI::die("parser3: illegal CGI call (missing SCRIPT_NAME)");
1.214 paf 597: /*
598: http://parser3/env.html?123 =OK
599: $request:uri=/env.html?123
600: REQUEST_URI='/env.html?123'
601: SCRIPT_NAME='/cgi-bin/parser3'
602: PATH_INFO='/env.html'
1.285 moko 603:
1.214 paf 604: http://parser3/cgi-bin/parser3/env.html?123 =ERROR
605: $request:uri=/cgi-bin/parser3/env.html?123
606: REQUEST_URI='/cgi-bin/parser3/env.html?123'
607: SCRIPT_NAME='/cgi-bin/parser3'
608: PATH_INFO='/env.html'
609: */
1.285 moko 610: size_t script_name_len = strlen(script_name);
611: size_t uri_len = strlen(request_info.uri);
1.283 moko 612: if(strncmp(request_info.uri, script_name, script_name_len)==0 && script_name_len != uri_len) // under IIS they are the same
1.349 moko 613: SAPI::die("parser3: illegal CGI call (REQUEST_URI starts with SCRIPT_NAME)");
1.284 moko 614: } else { // fcgiwrap minimalistic setup
1.315 moko 615: request_info.uri = request_info.query_string && *request_info.query_string ? pa_strcat(path_info, "?", request_info.query_string) : path_info;
1.214 paf 616: }
1.283 moko 617: } else{
1.373 moko 618: request_info.set_document_root(full_disk_path());
1.285 moko 619: request_info.uri = "";
1.283 moko 620: }
1.122 parser 621:
1.283 moko 622: request_info.content_type = getenv("CONTENT_TYPE");
1.348 moko 623: request_info.content_length = cgi ? (size_t)pa_atoul(getenv("CONTENT_LENGTH")) : 0; // only SAPI_Info_CGI can read POST
1.283 moko 624: request_info.cookie = getenv("HTTP_COOKIE");
625: request_info.mail_received = mail_received;
1.184 paf 626:
1.319 moko 627: request_info.argv = argv_extra;
1.245 misha 628:
1.233 paf 629: #ifdef PA_DEBUG_CGI_ENTRY_EXIT
1.362 moko 630: pa_log("request_info: method=%s, uri=%s, q=%s, dr=%s, pt=%s", request_info.method, request_info.uri, request_info.query_string, request_info.document_root, request_info.path_translated);
1.233 paf 631: #endif
632:
1.256 misha 633: {
1.387 ! moko 634: // CGI process can be killed on timeout
! 635: Response_timeout response_timeout(true);
! 636:
1.377 moko 637: // prepare to process request
638: Request r(*sapi_info, request_info, cgi ? String::Language(String::L_HTML|String::L_OPTIMIZE_BIT) : String::L_AS_IS);
1.297 moko 639: // initing ::request ptr for signal handlers
1.323 moko 640: RequestController rc(&r);
1.256 misha 641: // process the request
1.378 moko 642: r.core(locate_config(config_filespec, parser3_filespec));
1.387 ! moko 643: // clearing ::request in rc destructor to prevent signal handlers from accessing invalid memory
1.377 moko 644: // then the request is destroyed, before the libraries are unloaded: its pool cleanups call them (memcached)
1.122 parser 645: }
1.231 paf 646:
1.280 moko 647: // finalize libraries
1.225 paf 648: pa_globals_done();
1.218 paf 649: }
650:
1.231 paf 651: #ifdef PA_SUPPRESS_SYSTEM_EXCEPTION
1.316 moko 652: static const Exception call_real_parser_handler__do_PEH_return_it(bool cgi) {
1.231 paf 653: try {
1.316 moko 654: real_parser_handler(cgi);
1.231 paf 655: } catch(const Exception& e) {
656: return e;
1.122 parser 657: }
1.231 paf 658:
659: return Exception();
1.122 parser 660: }
1.272 moko 661:
1.316 moko 662: static void call_real_parser_handler__supress_system_exception(bool cgi) {
1.231 paf 663: Exception parser_exception;
664: LPEXCEPTION_POINTERS system_exception=0;
1.122 parser 665:
1.231 paf 666: __try {
1.316 moko 667: parser_exception=call_real_parser_handler__do_PEH_return_it(cgi);
1.288 moko 668: } __except ( (system_exception=GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) {
1.231 paf 669: if(system_exception)
670: if(_EXCEPTION_RECORD *er=system_exception->ExceptionRecord)
1.371 moko 671: throw Exception("system", 0, "0x%08X at %p", er->ExceptionCode, er->ExceptionAddress);
1.218 paf 672: else
1.272 moko 673: throw Exception("system", 0, "<no exception record>");
1.218 paf 674: else
1.272 moko 675: throw Exception("system", 0, "<no exception information>");
1.231 paf 676: }
677:
678: if(parser_exception)
679: throw Exception(parser_exception);
680: }
1.273 moko 681:
682: #define REAL_PARSER_HANDLER call_real_parser_handler__supress_system_exception
683: #else
684: #define REAL_PARSER_HANDLER real_parser_handler
1.218 paf 685: #endif
1.131 paf 686:
1.364 moko 687: static void usage(const char* message=NULL) {
688: if(message){
689: fprintf(stderr, message, parser3_filespec);
690: }
691:
1.188 paf 692: printf(
1.235 paf 693: "Parser/%s\n"
1.368 moko 694: "Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)\n"
1.350 moko 695: "Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>\n"
1.184 paf 696: "\n"
1.307 moko 697: "Usage: %s [options] [file]\n"
1.184 paf 698: "Options are:\n"
1.185 paf 699: #ifdef WITH_MAILRECEIVE
1.193 paf 700: " -m Parse mail, put received letter to $mail:received\n"
1.184 paf 701: #endif
1.193 paf 702: " -f config_file Use this config file (/path/to/auto.p)\n"
1.364 moko 703: " -l log_file Use this log file (/path/to/parser3.log)\n"
1.296 moko 704: " -p [host:]port Start web server on this port\n"
1.272 moko 705: " -h Display usage information (this message)\n",
1.376 moko 706: PARSER_VERSION_WITH_FEATURES,
1.364 moko 707: parser3_filespec ? parser3_filespec : "parser3" );
1.184 paf 708: exit(EINVAL);
709: }
710:
1.364 moko 711: #define ARG_REQUIRED \
712: if(c[1] || !*(++carg)){ \
713: fprintf(stderr, "%s: option '%c' requires an argument\n", parser3_filespec, *c); \
714: usage(); \
715: }
1.294 moko 716:
1.249 misha 717: int main(int argc, char *argv[]) {
1.382 moko 718: Temp_gc_references gc_references; // for pointers GC does not see
719:
1.233 paf 720: #ifdef PA_DEBUG_CGI_ENTRY_EXIT
1.362 moko 721: pa_log("main: entry");
1.233 paf 722: #endif
1.217 paf 723:
1.364 moko 724: if(!argc || !argv[0])
725: usage();
1.373 moko 726: parser3_filespec = backslashes_to_slashes(argv[0]);
1.364 moko 727:
1.294 moko 728: umask(2);
729:
730: // were we started as CGI?
1.316 moko 731: bool cgi=(getenv("SERVER_SOFTWARE") || getenv("SERVER_NAME") || getenv("GATEWAY_INTERFACE") || getenv("REQUEST_METHOD")) && !getenv("PARSER_VERSION");
1.365 moko 732: if(!cgi){
1.358 moko 733: sapi_info = &sapi_console;
1.365 moko 734: parser3_mode = "console";
735: }
1.294 moko 736:
1.203 paf 737: #ifdef SIGPIPE
1.325 moko 738: signal(SIGPIPE, SIGPIPE_handler);
1.203 paf 739: #endif
1.354 moko 740: char* raw_filespec_to_process = NULL;
1.210 paf 741: if(cgi) {
1.184 paf 742: raw_filespec_to_process=getenv("PATH_TRANSLATED");
1.319 moko 743: argv_extra=argv + 1;
1.210 paf 744: } else {
1.364 moko 745: char** carg = argv + 1;
746: for(;*carg; carg++){
747: if((*carg)[0] != '-')
1.184 paf 748: break;
1.245 misha 749:
1.364 moko 750: for(char* c=(*carg)+1; *c; c++){
751: switch (*c) {
1.245 misha 752: case 'h':
1.364 moko 753: usage();
1.245 misha 754: break;
755: case 'f':
1.364 moko 756: ARG_REQUIRED;
757: config_filespec=*carg;
1.245 misha 758: break;
1.362 moko 759: case 'l':
1.364 moko 760: ARG_REQUIRED;
761: log_filespec=*carg;
1.362 moko 762: break;
1.294 moko 763: case 'p':
1.364 moko 764: ARG_REQUIRED;
765: httpd_host_port=*carg;
1.365 moko 766: parser3_mode="httpd";
1.294 moko 767: break;
1.185 paf 768: #ifdef WITH_MAILRECEIVE
1.245 misha 769: case 'm':
770: mail_received=true;
1.365 moko 771: parser3_mode="mail";
1.245 misha 772: break;
773: #endif
774: default:
1.364 moko 775: fprintf(stderr, "%s: invalid option '%c'\n", parser3_filespec, *c);
776: usage();
1.245 misha 777: break;
778: }
1.184 paf 779: }
780: }
1.245 misha 781:
1.364 moko 782: if (*carg) {
783: raw_filespec_to_process=*carg;
1.294 moko 784: } else {
1.364 moko 785: if(!httpd_host_port)
786: usage("%s: file not specified\n");
1.10 paf 787: }
1.294 moko 788:
1.364 moko 789: if (httpd_host_port && mail_received)
790: usage("%s: -p and -m options should not be used together\n");
1.310 moko 791:
1.364 moko 792: argv_extra=carg;
1.10 paf 793: }
794:
1.374 moko 795: // prepare full disk path log_filespec here, as logging must not allocate
796: if(!log_filespec)
797: log_filespec=getenv(PARSER_LOG_ENV_NAME);
798: if(!log_filespec)
799: log_filespec=getenv(REDIRECT_PREFIX PARSER_LOG_ENV_NAME);
1.385 moko 800: if(log_filespec && strcmp(log_filespec, "-")) // "-" is stderr
1.374 moko 801: log_filespec=full_disk_path(log_filespec);
802:
1.264 moko 803: #ifdef _MSC_VER
1.100 parser 804: setmode(fileno(stdin), _O_BINARY);
805: setmode(fileno(stdout), _O_BINARY);
806: setmode(fileno(stderr), _O_BINARY);
807: #endif
808:
1.218 paf 809: #if defined(_MSC_VER) && defined(_DEBUG)
1.148 paf 810: // Get current flag
811: int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
812:
813: // Turn on leak-checking bit
814: tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
815:
816: // Set flag to the new value
817: _CrtSetDbgFlag( tmpFlag );
1.138 paf 818:
1.218 paf 819: _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
820: _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
1.138 paf 821: #endif
822:
1.318 moko 823: try { // global try
824: if(raw_filespec_to_process && *raw_filespec_to_process){
1.351 moko 825: filespec_to_process=full_disk_path(raw_filespec_to_process);
1.318 moko 826: }
1.10 paf 827:
1.316 moko 828: REAL_PARSER_HANDLER(cgi);
1.361 moko 829: } catch(const Exception& e) { // exception in config_handler
1.359 moko 830: SAPI::log(*sapi_info, "%s", e.comment());
831: SAPI::send_error(*sapi_info, e.comment(), strcmp(e.type(), "file.missing") ? "500" : "404");
1.16 paf 832: }
1.109 parser 833:
1.233 paf 834: #ifdef PA_DEBUG_CGI_ENTRY_EXIT
1.362 moko 835: pa_log("main: successful return");
1.233 paf 836: #endif
1.358 moko 837: return sapi_info->http_response_code < 100 ? sapi_info->http_response_code : 0;
1.1 paf 838: }
E-mail: