Annotation of parser3/src/classes/op.C, revision 1.163.12.1
1.1 paf 1: /** @file
1.9 paf 2: Parser: parser @b operators.
1.1 paf 3:
1.155 paf 4: Copyright (c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com)
1.71 paf 5: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.98 paf 6: */
1.1 paf 7:
1.163.12.1! paf 8: static const char * const IDENT_OP_C="$Date: 2005/12/01 15:51:06 $";
1.1 paf 9:
1.13 paf 10: #include "classes.h"
1.129 paf 11: #include "pa_vmethod_frame.h"
12:
1.1 paf 13: #include "pa_common.h"
1.134 paf 14: #include "pa_os.h"
1.1 paf 15: #include "pa_request.h"
16: #include "pa_vint.h"
17: #include "pa_sql_connection.h"
1.79 paf 18: #include "pa_vdate.h"
1.105 paf 19: #include "pa_vmethod_frame.h"
1.129 paf 20: #include "pa_vclass.h"
1.144 paf 21: #include "pa_charset.h"
1.1 paf 22:
1.18 parser 23: // limits
24:
1.162 paf 25: #define MAX_LOOPS 20000
1.18 parser 26:
1.10 paf 27: // defines
28:
1.82 paf 29: #define CASE_DEFAULT_VALUE "DEFAULT"
1.146 paf 30: #define PROCESS_MAIN_OPTION_NAME "main"
31: #define PROCESS_FILE_OPTION_NAME "file"
32: #define PROCESS_LINENO_OPTION_NAME "lineno"
1.11 paf 33:
1.10 paf 34: // class
35:
1.113 paf 36: class VClassMAIN: public VClass {
1.10 paf 37: public:
1.129 paf 38: VClassMAIN();
1.10 paf 39: };
40:
1.129 paf 41: // defines for statics
42:
43: #define SWITCH_DATA_NAME "SWITCH-DATA"
44: #define CACHE_DATA_NAME "CACHE-DATA"
1.163.12.1! paf 45: #define CYCLE_DATA_NAME "CYCLE-DATA"
! 46:
1.129 paf 47: #define EXCEPTION_VAR_NAME "exception"
48:
49: // statics
50:
51: //^switch ^case
52: static const String switch_data_name(SWITCH_DATA_NAME);
53: //^cache
54: static const String cache_data_name(CACHE_DATA_NAME);
1.163.12.1! paf 55: //^for
! 56: static const String cycle_data_name(CYCLE_DATA_NAME);
1.129 paf 57:
58: static const String exception_var_name(EXCEPTION_VAR_NAME);
59:
1.136 paf 60: // local defines
61:
62: #define CACHE_EXCEPTION_HANDLED_CACHE_NAME "cache"
63:
1.129 paf 64: // helpers
65:
1.130 paf 66: class Untaint_lang_name2enum: public Hash<const String::Body, String::Language> {
1.129 paf 67: public:
68: Untaint_lang_name2enum() {
69: #define ULN(name, LANG) \
1.130 paf 70: put(String::Body(name), (value_type)(String::L_##LANG));
1.129 paf 71: ULN("as-is", AS_IS);
72: ULN("optimized-as-is", AS_IS|String::L_OPTIMIZE_BIT);
73: ULN("file-spec", FILE_SPEC);
74: ULN("http-header", HTTP_HEADER);
75: ULN("mail-header", MAIL_HEADER);
76: ULN("uri", URI);
77: ULN("sql", SQL);
78: ULN("js", JS);
79: ULN("xml", XML);
80: ULN("optimized-xml", XML|String::L_OPTIMIZE_BIT);
81: ULN("html", HTML);
82: ULN("optimized-html", HTML|String::L_OPTIMIZE_BIT);
1.159 paf 83: ULN("regex", REGEX);
1.129 paf 84: #undef ULN
85: }
86: } untaint_lang_name2enum;
87:
1.10 paf 88: // methods
89:
1.129 paf 90: static void _if(Request& r, MethodParams& params) {
1.156 paf 91: bool condition=params.as_bool(0, "condition must be expression", r);
1.6 paf 92: if(condition)
1.156 paf 93: r.write_pass_lang(r.process(*params.get(1)));
1.129 paf 94: else if(params.count()>2)
1.156 paf 95: r.write_pass_lang(r.process(*params.get(2)));
1.1 paf 96: }
97:
1.129 paf 98: static void _untaint(Request& r, MethodParams& params) {
1.1 paf 99:
1.129 paf 100: String::Language lang;
101: if(params.count()==1)
102: lang=String::L_AS_IS; // mark as simply 'tainted'. useful in html from sql
1.59 paf 103: else {
1.129 paf 104: const String& lang_name=params.as_string(0, "lang must be string");
105: lang=untaint_lang_name2enum.get(lang_name);
1.59 paf 106: if(!lang)
1.78 paf 107: throw Exception(0,
1.59 paf 108: &lang_name,
109: "invalid taint language");
110: }
1.1 paf 111:
112: {
1.129 paf 113: Value& vbody=params.as_junction(params.count()-1, "body must be code");
1.1 paf 114:
115: Temp_lang temp_lang(r, lang); // set temporarily specified ^untaint[language;
1.86 paf 116: r.write_pass_lang(r.process(vbody)); // process marking tainted with that lang
1.1 paf 117: }
118: }
119:
1.129 paf 120: static void _taint(Request& r, MethodParams& params) {
121: String::Language lang;
122: if(params.count()==1)
123: lang=String::L_TAINTED; // mark as simply 'tainted'. useful in table:set
1.1 paf 124: else {
1.129 paf 125: const String& lang_name=params.as_string(0, "lang must be string");
126: lang=untaint_lang_name2enum.get(lang_name);
1.1 paf 127: if(!lang)
1.78 paf 128: throw Exception(0,
1.3 paf 129: &lang_name,
130: "invalid taint language");
1.1 paf 131: }
132:
133: {
1.129 paf 134: Value& vbody=params.as_no_junction(params.count()-1, "body must not be code");
1.1 paf 135:
1.129 paf 136: String result;
1.1 paf 137: result.append(
138: vbody.as_string(), // process marking tainted with that lang
139: lang, true); // force result language to specified
140: r.write_pass_lang(result);
141: }
142: }
143:
1.129 paf 144: static void _process(Request& r, MethodParams& params) {
145: Method* main_method;
146:
147: size_t index=0;
148: Value* target_self;
149: Value& maybe_target_self=params[index];
150: if(maybe_target_self.get_string() || maybe_target_self.get_junction())
151: target_self=&r.get_method_frame()->caller()->self();
152: else {
153: target_self=&maybe_target_self; index++;
154: }
155:
1.1 paf 156: {
1.129 paf 157: VStateless_class *target_class=target_self->get_last_derived_class();
1.116 paf 158: if(!target_class)
159: throw Exception("parser.runtime",
1.129 paf 160: 0,
1.116 paf 161: "no target class");
1.114 paf 162:
1.73 paf 163: // temporary remove language change
1.129 paf 164: Temp_lang temp_lang(r, String::L_PASS_APPENDED);
1.1 paf 165: // temporary zero @main so to maybe-replace it in processed code
1.129 paf 166: Temp_method temp_method_main(*target_class, main_method_name, 0);
1.1 paf 167: // temporary zero @auto so it wouldn't be auto-called in Request::use_buf
1.129 paf 168: Temp_method temp_method_auto(*target_class, auto_method_name, 0);
169:
1.146 paf 170: size_t options_index=index+1;
171: HashStringValue* options=0;
172: if(options_index<params.count()) {
173: Value& voptions=params.as_no_junction(options_index, "options must not be code");
174: options=voptions.get_hash();
175: if(!options)
176: throw Exception("parser.runtime",
177: 0,
178: "options must be hash");
179: }
180:
1.129 paf 181: const String* main_alias=0;
1.146 paf 182: const String* file_alias=0;
183: int line_no_alias_offset=0;
184: if(options) {
185: int valid_options=0;
186: if(Value* vmain_alias=options->get(PROCESS_MAIN_OPTION_NAME)) {
187: valid_options++;
188: main_alias=&vmain_alias->as_string();
189: }
190: if(Value* vfile_alias=options->get(PROCESS_FILE_OPTION_NAME)) {
191: valid_options++;
192: file_alias=&vfile_alias->as_string();
193: }
194: if(Value* vline_no_alias_offset=options->get(PROCESS_LINENO_OPTION_NAME)) {
195: valid_options++;
196: line_no_alias_offset=vline_no_alias_offset->as_int();
197: }
198:
199: if(valid_options!=options->count())
200: throw Exception("parser.runtime",
201: 0,
202: "called with invalid option");
203: }
1.129 paf 204:
1.146 paf 205: uint processe_file_no=file_alias?
206: r.register_file(r.absolute(*file_alias))
207: : pseudo_file_no__process;
208: // process...{string}
209: Value& vjunction=params.as_junction(index, "body must be code");
210: // evaluate source to process
211: const String& source=r.process_to_string(vjunction);
212: r.use_buf(*target_class,
213: source.cstr(String::L_UNSPECIFIED, r.connection(false)),
214: main_alias,
215: processe_file_no,
216: line_no_alias_offset);
1.1 paf 217:
1.73 paf 218: // main_method
1.129 paf 219: main_method=target_class->get_method(main_method_name);
1.73 paf 220: }
221: // after restoring current-request-lang
222: // maybe-execute @main[]
223: if(main_method) {
1.119 paf 224: // temporarily set method_frame's self to target_self
1.129 paf 225: Temp_method_frame_self tmfs(*r.get_method_frame(), *target_self);
1.73 paf 226: // execute!
227: r.execute(*main_method->parser_code);
1.1 paf 228: }
229: }
230:
1.138 paf 231: static void _rem(Request&, MethodParams& params) {
1.129 paf 232: params.as_junction(0, "body must be code");
1.1 paf 233: }
234:
1.129 paf 235: static void _while(Request& r, MethodParams& params) {
236: Value& vcondition=params.as_junction(0, "condition must be expression");
1.162 paf 237: Value& body_code=params.as_junction(1, "body must be code");
238: Value* delim_maybe_code=params.count()>2?¶ms[2]:0;
1.1 paf 239:
240: // while...
241: int endless_loop_count=0;
1.162 paf 242: bool need_delim=false;
1.1 paf 243: while(true) {
1.18 parser 244: if(++endless_loop_count>=MAX_LOOPS) // endless loop?
1.78 paf 245: throw Exception("parser.runtime",
1.129 paf 246: 0,
1.1 paf 247: "endless loop detected");
248:
1.86 paf 249: bool condition=r.process_to_value(vcondition,
1.1 paf 250: false/*don't intercept string*/).as_bool();
251: if(!condition) // ...condition is true
252: break;
253:
1.162 paf 254: StringOrValue sv_processed=r.process(body_code);
255: const String* s_processed=sv_processed.get_string();
256: if(delim_maybe_code && s_processed && s_processed->length()) { // delimiter set and we have body
257: if(need_delim) // need delim & iteration produced string?
258: r.write_pass_lang(r.process(*delim_maybe_code));
259: need_delim=true;
260: }
261: r.write_pass_lang(sv_processed);
1.1 paf 262: }
263: }
264:
1.129 paf 265: static void _use(Request& r, MethodParams& params) {
266: Value& vfile=params.as_no_junction(0, "file name must not be code");
1.113 paf 267: r.use_file(r.main_class, vfile.as_string());
1.1 paf 268: }
269:
1.163.12.1! paf 270: const int BREAK_BREAK=1;
! 271: const int BREAK_CONTINUE=2;
! 272:
! 273: static void set_break(Request& r, int abreak) {
! 274: void* data=r.classes_conf.get(cycle_data_name);
! 275: if(!data)
! 276: throw Exception("parser.runtime",
! 277: 0,
! 278: "without cycle");
! 279:
! 280: r.set_break(abreak);
! 281: }
! 282:
! 283: static void _break(Request& r, MethodParams&) {
! 284: set_break(r, BREAK_BREAK);
! 285: }
! 286:
! 287: static void _continue(Request& r, MethodParams&) {
! 288: set_break(r, BREAK_CONTINUE);
! 289: }
! 290:
1.129 paf 291: static void _for(Request& r, MethodParams& params) {
1.163.12.1! paf 292: Temp_hash_value<const String::Body, void*>
! 293: cycle_data_setter(r.classes_conf, cycle_data_name, /*any not null flag*/&r);
! 294:
1.129 paf 295: const String& var_name=params.as_string(0, "var name must be string");
296: int from=params.as_int(1, "from must be int", r);
297: int to=params.as_int(2, "to must be int", r);
298: Value& body_code=params.as_junction(3, "body must be code");
299: Value* delim_maybe_code=params.count()>4?¶ms[4]:0;
1.1 paf 300:
1.57 paf 301: if(to-from>=MAX_LOOPS) // too long loop?
1.78 paf 302: throw Exception("parser.runtime",
1.129 paf 303: 0,
1.57 paf 304: "endless loop detected");
305:
1.1 paf 306: bool need_delim=false;
1.129 paf 307: VInt* vint=new VInt(0);
1.154 paf 308:
309: VMethodFrame& caller=*r.get_method_frame()->caller();
310: caller.put_element(caller, var_name, vint, false);
1.1 paf 311: for(int i=from; i<=to; i++) {
312: vint->set_int(i);
313:
1.108 paf 314: StringOrValue sv_processed=r.process(body_code);
1.163.12.1! paf 315: int lbreak=r.get_break(); r.set_break(0);
1.129 paf 316: const String* s_processed=sv_processed.get_string();
317: if(delim_maybe_code && s_processed && s_processed->length()) { // delimiter set and we have body
1.108 paf 318: if(need_delim) // need delim & iteration produced string?
1.86 paf 319: r.write_pass_lang(r.process(*delim_maybe_code));
1.1 paf 320: need_delim=true;
321: }
1.108 paf 322: r.write_pass_lang(sv_processed);
1.163.12.1! paf 323: if(lbreak) {
! 324: if(lbreak==BREAK_BREAK)
! 325: break;
! 326: else {
! 327: assert(lbreak==BREAK_CONTINUE);
! 328: continue;
! 329: }
! 330: }
1.1 paf 331: }
332: }
333:
1.129 paf 334: static void _eval(Request& r, MethodParams& params) {
335: Value& expr=params.as_junction(0, "need expression");
1.1 paf 336: // evaluate expresion
1.129 paf 337: Value& value_result=r.process_to_value(expr,
1.1 paf 338: true/*don't intercept string*/).as_expr_result();
1.129 paf 339: if(params.count()>1) {
340: Value& fmt=params.as_no_junction(1, "fmt must not be code");
341: r.write_no_lang(String(format(value_result.as_double(), fmt.as_string().cstrm())));
1.91 paf 342: } else
1.129 paf 343: r.write_no_lang(value_result);
1.1 paf 344: }
345:
1.129 paf 346: static void _connect(Request& r, MethodParams& params) {
1.63 paf 347: #ifdef RESOURCES_DEBUG
348: struct timeval mt[2];
349: #endif
1.129 paf 350: Value& url=params.as_no_junction(0, "url must not be code");
351: Value& body_code=params.as_junction(1, "body must be code");
1.1 paf 352:
1.129 paf 353: Table* protocol2driver_and_client=0;
354: if(Value* sql=r.main_class.get_element(String(MAIN_SQL_NAME), r.main_class, false)) {
355: if(Value* element=sql->get_element(String(MAIN_SQL_DRIVERS_NAME), *sql, false)) {
1.113 paf 356: protocol2driver_and_client=element->get_table();
357: }
358: }
1.11 paf 359:
1.63 paf 360: #ifdef RESOURCES_DEBUG
361: //measure:before
362: gettimeofday(&mt[0],NULL);
363: #endif
1.1 paf 364: // connect
1.142 paf 365: SQL_Connection* connection=SQL_driver_manager->get_connection(url.as_string(),
1.144 paf 366: protocol2driver_and_client,
367: r.charsets.source().NAME().cstr());
1.1 paf 368:
1.63 paf 369: #ifdef RESOURCES_DEBUG
370: //measure:after connect
371: gettimeofday(&mt[1],NULL);
372:
373: double t[2];
374: for(int i=0;i<2;i++)
375: t[i]=mt[i].tv_sec+mt[i].tv_usec/1000000.0;
376:
377: r.sql_connect_time+=t[1]-t[0];
378: #endif
1.129 paf 379: Temp_connection temp_connection(r, connection);
1.1 paf 380: // execute body
1.53 parser 381: try {
1.86 paf 382: r.write_assign_lang(r.process(body_code));
1.129 paf 383: connection->commit();
384: connection->close();
1.75 paf 385: } catch(...) { // process problem
1.129 paf 386: connection->rollback();
387: connection->close();
388: rethrow;
1.1 paf 389: }
390: }
391:
1.41 parser 392: #ifndef DOXYGEN
1.129 paf 393: class Switch_data: public PA_Object {
394: public:
395: Request& r;
396: Value& searching;
397: Value* found;
398: Value* _default;
399: public:
400: Switch_data(Request& ar, Value& asearching):
401: r(ar), searching(asearching) {}
1.28 parser 402: };
1.41 parser 403: #endif
1.129 paf 404: static void _switch(Request& r, MethodParams& params) {
405: Switch_data* data=new Switch_data(r, r.process_to_value(params[0]));
1.135 paf 406: Temp_hash_value<const String::Body, void*>
1.129 paf 407: switch_data_setter(r.classes_conf, switch_data_name, data);
1.28 parser 408:
1.129 paf 409: Value& cases_code=params.as_junction(1, "switch cases must be code");
1.82 paf 410: // execution of found ^case[...]{code} must be in context of ^switch[...]{code}
411: // because of stacked WWrapper used there as wcontext
1.84 paf 412: r.process(cases_code, true/*intercept_string*/);
1.147 paf 413: if(Value* selected_code=data->found? data->found: data->_default)
1.107 paf 414: r.write_pass_lang(r.process(*selected_code));
1.28 parser 415: }
416:
1.129 paf 417: static void _case(Request& r, MethodParams& params) {
418: Switch_data* data=static_cast<Switch_data*>(r.classes_conf.get(switch_data_name));
1.38 parser 419: if(!data)
1.78 paf 420: throw Exception("parser.runtime",
1.129 paf 421: 0,
1.38 parser 422: "without switch");
1.28 parser 423:
1.129 paf 424: int count=params.count();
425: Value& code=params.as_junction(--count, "case result must be code");
1.83 paf 426:
1.163 paf 427: Value& searching=data->searching;
428: bool we_are_searching_string_or_void=searching.is_string() || searching.is_void();
1.28 parser 429: for(int i=0; i<count; i++) {
1.129 paf 430: Value& value=r.process_to_value(params[i]);
1.28 parser 431:
1.157 paf 432: if(value.is_string() && value.as_string() == CASE_DEFAULT_VALUE) {
1.129 paf 433: data->_default=&code;
1.28 parser 434: break;
435: }
436:
437: bool matches;
1.163 paf 438: if(we_are_searching_string_or_void)
439: matches=searching.as_string() == value.as_string();
1.28 parser 440: else
1.163 paf 441: matches=searching.as_double() == value.as_double();
1.28 parser 442:
443: if(matches) {
1.82 paf 444: if(data->found)
445: throw Exception("parser.runtime",
1.129 paf 446: 0,
1.82 paf 447: "duplicate found");
448:
1.129 paf 449: data->found=&code;
1.28 parser 450: break;
451: }
452: }
453: }
1.136 paf 454: #ifndef DOXYGEN
455: struct Try_catch_result {
456: StringOrValue processed_code;
457: const String* exception_should_be_handled;
458:
459: Try_catch_result(): exception_should_be_handled(0) {}
460: };
461: #endif
462:
463: /// used by ^try and ^cache, @returns $exception.handled[string] if any
464: template<class I>
465: static Try_catch_result try_catch(Request& r,
466: StringOrValue body_code(Request&, I), I info,
1.160 paf 467: Value* catch_code, bool could_be_handled_by_caller=false)
1.136 paf 468: {
469: Try_catch_result result;
470: if(!catch_code) {
471: result.processed_code=body_code(r, info);
472: return result;
473: }
474:
475: // taking snapshot of try-context
476: Request_context_saver try_context(r);
477: try {
478: result.processed_code=body_code(r, info);
479: } catch(const Exception& e) {
480: Request_context_saver throw_context(r); // taking snapshot of throw-context [stack trace contains error]
481: Request::Exception_details details=r.get_details(e);
482: try_context.restore(); // restoring try-context to perform catch-code
483:
484: Junction* junction=catch_code->get_junction();
485: Value* method_frame=junction->method_frame;
486: Value* saved_exception_var_value=method_frame->get_element(exception_var_name, *method_frame, false);
1.154 paf 487: VMethodFrame& frame=*junction->method_frame;
488: frame.put_element(frame, exception_var_name, &details.vhash, false);
1.136 paf 489: result.processed_code=r.process(*catch_code);
490:
491: // retriving $exception.handled, restoring $exception var
492: Value* vhandled=details.vhash.hash().get(exception_handled_part_name);
1.154 paf 493: frame.put_element(frame, exception_var_name, saved_exception_var_value, false);
1.136 paf 494:
495: bool bhandled=false;
496: if(vhandled) {
497: if(vhandled->is_string()) { // not simple $exception.handled(1/0)?
1.160 paf 498: if(could_be_handled_by_caller) { // and we can possibly handle it
499: result.exception_should_be_handled=vhandled->get_string(); // considering 'recovered' and let the caller recover
500: return result;
501: }
502:
503: bhandled=false;
504: } else
505: bhandled=vhandled->as_bool();
1.136 paf 506: }
507:
508: if(!bhandled) {
509: throw_context.restore(); // restoring throw-context [exception were not handled]
510: rethrow;
511: }
512: }
513: return result;
514: }
1.28 parser 515:
1.63 paf 516: // cache--
517:
518: // consts
519:
1.152 paf 520: const int DATA_STRING_SERIALIZED_VERSION=0x0006;
1.63 paf 521:
522: // helper types
523:
524: #ifndef DOXYGEN
525: struct Data_string_serialized_prolog {
526: int version;
1.79 paf 527: time_t expires;
1.63 paf 528: };
529: #endif
530:
1.68 paf 531: void cache_delete(const String& file_spec) {
532: file_delete(file_spec, false/*fail_on_read_problem*/);
1.63 paf 533: }
1.69 paf 534:
535: #ifndef DOXYGEN
1.135 paf 536: struct Cache_scope {
1.129 paf 537: public:
1.79 paf 538: time_t expires;
1.135 paf 539: const String* body_from_disk;
1.79 paf 540: };
1.69 paf 541: struct Locked_process_and_cache_put_action_info {
542: Request *r;
1.136 paf 543: Cache_scope *scope;
544: Value* body_code; Value* catch_code;
545: const String* processed_code;
1.69 paf 546: };
547: #endif
1.136 paf 548:
549:
550:
551: static StringOrValue process_cache_body_code(Request& r, Value* body_code) {
1.153 paf 552: return StringOrValue(r.process_to_string(*body_code));
1.136 paf 553: }
554:
1.129 paf 555: /* @todo maybe network order worth spending some effort?
556: don't bothering myself with network byte order,
557: am not planning to be able to move resulting file across platforms
558: */
1.69 paf 559: static void locked_process_and_cache_put_action(int f, void *context) {
560: Locked_process_and_cache_put_action_info& info=
561: *static_cast<Locked_process_and_cache_put_action_info *>(context);
1.129 paf 562:
1.160 paf 563:
564: const String* body_from_disk=info.scope->body_from_disk;
1.69 paf 565: // body->process
1.136 paf 566: Try_catch_result result=try_catch(*info.r,
567: process_cache_body_code, info.body_code,
1.160 paf 568: info.catch_code, body_from_disk!=0 /*we have something old=we can handle=recover later*/);
1.136 paf 569:
570: if(result.exception_should_be_handled) {
571: if(*result.exception_should_be_handled==CACHE_EXCEPTION_HANDLED_CACHE_NAME) {
1.160 paf 572: assert(body_from_disk);
573: info.processed_code=body_from_disk;
1.136 paf 574: } else
575: throw Exception("parser.runtime",
576: result.exception_should_be_handled,
577: "$"EXCEPTION_VAR_NAME"."EXCEPTION_HANDLED_PART_NAME" value must be "
578: "either boolean or string '"CACHE_EXCEPTION_HANDLED_CACHE_NAME"'");
579: } else
580: info.processed_code=&result.processed_code.as_string();
1.69 paf 581:
1.79 paf 582: // expiration time not spoiled by ^cache(0) or something?
1.136 paf 583: if(info.scope->expires > time(0)) {
1.79 paf 584: // string -serialize> buffer
1.136 paf 585: String::Cm serialized=info.processed_code->serialize(
1.129 paf 586: sizeof(Data_string_serialized_prolog));
1.79 paf 587: Data_string_serialized_prolog& prolog=
1.129 paf 588: *reinterpret_cast<Data_string_serialized_prolog *>(serialized.str);
1.79 paf 589: prolog.version=DATA_STRING_SERIALIZED_VERSION;
1.136 paf 590: prolog.expires=info.scope->expires;
1.79 paf 591:
592: // buffer -write> file
1.129 paf 593: write(f, serialized.str, serialized.length);
1.79 paf 594: } else // expired!
1.136 paf 595: info.scope->expires=0; // flag it so that could be easily checked by caller
1.69 paf 596: }
1.129 paf 597: const String* locked_process_and_cache_put(Request& r,
1.128 paf 598: Value& body_code,
1.136 paf 599: Value* catch_code,
600: Cache_scope& scope,
601: const String& file_spec)
602: {
1.140 paf 603: Locked_process_and_cache_put_action_info info={&r, &scope, &body_code, catch_code, 0};
1.69 paf 604:
1.129 paf 605: const String* result=file_write_action_under_lock(
1.69 paf 606: file_spec,
607: "cache_put", locked_process_and_cache_put_action, &info,
608: false/*as_text*/,
609: false/*do_append*/,
1.96 paf 610: false/*block*/,
1.136 paf 611: false/*fail on lock problem*/) ? info.processed_code: 0;
1.100 paf 612: time_t now=time(0);
1.136 paf 613: if(scope.expires<=now)
1.79 paf 614: cache_delete(file_spec);
615: return result;
1.63 paf 616: }
1.137 paf 617: #ifndef DOXYGEN
1.135 paf 618: struct Cache_get_result {
619: const String* body;
620: bool expired;
1.137 paf 621: };
622: #endif
623: static Cache_get_result cache_get(Request_charsets& charsets, const String& file_spec, time_t now) {
1.140 paf 624: Cache_get_result result={0, false};
1.135 paf 625:
1.129 paf 626: File_read_result file=file_read(charsets, file_spec,
1.63 paf 627: false/*as_text*/,
1.129 paf 628: 0, //no params
629: false/*fail_on_read_problem*/);
630: if(file.success && file.length/* ignore reads which are empty due to
1.72 paf 631: non-unary open+lockEX conflict with lockSH */) {
1.129 paf 632:
1.72 paf 633: Data_string_serialized_prolog& prolog=
1.129 paf 634: *reinterpret_cast<Data_string_serialized_prolog *>(file.str);
1.63 paf 635:
1.135 paf 636: String* body=new String;
1.72 paf 637: if(
1.129 paf 638: file.length>=sizeof(Data_string_serialized_prolog)
1.135 paf 639: && prolog.version==DATA_STRING_SERIALIZED_VERSION) {
640: if(body->deserialize(sizeof(Data_string_serialized_prolog), file.str, file.length)) {
641: result.body=body;
642: result.expired=prolog.expires <= now;
643: }
644: }
1.72 paf 645: }
1.63 paf 646:
1.135 paf 647: return result;
1.63 paf 648: }
1.129 paf 649: static time_t as_expires(Request& r, MethodParams& params,
1.79 paf 650: int index, time_t now) {
651: time_t result;
1.129 paf 652: if(Value* vdate=params[index].as(VDATE_TYPE, false))
653: result=static_cast<VDate*>(vdate)->get_time();
1.79 paf 654: else
1.129 paf 655: result=now+(time_t)params.as_double(index, "lifespan must be date or number", r);
1.79 paf 656:
657: return result;
658: }
1.129 paf 659: static const String& as_file_spec(Request& r, MethodParams& params, int index) {
660: return r.absolute(params.as_string(index, "filespec must be string"));
1.79 paf 661: }
1.129 paf 662: static void _cache(Request& r, MethodParams& params) {
1.158 paf 663: if(params.count()==0)
664: {
665: // return current expiration time
666: Cache_scope* scope=static_cast<Cache_scope*>(r.classes_conf.get(cache_data_name));
667: if(!scope)
668: throw Exception("parser.runtime",
669: 0,
670: "expire-time get without cache");
671: r.write_no_lang(*new VDate(scope->expires));
672: return;
673: }
674:
1.79 paf 675: time_t now=time(0);
676:
677: // ^cache[filename] ^cache(seconds) ^cache[expires date]
1.129 paf 678: if(params.count()==1) {
679: if(params[0].is_string()) { // filename?
1.79 paf 680: cache_delete(as_file_spec(r, params, 0));
681: return;
682: }
683:
684: // secods|expires date
1.135 paf 685: Cache_scope* scope=static_cast<Cache_scope*>(r.classes_conf.get(cache_data_name));
686: if(!scope)
1.79 paf 687: throw Exception("parser.runtime",
1.129 paf 688: 0,
1.79 paf 689: "expire-time reducing instruction without cache");
690:
1.129 paf 691: time_t expires=as_expires(r, params, 0, now);
1.135 paf 692: if(expires < scope->expires)
693: scope->expires=expires;
1.79 paf 694:
695: return;
1.149 paf 696: } else if(params.count()<3)
697: throw Exception("parser.runtime",
698: 0,
699: "invalid number of parameters");
1.63 paf 700:
701: // file_spec, expires, body code
1.129 paf 702: const String& file_spec=r.absolute(params.as_string(0, "filespec must be string"));
1.65 paf 703:
1.141 paf 704: Cache_scope scope={as_expires(r, params, 1, now), 0};
1.135 paf 705:
706: Temp_hash_value<const String::Body, void*>
707: cache_scope_setter(r.classes_conf, cache_data_name, &scope);
1.136 paf 708: Value& body_code=params.as_junction(2, "body_code must be code");
709: Value* catch_code=0;
710: if(params.count()>3)
711: catch_code=¶ms.as_junction(3, "catch_code must be code");
1.63 paf 712:
1.135 paf 713: if(scope.expires>now) { // valid 'expires' specified? try cached copy...
1.69 paf 714: // hence we don't hope to have unary create/lockEX
715: // we need some plan to live in a life like that, so...
716: // worst races plan:
717: // A B
718: // open
719: // |open
720: // lockSH
721: // |nonblocking-lockEX fails
722: // unlockSH
723: // close, cache_get returns 0
724: // open
725: // nonblocking-lockEX succeeds; process, write, close
726: // |retry1: open
727: // ...
728: // |lockSH succeeds; ...
729:
730: for(int retry=0; retry<2; retry++) {
1.135 paf 731: Cache_get_result cached=cache_get(r.charsets, file_spec, now);
1.136 paf 732: if(cached.body) { // have cached copy
1.135 paf 733: if(cached.expired)
734: scope.body_from_disk=cached.body; // storing for user to retrive it with ^cache[]
1.136 paf 735: else // and it's not expired yet
736: {
1.135 paf 737: // write it out
738: r.write_assign_lang(*cached.body);
739: // happy with it
740: return;
741: }
1.79 paf 742: }
1.69 paf 743:
1.135 paf 744: // non-blocked lock; process; cache it
745: if(const String* processed_body=
1.136 paf 746: locked_process_and_cache_put(r, body_code, catch_code, scope, file_spec)) {
1.135 paf 747: // write it out
748: r.write_assign_lang(*processed_body);
749: // happy with it
750: return;
751: } else { // somebody writing result right now
752: pa_sleep(0, 500000); // waiting half a second
753: retry=0; // prolonging our wait, than could cache_get it, without processing body_code
754: }
1.69 paf 755: }
1.78 paf 756: throw Exception(0,
1.69 paf 757: &file_spec,
758: "locking problem");
759: } else {
1.79 paf 760: // instructed not to cache; forget cached copy
1.68 paf 761: cache_delete(file_spec);
1.69 paf 762: // process
1.81 paf 763: const String& processed_body=r.process_to_string(body_code);
1.69 paf 764: // write it out
765: r.write_assign_lang(processed_body);
766: // happy with it
767: return;
768: }
769: // never reached
1.63 paf 770: }
771:
1.136 paf 772: static StringOrValue process_try_body_code(Request& r, Value* body_code) {
773: return r.process(*body_code);
774: }
1.129 paf 775: static void _try_operator(Request& r, MethodParams& params) {
776: Value& body_code=params.as_junction(0, "body_code must be code");
777: Value& catch_code=params.as_junction(1, "catch_code must be code");
1.74 paf 778:
1.136 paf 779: Try_catch_result result=try_catch(r,
780: process_try_body_code, &body_code,
781: &catch_code);
782:
783: if(result.exception_should_be_handled)
784: throw Exception("parser.runtime",
785: result.exception_should_be_handled,
786: "catch block must set $exception.handled to some boolean value, not string");
1.123 paf 787:
1.136 paf 788: // write out processed body_code or catch_code
789: r.write_pass_lang(result.processed_code);
1.74 paf 790: }
791:
1.138 paf 792: static void _throw_operator(Request&, MethodParams& params) {
1.129 paf 793: if(params.count()==1) {
794: if(HashStringValue *hash=params[0].get_hash()) {
795: const char* type=0;
796: if(Value* value=hash->get(exception_type_part_name))
1.78 paf 797: type=value->as_string().cstr();
1.129 paf 798: const String* source=0;
799: if(Value* value=hash->get(exception_source_part_name))
1.74 paf 800: source=&value->as_string();
1.138 paf 801: const char* comment=0;
1.129 paf 802: if(Value* value=hash->get(exception_comment_part_name))
1.74 paf 803: comment=value->as_string().cstr();
804:
1.78 paf 805: throw Exception(type,
1.129 paf 806: source?source:0,
807: "%s", comment?comment:"");
1.74 paf 808: } else
1.78 paf 809: throw Exception("parser.runtime",
1.129 paf 810: 0,
1.74 paf 811: "one-param version has hash param");
812: } else {
1.129 paf 813: const char* type=params.as_string(0, "type must be string").cstr();
814: const String& source=params.as_string(1, "source must be string");
815: const char* comment=params.count()>2? params.as_string(2, "comment must be string").cstr()
816: :0;
1.97 paf 817: throw Exception(type, &source, "%s", comment?comment:"");
1.74 paf 818: }
1.132 paf 819: }
1.129 paf 820:
1.150 paf 821: static void _sleep_operator(Request& r, MethodParams& params) {
822: double seconds=params.as_double(0, "seconds must be double", r);
1.151 paf 823: pa_sleep((int)trunc(seconds), (int)trunc(seconds*1000));
1.150 paf 824: }
825:
1.129 paf 826: #if defined(WIN32) && defined(_DEBUG)
827: # define PA_BPT
1.138 paf 828: static void _bpt(Request&, MethodParams&) {
1.129 paf 829: _asm int 3;
830: }
831: #endif
832:
1.10 paf 833: // constructor
834:
1.129 paf 835: VClassMAIN::VClassMAIN(): VClass() {
836: set_name(*new String(MAIN_CLASS_NAME));
837:
838: #ifdef PA_BPT
839: // ^bpt[]
840: add_native_method("bpt", Method::CT_ANY, _bpt, 0, 0);
841: #endif
1.121 paf 842:
1.1 paf 843: // ^if(condition){code-when-true}
844: // ^if(condition){code-when-true}{code-when-false}
1.10 paf 845: add_native_method("if", Method::CT_ANY, _if, 2, 3);
1.1 paf 846:
847: // ^untaint[as-is|uri|sql|js|html|html-typo]{code}
1.59 paf 848: add_native_method("untaint", Method::CT_ANY, _untaint, 1, 2);
1.1 paf 849:
850: // ^taint[as-is|uri|sql|js|html|html-typo]{code}
1.10 paf 851: add_native_method("taint", Method::CT_ANY, _taint, 1, 2);
1.1 paf 852:
853: // ^process[code]
1.146 paf 854: add_native_method("process", Method::CT_ANY, _process, 1, 3);
1.1 paf 855:
856: // ^rem{code}
1.51 parser 857: add_native_method("rem", Method::CT_ANY, _rem, 1, 10000);
1.1 paf 858:
859: // ^while(condition){code}
1.162 paf 860: add_native_method("while", Method::CT_ANY, _while, 2, 3);
1.1 paf 861:
862: // ^use[file]
1.10 paf 863: add_native_method("use", Method::CT_ANY, _use, 1, 1);
1.1 paf 864:
1.163.12.1! paf 865: // ^break[]
! 866: add_native_method("break", Method::CT_ANY, _break, 0, 0);
! 867: // ^continue[]
! 868: add_native_method("continue", Method::CT_ANY, _continue, 0, 0);
1.54 paf 869: // ^for[i](from-number;to-number-inclusive){code}[delim]
1.10 paf 870: add_native_method("for", Method::CT_ANY, _for, 3+1, 3+1+1);
1.1 paf 871:
872: // ^eval(expr)
873: // ^eval(expr)[format]
1.10 paf 874: add_native_method("eval", Method::CT_ANY, _eval, 1, 2);
1.52 parser 875:
1.1 paf 876: // ^connect[protocol://user:pass@host[:port]/database]{code with ^sql-s}
1.10 paf 877: add_native_method("connect", Method::CT_ANY, _connect, 2, 2);
878:
1.63 paf 879:
1.136 paf 880: // ^cache[file_spec](time){code}[{catch code}] time=0 no cache
1.65 paf 881: // ^cache[file_spec] delete cache
1.161 paf 882: // ^cache[] get current expiration time
883: add_native_method("cache", Method::CT_ANY, _cache, 0, 4);
1.63 paf 884:
1.28 parser 885: // switch
886:
887: // ^switch[value]{cases}
888: add_native_method("switch", Method::CT_ANY, _switch, 2, 2);
889:
890: // ^case[value]{code}
1.51 parser 891: add_native_method("case", Method::CT_ANY, _case, 2, 10000);
1.74 paf 892:
893: // try-catch
894:
895: // ^try{code}{catch code}
896: add_native_method("try", Method::CT_ANY, _try_operator, 2, 2);
897: // ^throw[$exception hash]
898: // ^throw[type;source;comment]
899: add_native_method("throw", Method::CT_ANY, _throw_operator, 1, 3);
900:
1.150 paf 901: add_native_method("sleep", Method::CT_ANY, _sleep_operator, 1, 1);
1.10 paf 902: }
1.11 paf 903:
904: // constructor & configurator
1.1 paf 905:
1.129 paf 906: VStateless_class& VClassMAIN_create() {
907: return *new VClassMAIN;
1.1 paf 908: }
E-mail: