Annotation of parser3/src/classes/file.C, revision 1.226
1.17 paf 1: /** @file
2: Parser: @b file parser class.
3:
1.218 moko 4: Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)
1.72 paf 5: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.91 paf 6: */
1.17 paf 7:
1.47 parser 8: #include "pa_config_includes.h"
9:
1.35 paf 10: #include "classes.h"
1.111 paf 11: #include "pa_vmethod_frame.h"
12:
1.1 paf 13: #include "pa_request.h"
14: #include "pa_vfile.h"
1.11 paf 15: #include "pa_table.h"
1.21 paf 16: #include "pa_vint.h"
1.24 paf 17: #include "pa_exec.h"
1.40 parser 18: #include "pa_vdate.h"
1.47 parser 19: #include "pa_dir.h"
20: #include "pa_vtable.h"
1.67 paf 21: #include "pa_charset.h"
1.109 paf 22: #include "pa_charsets.h"
1.121 paf 23: #include "pa_sql_connection.h"
1.147 misha 24: #include "pa_md5.h"
1.184 misha 25: #include "pa_vregex.h"
1.208 misha 26: #include "pa_version.h"
1.1 paf 27:
1.226 ! moko 28: volatile const char * IDENT_FILE_C="$Id: file.C,v 1.225 2013/03/14 02:50:58 misha Exp $";
1.218 moko 29:
1.32 paf 30: // defines
31:
1.90 paf 32: #define STDIN_EXEC_PARAM_NAME "stdin"
1.109 paf 33: #define CHARSET_EXEC_PARAM_NAME "charset"
1.48 parser 34:
1.131 paf 35: #define NAME_NAME "name"
1.224 misha 36: #define KEEP_EMPTY_DIRS_NAME "keep-empty-dirs"
1.225 misha 37: #define SUPPRESS_EXCEPTION_NAME "exception"
1.131 paf 38:
1.132 paf 39: // externs
40:
41: extern String sql_limit_name;
42: extern String sql_offset_name;
43:
1.111 paf 44: // class
45:
46: class MFile: public Methoded {
47: public: // VStateless_class
1.199 misha 48: Value* create_new_value(Pool&) { return new VFile(); }
1.111 paf 49: public:
50: MFile();
51: };
52:
53: // global variable
54:
55: DECLARE_CLASS_VAR(file, new MFile, 0);
56:
1.83 paf 57: // consts
58:
59: /// from apache-1.3|src|support|suexec.c
1.111 paf 60: static const char* suexec_safe_env_lst[]={
1.83 paf 61: "AUTH_TYPE",
62: "CONTENT_LENGTH",
63: "CONTENT_TYPE",
64: "DATE_GMT",
65: "DATE_LOCAL",
66: "DOCUMENT_NAME",
67: "DOCUMENT_PATH_INFO",
68: "DOCUMENT_ROOT",
69: "DOCUMENT_URI",
70: "FILEPATH_INFO",
71: "GATEWAY_INTERFACE",
72: "LAST_MODIFIED",
73: "PATH_INFO",
74: "PATH_TRANSLATED",
75: "QUERY_STRING",
76: "QUERY_STRING_UNESCAPED",
77: "REMOTE_ADDR",
78: "REMOTE_HOST",
79: "REMOTE_IDENT",
80: "REMOTE_PORT",
81: "REMOTE_USER",
82: "REDIRECT_QUERY_STRING",
83: "REDIRECT_STATUS",
84: "REDIRECT_URL",
85: "REQUEST_METHOD",
86: "REQUEST_URI",
87: "SCRIPT_FILENAME",
88: "SCRIPT_NAME",
89: "SCRIPT_URI",
90: "SCRIPT_URL",
91: "SERVER_ADMIN",
92: "SERVER_NAME",
93: "SERVER_ADDR",
94: "SERVER_PORT",
95: "SERVER_PROTOCOL",
96: "SERVER_SOFTWARE",
97: "UNIQUE_ID",
98: "USER_NAME",
99: "TZ",
100: NULL
101: };
102:
1.111 paf 103: // statics
1.33 paf 104:
1.112 paf 105: static const String::Body adate_name("adate");
106: static const String::Body mdate_name("mdate");
107: static const String::Body cdate_name("cdate");
1.32 paf 108:
1.1 paf 109: // methods
110:
1.111 paf 111: static void _save(Request& r, MethodParams& params) {
1.215 misha 112: bool is_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_NOT_BE_CODE));
1.152 misha 113: Value& vfile_name=params.as_no_junction(1, FILE_NAME_MUST_NOT_BE_CODE);
1.4 paf 114:
1.201 misha 115: Charset* asked_charset=0;
116: if(params.count()>2)
1.214 misha 117: if(HashStringValue* options=params.as_hash(2)){
1.202 misha 118: int valid_options=0;
1.201 misha 119: if(Value* vcharset_name=options->get(PA_CHARSET_NAME)){
120: asked_charset=&::charsets.get(vcharset_name->as_string().change_case(r.charsets.source(), String::CC_UPPER));
121: valid_options++;
122: }
123: if(valid_options != options->count())
1.207 misha 124: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.201 misha 125: }
126:
1.7 paf 127: // save
1.201 misha 128: GET_SELF(r, VFile).save(r.charsets, r.absolute(vfile_name.as_string()), is_text, asked_charset);
1.7 paf 129: }
130:
1.111 paf 131: static void _delete(Request& r, MethodParams& params) {
1.215 misha 132: const String& file_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE);
1.224 misha 133: bool keep_empty_dirs=false;
1.225 misha 134: bool fail_on_problem=true;
1.224 misha 135:
136: if(params.count()>1)
137: if(HashStringValue* options=params.as_hash(1)){
138: int valid_options=0;
139: if(Value* vkeep_empty_dirs=options->get(KEEP_EMPTY_DIRS_NAME)){
140: keep_empty_dirs=r.process_to_value(*vkeep_empty_dirs).as_bool();
141: valid_options++;
142: }
1.225 misha 143: if(Value* vsuppress_exception=options->get(SUPPRESS_EXCEPTION_NAME)){
144: fail_on_problem=r.process_to_value(*vsuppress_exception).as_bool();
145: valid_options++;
146: }
1.224 misha 147: if(valid_options != options->count())
148: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
149: }
1.7 paf 150:
151: // unlink
1.225 misha 152: file_delete(r.absolute(file_name), fail_on_problem, keep_empty_dirs);
1.1 paf 153: }
154:
1.111 paf 155: static void _move(Request& r, MethodParams& params) {
156: Value& vfrom_file_name=params.as_no_junction(0, "from file name must not be code");
157: Value& vto_file_name=params.as_no_junction(1, "to file name must not be code");
1.224 misha 158: bool keep_empty_dirs=false;
159:
160: if(params.count()>2)
161: if(HashStringValue* options=params.as_hash(2)){
162: int valid_options=0;
163: if(Value* vkeep_empty_dirs=options->get(KEEP_EMPTY_DIRS_NAME)){
164: keep_empty_dirs=r.process_to_value(*vkeep_empty_dirs).as_bool();
165: valid_options++;
166: }
167: if(valid_options != options->count())
168: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
169: }
1.45 parser 170:
1.51 parser 171: // move
1.68 paf 172: file_move(
1.45 parser 173: r.absolute(vfrom_file_name.as_string()),
1.224 misha 174: r.absolute(vto_file_name.as_string()),
175: keep_empty_dirs);
1.45 parser 176: }
177:
1.148 misha 178: static void copy_process_source(
1.180 misha 179: struct stat& ,
180: int from_file,
181: const String& , const char* /*fname*/, bool,
182: void *context) {
1.148 misha 183: int& to_file=*static_cast<int *>(context);
184:
185: int nCount=0;
186: do {
187: unsigned char buffer[FILE_BUFFER_SIZE];
1.150 misha 188: nCount = file_block_read(from_file, buffer, sizeof(buffer));
1.148 misha 189: int written=write(to_file, buffer, nCount);
190: if( written < 0 )
1.179 misha 191: throw Exception("file.access",
1.148 misha 192: 0,
193: "write failed: %s (%d)", strerror(errno), errno);
194:
195: } while(nCount > 0);
196: }
197:
198: static void copy_open_target(int f, void *from_spec) {
199: String& file_spec=*static_cast<String *>(from_spec);
200: file_read_action_under_lock(file_spec, "copy", copy_process_source, &f);
201: };
202:
203: static void _copy(Request& r, MethodParams& params) {
204: Value& vfrom_file_name=params.as_no_junction(0, "from file name must not be code");
205: Value& vto_file_name=params.as_no_junction(1, "to file name must not be code");
206:
207: String from_spec = r.absolute(vfrom_file_name.as_string());
208: const String& to_spec = r.absolute(vto_file_name.as_string());
209:
210: file_write_action_under_lock(
211: to_spec,
212: "copy",
213: copy_open_target,
1.149 misha 214: &from_spec);
1.148 misha 215: }
216:
1.111 paf 217: static void _load_pass_param(
1.180 misha 218: HashStringValue::key_type key,
219: HashStringValue::value_type value,
220: HashStringValue *dest) {
1.111 paf 221: dest->put(key, value);
222: }
1.180 misha 223:
1.111 paf 224: static void _load(Request& r, MethodParams& params) {
1.215 misha 225: bool as_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_NOT_BE_CODE));
226: const String& lfile_name=r.absolute(params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE));
1.9 paf 227:
1.180 misha 228: size_t param_index=params.count()-1;
1.215 misha 229: Value* param_value=param_index>1?¶ms.as_no_junction(param_index, "file name or options must not be code"):0;
1.180 misha 230:
1.183 misha 231: HashStringValue* options=0;
1.215 misha 232: const String* user_file_name=0;
1.183 misha 233:
234: if(param_value){
235: options=param_value->get_hash();
236: if(options || param_index>2)
237: param_index--;
238: if(param_index>1){
239: const String& luser_file_name=params.as_string(param_index, FILE_NAME_MUST_BE_STRING);
240: if(!luser_file_name.is_empty())
1.215 misha 241: user_file_name=&luser_file_name;
1.183 misha 242: }
243: }
244: if(!user_file_name)
1.215 misha 245: user_file_name=&lfile_name;
1.180 misha 246:
1.132 paf 247: size_t offset=0;
248: size_t limit=0;
1.183 misha 249:
1.180 misha 250: if(options){
1.132 paf 251: options=new HashStringValue(*options);
1.180 misha 252: if(Value *voffset=(Value *)options->get(sql_offset_name)){
1.132 paf 253: offset=r.process_to_value(*voffset).as_int();
254: }
1.180 misha 255: if(Value *vlimit=(Value *)options->get(sql_limit_name)){
1.132 paf 256: limit=r.process_to_value(*vlimit).as_int();
257: }
258: // no check on options count here, see file_read
259: }
1.182 misha 260: File_read_result file=file_load(r, lfile_name,
1.180 misha 261: as_text, options, true, 0, offset, limit
1.104 paf 262: );
1.9 paf 263:
1.111 paf 264: Value* vcontent_type=0;
1.168 misha 265: if(file.headers){
1.181 misha 266: if(Value* remote_content_type=file.headers->get(HTTP_CONTENT_TYPE_UPPER))
1.129 paf 267: vcontent_type=new VString(*new String(remote_content_type->as_string().cstr()));
268: }
1.221 misha 269:
1.111 paf 270: VFile& self=GET_SELF(r, VFile);
1.221 misha 271: self.set(true/*tainted*/, as_text, file.str, file.length, user_file_name, vcontent_type, &r);
1.194 misha 272:
1.168 misha 273: if(file.headers){
1.143 paf 274: file.headers->for_each<HashStringValue*>(_load_pass_param, &self.fields());
1.168 misha 275: } else {
276: size_t size;
277: time_t atime, mtime, ctime;
278:
1.169 misha 279: file_stat(lfile_name, size, atime, mtime, ctime);
1.168 misha 280:
281: HashStringValue& ff=self.fields();
282: ff.put(adate_name, new VDate(atime));
283: ff.put(mdate_name, new VDate(mtime));
284: ff.put(cdate_name, new VDate(ctime));
285: }
1.9 paf 286: }
287:
1.138 paf 288: static void _create(Request& r, MethodParams& params) {
1.215 misha 289: const String* mode=0;
290: const String* file_name=0;
291: bool is_text=true;
292:
293: // new format: ^file::create[string-or-file-content[;$.mode[text|binary] $.name[...] $.content-type[...] $.charset[...] ]]
294: size_t content_index=0;
295: size_t options_index=1;
296: bool extended_options=true;
297:
298: if(params.count()>=3){
299: // old format: ^file::create[text|binary;file-name;string-or-file-content[;options]]
300: mode=¶ms.as_string(0, MODE_MUST_NOT_BE_CODE);
301: is_text=VFile::is_text_mode(*mode);
302: file_name=¶ms.as_string(1, FILE_NAME_MUST_NOT_BE_CODE);
303: content_index=2;
304: options_index=3;
305: extended_options=false;
306: }
1.203 misha 307:
1.211 misha 308: VString* vcontent_type=0;
1.215 misha 309: Charset* asked_charset=0;
310: if(params.count()>options_index)
311: if(HashStringValue* options=params.as_hash(options_index)) {
1.203 misha 312: int valid_options=0;
1.215 misha 313: if(extended_options) {
314: if(Value* vmode=options->get(MODE_NAME)) {
315: mode=&vmode->as_string();
316: is_text=VFile::is_text_mode(*mode);
317: valid_options++;
318: }
319: if(Value* vfile_name=options->get(NAME_NAME)) {
320: file_name=&vfile_name->as_string();
321: valid_options++;
322: }
323: }
324: if(Value* vcharset_name=options->get(PA_CHARSET_NAME)) {
1.203 misha 325: asked_charset=&::charsets.get(vcharset_name->as_string().change_case(r.charsets.source(), String::CC_UPPER));
326: valid_options++;
327: }
1.211 misha 328: if(Value* value=options->get(CONTENT_TYPE_NAME)) {
329: vcontent_type=new VString(value->as_string());
330: valid_options++;
331: }
1.203 misha 332: if(valid_options != options->count())
1.207 misha 333: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.215 misha 334: }
1.211 misha 335:
1.215 misha 336: Value& vcontent=params.as_no_junction(content_index, "content must be string or file");
1.203 misha 337:
1.138 paf 338: VFile& self=GET_SELF(r, VFile);
1.194 misha 339:
1.215 misha 340: if(const String* content_str=vcontent.get_string()){
341: String::Body body=content_str->cstr_to_string_body_untaint(String::L_AS_IS); // explode content, honor tainting changes
342: if(asked_charset && is_text)
343: body=Charset::transcode(body, r.charsets.source(), *asked_charset);
1.222 moko 344: self.set(true/*tainted*/, is_text, body.cstrm(), body.length(), file_name, vcontent_type, &r);
1.215 misha 345: } else {
346: if(asked_charset)
347: throw Exception(PARSER_RUNTIME, 0, "charset option can not be used with file-content");
1.226 ! moko 348: self.set(*vcontent.as_vfile(String::L_AS_IS), mode != 0, is_text, file_name, vcontent_type, &r);
1.215 misha 349: }
350:
1.138 paf 351: }
352:
1.111 paf 353: static void _stat(Request& r, MethodParams& params) {
1.215 misha 354: const String& lfile_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE);
1.25 paf 355:
1.40 parser 356: size_t size;
357: time_t atime, mtime, ctime;
358: file_stat(r.absolute(lfile_name),
359: size,
360: atime, mtime, ctime);
1.25 paf 361:
1.111 paf 362: VFile& self=GET_SELF(r, VFile);
1.167 misha 363:
1.222 moko 364: self.set_binary(true/*tainted*/, 0/*no bytes*/, size, &lfile_name, 0, &r);
1.111 paf 365: HashStringValue& ff=self.fields();
366: ff.put(adate_name, new VDate(atime));
367: ff.put(mdate_name, new VDate(mtime));
368: ff.put(cdate_name, new VDate(ctime));
1.25 paf 369: }
370:
1.111 paf 371: static bool is_safe_env_key(const char* key) {
372: for(const char* validator=key; *validator; validator++) {
373: char c=*validator;
374: if(!(c>='A' && c<='Z' || c>='0' && c<='9' || c=='_' || c=='-'))
375: return false;
376: }
1.205 pretende 377: #ifdef PA_SAFE_MODE
1.88 paf 378: if(strncasecmp(key, "HTTP_", 5)==0)
1.83 paf 379: return true;
1.87 paf 380: if(strncasecmp(key, "CGI_", 4)==0)
1.83 paf 381: return true;
382: for(int i=0; suexec_safe_env_lst[i]; i++) {
1.87 paf 383: if(strcasecmp(key, suexec_safe_env_lst[i])==0)
1.83 paf 384: return true;
385: }
386: return false;
1.205 pretende 387: #else
388: return true;
389: #endif
1.83 paf 390: }
1.90 paf 391: #ifndef DOXYGEN
392: struct Append_env_pair_info {
1.141 paf 393: Request_charsets* charsets;
1.111 paf 394: HashStringString* env;
1.100 paf 395: Value* vstdin;
1.90 paf 396: };
397: #endif
1.111 paf 398: static void append_env_pair(
1.180 misha 399: HashStringValue::key_type akey,
400: HashStringValue::value_type avalue,
401: Append_env_pair_info *info) {
1.111 paf 402: if(akey==STDIN_EXEC_PARAM_NAME) {
403: info->vstdin=avalue;
404: } else if(akey==CHARSET_EXEC_PARAM_NAME) {
1.141 paf 405: // ignore, already processed
1.90 paf 406: } else {
1.111 paf 407: if(!is_safe_env_key(akey.cstr()))
1.156 misha 408: throw Exception(PARSER_RUNTIME,
1.111 paf 409: new String(akey, String::L_TAINTED),
1.90 paf 410: "not safe environment variable");
1.196 misha 411: info->env->put(akey, avalue->as_string().cstr_to_string_body_untaint(String::L_AS_IS, 0, info->charsets));
1.90 paf 412: }
1.22 paf 413: }
1.94 paf 414: #ifndef DOXYGEN
415: struct Pass_cgi_header_attribute_info {
1.111 paf 416: Charset* charset;
417: HashStringValue* fields;
418: Value* content_type;
1.94 paf 419: };
420: #endif
1.111 paf 421: static void pass_cgi_header_attribute(
1.180 misha 422: ArrayString::element_type astring,
423: Pass_cgi_header_attribute_info* info) {
1.111 paf 424: size_t colon_pos=astring->pos(':');
1.130 paf 425: if(colon_pos!=STRING_NOT_FOUND) {
1.111 paf 426: const String& key=astring->mid(0, colon_pos).change_case(
427: *info->charset, String::CC_UPPER);
1.130 paf 428: Value* value=new VString(astring->mid(colon_pos+1, astring->length()).trim());
1.111 paf 429: info->fields->put(key, value);
1.181 misha 430: if(key==HTTP_CONTENT_TYPE_UPPER)
1.111 paf 431: info->content_type=value;
1.94 paf 432: }
1.29 paf 433: }
1.155 misha 434:
435: static void append_to_argv(Request& r, ArrayString& argv, const String* str){
1.187 misha 436: if(!str->is_empty())
1.196 misha 437: argv+=new String(str->cstr_to_string_body_untaint(String::L_AS_IS, 0, &r.charsets), String::L_AS_IS);
1.155 misha 438: }
439:
1.90 paf 440: /// @todo fix `` in perl - they produced flipping consoles and no output to perl
1.194 misha 441: static void _exec_cgi(Request& r, MethodParams& params, bool cgi) {
1.215 misha 442: bool is_text=true;
1.194 misha 443: size_t param_index=0;
1.215 misha 444: const String& mode=params.as_string(0, FIRST_ARG_MUST_NOT_BE_CODE);
445: if(VFile::is_valid_mode(mode)) {
446: is_text=VFile::is_text_mode(mode);
1.194 misha 447: param_index++;
1.162 misha 448: }
449:
450: if(param_index>=params.count())
1.215 misha 451: throw Exception(PARSER_RUNTIME, 0, FILE_NAME_MUST_BE_SPECIFIED);
1.162 misha 452:
1.215 misha 453: const String& script_name=r.absolute(params.as_string(param_index++, FILE_NAME_MUST_NOT_BE_CODE));
1.23 paf 454:
1.111 paf 455: HashStringString env;
1.62 paf 456: #define ECSTR(name, value_cstr) \
1.111 paf 457: if(value_cstr) \
458: env.put( \
1.112 paf 459: String::Body(#name), \
1.192 misha 460: String::Body(*value_cstr?value_cstr:0)); \
1.82 paf 461: // passing SAPI::environment
1.111 paf 462: if(const char *const *pairs=SAPI::environment(r.sapi_info)) {
463: while(const char* pair=*pairs++)
464: if(const char* eq_at=strchr(pair, '='))
465: if(eq_at[1]) // has value
466: env.put(
467: pa_strdup(pair, eq_at-pair),
1.223 misha 468: pa_strdup(eq_at+1));
1.82 paf 469: }
470:
1.23 paf 471: // const
1.63 paf 472: ECSTR(GATEWAY_INTERFACE, "CGI/1.1");
1.216 misha 473: ECSTR("PARSER_VERSION", PARSER_VERSION);
1.23 paf 474: // from Request.info
1.111 paf 475: ECSTR(DOCUMENT_ROOT, r.request_info.document_root);
476: ECSTR(PATH_TRANSLATED, r.request_info.path_translated);
477: ECSTR(REQUEST_METHOD, r.request_info.method);
478: ECSTR(QUERY_STRING, r.request_info.query_string);
479: ECSTR(REQUEST_URI, r.request_info.uri);
480: ECSTR(CONTENT_TYPE, r.request_info.content_type);
1.200 misha 481: ECSTR(CONTENT_LENGTH, format(r.request_info.content_length, "%u"));
1.82 paf 482: // SCRIPT_*
1.119 paf 483: env.put(String::Body("SCRIPT_NAME"), script_name);
484: //env.put(String::Body("SCRIPT_FILENAME"), ??&script_name);
1.23 paf 485:
1.90 paf 486: // environment & stdin from param
1.111 paf 487: String *in=new String();
1.109 paf 488: Charset *charset=0; // default script works raw_in 'source' charset = no transcoding needed
1.162 misha 489: if(param_index < params.count()) {
1.220 misha 490: if(HashStringValue* user_env=params.as_hash(param_index++, "env")) {
1.141 paf 491: // $.charset [previewing to handle URI pieces]
492: if(Value* vcharset=user_env->get(CHARSET_EXEC_PARAM_NAME))
493: charset=&charsets.get(vcharset->as_string()
494: .change_case(r.charsets.source(), String::CC_UPPER));
495:
496: // $.others
497: Append_env_pair_info info={&r.charsets, &env, 0};
498: {
1.144 paf 499: // influence tainting
500: // main target -- $.QUERY_STRING -- URLencoding of tainted pieces to String::L_URI lang
1.141 paf 501: Temp_client_charset temp(r.charsets, charset? *charset: r.charsets.source());
1.143 paf 502: user_env->for_each<Append_env_pair_info*>(append_env_pair, &info);
1.141 paf 503: }
1.109 paf 504: // $.stdin
1.103 paf 505: if(info.vstdin) {
1.111 paf 506: if(const String* sstdin=info.vstdin->get_string()) {
1.213 moko 507: // untaint stdin
508: in = new String(sstdin->cstr_to_string_body_untaint(String::L_AS_IS), String::L_AS_IS);
1.103 paf 509: } else
1.199 misha 510: if(VFile* vfile=static_cast<VFile *>(info.vstdin->as("file")))
1.111 paf 511: in->append_know_length((const char* )vfile->value_ptr(), vfile->value_size(), String::L_TAINTED);
1.100 paf 512: else
1.156 misha 513: throw Exception(PARSER_RUNTIME,
1.111 paf 514: 0,
1.100 paf 515: STDIN_EXEC_PARAM_NAME " parameter must be string or file");
1.103 paf 516: }
1.90 paf 517: }
1.21 paf 518: }
519:
1.90 paf 520: // argv from params
1.111 paf 521: ArrayString argv;
1.162 misha 522: if(param_index < params.count()) {
1.180 misha 523: // influence tainting
524: Temp_client_charset temp(r.charsets, charset? *charset: r.charsets.source());
1.154 misha 525:
1.162 misha 526: for(size_t i=param_index; i<params.count(); i++) {
1.161 misha 527: Value& param=params.as_no_junction(i, PARAM_MUST_NOT_BE_CODE);
1.154 misha 528: if(param.is_defined()){
529: if(param.is_string()){
1.155 misha 530: append_to_argv(r, argv, param.get_string());
1.154 misha 531: } else {
1.155 misha 532: Table* table=param.get_table();
533: if(table){
534: for(size_t i=0; i<table->count(); i++) {
535: append_to_argv(r, argv, table->get(i)->get(0));
1.154 misha 536: }
537: } else {
1.156 misha 538: throw Exception(PARSER_RUNTIME,
1.154 misha 539: 0,
1.162 misha 540: "param must be string or table");
1.154 misha 541: }
542: }
1.145 misha 543: }
1.144 paf 544: }
1.21 paf 545: }
1.90 paf 546:
1.109 paf 547: // transcode if necessary
548: if(charset) {
1.111 paf 549: Charset::transcode(env, r.charsets.source(), *charset);
550: Charset::transcode(argv, r.charsets.source(), *charset);
551: in=&Charset::transcode(*in, r.charsets.source(), *charset);
552: }
553: // @todo
554: // ifdef WIN32 do OEM->ANSI transcode on some(.cmd?) programs to
555: // match silent conversion in OS
556:
557: // exec!
1.163 misha 558: PA_exec_result execution=pa_exec(false/*forced_allow*/, script_name, &env, argv, *in);
1.111 paf 559:
1.162 misha 560: File_read_result *file_out=&execution.out;
1.111 paf 561: String *real_err=&execution.err;
1.162 misha 562:
1.165 misha 563: // transcode err if necessary (@todo: need fix line breaks in err as well )
564: if(charset)
565: real_err=&Charset::transcode(*real_err, *charset, r.charsets.source());
566:
1.215 misha 567: if(file_out->length && is_text){
1.162 misha 568: fix_line_breaks(file_out->str, file_out->length);
569: // treat output as string
1.188 misha 570: String *real_out = new String(file_out->str);
1.162 misha 571:
1.165 misha 572: // transcode out if necessary
573: if(charset)
1.162 misha 574: real_out=&Charset::transcode(*real_out, *charset, r.charsets.source());
1.165 misha 575:
1.162 misha 576: // FIXME: unsafe cast
1.163 misha 577: file_out->str=const_cast<char *>(real_out->cstr()); // hacking a little
1.162 misha 578: file_out->length = real_out->length();
1.109 paf 579: }
580:
1.111 paf 581: VFile& self=GET_SELF(r, VFile);
1.109 paf 582:
1.162 misha 583: if(cgi) { // ^file::cgi
1.163 misha 584: const char* eol_marker=0;
585: size_t eol_marker_size;
586:
1.111 paf 587: // construct with 'out' body and header
1.165 misha 588: size_t dos_pos=(file_out->length)?strpos(file_out->str, "\r\n\r\n"):STRING_NOT_FOUND;
589: size_t unix_pos=(file_out->length)?strpos(file_out->str, "\n\n"):STRING_NOT_FOUND;
1.111 paf 590:
591: bool unix_header_break;
592: switch((dos_pos!=STRING_NOT_FOUND?10:00) + (unix_pos!=STRING_NOT_FOUND?01:00)) {
1.166 misha 593: case 10: // dos
594: unix_header_break=false;
595: break;
596: case 01: // unix
597: unix_header_break=true;
598: break;
599: case 11: // dos & unix
600: unix_header_break=unix_pos<dos_pos;
601: break;
602: default: // 00
603: unix_header_break=false; // calm down, compiler
1.179 misha 604: throw Exception("file.execute",
1.166 misha 605: 0,
606: "output does not contain CGI header; "
607: "exit status=%d; stdoutsize=%u; stdout: \"%s\"; stderrsize=%u; stderr: \"%s\"",
608: execution.status,
1.194 misha 609: file_out->length, (file_out->length) ? (file_out->str) : "",
610: real_err->length(), real_err->cstr());
1.166 misha 611: break; //never reached
1.111 paf 612: }
613:
1.165 misha 614: size_t header_break_pos;
1.111 paf 615: if(unix_header_break) {
616: header_break_pos=unix_pos;
1.165 misha 617: eol_marker="\n";
618: eol_marker_size=1;
1.111 paf 619: } else {
620: header_break_pos=dos_pos;
1.165 misha 621: eol_marker="\r\n";
622: eol_marker_size=2;
1.111 paf 623: }
1.21 paf 624:
1.162 misha 625: file_out->str[header_break_pos] = 0;
1.188 misha 626: String *header=new String(file_out->str);
1.162 misha 627: unsigned long headersize = header_break_pos+eol_marker_size*2;
628: file_out->str += headersize;
629: file_out->length -= headersize;
630:
1.164 misha 631: // $body
1.221 misha 632: self.set(false/*not tainted*/, is_text, file_out->str, file_out->length);
1.164 misha 633:
1.162 misha 634: // $fields << header
1.194 misha 635: if(header) {
1.162 misha 636: ArrayString rows;
637: size_t pos_after=0;
638: header->split(rows, pos_after, eol_marker);
639: Pass_cgi_header_attribute_info info={0, 0, 0};
640: info.charset=&r.charsets.source();
641: info.fields=&self.fields();
642: rows.for_each(pass_cgi_header_attribute, &info);
643: if(info.content_type)
644: self.fields().put(content_type_name, info.content_type);
645: }
1.164 misha 646: } else { // ^file::exec
1.166 misha 647: // $body
1.221 misha 648: self.set(false/*not tainted*/, is_text, file_out->str, file_out->length);
1.164 misha 649: }
1.163 misha 650:
1.42 parser 651: // $status
1.111 paf 652: self.fields().put(file_status_name, new VInt(execution.status));
1.21 paf 653:
654: // $stderr
1.187 misha 655: if(!real_err->is_empty())
1.21 paf 656: self.fields().put(
1.119 paf 657: String::Body("stderr"),
1.111 paf 658: new VString(*real_err));
1.21 paf 659: }
1.111 paf 660: static void _exec(Request& r, MethodParams& params) {
661: _exec_cgi(r, params, false);
1.41 parser 662: }
1.111 paf 663: static void _cgi(Request& r, MethodParams& params) {
664: _exec_cgi(r, params, true);
1.41 parser 665: }
666:
1.111 paf 667: static void _list(Request& r, MethodParams& params) {
668: Value& relative_path=params.as_no_junction(0, "path must not be code");
1.47 parser 669:
1.191 misha 670: VRegex* vregex=0;
1.184 misha 671: VRegexCleaner vrcleaner;
672: if(params.count()>1){
673: Value& regexp=params.as_no_junction(1, "regexp must not be code");
1.191 misha 674: if(regexp.is_defined()){
1.199 misha 675: if(Value* value=regexp.as(VREGEX_TYPE)){
1.191 misha 676: vregex=static_cast<VRegex*>(value);
677: } else {
678: vregex=new VRegex(r.charsets.source(), ®exp.as_string(), 0/*options*/);
679: vregex->study();
680: vrcleaner.vregex=vregex;
681: }
1.184 misha 682: }
1.114 paf 683: }
1.47 parser 684:
1.196 misha 685: const char* absolute_path_cstr=r.absolute(relative_path.as_string()).taint_cstr(String::L_FILE_SPEC);
1.47 parser 686:
1.111 paf 687: Table::columns_type columns(new ArrayString);
688: *columns+=new String("name");
689: Table& table=*new Table(columns);
1.47 parser 690:
1.184 misha 691: const int ovector_size=(1/*match*/)*3;
692: int ovector[ovector_size];
693:
1.47 parser 694: LOAD_DIR(absolute_path_cstr,
1.111 paf 695: const char* file_name_cstr=ffblk.ff_name;
696: size_t file_name_size=strlen(file_name_cstr);
1.47 parser 697:
1.184 misha 698: if(!vregex || vregex->exec(ffblk.ff_name, file_name_size, ovector, ovector_size)>=0) {
1.111 paf 699: Table::element_type row(new ArrayString);
1.190 misha 700: *row+=new String(pa_strdup(file_name_cstr, file_name_size), String::L_TAINTED);
1.111 paf 701: table+=row;
1.47 parser 702: }
703: );
704:
1.60 parser 705: // write out result
1.111 paf 706: r.write_no_lang(*new VTable(&table));
1.47 parser 707: }
1.21 paf 708:
1.69 paf 709: #ifndef DOXYGEN
710: struct Lock_execute_body_info {
1.111 paf 711: Request* r;
712: Value* body_code;
1.69 paf 713: };
714: #endif
1.111 paf 715: static void lock_execute_body(int , void *ainfo) {
716: Lock_execute_body_info& info=*static_cast<Lock_execute_body_info *>(ainfo);
1.69 paf 717: // execute body
1.78 paf 718: info.r->write_assign_lang(info.r->process(*info.body_code));
1.69 paf 719: };
1.111 paf 720: static void _lock(Request& r, MethodParams& params) {
1.152 misha 721: const String& file_spec=r.absolute(params.as_string(0, FILE_NAME_MUST_BE_STRING));
1.116 paf 722: Lock_execute_body_info info={
723: &r,
1.117 paf 724: ¶ms.as_junction(1, "body must be code")
1.116 paf 725: };
1.69 paf 726:
1.158 misha 727: file_write_action_under_lock(
728: file_spec,
729: "lock",
730: lock_execute_body,
731: &info);
1.69 paf 732: }
733:
1.219 misha 734: static size_t afterlastslash(const String& str) {
735: size_t pos=str.strrpbrk("/\\");
736: return pos!=STRING_NOT_FOUND?pos+1:0;
737: }
738:
739: static size_t afterlastslash(const String& str, size_t right) {
740: size_t pos=str.strrpbrk("/\\", 0, right);
741: return pos!=STRING_NOT_FOUND?pos+1:0;
742: }
743:
1.111 paf 744: static void _find(Request& r, MethodParams& params) {
1.215 misha 745: const String& file_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE);
1.219 misha 746:
1.215 misha 747: Value* not_found_code=(params.count()==2)?¶ms.as_junction(1, "not-found param must be code"):0;
748:
1.111 paf 749: const String* file_spec;
1.90 paf 750: if(file_name.first_char()=='/')
751: file_spec=&file_name;
752: else
1.111 paf 753: file_spec=&r.relative(r.request_info.uri, file_name);
1.90 paf 754:
755: // easy way
1.142 paf 756: if(file_exist(r.absolute(*file_spec))) {
1.96 paf 757: r.write_assign_lang(*file_spec);
1.90 paf 758: return;
759: }
760:
761: // monkey way
1.219 misha 762: size_t last_slash=file_spec->strrpbrk("/\\");
763: const String& dirname=file_spec->mid(0, last_slash!=STRING_NOT_FOUND?last_slash:0);
764: const String& basename=file_spec->mid(last_slash!=STRING_NOT_FOUND?last_slash+1:0, file_spec->length());
765:
766: size_t rpos=dirname.is_empty()?0:dirname.length()-1;
767: while((rpos=dirname.rskipchars("/\\", 0, rpos))!=STRING_NOT_FOUND){
768: size_t slash=dirname.strrpbrk("/\\", 0, rpos);
769: if(slash==STRING_NOT_FOUND)
770: break;
1.111 paf 771: String test_name;
1.219 misha 772: test_name << dirname.mid(0, slash+1);
773: test_name << basename;
1.142 paf 774: if(file_exist(r.absolute(test_name))) {
1.111 paf 775: r.write_assign_lang(test_name);
1.90 paf 776: return;
777: }
1.219 misha 778: rpos=slash;
1.90 paf 779: }
780:
781: // no way, not found
1.215 misha 782: if(not_found_code)
783: r.write_pass_lang(r.process(*not_found_code));
1.90 paf 784: }
785:
1.111 paf 786: static void _dirname(Request& r, MethodParams& params) {
1.152 misha 787: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.219 misha 788: // works as *nix dirname
789:
790: // empty > .
791: // / > /
792: // /a > /
793: // /a/ > /
1.180 misha 794: // /a/some.tar.gz > /a
1.219 misha 795: // /a/b/ > /a
796: // /a///b/ > /a
797: // /a/b/// > /a
798: // file > .
799:
800: if(file_spec.is_empty()) {
801: r.write_assign_lang(String("."));
802: return;
803: }
804:
805: size_t p;
806: size_t slash;
807: if((p=file_spec.rskipchars("/\\"))==STRING_NOT_FOUND)
808: r.write_assign_lang(String("/"));
809: else {
810: if((slash=file_spec.strrpbrk("/\\", 0, p))!=STRING_NOT_FOUND) {
811: if((p=file_spec.rskipchars("/\\", 0, slash))==STRING_NOT_FOUND)
812: p=slash;
813: r.write_assign_lang(file_spec.mid(0, p+1));
814: return;
815: }
1.189 misha 816: r.write_assign_lang(String("."));
1.219 misha 817: }
1.89 paf 818: }
819:
1.111 paf 820: static void _basename(Request& r, MethodParams& params) {
1.152 misha 821: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.219 misha 822: // works as *nix basename
823:
824: // empty > .
825: // / > /
826: // /a > a
827: // /a/ > a
1.180 misha 828: // /a/some.tar.gz > some.tar.gz
1.219 misha 829: // /a/b/ > b
830: // /a///b/ > b
831: // /a/b/// > b
832: // file > file
833:
834: if(file_spec.is_empty()) {
835: r.write_assign_lang(String("."));
836: return;
837: }
838:
839: size_t p=file_spec.rskipchars("/\\");
840: if(p==STRING_NOT_FOUND)
841: r.write_assign_lang(String("/"));
842: else
843: r.write_assign_lang(file_spec.mid(afterlastslash(file_spec, p), p+1));
1.89 paf 844: }
845:
1.111 paf 846: static void _justname(Request& r, MethodParams& params) {
1.152 misha 847: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.180 misha 848: // /a/some.tar.gz > some.tar
1.219 misha 849: // /a/b.c/ > empty
850: // /a/b.c > b
851: size_t pos=afterlastslash(file_spec);
852: size_t dotpos=file_spec.strrpbrk(".", pos);
853: r.write_assign_lang(file_spec.mid(pos, dotpos!=STRING_NOT_FOUND?dotpos:file_spec.length()));
1.89 paf 854: }
1.219 misha 855:
1.111 paf 856: static void _justext(Request& r, MethodParams& params) {
1.152 misha 857: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.180 misha 858: // /a/some.tar.gz > gz
1.219 misha 859: // /a/b.c/ > empty
860: size_t pos=afterlastslash(file_spec);
861: size_t dotpos=file_spec.strrpbrk(".", pos);
862: if(dotpos!=STRING_NOT_FOUND)
863: r.write_assign_lang(file_spec.mid(dotpos+1, file_spec.length()));
1.89 paf 864: }
865:
1.111 paf 866: static void _fullpath(Request& r, MethodParams& params) {
1.152 misha 867: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.111 paf 868: const String* result;
1.102 paf 869: if(file_spec.first_char()=='/')
870: result=&file_spec;
871: else {
872: // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif
873: const String& full_disk_path=r.absolute(file_spec);
1.111 paf 874: size_t document_root_length=strlen(r.request_info.document_root);
1.106 paf 875:
876: if(document_root_length>0) {
1.111 paf 877: char last_char=r.request_info.document_root[document_root_length-1];
1.106 paf 878: if(last_char == '/' || last_char == '\\')
879: --document_root_length;
880: }
1.111 paf 881: result=&full_disk_path.mid(document_root_length, full_disk_path.length());
1.102 paf 882: }
883: r.write_assign_lang(*result);
884: }
885:
1.121 paf 886: static void _sql_string(Request& r, MethodParams&) {
887: VFile& self=GET_SELF(r, VFile);
888:
889: const char *quoted=r.connection()->quote(self.value_ptr(), self.value_size());
890: r.write_assign_lang(*new String(quoted));
891: }
1.89 paf 892:
1.122 paf 893: #ifndef DOXYGEN
894: class File_sql_event_handlers: public SQL_Driver_query_event_handlers {
895: const String& statement_string; const char* statement_cstr;
896: int got_columns;
897: int got_cells;
898: public:
899: String::C value;
1.131 paf 900: const String* user_file_name;
901: const String* user_content_type;
1.122 paf 902: public:
903: File_sql_event_handlers(
904: const String& astatement_string, const char* astatement_cstr):
905: statement_string(astatement_string), statement_cstr(astatement_cstr),
906: got_columns(0),
907: got_cells(0),
908: user_file_name(0),
909: user_content_type(0) {}
910:
911: bool add_column(SQL_Error& error, const char* /*str*/, size_t /*length*/) {
912: if(got_columns++==3) {
1.156 misha 913: error=SQL_Error(PARSER_RUNTIME, "result must contain not more then 3 columns");
1.122 paf 914: return true;
915: }
916: return false;
917: }
918: bool before_rows(SQL_Error& /*error*/ ) { /* ignore */ return false; }
919: bool add_row(SQL_Error& /*error*/) { /* ignore */ return false; }
920: bool add_row_cell(SQL_Error& error, const char* str, size_t length) {
921: try {
922: switch(got_cells++) {
923: case 0:
924: value=String::C(str, length);
925: break;
926: case 1:
1.131 paf 927: if(!user_file_name) // user not specified?
1.190 misha 928: user_file_name=new String(str, String::L_TAINTED);
1.122 paf 929: break;
930: case 2:
1.131 paf 931: if(!user_content_type) // user not specified?
1.190 misha 932: user_content_type=new String(str, String::L_TAINTED);
1.122 paf 933: break;
934: default:
1.210 misha 935: error=SQL_Error(PARSER_RUNTIME, "result must not contain more then one row, three columns");
1.122 paf 936: return true;
937: }
938: return false;
939: } catch(...) {
940: error=SQL_Error("exception occured in File_sql_event_handlers::add_row_cell");
941: return true;
942: }
943: }
944: };
945: #endif
946: static void _sql(Request& r, MethodParams& params) {
1.131 paf 947: Value& statement=params.as_junction(0, "statement must be code");
1.122 paf 948:
949: Temp_lang temp_lang(r, String::L_SQL);
950: const String& statement_string=r.process_to_string(statement);
1.197 misha 951: const char* statement_cstr=statement_string.untaint_cstr(r.flang, r.connection());
1.195 misha 952:
1.122 paf 953: File_sql_event_handlers handlers(statement_string, statement_cstr);
1.131 paf 954:
1.173 misha 955: ulong limit=SQL_NO_LIMIT;
1.172 misha 956: ulong offset=0;
957:
1.131 paf 958: if(params.count()>1)
1.220 misha 959: if(HashStringValue* options=params.as_hash(1, "sql options")) {
1.131 paf 960: int valid_options=0;
961: if(Value* vfilename=options->get(NAME_NAME)) {
962: valid_options++;
963: handlers.user_file_name=&vfilename->as_string();
964: }
965: if(Value* vcontent_type=options->get(CONTENT_TYPE_NAME)) {
966: valid_options++;
967: handlers.user_content_type=&vcontent_type->as_string();
968: }
1.173 misha 969: if(Value* vlimit=options->get(sql_limit_name)) {
970: valid_options++;
971: limit=(ulong)r.process_to_value(*vlimit).as_double();
972: }
1.172 misha 973: if(Value* voffset=options->get(sql_offset_name)) {
974: valid_options++;
975: offset=(ulong)r.process_to_value(*voffset).as_double();
976: }
1.131 paf 977: if(valid_options!=options->count())
1.207 misha 978: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.131 paf 979: }
980:
981:
1.122 paf 982: r.connection()->query(
1.123 paf 983: statement_cstr,
984: 0, 0,
1.173 misha 985: offset, limit,
1.122 paf 986: handlers,
987: statement_string);
988:
989: if(!handlers.value)
1.156 misha 990: throw Exception(PARSER_RUNTIME,
1.122 paf 991: 0,
992: "produced no result");
993:
1.215 misha 994: VFile& self=GET_SELF(r, VFile);
1.122 paf 995:
1.222 moko 996: self.set_binary(true/*tainted*/, handlers.value.str, handlers.value.length, handlers.user_file_name
1.215 misha 997: , handlers.user_content_type ? new VString(*handlers.user_content_type) : 0
998: , &r);
1.122 paf 999: }
1.140 paf 1000:
1.139 paf 1001: static void _base64(Request& r, MethodParams& params) {
1.209 misha 1002: bool dynamic=!(&r.get_self() == file_class);
1003: if(dynamic) {
1.180 misha 1004: VFile& self=GET_SELF(r, VFile);
1005: if(params.count()) {
1.209 misha 1006: // decode:
1007: // ^file::base64[encoded] // backward
1.217 misha 1008: // ^file::base64[mode;user-file-name;encoded[;$.content-type[...] $.strict(true|false)]]
1.209 misha 1009: bool is_text=false;
1.217 misha 1010: bool strict=false;
1.209 misha 1011: VString* vcontent_type=0;
1.215 misha 1012: const String* user_file_name=0;
1.209 misha 1013: size_t param_index=0;
1014:
1015: if(params.count() > 1) {
1016: if(params.count() < 3)
1017: throw Exception(PARSER_RUNTIME,
1018: 0,
1.215 misha 1019: "constructor can not have less then 3 parameters (has %d parameters)",
1.209 misha 1020: params.count()); // actually it accepts 1 parameter (backward)
1021:
1.215 misha 1022: is_text=VFile::is_text_mode(params.as_string(0, MODE_MUST_NOT_BE_CODE));
1023: user_file_name=¶ms.as_string(1, FILE_NAME_MUST_BE_STRING);
1.209 misha 1024:
1025: if(params.count() == 4)
1026: if(HashStringValue* options=params.as_hash(3)) {
1027: int valid_options=0;
1028: if(Value* value=options->get(CONTENT_TYPE_NAME)) {
1029: vcontent_type=new VString(value->as_string());
1030: valid_options++;
1031: }
1.217 misha 1032: if(Value* vstrict=options->get(BASE64_STRICT_OPTION_NAME)) {
1033: strict=r.process_to_value(*vstrict).as_bool();
1034: valid_options++;
1035: }
1.209 misha 1036: if(valid_options!=options->count())
1037: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1038: }
1039:
1040: param_index=2;
1041: }
1042:
1043: const char* encoded=params.as_string(param_index, PARAMETER_MUST_BE_STRING).cstr();
1044:
1.180 misha 1045: char* decoded=0;
1046: size_t length=0;
1.217 misha 1047: pa_base64_decode(encoded, strlen(encoded), decoded, length, strict);
1.209 misha 1048:
1.221 misha 1049: self.set(true/*tainted*/, is_text, decoded, length, user_file_name, vcontent_type, &r);
1.180 misha 1050: } else {
1051: // encode: ^f.base64[]
1052: const char* encoded=pa_base64_encode(self.value_ptr(), self.value_size());
1.190 misha 1053: r.write_assign_lang(*new String(encoded, String::L_TAINTED/*once ?param=base64(something) was needed**/));
1.180 misha 1054: }
1.151 misha 1055: } else {
1.180 misha 1056: // encode: ^file:base64[filespec]
1.152 misha 1057: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.151 misha 1058: const char* encoded=pa_base64_encode(r.absolute(file_spec));
1.190 misha 1059: r.write_assign_lang(*new String(encoded, String::L_TAINTED/*once ?param=base64(something) was needed*/));
1.151 misha 1060: }
1.139 paf 1061: }
1.140 paf 1062:
1.146 misha 1063: static void _crc32(Request& r, MethodParams& params) {
1064: unsigned long crc32 = 0;
1065: if(&r.get_self() == file_class) {
1066: // ^file:crc32[file-name]
1067: if(params.count()) {
1.152 misha 1068: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.146 misha 1069: crc32=pa_crc32(r.absolute(file_spec));
1070: } else {
1.215 misha 1071: throw Exception(PARSER_RUNTIME, 0, FILE_NAME_MUST_BE_SPECIFIED);
1.146 misha 1072: }
1073: } else {
1074: // ^file.crc32[]
1075: VFile& self=GET_SELF(r, VFile);
1076: crc32=pa_crc32(self.value_ptr(), self.value_size());
1077: }
1078: r.write_no_lang(*new VInt(crc32));
1079: }
1080:
1081:
1.147 misha 1082: static void file_md5_file_action(
1.180 misha 1083: struct stat& finfo,
1084: int f,
1085: const String& , const char* /*fname*/, bool,
1086: void *context)
1.147 misha 1087: {
1088: PA_MD5_CTX& md5context=*static_cast<PA_MD5_CTX *>(context);
1089: if(finfo.st_size) {
1.148 misha 1090: int nCount=0;
1.147 misha 1091: do {
1092: unsigned char buffer[FILE_BUFFER_SIZE];
1.150 misha 1093: nCount = file_block_read(f, buffer, sizeof(buffer));
1.147 misha 1094: if ( nCount ){
1095: pa_MD5Update(&md5context, (const unsigned char*)buffer, nCount);
1096: }
1.148 misha 1097: } while(nCount > 0);
1.147 misha 1098: }
1099: }
1100:
1101: const char* pa_md5(const String& file_spec)
1102: {
1103: PA_MD5_CTX context;
1104: unsigned char digest[16];
1105: pa_MD5Init(&context);
1106: file_read_action_under_lock(file_spec, "md5", file_md5_file_action, &context);
1107: pa_MD5Final(digest, &context);
1108:
1109: return hex_string(digest, sizeof(digest), false);
1110: }
1111:
1112: const char* pa_md5(const char *in, size_t in_size)
1113: {
1114: PA_MD5_CTX context;
1115: unsigned char digest[16];
1116: pa_MD5Init(&context);
1117: pa_MD5Update(&context, (const unsigned char*)in, in_size);
1118: pa_MD5Final(digest, &context);
1119:
1120: return hex_string(digest, sizeof(digest), false);
1121: }
1122:
1123: static void _md5(Request& r, MethodParams& params) {
1124: const char* md5;
1125: if(&r.get_self() == file_class) {
1126: // ^file:md5[file-name]
1127: if(params.count()) {
1.152 misha 1128: const String& file_spec=params.as_string(0, FILE_NAME_MUST_BE_STRING);
1.147 misha 1129: md5=pa_md5(r.absolute(file_spec));
1130: } else {
1.215 misha 1131: throw Exception(PARSER_RUNTIME, 0, FILE_NAME_MUST_BE_SPECIFIED);
1.147 misha 1132: }
1133: } else {
1134: // ^file.md5[]
1135: VFile& self=GET_SELF(r, VFile);
1136: md5=pa_md5(self.value_ptr(), self.value_size());
1137:
1138: }
1139: r.write_no_lang(*new String(md5));
1140: }
1141:
1.32 paf 1142: // constructor
1143:
1.111 paf 1144: MFile::MFile(): Methoded("file") {
1.215 misha 1145: // ^file::create[text|binary;file-name;string-or-file[;options hash]]
1146: // ^file::create[string-or-file[;options hash]]
1147: add_native_method("create", Method::CT_DYNAMIC, _create, 1, 4);
1.138 paf 1148:
1.146 misha 1149: // ^file.save[mode;file-name]
1.201 misha 1150: // ^file.save[mode;file-name;$.charset[...]]
1151: add_native_method("save", Method::CT_DYNAMIC, _save, 2, 3);
1.7 paf 1152:
1.146 misha 1153: // ^file:delete[file-name]
1.225 misha 1154: // ^file:delete[file-name;$.keep-empty-dir(true)$.exception(false)]
1.224 misha 1155: add_native_method("delete", Method::CT_STATIC, _delete, 1, 2);
1.45 parser 1156:
1.146 misha 1157: // ^file:move[from-file-name;to-file-name]
1.224 misha 1158: // ^file:move[from-file-name;to-file-name;$.keep-empty-dir(true)]
1159: add_native_method("move", Method::CT_STATIC, _move, 2, 3);
1.8 paf 1160:
1.146 misha 1161: // ^file::load[mode;disk-name]
1162: // ^file::load[mode;disk-name;user-name]
1.201 misha 1163: // ^file::load[mode;disk-name;user-name;options hash]
1164: // ^file::load[mode;disk-name;options hash]
1.180 misha 1165: add_native_method("load", Method::CT_DYNAMIC, _load, 2, 4);
1.25 paf 1166:
1.146 misha 1167: // ^file::stat[disk-name]
1.32 paf 1168: add_native_method("stat", Method::CT_DYNAMIC, _stat, 1, 1);
1.21 paf 1169:
1.162 misha 1170: // ^file::cgi[mode;file-name]
1171: // ^file::cgi[mode;file-name;env hash]
1172: // ^file::cgi[mode;file-name;env hash;1cmd;2line;3ar;4g;5s]
1173: add_native_method("cgi", Method::CT_DYNAMIC, _cgi, 1, 3+50);
1174:
1175: // ^file::exec[mode;file-name]
1176: // ^file::exec[mode;file-name;env hash]
1177: // ^file::exec[mode;file-name;env hash;1cmd;2line;3ar;4g;5s]
1178: add_native_method("exec", Method::CT_DYNAMIC, _exec, 1, 3+50);
1.47 parser 1179:
1180: // ^file:list[path]
1181: // ^file:list[path][regexp]
1182: add_native_method("list", Method::CT_STATIC, _list, 1, 2);
1.69 paf 1183:
1184: // ^file:lock[path]{code}
1185: add_native_method("lock", Method::CT_STATIC, _lock, 2, 2);
1.90 paf 1186:
1.146 misha 1187: // ^file:find[file-name]
1188: // ^file:find[file-name]{when-not-found}
1.90 paf 1189: add_native_method("find", Method::CT_STATIC, _find, 1, 2);
1.47 parser 1190:
1.201 misha 1191: // ^file:dirname[/a/some.tar.gz]=/a
1.89 paf 1192: // ^file:dirname[/a/b/]=/a
1193: add_native_method("dirname", Method::CT_STATIC, _dirname, 1, 1);
1.201 misha 1194: // ^file:basename[/a/some.tar.gz]=some.tar.gz
1195: add_native_method("basename", Method::CT_STATIC, _basename, 1, 1);
1196: // ^file:justname[/a/some.tar.gz]=some.tar
1.89 paf 1197: add_native_method("justname", Method::CT_STATIC, _justname, 1, 1);
1.201 misha 1198: // ^file:justext[/a/some.tar.gz]=gz
1.89 paf 1199: add_native_method("justext", Method::CT_STATIC, _justext, 1, 1);
1.201 misha 1200: // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif
1.102 paf 1201: add_native_method("fullpath", Method::CT_STATIC, _fullpath, 1, 1);
1.121 paf 1202:
1.201 misha 1203: // ^file.sql-string[]
1.121 paf 1204: add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 0);
1.122 paf 1205:
1.209 misha 1206: // ^file::sql{}
1.201 misha 1207: // ^file::sql{}[options hash]
1.122 paf 1208: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
1.139 paf 1209:
1.209 misha 1210: // encode:
1211: // ^file.base64[]
1212: // ^file:base64[file-name]
1213: // decode:
1214: // ^file::base64[encoded] // backward
1215: // ^file::base64[mode;user-file-name;encoded]
1216: // ^file::base64[mode;user-file-name;encoded;$.content-type[...]]
1217: add_native_method("base64", Method::CT_ANY, _base64, 0, 4);
1.146 misha 1218:
1219: // ^file.crc32[]
1220: // ^file:crc32[file-name]
1221: add_native_method("crc32", Method::CT_ANY, _crc32, 0, 1);
1.147 misha 1222:
1223: // ^file.md5[]
1224: // ^file:md5[file-name]
1225: add_native_method("md5", Method::CT_ANY, _md5, 0, 1);
1226:
1.148 misha 1227: // ^file:copy[from-file-name;to-file-name]
1228: add_native_method("copy", Method::CT_STATIC, _copy, 2, 2);
1.1 paf 1229: }
E-mail: