|
|
| version 1.415, 2021/01/02 23:01:11 | version 1.433, 2025/05/25 20:55:23 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: request class main part. @see compile.C and execute.C. | Parser: request class main part. @see compile.C and execute.C. |
| Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2024 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_sapi.h" | #include "pa_sapi.h" |
| Line 33 | Line 33 |
| #include "pa_vmemory.h" | #include "pa_vmemory.h" |
| #include "pa_vconsole.h" | #include "pa_vconsole.h" |
| #include "pa_vdate.h" | #include "pa_vdate.h" |
| #include "pa_varray.h" | |
| volatile const char * IDENT_PA_REQUEST_C="$Id$" IDENT_PA_REQUEST_H IDENT_PA_REQUEST_CHARSETS_H IDENT_PA_REQUEST_INFO_H IDENT_PA_VCONSOLE_H; | volatile const char * IDENT_PA_REQUEST_C="$Id$" IDENT_PA_REQUEST_H IDENT_PA_REQUEST_CHARSETS_H IDENT_PA_REQUEST_INFO_H IDENT_PA_VCONSOLE_H; |
| Line 43 volatile const char * IDENT_PA_REQUEST_C | Line 44 volatile const char * IDENT_PA_REQUEST_C |
| /// content type of response when no $MAIN:defaults.content-type defined | /// content type of response when no $MAIN:defaults.content-type defined |
| const char* DEFAULT_CONTENT_TYPE="text/html"; | const char* DEFAULT_CONTENT_TYPE="text/html"; |
| const uint LOOP_LIMIT=20000; | const uint LOOP_LIMIT=100000; |
| const uint EXECUTE_RECOURSION_LIMIT=1000; | const uint ARRAY_LIMIT=1000000; |
| const uint EXECUTE_RECURSION_LIMIT=1000; | |
| const uint HTTPD_TIMEOUT=4; | const uint HTTPD_TIMEOUT=4; |
| const size_t FILE_SIZE_LIMIT=512*1024*1024; | const size_t FILE_SIZE_LIMIT=512*1024*1024; |
| Line 68 const String auto_method_name(AUTO_METHO | Line 70 const String auto_method_name(AUTO_METHO |
| static const String use_method_name(USE_METHOD_NAME); | static const String use_method_name(USE_METHOD_NAME); |
| static const String autouse_method_name(AUTOUSE_METHOD_NAME); | static const String autouse_method_name(AUTOUSE_METHOD_NAME); |
| const String exception_type_part_name(EXCEPTION_TYPE_PART_NAME); | const String::Body exception_type_part_name(EXCEPTION_TYPE_PART_NAME); |
| const String exception_source_part_name(EXCEPTION_SOURCE_PART_NAME); | const String::Body exception_source_part_name(EXCEPTION_SOURCE_PART_NAME); |
| const String exception_comment_part_name(EXCEPTION_COMMENT_PART_NAME); | const String::Body exception_comment_part_name(EXCEPTION_COMMENT_PART_NAME); |
| const String exception_handled_part_name(EXCEPTION_HANDLED_PART_NAME); | const String::Body exception_handled_part_name(EXCEPTION_HANDLED_PART_NAME); |
| static const String origin_key(ORIGIN_KEY); | static const String origin_key(ORIGIN_KEY); |
| int pa_loop_limit=LOOP_LIMIT; | int pa_loop_limit=LOOP_LIMIT; |
| int pa_execute_recoursion_limit=EXECUTE_RECOURSION_LIMIT; | int pa_array_limit=ARRAY_LIMIT; |
| int pa_execute_recursion_limit=EXECUTE_RECURSION_LIMIT; | |
| int pa_httpd_timeout=HTTPD_TIMEOUT; | int pa_httpd_timeout=HTTPD_TIMEOUT; |
| size_t pa_file_size_limit=FILE_SIZE_LIMIT; | size_t pa_file_size_limit=FILE_SIZE_LIMIT; |
| Line 101 static const String getter_protected_nam | Line 104 static const String getter_protected_nam |
| static const String locals_name("LOCALS"); | static const String locals_name("LOCALS"); |
| static const String limits_name("LIMITS"); | static const String limits_name("LIMITS"); |
| static const String loop_limit_name("max_loop"); | static const String loop_limit_name("max_loop"); |
| static const String recoursion_limit_name("max_recoursion"); | static const String array_limit_name("max_array_size"); |
| static const String recursion_limit_name("max_recursion"); | |
| static const String file_size_limit_name("max_file_size"); | static const String file_size_limit_name("max_file_size"); |
| static const String lock_wait_timeout_name("lock_wait_timeout"); | static const String lock_wait_timeout_name("lock_wait_timeout"); |
| static const String httpd_name("HTTPD"); | static const String httpd_name("HTTPD"); |
| Line 122 static const String content_disposition_ | Line 126 static const String content_disposition_ |
| static const String content_disposition_inline(CONTENT_DISPOSITION_INLINE); | static const String content_disposition_inline(CONTENT_DISPOSITION_INLINE); |
| static const String content_disposition_attachment(CONTENT_DISPOSITION_ATTACHMENT); | static const String content_disposition_attachment(CONTENT_DISPOSITION_ATTACHMENT); |
| const String content_disposition_filename_name(CONTENT_DISPOSITION_FILENAME_NAME); | |
| const String content_disposition_filename_name_asterisk("filename*"); | |
| // defines | // defines |
| #define CHARSET_NAME_UPPER "CHARSET" | #define CHARSET_NAME_UPPER "CHARSET" |
| Line 133 VStateless_class& VClassMAIN_create(); | Line 140 VStateless_class& VClassMAIN_create(); |
| // | // |
| Request::Request(SAPI_Info& asapi_info, Request_info& arequest_info, String::Language adefault_lang): | Request::Request(SAPI_Info& asapi_info, Request_info& arequest_info, String::Language adefault_lang): |
| // private | // private |
| anti_endless_execute_recoursion(0), | anti_endless_execute_recursion(0), |
| // public | // public |
| allow_class_replace(false), | allow_class_replace(false), |
| Line 242 VStateless_class& Request::get_class_ref | Line 249 VStateless_class& Request::get_class_ref |
| return *result; | return *result; |
| } | } |
| bool Request::add_class(const char* atype, VStateless_class *aclass){ | |
| if(!allow_class_replace){ | |
| if(!classes().put_dont_replace(atype, aclass)) | |
| return true; | |
| if(strcmp(atype, VARRAY_TYPE)) | |
| return false; | |
| } | |
| classes().put(atype, aclass); | |
| return true; | |
| } | |
| static void load_charset(HashStringValue::key_type akey, HashStringValue::value_type avalue, Request_charsets* charsets) { | static void load_charset(HashStringValue::key_type akey, HashStringValue::value_type avalue, Request_charsets* charsets) { |
| pa_charsets.load_charset(*charsets, akey, avalue->as_string()); | pa_charsets.load_charset(*charsets, akey, avalue->as_string()); |
| } | } |
| Line 325 void Request::configure_admin(VStateless | Line 343 void Request::configure_admin(VStateless |
| if(pa_loop_limit==0) pa_loop_limit=INT_MAX; | if(pa_loop_limit==0) pa_loop_limit=INT_MAX; |
| }, "LIMITS.%s must be int"); | }, "LIMITS.%s must be int"); |
| pa_execute_recoursion_limit=EXECUTE_RECOURSION_LIMIT; | pa_array_limit=ARRAY_LIMIT; |
| CONF_OPTION(limits, recoursion_limit_name, { | CONF_OPTION(limits, array_limit_name, { |
| pa_execute_recoursion_limit=option->as_int(); | pa_array_limit=option->as_int(); |
| if(pa_execute_recoursion_limit==0) pa_execute_recoursion_limit=INT_MAX; | if(pa_array_limit==0) pa_array_limit=INT_MAX; |
| }, "LIMITS.%s must be int"); | |
| pa_execute_recursion_limit=EXECUTE_RECURSION_LIMIT; | |
| CONF_OPTION(limits, recursion_limit_name, { | |
| pa_execute_recursion_limit=option->as_int(); | |
| if(pa_execute_recursion_limit==0) pa_execute_recursion_limit=INT_MAX; | |
| }, "LIMITS.%s must be int"); | }, "LIMITS.%s must be int"); |
| pa_file_size_limit=FILE_SIZE_LIMIT; | pa_file_size_limit=FILE_SIZE_LIMIT; |
| CONF_OPTION(limits, file_size_limit_name, { | CONF_OPTION(limits, file_size_limit_name, { |
| double limit=option->as_double(); | double limit=option->as_double(); |
| if(limit >= (double)SSIZE_MAX) | if(limit >= (double)SSIZE_MAX) |
| throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less then %.15g", file_size_limit_name.cstr(), (double)SSIZE_MAX); | throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less than %.15g", file_size_limit_name.cstr(), (double)SSIZE_MAX); |
| pa_file_size_limit=(size_t)limit; | pa_file_size_limit=(size_t)limit; |
| if(pa_file_size_limit==0) | if(pa_file_size_limit==0) |
| pa_file_size_limit=SSIZE_MAX; | pa_file_size_limit=SSIZE_MAX; |
| Line 345 void Request::configure_admin(VStateless | Line 369 void Request::configure_admin(VStateless |
| CONF_OPTION(limits, lock_wait_timeout_name, { | CONF_OPTION(limits, lock_wait_timeout_name, { |
| double limit=option->as_double(); | double limit=option->as_double(); |
| if(limit >= 3600*24) | if(limit >= 3600*24) |
| throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less then %d", lock_wait_timeout_name.cstr(), 3600*24); | throw Exception(PARSER_RUNTIME, 0, "$MAIN:LIMITS.%s must be less than %d", lock_wait_timeout_name.cstr(), 3600*24); |
| pa_lock_attempts=(unsigned int)(limit*2)+1; | pa_lock_attempts=(unsigned int)(limit*2)+1; |
| }, "LIMITS.%s must be number"); | }, "LIMITS.%s must be number"); |
| Line 425 Table &Request::Exception_trace::table(R | Line 449 Table &Request::Exception_trace::table(R |
| Operation::Origin origin=trace.origin(); | Operation::Origin origin=trace.origin(); |
| if(origin.file_no) { | if(origin.file_no) { |
| *row+=new String(r.file_list[origin.file_no], String::L_TAINTED); // 'file' column | *row+=new String(r.file_list[origin.file_no], String::L_TAINTED); // 'file' column |
| *row+=new String(String::Body::Format(1+origin.line), String::L_CLEAN); // 'lineno' column | *row+=new String(pa_uitoa(1+origin.line), String::L_CLEAN); // 'lineno' column |
| *row+=new String(String::Body::Format(1+origin.col), String::L_CLEAN); // 'colno' column | *row+=new String(pa_uitoa(1+origin.col), String::L_CLEAN); // 'colno' column |
| } | } |
| stack_trace+=row; | stack_trace+=row; |
| } | } |
| Line 460 void Request::configure() { | Line 484 void Request::configure() { |
| */ | */ |
| void Request::core(const char* config_filespec, bool header_only, const String &amain_method_name, const String* amain_class_name) { | void Request::core(const char* config_filespec, bool header_only, const String &amain_method_name, const String* amain_class_name) { |
| VFile* body_file=NULL; | |
| bool as_attachment=false; | |
| try { | try { |
| // loading config | // loading config |
| if(config_filespec) | if(config_filespec) |
| Line 489 void Request::core(const char* config_fi | Line 510 void Request::core(const char* config_fi |
| // extract response body | // extract response body |
| Value* body_value=response.fields().get(download_name_upper); // $response:download? | Value* body_value=response.fields().get(download_name_upper); // $response:download? |
| as_attachment=body_value!=0; | bool as_attachment=body_value!=0; |
| if(!body_value) | if(!body_value) |
| body_value=response.fields().get(body_name_upper); // $response:body | body_value=response.fields().get(body_name_upper); // $response:body |
| if(!body_value) | if(!body_value) |
| Line 504 void Request::core(const char* config_fi | Line 525 void Request::core(const char* config_fi |
| }); | }); |
| } | } |
| body_file=body_value->as_vfile(flang, &charsets); | // can throw exceptions while handling $response:download[] |
| output_result(body_value->as_vfile(flang, &charsets), header_only, as_attachment); | |
| } catch(const Exception& e) { // request handling problem | } catch(const Exception& e) { // request handling problem |
| try { | |
| // we're returning not result, but error explanation | |
| Request::Exception_details details=get_details(e); | // we're returning not result, but error explanation |
| const char* exception_cstr=get_exception_cstr(e, details); | Request::Exception_details details=get_details(e); |
| const char* exception_cstr=get_exception_cstr(e, details); | |
| // reset language to default | |
| flang=fdefault_lang; | // reset language to default |
| // reset response | flang=fdefault_lang; |
| response.fields().clear(); | // reset response |
| response.fields().clear(); | |
| SAPI::clear_headers(sapi_info); | |
| // this is what we'd return in $response:body | // this is what we'd return in $response:body |
| const String* body_string=0; | const String* body_string=0; |
| try { | |
| // maybe we'd be lucky enough as to report an error in a gracefull way... | // maybe we'd be lucky enough as to report an error in a gracefull way... |
| if(const Method *method=main_class.get_method(*new String(UNHANDLED_EXCEPTION_METHOD_NAME))) { | if(const Method *method=main_class.get_method(*new String(UNHANDLED_EXCEPTION_METHOD_NAME))) { |
| // preparing parameters to @unhandled_exception[exception;stack] | // preparing parameters to @unhandled_exception[exception;stack] |
| Line 541 void Request::core(const char* config_fi | Line 563 void Request::core(const char* config_fi |
| if(!vhandled || !vhandled->as_bool()) { | if(!vhandled || !vhandled->as_bool()) { |
| SAPI::log(sapi_info, "%s", exception_cstr); | SAPI::log(sapi_info, "%s", exception_cstr); |
| } | } |
| } catch(const Exception& e) { // exception in @unhandled_exception | |
| if(body_string) { // could report an error beautifully? | |
| VString body_vstring(*body_string); | |
| body_file=body_vstring.as_vfile(flang, &charsets); | |
| as_attachment=false; | |
| } else { | |
| // doing that ugly | |
| SAPI::send_error(sapi_info, exception_cstr, !strcmp(e.type(), "file.missing") ? "404" : "500"); | |
| return; | |
| } | |
| } catch(const Exception& e) { // exception in unhandled exception | |
| Request::Exception_details details=get_details(e); | Request::Exception_details details=get_details(e); |
| // unconditionally log the beast in exception handler | // logging both initial and new exceptions |
| throw Exception(0, 0, "Unhandled exception in %s", get_exception_cstr(e, details)); | SAPI::log(sapi_info, "%s", exception_cstr); |
| SAPI::log(sapi_info, "Exception in @unhandled_exception at %s", get_exception_cstr(e, details)); | |
| SAPI::send_error(sapi_info, "Exception in @unhandled_exception, details are available in Parser error log."); | |
| return; | |
| } | } |
| } | |
| // write out the result outside of try as network exceptions should not be handled by parser code. | if(body_string) { // could report an error beautifully? |
| output_result(body_file, header_only, as_attachment); | VString body_vstring(*body_string); |
| output_result(body_vstring.as_vfile(flang, &charsets), header_only, false); | |
| } else { | |
| // doing that ugly | |
| SAPI::send_error(sapi_info, exception_cstr, strcmp(e.type(), "file.missing") ? "500" : "404"); | |
| } | |
| } | |
| } | } |
| uint Request::register_file(String::Body file_spec) { | uint Request::register_file(String::Body file_spec) { |
| Line 691 void Request::use_buf(VStateless_class& | Line 709 void Request::use_buf(VStateless_class& |
| configure_admin(cclass/*, executed.method->name*/); | configure_admin(cclass/*, executed.method->name*/); |
| // locate and execute possible @auto[] static | // locate and execute possible @auto[] static |
| execute_method_if_exists(cclass, auto_method_name, vfilespec); | execute_auto_method_if_exists(cclass, auto_method_name, vfilespec); |
| cclass.enable_default_setter(); | cclass.enable_default_setter(); |
| } | } |
| Line 712 const String& Request::full_disk_path(co | Line 730 const String& Request::full_disk_path(co |
| result << relative_name; | result << relative_name; |
| return result; | return result; |
| } | } |
| if(relative_name.pos("://")!=STRING_NOT_FOUND // something like "http://xxx" | if(relative_name.starts_with("http://") || relative_name.starts_with("parser://") |
| #ifdef WIN32 | #ifdef WIN32 |
| || relative_name.pos(":")==1 // DRIVE: | || relative_name.pos(":")==1 // DRIVE: |
| || relative_name.starts_with("\\\\") // UNC1 | || relative_name.starts_with("\\\\") // UNC1 |
| Line 755 static void output_sole_piece(Request& r | Line 773 static void output_sole_piece(Request& r |
| output=Charset::transcode(output, r.charsets.source(), r.charsets.client()); | output=Charset::transcode(output, r.charsets.source(), r.charsets.client()); |
| // prepare header: Content-Length | // prepare header: Content-Length |
| SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, format(output.length, "%u")); | SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, pa_uitoa(output.length)); |
| // send header | // send header |
| SAPI::send_header(r.sapi_info); | SAPI::send_headers(r.sapi_info); |
| // send body | // send body |
| if(!header_only) | if(!header_only) |
| Line 787 static void parse_range(const String* s, | Line 805 static void parse_range(const String* s, |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| if(*p >= '0' && *p <= '9'){ | if(*p >= '0' && *p <= '9'){ |
| const char *s=p; | const char *n=p; |
| while(*p>='0' && *p<='9') p++; | while(*p>='0' && *p<='9') p++; |
| r.start = pa_atoul(pa_strdup(s,p-s)); | r.start = pa_atoul(pa_strdup(n, p-n)); |
| } | } |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| Line 799 static void parse_range(const String* s, | Line 817 static void parse_range(const String* s, |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| if(*p >= '0' && *p <= '9'){ | if(*p >= '0' && *p <= '9'){ |
| const char *s=p; | const char *n=p; |
| while(*p>='0' && *p<='9') p++; | while(*p>='0' && *p<='9') p++; |
| r.end = pa_atoul(pa_strdup(s,p-s)); | r.end = pa_atoul(pa_strdup(n, p-n)); |
| } | } |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| Line 822 struct Send_range_action_info { | Line 840 struct Send_range_action_info { |
| static void send_range(struct stat& /*finfo*/, int f, const String& /*file_spec*/, void *context){ | static void send_range(struct stat& /*finfo*/, int f, const String& /*file_spec*/, void *context){ |
| Send_range_action_info &info = *(Send_range_action_info*)context; | Send_range_action_info &info = *(Send_range_action_info*)context; |
| SAPI::send_header(info.r->sapi_info); | SAPI::send_headers(info.r->sapi_info); |
| pa_lseek(f, info.offset, SEEK_SET); | pa_lseek(f, info.offset, SEEK_SET); |
| const size_t BUFSIZE = 128*0x400; | char buf[FILE_BUFFER_SIZE]; |
| char buf[BUFSIZE]; | |
| do{ | do{ |
| size_t to_read = info.part_length < BUFSIZE ? (size_t)info.part_length : BUFSIZE; | size_t to_read = info.part_length < FILE_BUFFER_SIZE ? (size_t)info.part_length : FILE_BUFFER_SIZE; |
| size_t to_write = file_block_read(f, buf, to_read); | size_t to_write = file_block_read(f, buf, to_read); |
| if(to_write == 0) | if(to_write == 0) |
| Line 887 static void output_pieces(Request& r, bo | Line 904 static void output_pieces(Request& r, bo |
| } | } |
| } | } |
| SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, format((double)part_length, "%.15g")); | SAPI::add_header_attribute(r.sapi_info, HTTP_CONTENT_LENGTH, pa_uitoa(part_length)); |
| if(add_last_modified) | if(add_last_modified) |
| SAPI::add_header_attribute(r.sapi_info, "last-modified", attributed_meaning_to_string(date, String::L_AS_IS, true).cstr()); | SAPI::add_header_attribute(r.sapi_info, "last-modified", attributed_meaning_to_string(date, String::L_AS_IS, true).cstr()); |
| if(header_only){ | if(header_only){ |
| SAPI::send_header(r.sapi_info); | SAPI::send_headers(r.sapi_info); |
| } else { | } else { |
| Send_range_action_info info = { &r, offset, part_length}; | Send_range_action_info info = { &r, offset, part_length}; |
| file_read_action_under_lock(r.full_disk_path(filename), "send", send_range, &info); | file_read_action_under_lock(r.full_disk_path(filename), "send", send_range, &info); |
| Line 917 void Request::output_result(VFile* body_ | Line 934 void Request::output_result(VFile* body_ |
| // Content-Disposition, use $.name[<empty>] to avoid | // Content-Disposition, use $.name[<empty>] to avoid |
| const String* disposition_name = sname ? sname->is_empty() ? NULL : sname : sfile; | const String* disposition_name = sname ? sname->is_empty() ? NULL : sname : sfile; |
| if(disposition_name) { | if(disposition_name) { |
| String& filename = *new String(pa_filename(disposition_name->cstr()), String::L_URI); | |
| String& filename_asterisk = *new String(charsets.client().NAME(), String::L_CLEAN) << (*new String("''")) << filename; | |
| VHash& hash=*new VHash(); | VHash& hash=*new VHash(); |
| hash.hash().put(value_name, new VString(as_attachment ? content_disposition_attachment : content_disposition_inline)); | hash.hash().put(value_name, new VString(as_attachment ? content_disposition_attachment : content_disposition_inline)); |
| hash.hash().put(content_disposition_filename_name, new VString(*new String(*disposition_name, String::L_HTTP_HEADER))); | hash.hash().put(content_disposition_filename_name, new VString(filename)); |
| hash.hash().put(content_disposition_filename_name_asterisk, new VString(filename_asterisk)); | |
| response.fields().put(content_disposition_name_upper, &hash); | response.fields().put(content_disposition_name_upper, &hash); |
| } | } |
| Line 952 void Request::output_result(VFile* body_ | Line 973 void Request::output_result(VFile* body_ |
| VDate* vdate=0; | VDate* vdate=0; |
| if(Value* v=body_file->fields().get("mdate")) { | if(Value* v=body_file->fields().get("mdate")) { |
| if(Value* vdatep=v->as(VDATE_TYPE)) | vdate=dynamic_cast<VDate*>(v); |
| vdate=static_cast<VDate*>(vdatep); | if(!vdate) |
| else | |
| throw Exception(PARSER_RUNTIME, 0, "mdate must be a date"); | throw Exception(PARSER_RUNTIME, 0, "mdate must be a date"); |
| } | } |
| if(!vdate) | if(!vdate) |