|
|
| version 1.415, 2021/01/02 23:01:11 | version 1.449, 2026/09/27 22:44:35 |
|---|---|
| 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-2026 Art. Lebedev Studio (https://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; | // One recursion level uses ~0.85KB of stack under linux/gcc, ~1KB under win32 and ~2KB under win64. |
| const uint EXECUTE_RECOURSION_LIMIT=1000; | // MSVC win64 exception handling requires an extra ~10KB !!! for each throw, and execute() catches and |
| const uint HTTPD_TIMEOUT=4; | // rethrows on every level to provide a stack trace. |
| // So 1000 levels need ~1MB under linux (default 8MB stack is enough) and ~2+10=12MB under win64, | |
| // thus the stack is set to 32MB in the MSVC project settings. | |
| const int EXECUTE_RECURSION_LIMIT=1000; | |
| const int EXECUTE_RECURSION_RESERVE=100; | |
| const int LOOP_LIMIT=100000; | |
| const int ARRAY_LIMIT=1000000; | |
| const int HTTPD_TIMEOUT=4; | |
| const size_t FILE_SIZE_LIMIT=512*1024*1024; | const size_t FILE_SIZE_LIMIT=512*1024*1024; |
| // defines for globals | // defines for globals |
| Line 55 const size_t FILE_SIZE_LIMIT=512*1024*10 | Line 64 const size_t FILE_SIZE_LIMIT=512*1024*10 |
| #define USE_METHOD_NAME "use" | #define USE_METHOD_NAME "use" |
| #define AUTOUSE_METHOD_NAME "autouse" | #define AUTOUSE_METHOD_NAME "autouse" |
| #define EXCEPTION_TYPE_PART_NAME "type" | |
| #define EXCEPTION_SOURCE_PART_NAME "source" | |
| #define EXCEPTION_COMMENT_PART_NAME "comment" | |
| #define ORIGIN_KEY "origin" | #define ORIGIN_KEY "origin" |
| // globals | // globals |
| Line 68 const String auto_method_name(AUTO_METHO | Line 73 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 exception_source_part_name(EXCEPTION_SOURCE_PART_NAME); | |
| const String exception_comment_part_name(EXCEPTION_COMMENT_PART_NAME); | |
| const String 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 102 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 124 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 131 static const String content_disposition_ | Line 136 static const String content_disposition_ |
| VStateless_class& VClassMAIN_create(); | VStateless_class& VClassMAIN_create(); |
| // | // |
| void Request::recursion_limit_reached() { | |
| if(execute_recursion_limit>pa_execute_recursion_limit) | |
| throw Recursion_limit_exception(); | |
| execute_recursion_limit+=EXECUTE_RECURSION_RESERVE; | |
| throw Exception(PARSER_RUNTIME, 0, "call canceled - endless recursion detected"); | |
| } | |
| 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), |
| execute_recursion_limit(pa_execute_recursion_limit), | |
| // public | // public |
| allow_class_replace(false), | allow_class_replace(false), |
| Line 168 Request::Request(SAPI_Info& asapi_info, | Line 182 Request::Request(SAPI_Info& asapi_info, |
| pa_register_thread_request(*this); | pa_register_thread_request(*this); |
| // file_no=0 => unknown | // file_no=0 => unknown |
| file_list+="UNKNOWN"; | file_list+=String::Body("UNKNOWN"); |
| file_list+="-body of process-"; // pseudo_file_no__process | file_list+=String::Body("-body of process-"); // pseudo_file_no__process |
| // maybe expire old caches | // maybe expire old caches |
| cache_managers->maybe_expire(); | cache_managers->maybe_expire(); |
| Line 242 VStateless_class& Request::get_class_ref | Line 256 VStateless_class& Request::get_class_ref |
| return *result; | return *result; |
| } | } |
| bool Request::add_class(const char* atype, VStateless_class *aclass){ | |
| if(!classes().put_dont_replace(String::Body(atype), aclass)) | |
| return true; | |
| if(!allow_class_replace && strcmp(atype, VARRAY_TYPE)) | |
| return false; | |
| classes().remove(String::Body(atype)); | |
| 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 349 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; | |
| pa_execute_recursion_limit=min(pa_execute_recursion_limit, INT_MAX-EXECUTE_RECURSION_RESERVE); | |
| }, "LIMITS.%s must be int"); | }, "LIMITS.%s must be int"); |
| execute_recursion_limit=pa_execute_recursion_limit; | |
| 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 %s", file_size_limit_name.cstr(), pa_itoa(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 377 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 369 void Request::configure_admin(VStateless | Line 401 void Request::configure_admin(VStateless |
| methoded_array().configure_admin(*this); | methoded_array().configure_admin(*this); |
| } | } |
| const char* Request::get_exception_cstr(const Exception& e, Request::Exception_details& details) { | const char* Request::get_exception_cstr(const Exception& e, VException& details) { |
| #define PA_URI_FORMAT "%s: " | #define PA_URI_FORMAT "%s: " |
| #define PA_COMMENT_TYPE_FORMAT "%s [%s]" | #define PA_COMMENT_TYPE_FORMAT "%s [%s]" |
| Line 407 const char* Request::get_exception_cstr( | Line 439 const char* Request::get_exception_cstr( |
| return result; | return result; |
| } | } |
| Table &Request::Exception_trace::table(Request &r){ | Table &Request::Exception_trace::table(Request &r, const String* problem_source, size_t bottom, size_t top){ |
| // $stack[^table::create{name file lineno colno}] | // $stack[^table::create{name file lineno colno}] |
| Table::columns_type stack_trace_columns(new ArrayString); | Table::columns_type stack_trace_columns(new ArrayString); |
| *stack_trace_columns+=new String("name"); | *stack_trace_columns+=new String("name"); |
| Line 416 Table &Request::Exception_trace::table(R | Line 448 Table &Request::Exception_trace::table(R |
| *stack_trace_columns+=new String("colno"); | *stack_trace_columns+=new String("colno"); |
| Table& stack_trace=*new Table(stack_trace_columns); | Table& stack_trace=*new Table(stack_trace_columns); |
| if(!is_empty()/*signed!*/) | if(bottom<top) |
| for(size_t i=bottom_index(); i<top_index(); i++) { | for(size_t i=bottom+(get(bottom).name()==problem_source); i<top; i++) { |
| Trace trace=get(i); | Trace trace=get(i); |
| Table::element_type row(new ArrayString); | Table::element_type row(new ArrayString); |
| Line 425 Table &Request::Exception_trace::table(R | Line 457 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 439 void Request::configure() { | Line 471 void Request::configure() { |
| if(!configure_admin_done) | if(!configure_admin_done) |
| configure_admin(main_class); | configure_admin(main_class); |
| // configure not-admin=user options | |
| methoded_array().configure_user(*this); | |
| // $MAIN:MIME-TYPES | // $MAIN:MIME-TYPES |
| if(Value* element=main_class.get_element(mime_types_name)) | if(Value* element=main_class.get_element(mime_types_name)) |
| if(Table *table=element->get_table()) | if(Table *table=element->get_table()) |
| Line 460 void Request::configure() { | Line 489 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 515 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 530 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); | // OK, as native execution stack has unwound |
| const char* exception_cstr=get_exception_cstr(e, details); | anti_endless_execute_recursion=0; |
| // reset language to default | // we're returning not result, but error explanation |
| flang=fdefault_lang; | VException& details=*new VException(*this, e); |
| // reset response | const char* exception_cstr=get_exception_cstr(e, details); |
| response.fields().clear(); | |
| // reset language to default | |
| flang=fdefault_lang; | |
| // 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] |
| Table& stack_trace=exception_trace.table(*this); | Table& stack_trace=exception_trace.table(*this, details.problem_source); |
| details.expire_trace(); | |
| exception_trace.clear(); // forget all about previous life, in case there would be error inside of this method, error handled would not be mislead by old stack contents (see extract_origin) | exception_trace.clear(); // forget all about previous life, in case there would be error inside of this method, error handled would not be mislead by old stack contents (see extract_origin) |
| Value *params[]={&details.vhash, new VTable(&stack_trace)}; | Value *params[]={&details, new VTable(&stack_trace)}; |
| METHOD_FRAME_ACTION(*method, 0 /*no caller*/, main_class, { | METHOD_FRAME_ACTION(*method, 0 /*no caller*/, main_class, { |
| frame.store_params(params, 2); | frame.store_params(params, 2); |
| call(frame); | call(frame); |
| Line 537 void Request::core(const char* config_fi | Line 568 void Request::core(const char* config_fi |
| } | } |
| // conditionally log it | // conditionally log it |
| Value* vhandled=details.vhash.hash().get(exception_handled_part_name); | Value* vhandled=details.handled(); |
| 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 | |
| VException& details=*new VException(*this, e); | |
| // logging both initial and new exceptions | |
| 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; | |
| } | |
| if(body_string) { // could report an error beautifully? | if(body_string) { // could report an error beautifully? |
| VString body_vstring(*body_string); | VString body_vstring(*body_string); |
| output_result(body_vstring.as_vfile(flang, &charsets), header_only, false); | |
| body_file=body_vstring.as_vfile(flang, &charsets); | } else { |
| as_attachment=false; | // doing that ugly |
| } else { | SAPI::send_error(sapi_info, exception_cstr, strcmp(e.type(), "file.missing") ? "500" : "404"); |
| // 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); | |
| // unconditionally log the beast in exception handler | |
| throw Exception(0, 0, "Unhandled exception in %s", get_exception_cstr(e, details)); | |
| } | } |
| } | } |
| // write out the result outside of try as network exceptions should not be handled by parser code. | |
| output_result(body_file, header_only, as_attachment); | |
| } | } |
| uint Request::register_file(String::Body file_spec) { | uint Request::register_file(String::Body file_spec) { |
| Line 681 void Request::use_buf(VStateless_class& | Line 708 void Request::use_buf(VStateless_class& |
| ArrayClass& cclasses=compile(&aclass, source, main_alias, file_no, line_no_offset); | ArrayClass& cclasses=compile(&aclass, source, main_alias, file_no, line_no_offset); |
| VString* vfilespec= | VString* vfilespec= |
| new VString(*new String(file_list[file_no], String::L_TAINTED)); | new VString(file_list[file_no]); |
| for(size_t i=0; i<cclasses.count(); i++){ | for(size_t i=0; i<cclasses.count(); i++){ |
| VStateless_class& cclass=*cclasses.get(i); | VStateless_class& cclass=*cclasses.get(i); |
| Line 691 void Request::use_buf(VStateless_class& | Line 718 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 707 const String& Request::relative(const ch | Line 734 const String& Request::relative(const ch |
| } | } |
| const String& Request::full_disk_path(const String& relative_name) { | const String& Request::full_disk_path(const String& relative_name) { |
| if(is_os_absolute_path(relative_name)) | |
| return relative_name; | |
| if(relative_name.first_char()=='/') { | if(relative_name.first_char()=='/') { |
| String& result=*new String(pa_strdup(request_info.document_root)); | String& result=*new String(pa_strdup(request_info.document_root)); |
| 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 | |
| || relative_name.pos(":")==1 // DRIVE: | |
| || relative_name.starts_with("\\\\") // UNC1 | |
| || relative_name.starts_with("//") // UNC2 | |
| #endif | |
| ) | |
| return relative_name; | return relative_name; |
| return relative(request_info.path_translated ? request_info.path_translated : request_info.document_root, relative_name); | return relative(request_info.path_translated ? request_info.path_translated : request_info.document_root, relative_name); |
| Line 755 static void output_sole_piece(Request& r | Line 778 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 810 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)); // ((uint64_t)-1) is treated as UNSET |
| } | } |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| Line 799 static void parse_range(const String* s, | Line 822 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)); // ((uint64_t)-1) is treated as UNSET |
| } | } |
| while(*p==' ' || *p=='\t') p++; | while(*p==' ' || *p=='\t') p++; |
| Line 822 struct Send_range_action_info { | Line 845 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 860 static void output_pieces(Request& r, bo | Line 882 static void output_pieces(Request& r, bo |
| return SAPI::send_error(r.sapi_info, "", "416"); | return SAPI::send_error(r.sapi_info, "", "416"); |
| if(rg.start == UNSET && rg.end != UNSET){ | if(rg.start == UNSET && rg.end != UNSET){ |
| if(rg.end == 0) | |
| return SAPI::send_error(r.sapi_info, "", "416"); | |
| if(rg.end > content_length) | if(rg.end > content_length) |
| rg.end = content_length; | rg.end = content_length; |
| rg.start = content_length - rg.end; | rg.start = content_length - rg.end; |
| Line 879 static void output_pieces(Request& r, bo | Line 903 static void output_pieces(Request& r, bo |
| part_length = rg.end-rg.start+1; | part_length = rg.end-rg.start+1; |
| char buf[MAX_STRING]; | char buf[MAX_STRING]; |
| snprintf(buf, MAX_STRING, "bytes %.15g-%.15g/%.15g", (double)rg.start, (double)rg.end, (double)content_length); | snprintf(buf, MAX_STRING, "bytes %s-%s/%s", pa_uitoa(rg.start), pa_uitoa(rg.end), pa_uitoa(content_length)); |
| SAPI::add_header_attribute(r.sapi_info, HTTP_STATUS, "206"); | SAPI::add_header_attribute(r.sapi_info, HTTP_STATUS, "206"); |
| SAPI::add_header_attribute(r.sapi_info, "content-range", buf); | SAPI::add_header_attribute(r.sapi_info, "content-range", buf); |
| } else { | } else { |
| Line 887 static void output_pieces(Request& r, bo | Line 911 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 941 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 980 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) |
| Line 1010 const String& Request::transcode(const x | Line 1037 const String& Request::transcode(const x |
| } | } |
| #endif | #endif |
| Request::Exception_details Request::get_details(const Exception& e) { | VException::VException(Request& r, const Exception& e): |
| const String* problem_source=e.problem_source(); | fexception(e), fhandled(&VBool::get(false)), |
| VHash& vhash=*new VHash; HashStringValue& hash=vhash.hash(); | ftrace_bottom(r.exception_trace.bottom_index()), ftrace_top(r.exception_trace.top_index()), |
| Operation::Origin origin={0, 0, 0}; | origin(Operation::Origin::create(0, 0, 0)), problem_source(e.problem_source()) { |
| Request::Exception_trace& exception_trace=r.exception_trace; | |
| if(!exception_trace.is_empty()) { | if(!exception_trace.is_empty()) { |
| Trace bottom=exception_trace.bottom_value(); | Request::Trace bottom=exception_trace.bottom_value(); |
| origin=bottom.origin(); | origin=bottom.origin(); |
| if(!problem_source) { // we don't know who trigged the bug | if(!problem_source) // we don't know who trigged the bug |
| problem_source=bottom.name(); // we usually know source of next-from-throw-point exception did that | problem_source=bottom.name(); // we usually know source of next-from-throw-point exception did that |
| exception_trace.set_bottom_index(exception_trace.bottom_index()+1); | |
| } else if (bottom.name()==problem_source) { // it is that same guy? | |
| exception_trace.set_bottom_index(exception_trace.bottom_index()+1); // throw away that trace | |
| } else { | |
| // stack top contains not us, leaving intact to help ^throw | |
| } | |
| } | } |
| if(origin.file_no) | |
| ffile=r.file_list[origin.file_no]; | |
| } | |
| // $.type | Value* VException::handled() { |
| if(const char* type=e.type(true)) | return fhash ? fhash->get(Symbols::HANDLED_SYMBOL) : fhandled; |
| hash.put(exception_type_part_name, new VString(*new String(type))); | } |
| // $.source | |
| if(problem_source) | |
| hash.put(exception_source_part_name, new VString(*new String(*problem_source, String::L_TAINTED))); | |
| // $.file $.lineno $.colno | |
| if(origin.file_no){ | |
| hash.put("file", new VString(*new String(file_list[origin.file_no], String::L_TAINTED))); | |
| hash.put("lineno", new VInt(1+origin.line)); | |
| hash.put("colno", new VInt(1+origin.col)); | |
| } | |
| // $.comment | Table& VException::stack(Request& r) { |
| if(const char* comment=e.comment(true)) | if(ftrace_top==size_t(-1)) |
| hash.put(exception_comment_part_name, new VString(*new String(comment, String::L_TAINTED))); | throw Exception(PARSER_RUNTIME, 0, "exception stack used outside of context"); |
| return r.exception_trace.table(r, problem_source, ftrace_bottom, ftrace_top); | |
| } | |
| Value* VException::get_element(const String& name) { | |
| if(!fhash) { | |
| if(SYMBOLS_EQ(name,HANDLED_SYMBOL)) | |
| return fhandled; | |
| if(SYMBOLS_EQ(name,TYPE_SYMBOL)) | |
| return fexception.type(true) ? new VString(*new String(fexception.type())) : 0; | |
| if(SYMBOLS_EQ(name,SOURCE_SYMBOL)) | |
| return problem_source ? new VString(*problem_source) : 0; | |
| if(SYMBOLS_EQ(name,COMMENT_SYMBOL)) | |
| return fexception.comment(true) ? new VString(fexception.comment()) : 0; | |
| if(SYMBOLS_EQ(name,FILE_SYMBOL)) | |
| return origin.file_no ? new VString(ffile) : 0; | |
| if(SYMBOLS_EQ(name,LINENO_SYMBOL)) | |
| return origin.file_no ? new VInt(1+origin.line) : 0; | |
| if(SYMBOLS_EQ(name,COLNO_SYMBOL)) | |
| return origin.file_no ? new VInt(1+origin.col) : 0; | |
| if(SYMBOLS_EQ(name,FIELDS_SYMBOL)) | |
| return this; | |
| } | |
| // Computed field names need not be interned: use ordinary hash lookup. | |
| return VHashReference::get_element(name); | |
| } | |
| const VJunction* VException::put_element(const String& name, Value* value) { | |
| if(!fhash && SYMBOLS_EQ(name,HANDLED_SYMBOL)) { | |
| fhandled=value; | |
| return 0; | |
| } | |
| return VHashReference::put_element(name, value); | |
| } | |
| HashStringValue& VException::hash() { | |
| if(fhash) | |
| return *fhash; | |
| HashStringValue& result=*new HashStringValue; | |
| // $.handled(0) | if(const char* type=fexception.type(true)) |
| hash.put(exception_handled_part_name, &VBool::get(false)); | result.put(Symbols::TYPE_SYMBOL, new VString(*new String(type))); |
| if(problem_source) | |
| result.put(Symbols::SOURCE_SYMBOL, new VString(*problem_source)); | |
| if(origin.file_no) { | |
| result.put(Symbols::FILE_SYMBOL, new VString(ffile)); | |
| result.put(Symbols::LINENO_SYMBOL, new VInt(1+origin.line)); | |
| result.put(Symbols::COLNO_SYMBOL, new VInt(1+origin.col)); | |
| } | |
| if(const char* comment=fexception.comment(true)) | |
| result.put(Symbols::COMMENT_SYMBOL, new VString(comment)); | |
| result.put(Symbols::HANDLED_SYMBOL, fhandled); | |
| return Request::Exception_details(origin, problem_source, vhash); | fhash=&result; |
| return result; | |
| } | } |
| Temp_value_element::Temp_value_element(Request& arequest, Value& awhere, const String& aname, Value* awhat) : | Temp_value_element::Temp_value_element(Request& arequest, Value& awhere, const String& aname, Value* awhat) : |