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?&params.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+=&params.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=&params.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:                                &regexp->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:                &params.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=&params.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: