--- parser3/src/main/pa_request.C 2021/01/02 23:01:11 1.415 +++ parser3/src/main/pa_request.C 2026/10/13 16:14:53 1.461 @@ -1,8 +1,8 @@ /** @file Parser: request class main part. @see compile.C and execute.C. - Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) - Author: Alexandr Petrosian (http://paf.design.ru) + Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) + Authors: Konstantin Morshnev , Alexandr Petrosian */ #include "pa_sapi.h" @@ -33,8 +33,10 @@ #include "pa_vmemory.h" #include "pa_vconsole.h" #include "pa_vdate.h" +#include "pa_varray.h" +#include "pa_code_cache.h" -volatile const char * IDENT_PA_REQUEST_C="$Id: pa_request.C,v 1.415 2021/01/02 23:01:11 moko Exp $" 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: pa_request.C,v 1.461 2026/10/13 16:14:53 moko Exp $" IDENT_PA_REQUEST_H IDENT_PA_REQUEST_CHARSETS_H IDENT_PA_REQUEST_INFO_H IDENT_PA_VCONSOLE_H; // consts @@ -43,9 +45,19 @@ volatile const char * IDENT_PA_REQUEST_C /// content type of response when no $MAIN:defaults.content-type defined const char* DEFAULT_CONTENT_TYPE="text/html"; -const uint LOOP_LIMIT=20000; -const uint EXECUTE_RECOURSION_LIMIT=1000; -const uint HTTPD_TIMEOUT=4; +// One recursion level uses ~0.85KB of stack under linux/gcc, ~1KB under win32 and ~2KB under win64. +// MSVC win64 exception handling requires an extra ~10KB !!! for each throw, and execute() catches and +// 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; // defines for globals @@ -55,10 +67,6 @@ const size_t FILE_SIZE_LIMIT=512*1024*10 #define USE_METHOD_NAME "use" #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" // globals @@ -68,15 +76,11 @@ const String auto_method_name(AUTO_METHO static const String use_method_name(USE_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); 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; size_t pa_file_size_limit=FILE_SIZE_LIMIT; @@ -101,12 +105,16 @@ static const String getter_protected_nam static const String locals_name("LOCALS"); static const String limits_name("LIMITS"); 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 lock_wait_timeout_name("lock_wait_timeout"); static const String httpd_name("HTTPD"); static const String httpd_timeout_name("timeout"); 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 post_process_method_name("postprocess"); @@ -122,6 +130,9 @@ static const String content_disposition_ static const String content_disposition_inline(CONTENT_DISPOSITION_INLINE); 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 #define CHARSET_NAME_UPPER "CHARSET" @@ -131,9 +142,18 @@ static const String content_disposition_ 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): // private - anti_endless_execute_recoursion(0), + anti_endless_execute_recursion(0), + execute_recursion_limit(pa_execute_recursion_limit), // public allow_class_replace(false), @@ -160,16 +180,18 @@ Request::Request(SAPI_Info& asapi_info, // private configure_admin_done(false), - // private defaults - fdefault_lang(adefault_lang), // private mime types - mime_types(0) + mime_types(0), + fheaders_sent(false) { pa_register_thread_request(*this); // file_no=0 => unknown - file_list+="UNKNOWN"; - file_list+="-body of process-"; // pseudo_file_no__process + file_list+=String::Body("UNKNOWN"); + 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 cache_managers->maybe_expire(); @@ -242,6 +264,16 @@ VStateless_class& Request::get_class_ref 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) { pa_charsets.load_charset(*charsets, akey, avalue->as_string()); } @@ -325,17 +357,25 @@ void Request::configure_admin(VStateless if(pa_loop_limit==0) pa_loop_limit=INT_MAX; }, "LIMITS.%s must be int"); - pa_execute_recoursion_limit=EXECUTE_RECOURSION_LIMIT; - CONF_OPTION(limits, recoursion_limit_name, { - pa_execute_recoursion_limit=option->as_int(); - if(pa_execute_recoursion_limit==0) pa_execute_recoursion_limit=INT_MAX; + pa_array_limit=ARRAY_LIMIT; + CONF_OPTION(limits, array_limit_name, { + pa_array_limit=option->as_int(); + 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"); + execute_recursion_limit=pa_execute_recursion_limit; pa_file_size_limit=FILE_SIZE_LIMIT; CONF_OPTION(limits, file_size_limit_name, { double limit=option->as_double(); 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; if(pa_file_size_limit==0) pa_file_size_limit=SSIZE_MAX; @@ -345,7 +385,7 @@ void Request::configure_admin(VStateless CONF_OPTION(limits, lock_wait_timeout_name, { double limit=option->as_double(); 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; }, "LIMITS.%s must be number"); @@ -364,12 +404,18 @@ void Request::configure_admin(VStateless 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 // until someone with less privileges have overriden them 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_COMMENT_TYPE_FORMAT "%s [%s]" @@ -407,7 +453,7 @@ const char* Request::get_exception_cstr( 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}] Table::columns_type stack_trace_columns(new ArrayString); *stack_trace_columns+=new String("name"); @@ -416,8 +462,8 @@ Table &Request::Exception_trace::table(R *stack_trace_columns+=new String("colno"); Table& stack_trace=*new Table(stack_trace_columns); - if(!is_empty()/*signed!*/) - for(size_t i=bottom_index(); iget_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. MAIN class consists of all the auto.p files we'd manage to find @@ -459,14 +507,12 @@ void Request::configure() { @test log stack trace */ -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; - +void Request::core(const char* config_filespec, const String &amain_method_name, const String* amain_class_name) { + bool header_only=is_header_only(request_info); try { // loading config if(config_filespec) - use_file_directly(*new String(config_filespec)); + use_file_directly(*new String(backslashes_to_slashes(config_filespec))); // filling mail received mail.fill_received(*this); @@ -487,9 +533,13 @@ void Request::core(const char* config_fi if(!body_string) throw Exception(PARSER_RUNTIME, &amain_method_name, "method not found in class %s", main.type()); + // ^response:send[] was used: only its parts are sent + if(fheaders_sent) + return; + // extract response body 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) body_value=response.fields().get(body_name_upper); // $response:body if(!body_value) @@ -504,31 +554,34 @@ 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 - try { - // we're returning not result, but error explanation - Request::Exception_details details=get_details(e); - const char* exception_cstr=get_exception_cstr(e, details); + // OK, as native execution stack has unwound + anti_endless_execute_recursion=0; - // reset language to default - flang=fdefault_lang; - // reset response - response.fields().clear(); + // we're returning not result, but error explanation + VException& details=*new VException(*this, e); + const char* exception_cstr=get_exception_cstr(e, details); + + // reset response + response.fields().clear(); + SAPI::clear_headers(sapi_info); - // this is what we'd return in $response:body - const String* body_string=0; + // this is what we'd return in $response:body + const String* body_string=0; + try { // 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))) { // 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) - Value *params[]={&details.vhash, new VTable(&stack_trace)}; + Value *params[]={&details, new VTable(&stack_trace)}; METHOD_FRAME_ACTION(*method, 0 /*no caller*/, main_class, { frame.store_params(params, 2); call(frame); @@ -537,31 +590,28 @@ void Request::core(const char* config_fi } // conditionally log it - Value* vhandled=details.vhash.hash().get(exception_handled_part_name); + Value* vhandled=details.handled(); if(!vhandled || !vhandled->as_bool()) { 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(!fheaders_sent) + 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? - 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); - // unconditionally log the beast in exception handler - throw Exception(0, 0, "Unhandled exception in %s", get_exception_cstr(e, details)); + if(body_string) { // could report an error beautifully? + VString body_vstring(*body_string); + output_result(body_vstring.as_vfile(flang, &charsets), header_only, false); + } else if(!fheaders_sent) { + // doing that ugly + SAPI::send_error(sapi_info, exception_cstr, strcmp(e.type(), "file.missing") ? "500" : "404"); } } - - // 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) { @@ -579,7 +629,7 @@ void Request::use_file_directly(const St return; if(with_auto_p) { - // loading auto.p files from document_root/.. + // loading auto.p files from document_root/.. // to the one beside requested file. // all assigned bases from upper dir const char* target=file_spec.cstr(); @@ -588,12 +638,9 @@ void Request::use_file_directly(const St request_info.path_translated=target; const char* after=target; - size_t drlen=strlen(request_info.document_root); - if(memcmp(after, request_info.document_root, drlen)==0) { - after+=drlen; - if(after[-1]=='/') - --after; - } + size_t drlen=strlen(request_info.document_root_path); + if(path_cmp(after, request_info.document_root_path, drlen)==0) + after+=drlen; // to start from document_root/auto.p while(const char* before=strchr(after, '/')) { String& sfile_spec=*new String; @@ -607,21 +654,32 @@ 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)) - 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) { - if(file_name.is_empty()) +void Request::use_file(const String& afile_name, const String* use_filespec/*absolute*/, bool with_auto_p) { + if(afile_name.is_empty()) throw Exception(PARSER_RUNTIME, 0, "usage failed - no filename was specified"); 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] - filespec=&full_disk_path(file_name); + 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(afile_name); 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) filespec=file_exist(use_filespec->mid(0, last_slash_pos), file_name); // found in current dir? } @@ -671,17 +729,21 @@ 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 Temp_method temp_method_conf(aclass, conf_method_name, 0); // temporary zero @auto to avoid it second execution Temp_method temp_method_auto(aclass, auto_method_name, 0); // 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= - new VString(*new String(file_list[file_no], String::L_TAINTED)); + new VString(file_list[file_no]); for(size_t i=0; iname*/); // 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(); } } 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); String& result=*new String; if(rsplit(hpath, '/')) // if something/splitted @@ -706,22 +769,54 @@ const String& Request::relative(const ch return result; } -const String& Request::full_disk_path(const String& relative_name) { - if(relative_name.first_char()=='/') { - String& result=*new String(pa_strdup(request_info.document_root)); - result << relative_name; - return result; - } - if(relative_name.pos("://")!=STRING_NOT_FOUND // something like "http://xxx" +void Request_info::set_document_root(const char* adocument_root) { + document_root=adocument_root; + char* path=backslashes_to_slashes(adocument_root); + if(path) { + size_t length=strlen(path); + while(length && path[length-1]=='/') + 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 - || relative_name.pos(":")==1 // DRIVE: - || relative_name.starts_with("\\\\") // UNC1 - || relative_name.starts_with("//") // UNC2 + throw Exception(PARSER_RUNTIME, &relative_name, "must be file:///path, file://localhost/path or file://server/share/path"); +#else + throw Exception(PARSER_RUNTIME, &relative_name, "must be file:///path or file://localhost/path"); #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); + } + + 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); - return relative(request_info.path_translated ? request_info.path_translated : request_info.document_root, relative_name); + 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 @@ -755,10 +850,10 @@ static void output_sole_piece(Request& r output=Charset::transcode(output, r.charsets.source(), r.charsets.client()); // 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 - SAPI::send_header(r.sapi_info); + SAPI::send_headers(r.sapi_info); // send body if(!header_only) @@ -787,9 +882,9 @@ static void parse_range(const String* s, while(*p==' ' || *p=='\t') p++; if(*p >= '0' && *p <= '9'){ - const char *s=p; + const char *n=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++; @@ -799,9 +894,9 @@ static void parse_range(const String* s, while(*p==' ' || *p=='\t') p++; if(*p >= '0' && *p <= '9'){ - const char *s=p; + const char *n=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++; @@ -822,13 +917,12 @@ struct Send_range_action_info { 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; - SAPI::send_header(info.r->sapi_info); + SAPI::send_headers(info.r->sapi_info); pa_lseek(f, info.offset, SEEK_SET); - const size_t BUFSIZE = 128*0x400; - char buf[BUFSIZE]; + char buf[FILE_BUFFER_SIZE]; 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); if(to_write == 0) @@ -860,6 +954,8 @@ static void output_pieces(Request& r, bo return SAPI::send_error(r.sapi_info, "", "416"); if(rg.start == UNSET && rg.end != UNSET){ + if(rg.end == 0) + return SAPI::send_error(r.sapi_info, "", "416"); if(rg.end > content_length) rg.end = content_length; rg.start = content_length - rg.end; @@ -879,7 +975,7 @@ static void output_pieces(Request& r, bo part_length = rg.end-rg.start+1; 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, "content-range", buf); } else { @@ -887,13 +983,13 @@ 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) SAPI::add_header_attribute(r.sapi_info, "last-modified", attributed_meaning_to_string(date, String::L_AS_IS, true).cstr()); if(header_only){ - SAPI::send_header(r.sapi_info); + SAPI::send_headers(r.sapi_info); } else { Send_range_action_info info = { &r, offset, part_length}; file_read_action_under_lock(r.full_disk_path(filename), "send", send_range, &info); @@ -901,6 +997,9 @@ static void output_pieces(Request& r, bo } void Request::output_result(VFile* body_file, bool header_only, bool as_attachment) { + if(fheaders_sent) + return; + // header: cookies cookie.output_result(sapi_info); @@ -917,9 +1016,13 @@ void Request::output_result(VFile* body_ // Content-Disposition, use $.name[] to avoid const String* disposition_name = sname ? sname->is_empty() ? NULL : sname : sfile; 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(); 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); } @@ -952,9 +1055,8 @@ void Request::output_result(VFile* body_ VDate* vdate=0; if(Value* v=body_file->fields().get("mdate")) { - if(Value* vdatep=v->as(VDATE_TYPE)) - vdate=static_cast(vdatep); - else + vdate=dynamic_cast(v); + if(!vdate) throw Exception(PARSER_RUNTIME, 0, "mdate must be a date"); } if(!vdate) @@ -970,6 +1072,27 @@ void Request::output_result(VFile* body_ } } +void Request::send(Value& part) { + // converted before the headers: a stat-ed file throws here + VFile* file=dynamic_cast(&part); + String::C output=file ? String::C(file->value_ptr(), file->value_size()) : String::C(part.get_string()->cstr_to_string_body_untaint(flang, 0, &charsets)); + if(!file || file->is_text_mode()) + output=Charset::transcode(output, charsets.source(), charsets.client()); + + if(!fheaders_sent) { + fheaders_sent=true; + // the headers: cookies and $response:fields, without Content-Length + cookie.output_result(sapi_info); + response.fields().put_dont_replace(content_type_name_upper, new VString(*new String(DEFAULT_CONTENT_TYPE))); + Add_header_attribute_info info(*this); + response.fields().for_each(add_header_attribute, &info); + SAPI::send_headers(sapi_info); + } + + 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) { return mime_type_of(file_name?file_name->taint_cstr(String::L_FILE_SPEC):0); } @@ -1010,47 +1133,83 @@ const String& Request::transcode(const x } #endif -Request::Exception_details Request::get_details(const Exception& e) { - const String* problem_source=e.problem_source(); - VHash& vhash=*new VHash; HashStringValue& hash=vhash.hash(); - Operation::Origin origin={0, 0, 0}; +VException::VException(Request& r, const Exception& e): + fexception(e), fhandled(&VBool::get(false)), + ftrace_bottom(r.exception_trace.bottom_index()), ftrace_top(r.exception_trace.top_index()), + 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()) { - Trace bottom=exception_trace.bottom_value(); + Request::Trace bottom=exception_trace.bottom_value(); 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 - 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 - if(const char* type=e.type(true)) - 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)); - } +Value* VException::handled() { + return fhash ? fhash->get(Symbols::HANDLED_SYMBOL) : fhandled; +} - // $.comment - if(const char* comment=e.comment(true)) - hash.put(exception_comment_part_name, new VString(*new String(comment, String::L_TAINTED))); +Table& VException::stack(Request& r) { + if(ftrace_top==size_t(-1)) + 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) - hash.put(exception_handled_part_name, &VBool::get(false)); + if(const char* type=fexception.type(true)) + 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) :