--- parser3/src/classes/file.C 2004/07/26 10:44:21 1.126 +++ parser3/src/classes/file.C 2005/08/09 08:14:47 1.136 @@ -1,11 +1,11 @@ /** @file Parser: @b file parser class. - Copyright (c) 2001-2004 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char * const IDENT_FILE_C="$Date: 2004/07/26 10:44:21 $"; +static const char * const IDENT_FILE_C="$Date: 2005/08/09 08:14:47 $"; #include "pa_config_includes.h" @@ -25,8 +25,6 @@ static const char * const IDENT_FILE_C=" #include "pa_charset.h" #include "pa_charsets.h" #include "pa_sql_connection.h" -#include "pa_vresponse.h" -#include "pa_vcookie.h" // defines @@ -35,12 +33,19 @@ static const char * const IDENT_FILE_C=" #define STDIN_EXEC_PARAM_NAME "stdin" #define CHARSET_EXEC_PARAM_NAME "charset" +#define NAME_NAME "name" + +// externs + +extern String sql_limit_name; +extern String sql_offset_name; + // class class MFile: public Methoded { public: // VStateless_class - Value* create_new_value(Pool&) { return new VFile(); } + Value* create_new_value(Pool&, HashStringValue&) { return new VFile(); } public: // Methoded bool used_directly() { return true; } @@ -160,9 +165,24 @@ static void _load(Request& r, MethodPara if(third_param_hash) alt_filename_param_index++; + HashStringValue* options=third_param_hash; + size_t offset=0; + size_t limit=0; + if(options) { + options=new HashStringValue(*options); + if(Value *voffset=(Value *)options->get(sql_offset_name)) { + options->remove(sql_offset_name); + offset=r.process_to_value(*voffset).as_int(); + } + if(Value *vlimit=(Value *)options->get(sql_limit_name)) { + options->remove(sql_limit_name); + limit=r.process_to_value(*vlimit).as_int(); + } + // no check on options count here, see file_read + } File_read_result file=file_read(r.charsets, lfile_name, is_text_mode(vmode_name.as_string()), - third_param_hash + options, true, 0, offset, limit ); const char *user_file_name=params.count()>alt_filename_param_index? @@ -171,7 +191,10 @@ static void _load(Request& r, MethodPara Value* vcontent_type=0; if(file.headers) - vcontent_type=file.headers->get(content_type_name); + { + if(Value* remote_content_type=file.headers->get("CONTENT-TYPE")) + vcontent_type=new VString(*new String(remote_content_type->as_string().cstr())); + } if(!vcontent_type) vcontent_type=new VString(r.mime_type_of(user_file_name)); @@ -237,7 +260,7 @@ static void append_env_pair( throw Exception("parser.runtime", new String(akey, String::L_TAINTED), "not safe environment variable"); - info->env->put(akey, avalue->as_string().cstr(String::L_UNSPECIFIED)); + info->env->put(akey, avalue->as_string().cstr_to_string_body(String::L_UNSPECIFIED)); } } #ifndef DOXYGEN @@ -251,10 +274,10 @@ static void pass_cgi_header_attribute( ArrayString::element_type astring, Pass_cgi_header_attribute_info* info) { size_t colon_pos=astring->pos(':'); - if(colon_pos==STRING_NOT_FOUND) { + if(colon_pos!=STRING_NOT_FOUND) { const String& key=astring->mid(0, colon_pos).change_case( *info->charset, String::CC_UPPER); - Value* value=new VString(astring->mid(colon_pos+1, astring->length())); + Value* value=new VString(astring->mid(colon_pos+1, astring->length()).trim()); info->fields->put(key, value); if(key=="CONTENT-TYPE") info->content_type=value; @@ -643,8 +666,8 @@ class File_sql_event_handlers: public SQ int got_cells; public: String::C value; - String* user_file_name; - String* user_content_type; + const String* user_file_name; + const String* user_content_type; public: File_sql_event_handlers( const String& astatement_string, const char* astatement_cstr): @@ -670,10 +693,12 @@ public: value=String::C(str, length); break; case 1: - user_file_name=new String(str, length, true); + if(!user_file_name) // user not specified? + user_file_name=new String(str, length, true); break; case 2: - user_content_type=new String(str, length, true); + if(!user_content_type) // user not specified? + user_content_type=new String(str, length, true); break; default: error=SQL_Error("parser.runtime", "result must not contain more then one row, three rows"); @@ -688,17 +713,33 @@ public: }; #endif static void _sql(Request& r, MethodParams& params) { - const String* user_file_name=0; - if(params.get(0)->is_string()) - user_file_name=¶ms.get(0)->as_string(); - - Value& statement=params.as_junction(params.count()-1, "statement must be code"); + Value& statement=params.as_junction(0, "statement must be code"); Temp_lang temp_lang(r, String::L_SQL); const String& statement_string=r.process_to_string(statement); const char* statement_cstr= statement_string.cstr(String::L_UNSPECIFIED, r.connection()); File_sql_event_handlers handlers(statement_string, statement_cstr); + + if(params.count()>1) + if(HashStringValue* options= + params.as_no_junction(1, "param must not be code").get_hash()) { + int valid_options=0; + if(Value* vfilename=options->get(NAME_NAME)) { + valid_options++; + handlers.user_file_name=&vfilename->as_string(); + } + if(Value* vcontent_type=options->get(CONTENT_TYPE_NAME)) { + valid_options++; + handlers.user_content_type=&vcontent_type->as_string(); + } + if(valid_options!=options->count()) + throw Exception("parser.runtime", + 0, + "called with invalid option"); + } + + r.connection()->query( statement_cstr, 0, 0, @@ -711,266 +752,7 @@ static void _sql(Request& r, MethodParam 0, "produced no result"); - if(!user_file_name) -class send_attr_info -{ -public: - send_attr_info(Request *t) : r(t), add_content_type(true), add_last_modified(true), add_content_disposition(true) {} - Request *r; - bool add_content_type; - bool add_last_modified; - bool add_content_disposition; -}; - -static void send_add_header_attribute( - HashStringValue::key_type aattribute, - HashStringValue::value_type ameaning, - send_attr_info *r) -{ - const char *a = aattribute.cstr(); - SAPI::add_header_attribute(r->r->sapi_info, - a, - attributed_meaning_to_string(*ameaning, String::L_HTTP_HEADER, false). - cstr(String::L_UNSPECIFIED)); - if(strcasecmp(a, "content-type")==0) - r->add_content_type = false; - else if(strcasecmp(a, "last-modified")==0) - r->add_last_modified = false; - else if(strcasecmp(a, "content-disposition")==0) - r->add_content_disposition = false; -} - -struct RANGE -{ - size_t start; - size_t end; -}; - -static void parse_range(const String* s, Array &ar) -{ - const char *p = s->cstr(); - if(s->starts_with("bytes=")) - p += 6; - RANGE r; - while(*p){ - r.start = (size_t)-1; - r.end = (size_t)-1; - if(*p >= '0' && *p <= '9'){ - r.start = atol(p); - while(*p>='0' && *p<='9') ++p; - } - if(*p++ != '-') break; - if(*p >= '0' && *p <= '9'){ - r.end = atol(p); - while(*p>='0' && *p<='9') ++p; - } - if(*p == ',') ++p; - ar += r; - } -} - -class auto_file -{ -protected: - FILE *f; -public: - auto_file(FILE *t){ - f = t; - } - ~auto_file(){ - if(f != 0){ - fclose(f); - f = 0; - } - } - operator FILE*(){ - return f; - } -}; - -// ^file:send[filename] -// ^file:send[filename;options hash] -// ^file:send[local_filename;remote_filename] -// ^file:send[local_filename;remote_filename;options hash] -static void _send(Request& r, MethodParams& params) { - SAPI::add_header_attribute(r.sapi_info, "Accept-Ranges", "bytes"); - if(r.response.fields().get("ignore")!=0) throw Exception("parser.runtime", 0, "^file:send not allowed here"); - Value *to_file_name = 0; - Value *options = 0; - Value *from_file_name = params.get(0); - const char *c_from_file_name=0, *disposition=0; - if(!from_file_name->is("string")) throw Exception("parser.runtime", 0, "filename must be string"); - - size_t count = params.count(); - if(count > 1){ - to_file_name = params.get(1); - if(to_file_name->is("hash")){ - options = to_file_name; - to_file_name = 0; - }else if(count > 2){ - options = params.get(2); - if(!options->is("hash")) throw Exception("parser.runtime", 0, "options parameter must be hash"); - } - } - - c_from_file_name=r.absolute(from_file_name->as_string()).cstr(); - - size_t offset = 0; - size_t limit = (size_t)-1; - send_attr_info info(&r); - VDate *date = 0; - - if(options){ - HashStringValue *opts = options->get_hash(); - if(opts == 0) - throw Exception("parser.runtime", 0, "options must be hash"); - Value *v; - int valid_options = 0; - if(v = opts->get("offset")){ - ++valid_options; - offset = v->as_int(); - } - if(v = opts->get("limit")){ - ++valid_options; - limit = v->as_int(); - } - if(v = opts->get("headers")){ - ++valid_options; - HashStringValue *headers = v->get_hash(); - if(headers == 0) - throw Exception("parser.runtime", 0, "headers must be hash"); - headers->for_each(send_add_header_attribute, &info); - } - if(v = opts->get("mdate")){ - ++valid_options; - if(Value* vdate=v->as(VDATE_TYPE, false)) - date=static_cast(vdate); - else throw Exception("parser.runtime", 0, "mdate must be a date"); - } - if(v = opts->get("disposition")){ - ++valid_options; - if(!v->is("string")) throw Exception("parser.runtime", 0, "disposition must be a string"); - disposition = v->get_string()->cstr(); - if(strcmp(disposition, "inline") && strcmp(disposition, "attachment")) throw Exception("parser.runtime", 0, "disposition can be only 'inline' or 'attachment'"); - } - if(valid_options != opts->count()) - throw Exception("parser.runtime", 0, "invalid option passed"); - } - - auto_file f = fopen(c_from_file_name, "rb"); - if(f == 0) - throw Exception("parser.runtime", 0, "Can't open file"); - - if(fseek(f, 0, SEEK_END)!=0) - throw Exception("parser.runtime", 0, "Can't seek file"); - - size_t file_length = (size_t)ftell(f); - if(file_length == (size_t)-1) - throw Exception("parser.runtime", 0, "can't get file size"); - if(file_length <= offset) - throw Exception("parser.runtime", 0, "offset too big"); - - size_t content_length = file_length-offset; - if(limit != (size_t)-1) - content_length = limit ar; - parse_range(new String(range), ar); - size_t count = ar.count(); - if(count == 1){ - RANGE &rg = ar.get_ref(0); - if(rg.start == (size_t)-1 && rg.end == (size_t)-1){ - SAPI::add_header_attribute(r.sapi_info, "status", "416 Requested Range Not Satisfiable"); - return; - } - if(rg.start == (size_t)-1 && rg.end != (size_t)-1){ - rg.start = content_length - rg.end; - rg.end = content_length; - offset += rg.start; - part_length = rg.end-rg.start; - }else if(rg.start != (size_t)-1 && rg.end == (size_t)-1){ - rg.end = content_length-1; - offset += rg.start; - part_length -= rg.start; - } - if(part_length == 0){ - SAPI::add_header_attribute(r.sapi_info, "status", "204 No Content"); - return; - } - SAPI::add_header_attribute(r.sapi_info, "status", "206 Partial Content"); - snprintf((char*)buf, BUFSIZE, "bytes %u-%u/%u", rg.start, rg.end, content_length); - SAPI::add_header_attribute(r.sapi_info, "Content-Range", (char*)buf); - }else if(count != 0){ - SAPI::add_header_attribute(r.sapi_info, "status", "501 Not Implemented"); - return; - } - } - - fseek(f, offset, SEEK_SET); - snprintf((char*)buf, BUFSIZE, "%u", part_length); - SAPI::add_header_attribute(r.sapi_info, "Content-Length", (char*)buf); - - if(info.add_content_disposition && disposition){ - const char *fname = 0; - if(to_file_name){ - fname = to_file_name->as_string().cstr(); - }else{ - const char *fname = c_from_file_name; - const char *p1 = strrchr(fname, '/'); - const char *p2 = strrchr(fname, '\\'); - if(p1 || p2) - fname = max(p1, p2)+1; - } - - snprintf((char*)buf, BUFSIZE, "%s; filename=\"%s\"", disposition, fname); - SAPI::add_header_attribute(r.sapi_info, "Content-Disposition", (char*)buf); - } - if(info.add_content_type) - SAPI::add_header_attribute(r.sapi_info, "Content-Type", r.mime_type_of(c_from_file_name).cstr()); - if(info.add_last_modified){ - if(date == 0){ - struct stat st; - if(stat(c_from_file_name, &st)!=0) throw Exception("parser.runtime", 0, "can't get file stat"); - date = new VDate(st.st_mtime); - } - const String &s = attributed_meaning_to_string(*date, String::L_AS_IS, true); - SAPI::add_header_attribute(r.sapi_info, "Last-Modified", s.cstr()); - } - r.cookie.output_result(r.sapi_info); - SAPI::send_header(r.sapi_info); - - const char* request_method=getenv("REQUEST_METHOD"); - bool header_only=request_method && strcasecmp(request_method, "HEAD")==0; - size_t sent = 0; - if(!header_only){ - size_t to_read = 0; - size_t size = 0; - do{ - to_read = part_lengthcstr(): 0; + const char* user_file_name_cstr=handlers.user_file_name? handlers.user_file_name->cstr(): 0; VString* vcontent_type=handlers.user_content_type? new VString(*handlers.user_content_type) @@ -1003,12 +785,12 @@ MFile::MFile(): Methoded("file") { // ^cgi[file-name] // ^cgi[file-name;env hash] // ^cgi[file-name;env hash;1cmd;2line;3ar;4g;5s] - add_native_method("cgi", Method::CT_DYNAMIC, _cgi, 1, 2+10); + add_native_method("cgi", Method::CT_DYNAMIC, _cgi, 1, 2+50); // ^exec[file-name] // ^exec[file-name;env hash] // ^exec[file-name;env hash;1cmd;2line;3ar;4g;5s] - add_native_method("exec", Method::CT_DYNAMIC, _exec, 1, 2+10); + add_native_method("exec", Method::CT_DYNAMIC, _exec, 1, 2+50); // ^file:list[path] // ^file:list[path][regexp] @@ -1025,12 +807,6 @@ MFile::MFile(): Methoded("file") { // ^file:dirname[/a/b/]=/a add_native_method("dirname", Method::CT_STATIC, _dirname, 1, 1); // ^file:basename[/a/some.tar.gz]=some.tar.gz - - // ^file:send[filename] - // ^file:send[filename;options hash] - // ^file:send[filename;new_filename] - // ^file:send[filename;new_filename;options hash] - add_native_method("send", Method::CT_STATIC, _send, 1, 3); add_native_method("basename", Method::CT_STATIC, _basename, 1, 1); // ^file:justname[/a/some.tar.gz]=some.tar add_native_method("justname", Method::CT_STATIC, _justname, 1, 1);