|
|
| version 1.294, 2024/12/11 03:22:23 | version 1.304, 2026/10/02 19:58:41 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: @b file parser class. | Parser: @b file parser class. |
| Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) |
| Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru> | Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru> |
| */ | */ |
| Line 35 volatile const char * IDENT_FILE_C="$Id$ | Line 35 volatile const char * IDENT_FILE_C="$Id$ |
| #define CHARSET_EXEC_PARAM_NAME "charset" | #define CHARSET_EXEC_PARAM_NAME "charset" |
| #define NAME_NAME "name" | #define NAME_NAME "name" |
| #define MODE_APPEND "append" | |
| #define KEEP_EMPTY_DIRS_NAME "keep-empty-dirs" | #define KEEP_EMPTY_DIRS_NAME "keep-empty-dirs" |
| #define SUPPRESS_EXCEPTION_NAME "exception" | #define SUPPRESS_EXCEPTION_NAME "exception" |
| Line 133 static const String::Body cdate_name("cd | Line 134 static const String::Body cdate_name("cd |
| // methods | // methods |
| static void _save(Request& r, MethodParams& params) { | static void _save(Request& r, MethodParams& params) { |
| bool do_append=false; | |
| bool is_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_BE_STRING)); | bool is_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_BE_STRING)); |
| const String& file_name=params.as_file_name(1); | const String& file_name=params.as_file_name(1); |
| Line 144 static void _save(Request& r, MethodPara | Line 146 static void _save(Request& r, MethodPara |
| asked_charset=&pa_charsets.get(vcharset_name->as_string()); | asked_charset=&pa_charsets.get(vcharset_name->as_string()); |
| valid_options++; | valid_options++; |
| } | } |
| if(Value* vappend=options->get(MODE_APPEND)){ | |
| do_append=vappend->as_bool(); | |
| valid_options++; | |
| } | |
| if(valid_options != options->count()) | if(valid_options != options->count()) |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); |
| } | } |
| // save | // save |
| GET_SELF(r, VFile).save(r.charsets, r.full_disk_path(file_name), is_text, asked_charset); | GET_SELF(r, VFile).save(r.charsets, r.full_disk_path(file_name), is_text, do_append, asked_charset); |
| } | } |
| static void _delete(Request& r, MethodParams& params) { | static void _delete(Request& r, MethodParams& params) { |
| Line 173 static void _delete(Request& r, MethodPa | Line 179 static void _delete(Request& r, MethodPa |
| } | } |
| // unlink | // unlink |
| file_delete(r.full_disk_path(file_name), fail_on_problem, keep_empty_dirs); | const String* path=fail_on_problem ? &r.full_disk_path(file_name) : r.real_disk_path(file_name); |
| if(path) | |
| file_delete(*path, fail_on_problem, keep_empty_dirs); | |
| } | } |
| static void _move(Request& r, MethodParams& params) { | static void _move(Request& r, MethodParams& params) { |
| Line 208 static void copy_process_source(struct s | Line 216 static void copy_process_source(struct s |
| nCount = file_block_read(from_file, buffer, sizeof(buffer)); | nCount = file_block_read(from_file, buffer, sizeof(buffer)); |
| int written=write(to_file, buffer, nCount); | int written=write(to_file, buffer, nCount); |
| if( written < 0 ) | if( written < 0 ) |
| throw Exception("file.access", | throw Exception("file.write", 0, "write failed: %s (%d)", strerror(errno), errno); |
| 0, | |
| "write failed: %s (%d)", strerror(errno), errno); | |
| } while(nCount > 0); | } while(nCount > 0); |
| } | } |
| Line 228 static void _copy(Request& r, MethodPara | Line 234 static void _copy(Request& r, MethodPara |
| if(params.count()>2) | if(params.count()>2) |
| if(HashStringValue* options=params.as_hash(2)){ | if(HashStringValue* options=params.as_hash(2)){ |
| int valid_options=0; | int valid_options=0; |
| if(Value* vappend=options->get("append")){ | if(Value* vappend=options->get(MODE_APPEND)){ |
| append=r.process(*vappend).as_bool(); | append=r.process(*vappend).as_bool(); |
| valid_options++; | valid_options++; |
| } | } |
| Line 257 static void _load_pass_param( | Line 263 static void _load_pass_param( |
| static void _load(Request& r, MethodParams& params) { | static void _load(Request& r, MethodParams& params) { |
| bool as_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_BE_STRING)); | bool as_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_BE_STRING)); |
| const String& lfile_name=r.full_disk_path(params.as_file_name(1)); | const String& lfile_name=r.full_disk_path(params.as_file_name(1), PA_ALLOW_HTTP); |
| size_t param_index=params.count()-1; | size_t param_index=params.count()-1; |
| Value* param_value=param_index>1?¶ms.as_no_junction(param_index, "file name or options must not be code"):0; | Value* param_value=param_index>1?¶ms.as_no_junction(param_index, "file name or options must not be code"):0; |
| Line 490 static void _exec_cgi(Request& r, Method | Line 496 static void _exec_cgi(Request& r, Method |
| const String& script_name=r.full_disk_path(params.as_file_name(param_index++)); | const String& script_name=r.full_disk_path(params.as_file_name(param_index++)); |
| HashStringString env; | HashStringString env; |
| #define ECSTR(name, value_cstr) if(value_cstr) env.put(#name, value_cstr); | #define ECSTR(name, value_cstr) if(value_cstr) env.put(#name, String::Body(value_cstr)); |
| // passing environment | // passing environment |
| for(SAPI::Env::Iterator i(r.sapi_info); i; i.next() ) | for(SAPI::Env::Iterator i(r.sapi_info); i; i.next() ) |
| env.put(i.key(), i.value() ); | env.put(i.key(), String::Body(i.value()) ); |
| // const | // const |
| ECSTR(GATEWAY_INTERFACE, "CGI/1.1"); | ECSTR(GATEWAY_INTERFACE, "CGI/1.1"); |
| Line 701 static void _list(Request& r, MethodPara | Line 707 static void _list(Request& r, MethodPara |
| Value& relative_path=params.as_no_junction(0, "path must not be code"); | Value& relative_path=params.as_no_junction(0, "path must not be code"); |
| bool stat=false; | bool stat=false; |
| bool fail_on_problem=false; | |
| VRegex* vregex=0; | VRegex* vregex=0; |
| VRegexCleaner vrcleaner; | VRegexCleaner vrcleaner; |
| Line 718 static void _list(Request& r, MethodPara | Line 725 static void _list(Request& r, MethodPara |
| vfilter=value; | vfilter=value; |
| valid_options++; | valid_options++; |
| } | } |
| if(Value* vexception=options->get(SUPPRESS_EXCEPTION_NAME)) { | |
| fail_on_problem=r.process(*vexception).as_bool(); | |
| valid_options++; | |
| } | |
| if(valid_options!=options->count()) | if(valid_options!=options->count()) |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); |
| } else { | } else { |
| Line 739 static void _list(Request& r, MethodPara | Line 750 static void _list(Request& r, MethodPara |
| } | } |
| } | } |
| const char* absolute_path_cstr=r.full_disk_path(relative_path.as_string()).taint_cstr(String::L_FILE_SPEC); | |
| Table::Action_options table_options; | Table::Action_options table_options; |
| Table& table=*new Table(file_list_table_template(), table_options); | Table& table=*new Table(file_list_table_template(), table_options); |
| // no exception for a uri or an invalid or tainted file:// | |
| const String* path=fail_on_problem ? &r.full_disk_path(relative_path.as_string()) : r.real_disk_path(relative_path.as_string()); | |
| if(!path) { | |
| r.write(*new VTable(&table)); | |
| return; | |
| } | |
| const char* absolute_path_cstr=path->taint_cstr(String::L_FILE_SPEC); | |
| const int ovector_size=(1/*match*/)*3; | const int ovector_size=(1/*match*/)*3; |
| int ovector[ovector_size]; | int ovector[ovector_size]; |
| Line 763 static void _list(Request& r, MethodPara | Line 780 static void _list(Request& r, MethodPara |
| } | } |
| table+=row; | table+=row; |
| } | } |
| , | |
| if(fail_on_problem) | |
| throw Exception(errno==EACCES ? "file.access" : (errno==ENOENT || errno==ENOTDIR) ? "file.missing" : 0, | |
| path, "list failed: %s (%d), actual filename '%s'", strerror(errno), errno, absolute_path_cstr); | |
| ); | ); |
| // write out result | // write out result |
| Line 806 static size_t afterlastslash(const Strin | Line 827 static size_t afterlastslash(const Strin |
| return pos!=STRING_NOT_FOUND?pos+1:0; | return pos!=STRING_NOT_FOUND?pos+1:0; |
| } | } |
| // absolute web path or null when outside of document_root | |
| static const String* web_path(Request& r, const String& file_spec, bool fail_on_problem) { | |
| if(file_spec.first_char()=='/') // already absolute, just changing backslashes | |
| return &backslashes_to_slashes(file_spec); | |
| // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif | |
| const String* disk_path=fail_on_problem ? &r.full_disk_path(file_spec) : r.real_disk_path(file_spec); | |
| if(!disk_path) // a uri or an invalid file:// one | |
| return 0; | |
| const char* document_root=r.request_info.document_root_path; | |
| size_t document_root_length=strlen(document_root); | |
| const String& result=disk_path->mid(document_root_length, disk_path->length()); | |
| // checking the result is inside document root; | |
| // first checking the segment boundary: /root must not match /root-but-longer | |
| return result.first_char()=='/' && !path_cmp(disk_path->cstr(), document_root, document_root_length) ? &result : 0; | |
| } | |
| static void _find(Request& r, MethodParams& params) { | static void _find(Request& r, MethodParams& params) { |
| const String& file_name=params.as_string(0, FILE_NAME_MUST_BE_STRING); | const String& file_name=params.as_string(0, FILE_NAME_MUST_BE_STRING); |
| Value* not_found_code=(params.count()==2)?¶ms.as_junction(1, "not-found param must be code"):0; | Value* not_found_code=(params.count()==2)?¶ms.as_junction(1, "not-found param must be code"):0; |
| const String* file_spec; | const String* file_spec; |
| if(file_name.first_char()=='/') | if(path_scheme(file_name) || is_os_absolute_path(file_name)) |
| file_spec=&file_name; | file_spec=web_path(r, file_name, false); // do not throw |
| else | else { |
| file_spec=&r.relative(r.request_info.uri, file_name); | const String& name=backslashes_to_slashes(file_name); |
| file_spec=name.first_char()=='/' ? &name : &r.relative(r.request_info.uri, name); | |
| // easy way | |
| if(file_exist(r.full_disk_path(*file_spec))) { | |
| r.write(*file_spec); | |
| return; | |
| } | } |
| // monkey way | if(file_spec) { |
| size_t last_slash=file_spec->strrpbrk("/\\"); | // easy way |
| const String& dirname=file_spec->mid(0, last_slash!=STRING_NOT_FOUND?last_slash:0); | if(file_exist(r.full_disk_path(*file_spec))) { |
| const String& basename=file_spec->mid(last_slash!=STRING_NOT_FOUND?last_slash+1:0, file_spec->length()); | r.write(*file_spec); |
| size_t rpos=dirname.is_empty()?0:dirname.length()-1; | |
| while((rpos=dirname.rskipchars("/\\", 0, rpos))!=STRING_NOT_FOUND){ | |
| size_t slash=dirname.strrpbrk("/\\", 0, rpos); | |
| if(slash==STRING_NOT_FOUND) | |
| break; | |
| String test_name; | |
| test_name << dirname.mid(0, slash+1); | |
| test_name << basename; | |
| if(file_exist(r.full_disk_path(test_name))) { | |
| r.write(test_name); | |
| return; | return; |
| } | } |
| rpos=slash; | |
| // monkey way | |
| size_t last_slash=file_spec->strrchr('/'); | |
| const String& dirname=file_spec->mid(0, last_slash!=STRING_NOT_FOUND?last_slash:0); | |
| const String& basename=file_spec->mid(last_slash!=STRING_NOT_FOUND?last_slash+1:0, file_spec->length()); | |
| size_t rpos=dirname.is_empty()?0:dirname.length()-1; | |
| while((rpos=dirname.rskipchars("/", 0, rpos))!=STRING_NOT_FOUND){ | |
| size_t slash=dirname.strrchr('/', rpos); | |
| if(slash==STRING_NOT_FOUND) | |
| break; | |
| String test_name; | |
| test_name << dirname.mid(0, slash+1); | |
| test_name << basename; | |
| if(file_exist(r.full_disk_path(test_name))) { | |
| r.write(test_name); | |
| return; | |
| } | |
| rpos=slash; | |
| } | |
| } | } |
| // no way, not found | // no way, not found |
| Line 930 static void _justext(Request& r, MethodP | Line 975 static void _justext(Request& r, MethodP |
| static void _fullpath(Request& r, MethodParams& params) { | static void _fullpath(Request& r, MethodParams& params) { |
| const String& file_spec=params.as_file_spec(0); | const String& file_spec=params.as_file_spec(0); |
| const String* result; | if(const String* result=web_path(r, file_spec, true)) |
| if(file_spec.first_char()=='/') | r.write(*result); |
| result=&file_spec; | else |
| else { | throw Exception(PARSER_RUNTIME, &file_spec, "is outside of $request:document-root [%s]", r.request_info.document_root); |
| // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif | } |
| const String& full_disk_path=r.full_disk_path(file_spec); | |
| size_t document_root_length=strlen(r.request_info.document_root); | static void _diskpath(Request& r, MethodParams& params) { |
| r.write(r.full_disk_path(params.as_file_spec(0))); | |
| if(document_root_length>0) { | |
| char last_char=r.request_info.document_root[document_root_length-1]; | |
| if(last_char == '/' || last_char == '\\') | |
| --document_root_length; | |
| } | |
| result=&full_disk_path.mid(document_root_length, full_disk_path.length()); | |
| } | |
| r.write(*result); | |
| } | } |
| static void _sql_string(Request& r, MethodParams&) { | static void _sql_string(Request& r, MethodParams&) { |
| Line 1066 extern Base64Options base64_encode_optio | Line 1103 extern Base64Options base64_encode_optio |
| Base64Options base64_decode_options(Request& r, HashStringValue* options, VString** vcontent_type) { | Base64Options base64_decode_options(Request& r, HashStringValue* options, VString** vcontent_type) { |
| Base64Options result; | Base64Options result; |
| if(options) { | if(options) { |
| int valid_options=0; | |
| for(HashStringValue::Iterator i(*options); i; i.next() ) { | for(HashStringValue::Iterator i(*options); i; i.next() ) { |
| String::Body key=i.key(); | String::Body key=i.key(); |
| Value* value=i.value(); | Value* value=i.value(); |
| if(key == "pad") { | if(key == "pad") { |
| result.pad=r.process(*value).as_bool(); | result.pad=r.process(*value).as_bool(); |
| valid_options++; | |
| } else if(key == "strict") { | } else if(key == "strict") { |
| result.strict=r.process(*value).as_bool(); | result.strict=r.process(*value).as_bool(); |
| valid_options++; | |
| } else if(key == CONTENT_TYPE_NAME) { | } else if(key == CONTENT_TYPE_NAME) { |
| *vcontent_type=new VString(value->as_string()); | *vcontent_type=new VString(value->as_string()); |
| valid_options++; | |
| } else if(key == "url-safe") { | } else if(key == "url-safe") { |
| if(r.process(*value).as_bool()) | if(r.process(*value).as_bool()) |
| result.set_url_safe_abc(); | result.set_url_safe_abc(); |
| valid_options++; | } else |
| } | throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); |
| } | } |
| if(valid_options != options->count()) | |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | |
| } | } |
| return result; | return result; |
| } | } |
| Line 1229 MFile::MFile(): Methoded("file") { | Line 1259 MFile::MFile(): Methoded("file") { |
| add_native_method("create", Method::CT_DYNAMIC, _create, 1, 4); | add_native_method("create", Method::CT_DYNAMIC, _create, 1, 4); |
| // ^file.save[mode;file-name] | // ^file.save[mode;file-name] |
| // ^file.save[mode;file-name;$.charset[...]] | // ^file.save[mode;file-name;$.charset[...]$.append(false)] |
| add_native_method("save", Method::CT_DYNAMIC, _save, 2, 3); | add_native_method("save", Method::CT_DYNAMIC, _save, 2, 3); |
| // ^file:delete[file-name] | // ^file:delete[file-name] |
| Line 1282 MFile::MFile(): Methoded("file") { | Line 1312 MFile::MFile(): Methoded("file") { |
| add_native_method("justext", Method::CT_STATIC, _justext, 1, 1); | add_native_method("justext", Method::CT_STATIC, _justext, 1, 1); |
| // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif | // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif |
| add_native_method("fullpath", Method::CT_STATIC, _fullpath, 1, 1); | add_native_method("fullpath", Method::CT_STATIC, _fullpath, 1, 1); |
| // /some/page.html: ^file:diskpath[a.gif] => /document/root/some/a.gif, file:// and C:\ resolved as well | |
| add_native_method("diskpath", Method::CT_STATIC, _diskpath, 1, 1); | |
| // ^file.sql-string[] | // ^file.sql-string[] |
| add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 0); | add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 0); |