Annotation of parser3/src/classes/file.C, revision 1.126
1.17 paf 1: /** @file
2: Parser: @b file parser class.
3:
1.120 paf 4: Copyright (c) 2001-2004 ArtLebedev Group (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.126 ! paf 8: static const char * const IDENT_FILE_C="$Date: 2004/07/26 10:17:04 $";
1.47 parser 9:
10: #include "pa_config_includes.h"
11:
12: #include "pcre.h"
1.1 paf 13:
1.35 paf 14: #include "classes.h"
1.111 paf 15: #include "pa_vmethod_frame.h"
16:
1.1 paf 17: #include "pa_request.h"
18: #include "pa_vfile.h"
1.11 paf 19: #include "pa_table.h"
1.21 paf 20: #include "pa_vint.h"
1.24 paf 21: #include "pa_exec.h"
1.40 parser 22: #include "pa_vdate.h"
1.47 parser 23: #include "pa_dir.h"
24: #include "pa_vtable.h"
1.67 paf 25: #include "pa_charset.h"
1.109 paf 26: #include "pa_charsets.h"
1.121 paf 27: #include "pa_sql_connection.h"
1.126 ! paf 28: #include "pa_vresponse.h"
! 29: #include "pa_vcookie.h"
1.1 paf 30:
1.32 paf 31: // defines
32:
1.48 parser 33: #define TEXT_MODE_NAME "text"
1.125 paf 34: #define BINARY_MODE_NAME "binary"
1.90 paf 35: #define STDIN_EXEC_PARAM_NAME "stdin"
1.109 paf 36: #define CHARSET_EXEC_PARAM_NAME "charset"
1.48 parser 37:
1.111 paf 38: // class
39:
40: class MFile: public Methoded {
41: public: // VStateless_class
42:
1.114 paf 43: Value* create_new_value(Pool&) { return new VFile(); }
1.111 paf 44:
45: public: // Methoded
46: bool used_directly() { return true; }
47:
48: public:
49: MFile();
50:
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.125 paf 111: static bool is_text_mode(const String& mode) {
112: if(mode==TEXT_MODE_NAME)
113: return true;
114: if(mode==BINARY_MODE_NAME)
115: return false;
116: throw Exception("parser.runtime",
117: &mode,
118: "is invalid mode, must be either '"TEXT_MODE_NAME"' or '"BINARY_MODE_NAME"'");
119: }
120:
1.111 paf 121: static void _save(Request& r, MethodParams& params) {
122: Value& vmode_name=params. as_no_junction(0, "mode must not be code");
123: Value& vfile_name=params.as_no_junction(1, "file name must not be code");
1.4 paf 124:
1.7 paf 125: // save
1.111 paf 126: GET_SELF(r, VFile).save(r.absolute(vfile_name.as_string()),
1.125 paf 127: is_text_mode(vmode_name.as_string()));
1.7 paf 128: }
129:
1.111 paf 130: static void _delete(Request& r, MethodParams& params) {
131: Value& vfile_name=params.as_no_junction(0, "file name must not be code");
1.7 paf 132:
133: // unlink
1.68 paf 134: file_delete(r.absolute(vfile_name.as_string()));
1.1 paf 135: }
136:
1.111 paf 137: static void _move(Request& r, MethodParams& params) {
138: Value& vfrom_file_name=params.as_no_junction(0, "from file name must not be code");
139: Value& vto_file_name=params.as_no_junction(1, "to file name must not be code");
1.45 parser 140:
1.51 parser 141: // move
1.68 paf 142: file_move(
1.45 parser 143: r.absolute(vfrom_file_name.as_string()),
144: r.absolute(vto_file_name.as_string()));
145: }
146:
1.111 paf 147: static void _load_pass_param(
148: HashStringValue::key_type key,
149: HashStringValue::value_type value,
150: HashStringValue *dest) {
151: dest->put(key, value);
152: }
153: static void _load(Request& r, MethodParams& params) {
154: Value& vmode_name=params. as_no_junction(0, "mode must not be code");
155: const String& lfile_name=r.absolute(params.as_no_junction(1, "file name must not be code").as_string());
156: Value* third_param=params.count()>2?¶ms.as_no_junction(2, "filename or options must not be code")
157: :0;
158: HashStringValue* third_param_hash=third_param?third_param->get_hash():0;
159: size_t alt_filename_param_index=2;
1.104 paf 160: if(third_param_hash)
161: alt_filename_param_index++;
1.9 paf 162:
1.111 paf 163: File_read_result file=file_read(r.charsets, lfile_name,
1.125 paf 164: is_text_mode(vmode_name.as_string()),
1.111 paf 165: third_param_hash
1.104 paf 166: );
1.9 paf 167:
1.111 paf 168: const char *user_file_name=params.count()>alt_filename_param_index?
169: params.as_string(alt_filename_param_index, "filename must be string").cstr()
170: :lfile_name.cstr(String::L_FILE_SPEC);
171:
172: Value* vcontent_type=0;
173: if(file.headers)
174: vcontent_type=file.headers->get(content_type_name);
1.104 paf 175: if(!vcontent_type)
1.111 paf 176: vcontent_type=new VString(r.mime_type_of(user_file_name));
1.10 paf 177:
1.111 paf 178: VFile& self=GET_SELF(r, VFile);
179: self.set(true/*tainted*/, file.str, file.length, user_file_name, vcontent_type);
180: if(file.headers)
181: file.headers->for_each(_load_pass_param, &self.fields());
1.9 paf 182: }
183:
1.111 paf 184: static void _stat(Request& r, MethodParams& params) {
185: Value& vfile_name=params.as_no_junction(0, "file name must not be code");
1.25 paf 186:
187: const String& lfile_name=vfile_name.as_string();
188:
1.40 parser 189: size_t size;
190: time_t atime, mtime, ctime;
191: file_stat(r.absolute(lfile_name),
192: size,
193: atime, mtime, ctime);
1.25 paf 194:
1.111 paf 195: VFile& self=GET_SELF(r, VFile);
196: self.set(true/*tainted*/, 0/*no bytes*/, size);
197: HashStringValue& ff=self.fields();
198: ff.put(adate_name, new VDate(atime));
199: ff.put(mdate_name, new VDate(mtime));
200: ff.put(cdate_name, new VDate(ctime));
201: ff.put(content_type_name, new VString(r.mime_type_of(lfile_name.cstr(String::L_FILE_SPEC))));
1.25 paf 202: }
203:
1.111 paf 204: static bool is_safe_env_key(const char* key) {
205: for(const char* validator=key; *validator; validator++) {
206: char c=*validator;
207: if(!(c>='A' && c<='Z' || c>='0' && c<='9' || c=='_' || c=='-'))
208: return false;
209: }
1.88 paf 210: if(strncasecmp(key, "HTTP_", 5)==0)
1.83 paf 211: return true;
1.87 paf 212: if(strncasecmp(key, "CGI_", 4)==0)
1.83 paf 213: return true;
214: for(int i=0; suexec_safe_env_lst[i]; i++) {
1.87 paf 215: if(strcasecmp(key, suexec_safe_env_lst[i])==0)
1.83 paf 216: return true;
217: }
218: return false;
219: }
1.90 paf 220: #ifndef DOXYGEN
221: struct Append_env_pair_info {
1.111 paf 222: HashStringString* env;
1.100 paf 223: Value* vstdin;
1.109 paf 224: Value* vcharset;
1.90 paf 225: };
226: #endif
1.111 paf 227: static void append_env_pair(
228: HashStringValue::key_type akey,
229: HashStringValue::value_type avalue,
230: Append_env_pair_info *info) {
231: if(akey==STDIN_EXEC_PARAM_NAME) {
232: info->vstdin=avalue;
233: } else if(akey==CHARSET_EXEC_PARAM_NAME) {
234: info->vcharset=avalue;
1.90 paf 235: } else {
1.111 paf 236: if(!is_safe_env_key(akey.cstr()))
1.90 paf 237: throw Exception("parser.runtime",
1.111 paf 238: new String(akey, String::L_TAINTED),
1.90 paf 239: "not safe environment variable");
1.124 paf 240: info->env->put(akey, avalue->as_string().cstr(String::L_UNSPECIFIED));
1.90 paf 241: }
1.22 paf 242: }
1.94 paf 243: #ifndef DOXYGEN
244: struct Pass_cgi_header_attribute_info {
1.111 paf 245: Charset* charset;
246: HashStringValue* fields;
247: Value* content_type;
1.94 paf 248: };
249: #endif
1.111 paf 250: static void pass_cgi_header_attribute(
251: ArrayString::element_type astring,
252: Pass_cgi_header_attribute_info* info) {
253: size_t colon_pos=astring->pos(':');
254: if(colon_pos==STRING_NOT_FOUND) {
255: const String& key=astring->mid(0, colon_pos).change_case(
256: *info->charset, String::CC_UPPER);
257: Value* value=new VString(astring->mid(colon_pos+1, astring->length()));
258: info->fields->put(key, value);
1.94 paf 259: if(key=="CONTENT-TYPE")
1.111 paf 260: info->content_type=value;
1.94 paf 261: }
1.29 paf 262: }
1.90 paf 263: /// @todo fix `` in perl - they produced flipping consoles and no output to perl
1.111 paf 264: static void _exec_cgi(Request& r, MethodParams& params,
1.41 parser 265: bool cgi) {
1.21 paf 266:
1.111 paf 267: Value& vfile_name=params.as_no_junction(0, "file name must not be code");
1.21 paf 268:
1.23 paf 269: const String& script_name=r.absolute(vfile_name.as_string());
270:
1.111 paf 271: HashStringString env;
1.62 paf 272: #define ECSTR(name, value_cstr) \
1.111 paf 273: if(value_cstr) \
274: env.put( \
1.112 paf 275: String::Body(#name), \
276: String::Body(value_cstr, 0)); \
1.82 paf 277: // passing SAPI::environment
1.111 paf 278: if(const char *const *pairs=SAPI::environment(r.sapi_info)) {
279: while(const char* pair=*pairs++)
280: if(const char* eq_at=strchr(pair, '='))
281: if(eq_at[1]) // has value
282: env.put(
283: pa_strdup(pair, eq_at-pair),
284: pa_strdup(eq_at+1, 0));
1.82 paf 285: }
286:
1.23 paf 287: // const
1.63 paf 288: ECSTR(GATEWAY_INTERFACE, "CGI/1.1");
1.23 paf 289: // from Request.info
1.111 paf 290: ECSTR(DOCUMENT_ROOT, r.request_info.document_root);
291: ECSTR(PATH_TRANSLATED, r.request_info.path_translated);
292: ECSTR(REQUEST_METHOD, r.request_info.method);
293: ECSTR(QUERY_STRING, r.request_info.query_string);
294: ECSTR(REQUEST_URI, r.request_info.uri);
295: ECSTR(CONTENT_TYPE, r.request_info.content_type);
1.23 paf 296: char content_length_cstr[MAX_NUMBER];
1.111 paf 297: snprintf(content_length_cstr, MAX_NUMBER, "%u", r.request_info.content_length);
298: //String content_length(content_length_cstr);
1.62 paf 299: ECSTR(CONTENT_LENGTH, content_length_cstr);
1.82 paf 300: // SCRIPT_*
1.119 paf 301: env.put(String::Body("SCRIPT_NAME"), script_name);
302: //env.put(String::Body("SCRIPT_FILENAME"), ??&script_name);
1.23 paf 303:
1.111 paf 304: bool stdin_specified=false;
1.90 paf 305: // environment & stdin from param
1.111 paf 306: String *in=new String();
1.109 paf 307: Charset *charset=0; // default script works raw_in 'source' charset = no transcoding needed
1.111 paf 308: if(params.count()>1) {
309: Value& venv=params.as_no_junction(1, "env must not be code");
310: if(HashStringValue* user_env=venv.get_hash()) {
1.116 paf 311: Append_env_pair_info info={&env, 0, 0};
1.90 paf 312: user_env->for_each(append_env_pair, &info);
1.109 paf 313: // $.stdin
1.103 paf 314: if(info.vstdin) {
1.111 paf 315: stdin_specified=true;
316: if(const String* sstdin=info.vstdin->get_string()) {
317: in->append(*sstdin, String::L_CLEAN, true);
1.103 paf 318: } else
1.111 paf 319: if(VFile* vfile=static_cast<VFile *>(info.vstdin->as("file", false)))
320: in->append_know_length((const char* )vfile->value_ptr(), vfile->value_size(), String::L_TAINTED);
1.100 paf 321: else
322: throw Exception("parser.runtime",
1.111 paf 323: 0,
1.100 paf 324: STDIN_EXEC_PARAM_NAME " parameter must be string or file");
1.103 paf 325: }
1.109 paf 326: // $.charset
327: if(info.vcharset)
1.111 paf 328: charset=&charsets.get(info.vcharset->as_string()
329: .change_case(r.charsets.source(), String::CC_UPPER));
1.90 paf 330: }
1.21 paf 331: }
332:
1.90 paf 333: // argv from params
1.111 paf 334: ArrayString argv;
335: if(params.count()>2) {
336: for(size_t i=2; i<params.count(); i++)
337: argv+=¶ms.as_string(i, "parameter must be string");
1.21 paf 338: }
1.90 paf 339:
1.109 paf 340: // transcode if necessary
341: if(charset) {
1.111 paf 342: Charset::transcode(env, r.charsets.source(), *charset);
343: Charset::transcode(argv, r.charsets.source(), *charset);
344: in=&Charset::transcode(*in, r.charsets.source(), *charset);
345: }
346: // @todo
347: // ifdef WIN32 do OEM->ANSI transcode on some(.cmd?) programs to
348: // match silent conversion in OS
349:
350: // exec!
351: PA_exec_result execution=
352: pa_exec(false/*forced_allow*/, script_name, &env, argv, *in);
353:
354: String *real_out=&execution.out;
355: String *real_err=&execution.err;
356: // transcode if necessary
357: if(charset) {
358: real_out=&Charset::transcode(*real_out, *charset, r.charsets.source());
359: real_err=&Charset::transcode(*real_err, *charset, r.charsets.source());
1.109 paf 360: }
361:
1.111 paf 362: VFile& self=GET_SELF(r, VFile);
1.109 paf 363:
1.111 paf 364: const String* body=real_out; // ^file:exec
365: const char* eol_marker=0; size_t eol_marker_size;
366: const String* header=0;
1.41 parser 367: if(cgi) { // ^file:cgi
1.111 paf 368: // construct with 'out' body and header
369: size_t dos_pos=real_out->pos("\r\n\r\n", 4);
370: size_t unix_pos=real_out->pos("\n\n", 2);
371:
372: bool unix_header_break;
373: switch((dos_pos!=STRING_NOT_FOUND?10:00) + (unix_pos!=STRING_NOT_FOUND?01:00)) {
374: case 10: // dos
375: unix_header_break=false;
376: break;
377: case 01: // unix
378: unix_header_break=true;
379: break;
380: case 11: // dos & unix
381: unix_header_break=unix_pos<dos_pos;
382: break;
383: default: // 00
384: unix_header_break=false; // calm down, compiler
1.74 paf 385: throw Exception(0,
1.111 paf 386: 0,
1.90 paf 387: "output does not contain CGI header; "
388: "exit status=%d; stdoutsize=%u; stdout: \"%s\"; stderrsize=%u; stderr: \"%s\"",
1.111 paf 389: execution.status,
390: (uint)real_out->length(), real_out->cstr(),
391: (uint)real_err->length(), real_err->cstr());
392: break; //never reached
393: }
394:
395: int header_break_pos;
396: if(unix_header_break) {
397: header_break_pos=unix_pos;
398: eol_marker="\n"; eol_marker_size=1;
399: } else {
400: header_break_pos=dos_pos;
401: eol_marker="\r\n"; eol_marker_size=2;
402: }
1.21 paf 403:
1.109 paf 404: header=&real_out->mid(0, header_break_pos);
1.111 paf 405: body=&real_out->mid(header_break_pos+eol_marker_size*2, real_out->length());
1.29 paf 406: }
1.41 parser 407: // body
1.111 paf 408: self.set(false/*not tainted*/, body->cstr(), body->length());
1.94 paf 409:
410: // $fields << header
1.98 paf 411: if(header && eol_marker) {
1.111 paf 412: ArrayString rows;
413: size_t pos_after=0;
414: header->split(rows, pos_after, eol_marker);
1.116 paf 415: Pass_cgi_header_attribute_info info={0, 0, 0};
1.111 paf 416: info.charset=&r.charsets.source();
417: info.fields=&self.fields();
1.94 paf 418: rows.for_each(pass_cgi_header_attribute, &info);
419: if(info.content_type)
1.111 paf 420: self.fields().put(content_type_name, info.content_type);
1.94 paf 421: }
1.21 paf 422:
1.42 parser 423: // $status
1.111 paf 424: self.fields().put(file_status_name, new VInt(execution.status));
1.21 paf 425:
426: // $stderr
1.111 paf 427: if(real_err->length())
1.21 paf 428: self.fields().put(
1.119 paf 429: String::Body("stderr"),
1.111 paf 430: new VString(*real_err));
1.21 paf 431: }
1.111 paf 432: static void _exec(Request& r, MethodParams& params) {
433: _exec_cgi(r, params, false);
1.41 parser 434: }
1.111 paf 435: static void _cgi(Request& r, MethodParams& params) {
436: _exec_cgi(r, params, true);
1.41 parser 437: }
438:
1.111 paf 439: static void _list(Request& r, MethodParams& params) {
440: Value& relative_path=params.as_no_junction(0, "path must not be code");
1.47 parser 441:
1.111 paf 442: const String* regexp;
1.47 parser 443: pcre *regexp_code;
1.81 paf 444: const int ovecsize=(1/*match*/)*3;
445: int ovector[ovecsize];
1.111 paf 446: if(params.count()>1) {
447: regexp=¶ms.as_no_junction(1, "regexp must not be code").as_string();
1.47 parser 448:
1.111 paf 449: const char* pattern=regexp->cstr();
450: const char* errptr;
1.47 parser 451: int erroffset;
452: regexp_code=pcre_compile(pattern, PCRE_EXTRA | PCRE_DOTALL,
453: &errptr, &erroffset,
1.111 paf 454: r.charsets.source().pcre_tables);
1.47 parser 455:
456: if(!regexp_code)
1.74 paf 457: throw Exception(0,
1.111 paf 458: ®exp->mid(erroffset, regexp->length()),
1.47 parser 459: "regular expression syntax error - %s", errptr);
1.114 paf 460: } else {
461: regexp=0; // not used, just to calm down compiler
1.47 parser 462: regexp_code=0;
1.114 paf 463: }
1.47 parser 464:
465:
1.111 paf 466: const char* absolute_path_cstr=r.absolute(relative_path.as_string()).cstr(String::L_FILE_SPEC);
1.47 parser 467:
1.111 paf 468: Table::columns_type columns(new ArrayString);
469: *columns+=new String("name");
470: Table& table=*new Table(columns);
1.47 parser 471:
472: LOAD_DIR(absolute_path_cstr,
1.111 paf 473: const char* file_name_cstr=ffblk.ff_name;
474: size_t file_name_size=strlen(file_name_cstr);
1.47 parser 475: bool suits=true;
476: if(regexp_code) {
477: int exec_result=pcre_exec(regexp_code, 0,
478: ffblk.ff_name, file_name_size, 0,
479: 0, ovector, ovecsize);
480:
481: if(exec_result==PCRE_ERROR_NOMATCH)
482: suits=false;
483: else if(exec_result<0) {
484: (*pcre_free)(regexp_code);
1.74 paf 485: throw Exception(0,
1.47 parser 486: regexp,
487: "regular expression execute (%d)",
488: exec_result);
489: }
490: }
491:
492: if(suits) {
1.111 paf 493: Table::element_type row(new ArrayString);
494: *row+=new String(pa_strdup(file_name_cstr, file_name_size), file_name_size, true);
495: table+=row;
1.47 parser 496: }
497: );
498:
499: if(regexp_code)
1.111 paf 500: pcre_free(regexp_code);
1.47 parser 501:
1.60 parser 502: // write out result
1.111 paf 503: r.write_no_lang(*new VTable(&table));
1.47 parser 504: }
1.21 paf 505:
1.69 paf 506: #ifndef DOXYGEN
507: struct Lock_execute_body_info {
1.111 paf 508: Request* r;
509: Value* body_code;
1.69 paf 510: };
511: #endif
1.111 paf 512: static void lock_execute_body(int , void *ainfo) {
513: Lock_execute_body_info& info=*static_cast<Lock_execute_body_info *>(ainfo);
1.69 paf 514: // execute body
1.78 paf 515: info.r->write_assign_lang(info.r->process(*info.body_code));
1.69 paf 516: };
1.111 paf 517: static void _lock(Request& r, MethodParams& params) {
518: const String& file_spec=r.absolute(params.as_string(0, "file name must be string"));
1.116 paf 519: Lock_execute_body_info info={
520: &r,
1.117 paf 521: ¶ms.as_junction(1, "body must be code")
1.116 paf 522: };
1.69 paf 523:
1.70 paf 524: file_write_action_under_lock(file_spec, "lock", lock_execute_body, &info);
1.69 paf 525: }
526:
1.111 paf 527: static int lastposafter(const String& s, size_t after, const char* substr, size_t substr_size, bool beforelast=false) {
1.114 paf 528: size_t size=0; // just to calm down compiler
1.89 paf 529: if(beforelast)
1.111 paf 530: size=s.length();
1.116 paf 531: size_t at;
1.112 paf 532: while((at=s.pos(String::Body(substr, substr_size), after))!=STRING_NOT_FOUND) {
1.89 paf 533: size_t newafter=at+substr_size/*skip substr*/;
534: if(beforelast && newafter==size)
535: break;
536: after=newafter;
537: }
538:
539: return after;
540: }
541:
1.111 paf 542: static void _find(Request& r, MethodParams& params) {
543: const String& file_name=params.as_no_junction(0, "file name must not be code").as_string();
544: const String* file_spec;
1.90 paf 545: if(file_name.first_char()=='/')
546: file_spec=&file_name;
547: else
1.111 paf 548: file_spec=&r.relative(r.request_info.uri, file_name);
1.90 paf 549:
550: // easy way
551: if(file_readable(r.absolute(*file_spec))) {
1.96 paf 552: r.write_assign_lang(*file_spec);
1.90 paf 553: return;
554: }
555:
556: // monkey way
557: int after_base_slash=lastposafter(*file_spec, 0, "/", 1);
1.111 paf 558: const String* dirname=&file_spec->mid(0, after_base_slash);
559: const String& basename=file_spec->mid(after_base_slash, file_spec->length());
1.90 paf 560:
561: int after_monkey_slash;
562: while((after_monkey_slash=lastposafter(*dirname, 0, "/", 1, true))>0) {
1.111 paf 563: String test_name;
564: test_name<<*(dirname=&dirname->mid(0, after_monkey_slash));
565: test_name<<basename;
566: if(file_readable(r.absolute(test_name))) {
567: r.write_assign_lang(test_name);
1.90 paf 568: return;
569: }
570: }
571:
572: // no way, not found
1.111 paf 573: if(params.count()==2) {
574: Value& not_found_code=params.as_junction(1, "not-found param must be code");
1.90 paf 575: r.write_pass_lang(r.process(not_found_code));
576: }
577: }
578:
1.111 paf 579: static void _dirname(Request& r, MethodParams& params) {
580: const String& file_spec=params.as_string(0, "file name must be string");
1.89 paf 581: // /a/some.tar.gz > /a
582: // /a/b/ > /a
583: int afterslash=lastposafter(file_spec, 0, "/", 1, true);
584: if(afterslash>0)
585: r.write_assign_lang(file_spec.mid(0, afterslash==1?1:afterslash-1));
586: else
1.111 paf 587: r.write_assign_lang(String(".", 1));
1.89 paf 588: }
589:
1.111 paf 590: static void _basename(Request& r, MethodParams& params) {
591: const String& file_spec=params.as_string(0, "file name must be string");
1.89 paf 592: // /a/some.tar.gz > some.tar.gz
593: int afterslash=lastposafter(file_spec, 0, "/", 1);
1.111 paf 594: r.write_assign_lang(file_spec.mid(afterslash, file_spec.length()));
1.89 paf 595: }
596:
1.111 paf 597: static void _justname(Request& r, MethodParams& params) {
598: const String& file_spec=params.as_string(0, "file name must be string");
1.89 paf 599: // /a/some.tar.gz > some.tar
600: int afterslash=lastposafter(file_spec, 0, "/", 1);
601: int afterdot=lastposafter(file_spec, afterslash, ".", 1);
1.111 paf 602: r.write_assign_lang(file_spec.mid(afterslash, afterdot!=afterslash?afterdot-1:file_spec.length()));
1.89 paf 603: }
1.111 paf 604: static void _justext(Request& r, MethodParams& params) {
605: const String& file_spec=params.as_string(0, "file name must be string");
1.89 paf 606: // /a/some.tar.gz > gz
607: int afterdot=lastposafter(file_spec, 0, ".", 1);
608: if(afterdot>0)
1.111 paf 609: r.write_assign_lang(file_spec.mid(afterdot, file_spec.length()));
1.89 paf 610: }
611:
1.111 paf 612: static void _fullpath(Request& r, MethodParams& params) {
613: const String& file_spec=params.as_string(0, "file name must be string");
614: const String* result;
1.102 paf 615: if(file_spec.first_char()=='/')
616: result=&file_spec;
617: else {
618: // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif
619: const String& full_disk_path=r.absolute(file_spec);
1.111 paf 620: size_t document_root_length=strlen(r.request_info.document_root);
1.106 paf 621:
622: if(document_root_length>0) {
1.111 paf 623: char last_char=r.request_info.document_root[document_root_length-1];
1.106 paf 624: if(last_char == '/' || last_char == '\\')
625: --document_root_length;
626: }
1.111 paf 627: result=&full_disk_path.mid(document_root_length, full_disk_path.length());
1.102 paf 628: }
629: r.write_assign_lang(*result);
630: }
631:
1.121 paf 632: static void _sql_string(Request& r, MethodParams&) {
633: VFile& self=GET_SELF(r, VFile);
634:
635: const char *quoted=r.connection()->quote(self.value_ptr(), self.value_size());
636: r.write_assign_lang(*new String(quoted));
637: }
1.89 paf 638:
1.122 paf 639: #ifndef DOXYGEN
640: class File_sql_event_handlers: public SQL_Driver_query_event_handlers {
641: const String& statement_string; const char* statement_cstr;
642: int got_columns;
643: int got_cells;
644: public:
645: String::C value;
646: String* user_file_name;
647: String* user_content_type;
648: public:
649: File_sql_event_handlers(
650: const String& astatement_string, const char* astatement_cstr):
651: statement_string(astatement_string), statement_cstr(astatement_cstr),
652: got_columns(0),
653: got_cells(0),
654: user_file_name(0),
655: user_content_type(0) {}
656:
657: bool add_column(SQL_Error& error, const char* /*str*/, size_t /*length*/) {
658: if(got_columns++==3) {
659: error=SQL_Error("parser.runtime", "result must contain not more then 3 columns");
660: return true;
661: }
662: return false;
663: }
664: bool before_rows(SQL_Error& /*error*/ ) { /* ignore */ return false; }
665: bool add_row(SQL_Error& /*error*/) { /* ignore */ return false; }
666: bool add_row_cell(SQL_Error& error, const char* str, size_t length) {
667: try {
668: switch(got_cells++) {
669: case 0:
670: value=String::C(str, length);
671: break;
672: case 1:
673: user_file_name=new String(str, length, true);
674: break;
675: case 2:
676: user_content_type=new String(str, length, true);
677: break;
678: default:
679: error=SQL_Error("parser.runtime", "result must not contain more then one row, three rows");
680: return true;
681: }
682: return false;
683: } catch(...) {
684: error=SQL_Error("exception occured in File_sql_event_handlers::add_row_cell");
685: return true;
686: }
687: }
688: };
689: #endif
690: static void _sql(Request& r, MethodParams& params) {
691: const String* user_file_name=0;
692: if(params.get(0)->is_string())
693: user_file_name=¶ms.get(0)->as_string();
694:
695: Value& statement=params.as_junction(params.count()-1, "statement must be code");
696:
697: Temp_lang temp_lang(r, String::L_SQL);
698: const String& statement_string=r.process_to_string(statement);
699: const char* statement_cstr=
700: statement_string.cstr(String::L_UNSPECIFIED, r.connection());
701: File_sql_event_handlers handlers(statement_string, statement_cstr);
702: r.connection()->query(
1.123 paf 703: statement_cstr,
704: 0, 0,
705: 0, 0,
1.122 paf 706: handlers,
707: statement_string);
708:
709: if(!handlers.value)
710: throw Exception("parser.runtime",
711: 0,
712: "produced no result");
713:
714: if(!user_file_name)
1.126 ! paf 715: class send_attr_info
! 716: {
! 717: public:
! 718: send_attr_info(Request *t) : r(t), add_content_type(true), add_last_modified(true), add_content_disposition(true) {}
! 719: Request *r;
! 720: bool add_content_type;
! 721: bool add_last_modified;
! 722: bool add_content_disposition;
! 723: };
! 724:
! 725: static void send_add_header_attribute(
! 726: HashStringValue::key_type aattribute,
! 727: HashStringValue::value_type ameaning,
! 728: send_attr_info *r)
! 729: {
! 730: const char *a = aattribute.cstr();
! 731: SAPI::add_header_attribute(r->r->sapi_info,
! 732: a,
! 733: attributed_meaning_to_string(*ameaning, String::L_HTTP_HEADER, false).
! 734: cstr(String::L_UNSPECIFIED));
! 735: if(strcasecmp(a, "content-type")==0)
! 736: r->add_content_type = false;
! 737: else if(strcasecmp(a, "last-modified")==0)
! 738: r->add_last_modified = false;
! 739: else if(strcasecmp(a, "content-disposition")==0)
! 740: r->add_content_disposition = false;
! 741: }
! 742:
! 743: struct RANGE
! 744: {
! 745: size_t start;
! 746: size_t end;
! 747: };
! 748:
! 749: static void parse_range(const String* s, Array<RANGE> &ar)
! 750: {
! 751: const char *p = s->cstr();
! 752: if(s->starts_with("bytes="))
! 753: p += 6;
! 754: RANGE r;
! 755: while(*p){
! 756: r.start = (size_t)-1;
! 757: r.end = (size_t)-1;
! 758: if(*p >= '0' && *p <= '9'){
! 759: r.start = atol(p);
! 760: while(*p>='0' && *p<='9') ++p;
! 761: }
! 762: if(*p++ != '-') break;
! 763: if(*p >= '0' && *p <= '9'){
! 764: r.end = atol(p);
! 765: while(*p>='0' && *p<='9') ++p;
! 766: }
! 767: if(*p == ',') ++p;
! 768: ar += r;
! 769: }
! 770: }
! 771:
! 772: class auto_file
! 773: {
! 774: protected:
! 775: FILE *f;
! 776: public:
! 777: auto_file(FILE *t){
! 778: f = t;
! 779: }
! 780: ~auto_file(){
! 781: if(f != 0){
! 782: fclose(f);
! 783: f = 0;
! 784: }
! 785: }
! 786: operator FILE*(){
! 787: return f;
! 788: }
! 789: };
! 790:
! 791: // ^file:send[filename]
! 792: // ^file:send[filename;options hash]
! 793: // ^file:send[local_filename;remote_filename]
! 794: // ^file:send[local_filename;remote_filename;options hash]
! 795: static void _send(Request& r, MethodParams& params) {
! 796: SAPI::add_header_attribute(r.sapi_info, "Accept-Ranges", "bytes");
! 797: if(r.response.fields().get("ignore")!=0) throw Exception("parser.runtime", 0, "^file:send not allowed here");
! 798: Value *to_file_name = 0;
! 799: Value *options = 0;
! 800: Value *from_file_name = params.get(0);
! 801: const char *c_from_file_name=0, *disposition=0;
! 802: if(!from_file_name->is("string")) throw Exception("parser.runtime", 0, "filename must be string");
! 803:
! 804: size_t count = params.count();
! 805: if(count > 1){
! 806: to_file_name = params.get(1);
! 807: if(to_file_name->is("hash")){
! 808: options = to_file_name;
! 809: to_file_name = 0;
! 810: }else if(count > 2){
! 811: options = params.get(2);
! 812: if(!options->is("hash")) throw Exception("parser.runtime", 0, "options parameter must be hash");
! 813: }
! 814: }
! 815:
! 816: c_from_file_name=r.absolute(from_file_name->as_string()).cstr();
! 817:
! 818: size_t offset = 0;
! 819: size_t limit = (size_t)-1;
! 820: send_attr_info info(&r);
! 821: VDate *date = 0;
! 822:
! 823: if(options){
! 824: HashStringValue *opts = options->get_hash();
! 825: if(opts == 0)
! 826: throw Exception("parser.runtime", 0, "options must be hash");
! 827: Value *v;
! 828: int valid_options = 0;
! 829: if(v = opts->get("offset")){
! 830: ++valid_options;
! 831: offset = v->as_int();
! 832: }
! 833: if(v = opts->get("limit")){
! 834: ++valid_options;
! 835: limit = v->as_int();
! 836: }
! 837: if(v = opts->get("headers")){
! 838: ++valid_options;
! 839: HashStringValue *headers = v->get_hash();
! 840: if(headers == 0)
! 841: throw Exception("parser.runtime", 0, "headers must be hash");
! 842: headers->for_each(send_add_header_attribute, &info);
! 843: }
! 844: if(v = opts->get("mdate")){
! 845: ++valid_options;
! 846: if(Value* vdate=v->as(VDATE_TYPE, false))
! 847: date=static_cast<VDate*>(vdate);
! 848: else throw Exception("parser.runtime", 0, "mdate must be a date");
! 849: }
! 850: if(v = opts->get("disposition")){
! 851: ++valid_options;
! 852: if(!v->is("string")) throw Exception("parser.runtime", 0, "disposition must be a string");
! 853: disposition = v->get_string()->cstr();
! 854: if(strcmp(disposition, "inline") && strcmp(disposition, "attachment")) throw Exception("parser.runtime", 0, "disposition can be only 'inline' or 'attachment'");
! 855: }
! 856: if(valid_options != opts->count())
! 857: throw Exception("parser.runtime", 0, "invalid option passed");
! 858: }
! 859:
! 860: auto_file f = fopen(c_from_file_name, "rb");
! 861: if(f == 0)
! 862: throw Exception("parser.runtime", 0, "Can't open file");
! 863:
! 864: if(fseek(f, 0, SEEK_END)!=0)
! 865: throw Exception("parser.runtime", 0, "Can't seek file");
! 866:
! 867: size_t file_length = (size_t)ftell(f);
! 868: if(file_length == (size_t)-1)
! 869: throw Exception("parser.runtime", 0, "can't get file size");
! 870: if(file_length <= offset)
! 871: throw Exception("parser.runtime", 0, "offset too big");
! 872:
! 873: size_t content_length = file_length-offset;
! 874: if(limit != (size_t)-1)
! 875: content_length = limit<content_length?limit:content_length;
! 876:
! 877: size_t part_length = content_length;
! 878:
! 879: const size_t BUFSIZE = 4096;
! 880: unsigned char buf[BUFSIZE];
! 881: const char *range = SAPI::get_env(r.sapi_info, "HTTP_RANGE");
! 882: if(range){
! 883: Array<RANGE> ar;
! 884: parse_range(new String(range), ar);
! 885: size_t count = ar.count();
! 886: if(count == 1){
! 887: RANGE &rg = ar.get_ref(0);
! 888: if(rg.start == (size_t)-1 && rg.end == (size_t)-1){
! 889: SAPI::add_header_attribute(r.sapi_info, "status", "416 Requested Range Not Satisfiable");
! 890: return;
! 891: }
! 892: if(rg.start == (size_t)-1 && rg.end != (size_t)-1){
! 893: rg.start = content_length - rg.end;
! 894: rg.end = content_length;
! 895: offset += rg.start;
! 896: part_length = rg.end-rg.start;
! 897: }else if(rg.start != (size_t)-1 && rg.end == (size_t)-1){
! 898: rg.end = content_length-1;
! 899: offset += rg.start;
! 900: part_length -= rg.start;
! 901: }
! 902: if(part_length == 0){
! 903: SAPI::add_header_attribute(r.sapi_info, "status", "204 No Content");
! 904: return;
! 905: }
! 906: SAPI::add_header_attribute(r.sapi_info, "status", "206 Partial Content");
! 907: snprintf((char*)buf, BUFSIZE, "bytes %u-%u/%u", rg.start, rg.end, content_length);
! 908: SAPI::add_header_attribute(r.sapi_info, "Content-Range", (char*)buf);
! 909: }else if(count != 0){
! 910: SAPI::add_header_attribute(r.sapi_info, "status", "501 Not Implemented");
! 911: return;
! 912: }
! 913: }
! 914:
! 915: fseek(f, offset, SEEK_SET);
! 916: snprintf((char*)buf, BUFSIZE, "%u", part_length);
! 917: SAPI::add_header_attribute(r.sapi_info, "Content-Length", (char*)buf);
! 918:
! 919: if(info.add_content_disposition && disposition){
! 920: const char *fname = 0;
! 921: if(to_file_name){
! 922: fname = to_file_name->as_string().cstr();
! 923: }else{
! 924: const char *fname = c_from_file_name;
! 925: const char *p1 = strrchr(fname, '/');
! 926: const char *p2 = strrchr(fname, '\\');
! 927: if(p1 || p2)
! 928: fname = max(p1, p2)+1;
! 929: }
! 930:
! 931: snprintf((char*)buf, BUFSIZE, "%s; filename=\"%s\"", disposition, fname);
! 932: SAPI::add_header_attribute(r.sapi_info, "Content-Disposition", (char*)buf);
! 933: }
! 934: if(info.add_content_type)
! 935: SAPI::add_header_attribute(r.sapi_info, "Content-Type", r.mime_type_of(c_from_file_name).cstr());
! 936: if(info.add_last_modified){
! 937: if(date == 0){
! 938: struct stat st;
! 939: if(stat(c_from_file_name, &st)!=0) throw Exception("parser.runtime", 0, "can't get file stat");
! 940: date = new VDate(st.st_mtime);
! 941: }
! 942: const String &s = attributed_meaning_to_string(*date, String::L_AS_IS, true);
! 943: SAPI::add_header_attribute(r.sapi_info, "Last-Modified", s.cstr());
! 944: }
! 945: r.cookie.output_result(r.sapi_info);
! 946: SAPI::send_header(r.sapi_info);
! 947:
! 948: const char* request_method=getenv("REQUEST_METHOD");
! 949: bool header_only=request_method && strcasecmp(request_method, "HEAD")==0;
! 950: size_t sent = 0;
! 951: if(!header_only){
! 952: size_t to_read = 0;
! 953: size_t size = 0;
! 954: do{
! 955: to_read = part_length<BUFSIZE?part_length:BUFSIZE;
! 956: to_read = fread(buf, 1, to_read, f);
! 957: if(to_read == 0)
! 958: break;
! 959: size = SAPI::send_body(r.sapi_info, buf, to_read);
! 960: sent += size;
! 961: if(size != to_read)
! 962: break;
! 963: part_length -= to_read;
! 964: }while(part_length);
! 965: }
! 966: // set flag to bypass other outputs
! 967: r.response.fields().put("ignore", new VString(*new String("y")));
! 968: r.write_no_lang(*new VInt(sent));
! 969: }
! 970:
1.122 paf 971: user_file_name=handlers.user_file_name;
972:
973: const char* user_file_name_cstr=user_file_name? user_file_name->cstr(): 0;
974:
975: VString* vcontent_type=handlers.user_content_type?
976: new VString(*handlers.user_content_type)
977: : user_file_name_cstr?
978: new VString(r.mime_type_of(user_file_name_cstr))
979: : 0;
980: VFile& self=GET_SELF(r, VFile);
981: self.set(true/*tainted*/, handlers.value.str, handlers.value.length, user_file_name_cstr, vcontent_type);
982: }
983:
1.32 paf 984: // constructor
985:
1.111 paf 986: MFile::MFile(): Methoded("file") {
1.48 parser 987: // ^save[mode;file-name]
988: add_native_method("save", Method::CT_DYNAMIC, _save, 2, 2);
1.7 paf 989:
990: // ^delete[file-name]
1.32 paf 991: add_native_method("delete", Method::CT_STATIC, _delete, 1, 1);
1.45 parser 992:
993: // ^move[from-file-name;to-file-name]
994: add_native_method("move", Method::CT_STATIC, _move, 2, 2);
1.8 paf 995:
1.48 parser 996: // ^load[mode;disk-name]
997: // ^load[mode;disk-name;user-name]
998: add_native_method("load", Method::CT_DYNAMIC, _load, 2, 3);
1.25 paf 999:
1000: // ^stat[disk-name]
1.32 paf 1001: add_native_method("stat", Method::CT_DYNAMIC, _stat, 1, 1);
1.21 paf 1002:
1.36 paf 1003: // ^cgi[file-name]
1004: // ^cgi[file-name;env hash]
1005: // ^cgi[file-name;env hash;1cmd;2line;3ar;4g;5s]
1.41 parser 1006: add_native_method("cgi", Method::CT_DYNAMIC, _cgi, 1, 2+10);
1007:
1008: // ^exec[file-name]
1009: // ^exec[file-name;env hash]
1010: // ^exec[file-name;env hash;1cmd;2line;3ar;4g;5s]
1011: add_native_method("exec", Method::CT_DYNAMIC, _exec, 1, 2+10);
1.47 parser 1012:
1013: // ^file:list[path]
1014: // ^file:list[path][regexp]
1015: add_native_method("list", Method::CT_STATIC, _list, 1, 2);
1.69 paf 1016:
1017: // ^file:lock[path]{code}
1018: add_native_method("lock", Method::CT_STATIC, _lock, 2, 2);
1.90 paf 1019:
1020: // ^find[file-name]
1021: // ^find[file-name]{when-not-found}
1022: add_native_method("find", Method::CT_STATIC, _find, 1, 2);
1.47 parser 1023:
1.89 paf 1024: // ^file:dirname[/a/some.tar.gz]=/a
1025: // ^file:dirname[/a/b/]=/a
1026: add_native_method("dirname", Method::CT_STATIC, _dirname, 1, 1);
1027: // ^file:basename[/a/some.tar.gz]=some.tar.gz
1.126 ! paf 1028:
! 1029: // ^file:send[filename]
! 1030: // ^file:send[filename;options hash]
! 1031: // ^file:send[filename;new_filename]
! 1032: // ^file:send[filename;new_filename;options hash]
! 1033: add_native_method("send", Method::CT_STATIC, _send, 1, 3);
1.89 paf 1034: add_native_method("basename", Method::CT_STATIC, _basename, 1, 1);
1035: // ^file:justname[/a/some.tar.gz]=some.tar
1036: add_native_method("justname", Method::CT_STATIC, _justname, 1, 1);
1037: // ^file:justext[/a/some.tar.gz]=gz
1038: add_native_method("justext", Method::CT_STATIC, _justext, 1, 1);
1.102 paf 1039: // /some/page.html: ^file:fullpath[a.gif] => /some/a.gif
1040: add_native_method("fullpath", Method::CT_STATIC, _fullpath, 1, 1);
1.121 paf 1041:
1042: // ^file.sql-string[]
1043: add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 0);
1.122 paf 1044:
1045: // ^file::sql[[alt_name]]{}
1046: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
1.1 paf 1047: }
E-mail: