|
|
| version 1.415, 2021/01/02 23:01:11 | version 1.464, 2026/10/16 13:55:16 |
|---|---|
| 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 26 | Line 26 |
| #include "pa_charsets.h" | #include "pa_charsets.h" |
| #include "pa_cache_managers.h" | #include "pa_cache_managers.h" |
| #include "pa_http.h" | #include "pa_http.h" |
| #include "pa_websocket.h" | |
| #include "pa_vmail.h" | #include "pa_vmail.h" |
| #include "pa_vform.h" | #include "pa_vform.h" |
| #include "pa_vcookie.h" | #include "pa_vcookie.h" |
| Line 33 | Line 34 |
| #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" | |
| #include "pa_code_cache.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 46 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. | |
| // Worse, win64 keeps the unwound frames until the catch block returns, so a handler running away | |
| // in its turn starts on top of them and needs one more such budget. The stack is set to 40MB in | |
| // the MSVC project settings to allow nested three runaways. | |
| 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 68 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 77 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 106 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"); |
| static const String httpd_timeout_name("timeout"); | static const String httpd_timeout_name("timeout"); |
| static const String httpd_mode_name("mode"); | static const String httpd_mode_name("mode"); |
| #ifdef CODE_CACHE | |
| static const String httpd_code_cache_name("code-cache"); | |
| #endif | |
| static const String conf_method_name("conf"); | static const String conf_method_name("conf"); |
| static const String post_process_method_name("postprocess"); | static const String post_process_method_name("postprocess"); |
| Line 122 static const String content_disposition_ | Line 131 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 143 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), |
| method_frame(0), | method_frame(0), |
| rcontext(0), | rcontext(0), |
| wcontext(0), | wcontext(0), |
| flang(adefault_lang), | |
| fconnection(0), | fconnection(0), |
| fin_cycle(0), | fin_cycle(0), |
| fskip(SKIP_NOTHING), | fskip(SKIP_NOTHING), |
| Line 153 Request::Request(SAPI_Info& asapi_info, | Line 173 Request::Request(SAPI_Info& asapi_info, |
| main_class(VClassMAIN_create()), | main_class(VClassMAIN_create()), |
| form(*new VForm(charsets, arequest_info)), | form(*new VForm(charsets, arequest_info)), |
| mail(*new VMail), | mail(*new VMail), |
| response(*new VResponse(arequest_info, charsets)), | response(*new VResponse(arequest_info, charsets, adefault_lang)), |
| cookie(*new VCookie(charsets, arequest_info)), | cookie(*new VCookie(charsets, arequest_info)), |
| console(*new VConsole), | console(*new VConsole), |
| websocket(0), | |
| // private | // private |
| configure_admin_done(false), | configure_admin_done(false), |
| // private defaults | |
| fdefault_lang(adefault_lang), | |
| // private mime types | // private mime types |
| mime_types(0) | mime_types(0) |
| { | { |
| 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 |
| #ifdef CODE_CACHE | |
| Code_cache_manager::add_files(file_list); | |
| #endif | |
| // 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 264 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 357 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 385 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 364 void Request::configure_admin(VStateless | Line 404 void Request::configure_admin(VStateless |
| HTTPD_Server::set_mode(option->as_string()); | HTTPD_Server::set_mode(option->as_string()); |
| } | } |
| #ifdef CODE_CACHE | |
| CONF_OPTION(httpd, httpd_code_cache_name, { | |
| Code_cache_manager::set_enabled(option->as_bool()); | |
| }, "HTTPD.%s must be bool"); | |
| #endif | |
| // configure method_frame options | // configure method_frame options |
| // until someone with less privileges have overriden them | // until someone with less privileges have overriden them |
| 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 453 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 462 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 471 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 485 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()) |
| mime_types=table; | mime_types=table; |
| } | } |
| /// HEAD request: only the headers are sent | |
| static bool is_header_only(Request_info& info) { | |
| return info.method && strcasecmp(info.method, "HEAD")==0; | |
| } | |
| /** | /** |
| load MAIN class, execute @main. | load MAIN class, execute @main. |
| MAIN class consists of all the auto.p files we'd manage to find | MAIN class consists of all the auto.p files we'd manage to find |
| Line 459 void Request::configure() { | Line 507 void Request::configure() { |
| @test log stack trace | @test log stack trace |
| */ | */ |
| 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, const String &amain_method_name, const String* amain_class_name) { |
| VFile* body_file=NULL; | bool header_only=is_header_only(request_info); |
| bool as_attachment=false; | |
| try { | try { |
| // loading config | // loading config |
| if(config_filespec) | if(config_filespec) |
| use_file_directly(*new String(config_filespec)); | use_file_directly(*new String(backslashes_to_slashes(config_filespec))); |
| // filling mail received | // filling mail received |
| mail.fill_received(*this); | mail.fill_received(*this); |
| Line 487 void Request::core(const char* config_fi | Line 533 void Request::core(const char* config_fi |
| if(!body_string) | if(!body_string) |
| throw Exception(PARSER_RUNTIME, &amain_method_name, "method not found in class %s", main.type()); | throw Exception(PARSER_RUNTIME, &amain_method_name, "method not found in class %s", main.type()); |
| if(response.started==VResponse::WEBSOCKET) | |
| websocket->close(); // graceful: waits for the Close of the client | |
| // ignore the result in websocket or stream mode | |
| if(response.started) | |
| return; | |
| // 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 556 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(response.lang, &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 response | |
| response.fields().clear(); | |
| response.lang=response.default_lang; | |
| 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 593 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)); | |
| if(!response.started) | |
| 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(response.lang, &charsets), header_only, false); | |
| body_file=body_vstring.as_vfile(flang, &charsets); | } else if(!response.started) { |
| 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 579 void Request::use_file_directly(const St | Line 632 void Request::use_file_directly(const St |
| return; | return; |
| if(with_auto_p) { | if(with_auto_p) { |
| // loading auto.p files from document_root/.. | // loading auto.p files from document_root/.. |
| // to the one beside requested file. | // to the one beside requested file. |
| // all assigned bases from upper dir | // all assigned bases from upper dir |
| const char* target=file_spec.cstr(); | const char* target=file_spec.cstr(); |
| Line 588 void Request::use_file_directly(const St | Line 641 void Request::use_file_directly(const St |
| request_info.path_translated=target; | request_info.path_translated=target; |
| const char* after=target; | const char* after=target; |
| size_t drlen=strlen(request_info.document_root); | size_t drlen=strlen(request_info.document_root_path); |
| if(memcmp(after, request_info.document_root, drlen)==0) { | if(path_cmp(after, request_info.document_root_path, drlen)==0) |
| after+=drlen; | after+=drlen; // to start from document_root/auto.p |
| if(after[-1]=='/') | |
| --after; | |
| } | |
| while(const char* before=strchr(after, '/')) { | while(const char* before=strchr(after, '/')) { |
| String& sfile_spec=*new String; | String& sfile_spec=*new String; |
| Line 607 void Request::use_file_directly(const St | Line 657 void Request::use_file_directly(const St |
| } | } |
| } | } |
| Code_cache* code=0; | |
| #ifdef CODE_CACHE | |
| if(Code_cache* cached=Code_cache_manager::get(file_spec)) { | |
| use_buf(main_class, 0, 0, cached->file_no, 0, cached); | |
| return; | |
| } | |
| // to record the compilation; only while the httpd config runs | |
| code=Code_cache_manager::create(file_spec); | |
| #endif | |
| if(const char* source=file_read_text(charsets, file_spec, true)) | if(const char* source=file_read_text(charsets, file_spec, true)) |
| use_buf(main_class, source, 0, register_file(file_spec)); | use_buf(main_class, source, 0, register_file(file_spec), 0, code); |
| } | } |
| void Request::use_file(const String& file_name, const String* use_filespec/*absolute*/, bool with_auto_p) { | void Request::use_file(const String& afile_name, const String* use_filespec/*absolute*/, bool with_auto_p) { |
| if(file_name.is_empty()) | if(afile_name.is_empty()) |
| throw Exception(PARSER_RUNTIME, 0, "usage failed - no filename was specified"); | throw Exception(PARSER_RUNTIME, 0, "usage failed - no filename was specified"); |
| const String* filespec=0; | const String* filespec=0; |
| const String& file_name=backslashes_to_slashes(afile_name); | |
| if(file_name.first_char()=='/') //absolute path? [no need to scan MAIN:CLASS_PATH] | if(file_name.first_char()=='/' || path_scheme(afile_name) || is_os_absolute_path(afile_name)) // path is not relative, no need to scan MAIN:CLASS_PATH |
| filespec=&full_disk_path(file_name); | filespec=&full_disk_path(afile_name); |
| else if(use_filespec){ // search in current dir first | else if(use_filespec){ // search in current dir first |
| size_t last_slash_pos=use_filespec->strrpbrk("/"); | size_t last_slash_pos=use_filespec->strrchr('/'); |
| if(last_slash_pos!=STRING_NOT_FOUND) | if(last_slash_pos!=STRING_NOT_FOUND) |
| filespec=file_exist(use_filespec->mid(0, last_slash_pos), file_name); // found in current dir? | filespec=file_exist(use_filespec->mid(0, last_slash_pos), file_name); // found in current dir? |
| } | } |
| Line 671 void Request::use_file(const String& fil | Line 732 void Request::use_file(const String& fil |
| } | } |
| } | } |
| void Request::use_buf(VStateless_class& aclass, const char* source, const String* main_alias, uint file_no, int line_no_offset) { | void Request::use_buf(VStateless_class& aclass, const char* source, const String* main_alias, uint file_no, int line_no_offset, Code_cache* code) { |
| // temporary zero @conf to avoid it second execution | // temporary zero @conf to avoid it second execution |
| Temp_method temp_method_conf(aclass, conf_method_name, 0); | Temp_method temp_method_conf(aclass, conf_method_name, 0); |
| // temporary zero @auto to avoid it second execution | // temporary zero @auto to avoid it second execution |
| Temp_method temp_method_auto(aclass, auto_method_name, 0); | Temp_method temp_method_auto(aclass, auto_method_name, 0); |
| // compile loaded classes | // compile loaded classes |
| ArrayClass& cclasses=compile(&aclass, source, main_alias, file_no, line_no_offset); | ArrayClass& cclasses= |
| #ifdef CODE_CACHE | |
| !source ? code->replay(*this, aclass) : | |
| #endif | |
| compile(&aclass, source, main_alias, file_no, line_no_offset, code); | |
| 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 756 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(); |
| } | } |
| } | } |
| const String& Request::relative(const char* apath, const String& relative_name) { | const String& Request::relative(const char* apath, const String& relative_name) { |
| // apath is a URI or a path already normalized by the target, always slash separated | |
| char *hpath=pa_strdup(apath); | char *hpath=pa_strdup(apath); |
| String& result=*new String; | String& result=*new String; |
| if(rsplit(hpath, '/')) // if something/splitted | if(rsplit(hpath, '/')) // if something/splitted |
| Line 706 const String& Request::relative(const ch | Line 772 const String& Request::relative(const ch |
| return result; | return result; |
| } | } |
| const String& Request::full_disk_path(const String& relative_name) { | void Request_info::set_document_root(const char* adocument_root) { |
| if(relative_name.first_char()=='/') { | document_root=adocument_root; |
| String& result=*new String(pa_strdup(request_info.document_root)); | char* path=backslashes_to_slashes(adocument_root); |
| result << relative_name; | if(path) { |
| return result; | size_t length=strlen(path); |
| } | while(length && path[length-1]=='/') |
| if(relative_name.pos("://")!=STRING_NOT_FOUND // something like "http://xxx" | path[--length]=0; |
| } | |
| document_root_path=path; | |
| } | |
| const String& Request::full_disk_path(const String& relative_name, uint allowed) { | |
| if(relative_name.first_char()!='/') { | |
| if(uint scheme=path_scheme(relative_name)) { | |
| check_path_scheme(scheme, relative_name, allowed); | |
| if(scheme!=PA_SCHEME_FILE) | |
| return relative_name; // an allowed uri is passed through | |
| if(const String* path=file_uri_to_path(relative_name)) | |
| return backslashes_to_slashes(*path); | |
| #ifdef WIN32 | #ifdef WIN32 |
| || relative_name.pos(":")==1 // DRIVE: | throw Exception(PARSER_RUNTIME, &relative_name, "must be file:///path, file://localhost/path or file://server/share/path"); |
| || relative_name.starts_with("\\\\") // UNC1 | #else |
| || relative_name.starts_with("//") // UNC2 | throw Exception(PARSER_RUNTIME, &relative_name, "must be file:///path or file://localhost/path"); |
| #endif | #endif |
| ) | } |
| return relative_name; | // before '\' becomes '/', to avoid mixing the UNC path \\server with web path //server |
| if(is_os_absolute_path(relative_name)) | |
| return backslashes_to_slashes(relative_name); | |
| } | |
| return relative(request_info.path_translated ? request_info.path_translated : request_info.document_root, relative_name); | const String& path=backslashes_to_slashes(relative_name); |
| if(path.first_char()!='/') | |
| return relative(request_info.path_translated ? request_info.path_translated : pa_strcat(request_info.document_root_path, "/"), path); | |
| String& result=*new String(request_info.document_root_path); | |
| result << path; | |
| return result; | |
| } | |
| const String* Request::real_disk_path(const String& name) { | |
| switch(path_scheme(name)) { | |
| case PA_SCHEME_NONE: return &full_disk_path(name); | |
| case PA_SCHEME_FILE: { | |
| const String* path=clean_path_scheme(PA_SCHEME_FILE, name) ? file_uri_to_path(name) : 0; | |
| return path ? &backslashes_to_slashes(*path) : 0; | |
| } | |
| default: return 0; // not a disk path | |
| } | |
| } | } |
| #ifndef DOXYGEN | #ifndef DOXYGEN |
| Line 755 static void output_sole_piece(Request& r | Line 853 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 885 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 897 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 920 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 957 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 978 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 986 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 901 static void output_pieces(Request& r, bo | Line 1000 static void output_pieces(Request& r, bo |
| } | } |
| void Request::output_result(VFile* body_file, bool header_only, bool as_attachment) { | void Request::output_result(VFile* body_file, bool header_only, bool as_attachment) { |
| if(response.started) | |
| return; | |
| // header: cookies | // header: cookies |
| cookie.output_result(sapi_info); | cookie.output_result(sapi_info); |
| Line 917 void Request::output_result(VFile* body_ | Line 1019 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 1058 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 970 void Request::output_result(VFile* body_ | Line 1075 void Request::output_result(VFile* body_ |
| } | } |
| } | } |
| void Request::start(VResponse::Started mode) { | |
| // the headers: cookies and $response:fields, without Content-Length | |
| // prepare before response.started is set: after that an exception is not reported to the client | |
| cookie.output_result(sapi_info); | |
| Add_header_attribute_info info(*this); | |
| response.fields().for_each<Add_header_attribute_info*>(add_header_attribute, &info); | |
| response.started=mode; | |
| SAPI::send_headers(sapi_info); | |
| } | |
| void Request::stream(Value& part) { | |
| if(response.started==VResponse::WEBSOCKET) | |
| throw Exception("websocket", 0, "unable to stream: websocket is active"); | |
| // convert before response.started is set: after that an exception is not reported to the client | |
| VFile* file=dynamic_cast<VFile*>(&part); | |
| String::C output=file ? String::C(file->value_ptr(), file->value_size()) : String::C(part.get_string()->cstr_to_string_body_untaint(response.lang, 0, &charsets)); | |
| if(!file || file->is_text_mode()) | |
| output=Charset::transcode(output, charsets.source(), charsets.client()); | |
| if(!response.started) { | |
| response.fields().put_dont_replace(content_type_name_upper, new VString(*new String(DEFAULT_CONTENT_TYPE))); | |
| start(VResponse::STREAM); | |
| } | |
| if(!is_header_only(request_info)) | |
| SAPI::send_body(sapi_info, output.str, output.length); | |
| } | |
| const String& Request::mime_type_of(const String* file_name) { | const String& Request::mime_type_of(const String* file_name) { |
| return mime_type_of(file_name?file_name->taint_cstr(String::L_FILE_SPEC):0); | return mime_type_of(file_name?file_name->taint_cstr(String::L_FILE_SPEC):0); |
| } | } |
| Line 1010 const String& Request::transcode(const x | Line 1144 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) : |