|
|
|
|
|
|
|
constant cvs_version = "$Id$"; |
#include <module.h> |
#include <module_constants.h> |
#include <roxen.h> |
#include <request_trace.h> |
#include <timers.h> |
|
#define CATCH(P,X) do{mixed e;if(e=catch{X;})report_error("While "+P+"\n"+describe_backtrace(e));}while(0) |
|
|
constant pike_cycle_depth = 0; |
|
|
|
|
|
|
#define LOC_S(X,Y) _STR_LOCALE("roxen_start",X,Y) |
#define LOC_C(X,Y) _STR_LOCALE("roxen_config",X,Y) |
#define LOC_M(X,Y) _STR_LOCALE("roxen_message",X,Y) |
#define DLOCALE(X,Y) _DEF_LOCALE("roxen_config",X,Y) |
|
#ifdef THROTTLING_DEBUG |
#undef THROTTLING_DEBUG |
#define THROTTLING_DEBUG(X) report_debug("Throttling: "+X+"\n") |
#else |
#define THROTTLING_DEBUG(X) |
#endif |
|
#ifdef REQUEST_DEBUG |
# define REQUEST_WERR(X) report_debug("CONFIG: "+X+"\n") |
#else |
# define REQUEST_WERR(X) |
#endif |
|
|
#ifdef AVERAGE_PROFILING |
|
#if !constant(gethrvtime) |
#define gethrvtime() gethrtime() |
#endif /* !constant(gethrvtime) */ |
|
class ProfStack |
{ |
array current_stack = ({}); |
|
void enter( string k, RequestID id ) |
{ |
current_stack += ({ ({ k, gethrtime(), gethrvtime() }) }); |
} |
|
void leave( string k, RequestID id ) |
{ |
int t0 = gethrtime(); |
int t1 = gethrvtime(); |
|
if( !sizeof(current_stack ) ) |
{ |
|
return; |
} |
|
int i = sizeof( current_stack )-1; |
while( current_stack[ i ][0] != k && i >= 0 ) i--; |
|
if(i < 0 ) |
{ |
return; |
} |
void low_leave( int i ) |
{ |
int tt = t0-current_stack[i][1]; |
int ttv = t1-current_stack[i][2]; |
|
if( i > 0 ) |
{ |
current_stack[i-1][1]+=tt+gethrtime()-t0; |
current_stack[i-1][2]+=ttv+gethrvtime()-t1; |
} |
current_stack = current_stack[..i-1]; |
add_prof_entry( id, k, tt, ttv ); |
}; |
|
if( i != sizeof( current_stack )-1 ) |
{ |
for( int j = sizeof( current_stack )-1; j>=i; j-- ) |
low_leave( j ); |
return; |
} |
low_leave( i ); |
} |
} |
|
class ProfInfo( string url ) |
{ |
mapping data = ([]); |
void add( string k, int h, int hrv ) |
{ |
if( !data[k] ) |
data[k] = ({ h, hrv, 1 }); |
else |
{ |
data[k][0]+=h; |
data[k][1]+=hrv; |
data[k][2]++; |
} |
} |
|
array summarize_table( ) |
{ |
array table = ({}); |
int n, t, v; |
foreach( indices( data ), string k ) |
table += ({ ({ k, |
sprintf( "%d", (n=data[k][2]) ), |
sprintf("%5.2f",(t=data[k][0])/1000000.0), |
sprintf("%5.2f", (v=data[k][1])/1000000.0), |
sprintf("%8.2f", t/n/1000.0), |
sprintf("%8.2f",v/n/1000.0), }) }); |
sort( (array(float))column(table,2), table ); |
return reverse(table); |
} |
|
void dump( ) |
{ |
write( "\n"+url+": \n" ); |
ADT.Table.table t = ADT.Table.table( summarize_table(), |
({ "What", "Calls", |
"Time", "CPU", |
"t/call(ms)", "cpu/call(ms)" })); |
|
write( ADT.Table.ASCII.encode( t )+"\n" ); |
|
} |
} |
|
mapping profiling_info = ([]); |
|
void debug_write_prof( ) |
{ |
foreach( sort( indices( profiling_info ) ), string p ) |
profiling_info[p]->dump(); |
} |
|
void add_prof_entry( RequestID id, string k, int hr, int hrv ) |
{ |
string l = id->not_query; |
|
if( has_prefix( l, query_internal_location() ) ) |
l = dirname( l ); |
|
if( !profiling_info[l] ) |
profiling_info[l] = ProfInfo(l); |
|
profiling_info[l]->add( k, hr, hrv ); |
} |
|
void avg_prof_enter( string name, string type, RequestID id ) |
{ |
if( !id->misc->prof_stack ) |
id->misc->prof_stack = ProfStack(); |
id->misc->prof_stack->enter( name+":"+type,id ); |
} |
void avg_prof_leave( string name, string type, RequestID id ) |
{ |
if( !id->misc->prof_stack ) id->misc->prof_stack = ProfStack(); |
id->misc->prof_stack->leave( name+":"+type,id ); |
} |
#endif |
|
|
|
inherit Configuration; |
inherit "basic_defvar"; |
|
protected mapping(RequestID:mapping) current_connections = |
set_weak_flag( ([ ]), 1 ); |
|
void connection_add( RequestID id, mapping data ) |
|
|
|
|
|
|
|
|
|
|
|
{ |
current_connections[id] = data; |
} |
|
mapping connection_drop( RequestID id ) |
|
|
{ |
return m_delete( current_connections, id ); |
} |
|
mapping(RequestID:mapping) connection_get( ) |
|
{ |
return current_connections; |
} |
|
|
string name = roxen->bootstrap_info->get(); |
|
|
class DataCacheImpl |
{ |
protected typedef array(string|mapping(string:mixed))|string| |
function(string, RequestID:string|int) EntryType; |
|
protected mapping(string:EntryType) cache = ([]); |
|
protected int current_size; |
protected int max_size; |
protected int max_file_size; |
|
protected int hits, misses; |
|
mapping get_cache_stats() |
{ |
return ([ "current_size" : current_size, |
"max_size" : max_size, |
"max_file_size" : max_file_size, |
"entries" : sizeof(cache), |
"hits" : hits, |
"misses" : misses ]); |
} |
|
void flush() |
{ |
#ifndef RAM_CACHE_NO_RELOAD_FLUSH |
current_size = 0; |
cache = ([]); |
#endif |
} |
|
|
|
|
|
|
#define CALC_ENTRY_SIZE(key, data) (sizeof (data) + sizeof (key) + 1024) |
#define CALC_VARY_CB_SIZE(key) (sizeof (key) + 128) |
|
|
protected void really_low_expire_entry(string key) |
{ |
EntryType e = m_delete(cache, key); |
if (arrayp(e)) { |
current_size -= CALC_ENTRY_SIZE (key, e[0]); |
if (e[1]->co_handle) { |
remove_call_out(e[1]->co_handle); |
} |
if (CacheKey cachekey = e[1]->key) { |
destruct (cachekey); |
} |
} else if (!zero_type(e)) { |
current_size -= CALC_VARY_CB_SIZE(key); |
} |
} |
|
|
protected int low_expire_entry(string key_prefix) |
{ |
if (!key_prefix) return 0; |
if (arrayp(cache[key_prefix])) { |
|
really_low_expire_entry(key_prefix); |
return 1; |
} |
|
int res = 0; |
foreach(indices(cache); int ind; string key) { |
if (!key) continue; |
if (has_prefix(key, key_prefix)) { |
really_low_expire_entry(key); |
res++; |
} |
} |
return res; |
} |
|
void expire_entry(string key_prefix, RequestID|void id) |
{ |
if (!id) { |
low_expire_entry(key_prefix); |
return; |
} |
string url = key_prefix; |
sscanf(url, "%[^\0]", url); |
while(1) { |
EntryType val; |
if (arrayp(val = cache[key_prefix])) { |
current_size -= CALC_ENTRY_SIZE (key_prefix, val[0]); |
m_delete(cache, key_prefix); |
return; |
} else if (!zero_type(val)) { |
current_size -= CALC_VARY_CB_SIZE(key_prefix); |
} |
if (!val) { |
return; |
} |
|
string|array(string) key_frag; |
if (stringp(val)) { |
key_frag = id->request_headers[val]; |
} else { |
key_frag = val(url, id); |
} |
if (key_frag) |
|
key_frag = replace (key_frag, "\0", "\0\1"); |
else key_frag = ""; |
key_prefix += "\0\0" + key_frag; |
} |
} |
|
|
void clear_some_cache() |
{ |
|
array(string) q = indices(cache); |
if(!sizeof(q)) |
{ |
current_size=0; |
return; |
} |
|
|
sort(q); |
for(int i = 0; i < sizeof(q)/10 + 1; i++) { |
int r = random(sizeof(q)); |
string key_prefix = q[r = random(sizeof(q))]; |
if (!key_prefix) continue; |
for(;r < sizeof(q); r++,i++) { |
if (!q[r]) continue; |
if (!has_prefix(q[r], key_prefix)) break; |
really_low_expire_entry(q[r]); |
q[r] = 0; |
} |
} |
} |
|
void set(string url, string data, mapping meta, int expire, RequestID id) |
{ |
int entry_size = CALC_ENTRY_SIZE (url, data); |
|
if( entry_size > max_file_size ) { |
|
|
SIMPLE_TRACE_ENTER (this, "Result of size %d is too large " |
"to store in the protocol cache (limit %d)", |
entry_size, max_file_size); |
SIMPLE_TRACE_LEAVE (""); |
return; |
} |
|
SIMPLE_TRACE_ENTER (this, "Storing result of size %d in the protocol cache " |
"using key %O (expire in %ds)", |
entry_size, url, expire); |
string key = url; |
|
foreach(id->misc->vary_cb_order || ({}), |
string|function(string, RequestID: string|int) vary_cb) { |
array(string|mapping(string:mixed))|string| |
function(string, RequestID:string|int) old = cache[key]; |
if (old && (old != vary_cb)) { |
SIMPLE_TRACE_ENTER (this, "Registering vary cb %O - conflicts with " |
"existing entry %s, old entry expired", |
vary_cb, |
(arrayp (old) ? "of size " + sizeof (old[0]) : |
sprintf ("%O", old))); |
low_expire_entry(key); |
old = UNDEFINED; |
SIMPLE_TRACE_LEAVE (""); |
} |
cache[key] = vary_cb; |
if(!old) { |
current_size += CALC_VARY_CB_SIZE(key); |
} |
|
SIMPLE_TRACE_ENTER (this, "Registering vary cb %O", vary_cb); |
|
string key_frag; |
if (stringp(vary_cb)) { |
string|array(string) header = id->request_headers[vary_cb]; |
if (arrayp(header)) key_frag = header * ","; |
else key_frag = header; |
} else { |
int|string frag = vary_cb(url, id); |
if (intp(frag) && frag) { |
key_frag = frag->digits(256); |
} else { |
key_frag = frag; |
} |
} |
|
SIMPLE_TRACE_LEAVE ("Vary cb resolved to key fragment %O", |
key_frag || ""); |
|
if (key_frag) |
|
key_frag = replace (key_frag, "\0", "\0\1"); |
else key_frag = ""; |
key += "\0\0" + key_frag; |
entry_size += 2 + sizeof(key_frag); |
} |
|
array(string|mapping(string:mixed))|string| |
function(string, RequestID:string) old = cache[key]; |
if (old) { |
SIMPLE_TRACE_LEAVE ("Entry conflicts with existing entry %s, " |
"old entry expired", |
(arrayp (old) ? "of size " + sizeof (old[0]) : |
sprintf ("%O", old))); |
low_expire_entry(key); |
} |
else |
SIMPLE_TRACE_LEAVE (""); |
|
current_size += entry_size; |
cache[key] = ({ data, meta }); |
|
|
|
meta->co_handle = call_out(really_low_expire_entry, expire, key); |
int n; |
while( (current_size > max_size) && (n++<10)) |
clear_some_cache(); |
} |
|
array(string|mapping(string:mixed)) get(string url, RequestID id) |
{ |
SIMPLE_TRACE_ENTER (this, "Looking up entry for %O in the protocol cache", |
url); |
|
array(string|mapping(string:mixed))|string| |
function(string, RequestID:string|int) res; |
string key = url; |
while(1) { |
id->misc->protcache_cost++; |
if (arrayp(res = cache[key])) { |
hits++; |
SIMPLE_TRACE_LEAVE ("Found entry of size %d", sizeof (res[0])); |
return [array(string|mapping(string:mixed))]res; |
} |
if (!res) { |
misses++; |
SIMPLE_TRACE_LEAVE ("Found no entry"); |
return UNDEFINED; |
} |
|
SIMPLE_TRACE_ENTER (this, "Found vary cb %O", res); |
|
string key_frag; |
if (stringp(res)) { |
string|array(string) header = id->request_headers[res]; |
if (arrayp(header)) key_frag = header * ","; |
else key_frag = header; |
} else { |
int|string frag = res(url, id); |
if (intp(frag) && frag) { |
key_frag = frag->digits(256); |
} else { |
key_frag = frag; |
} |
} |
|
SIMPLE_TRACE_LEAVE ("Vary cb resolved to key fragment %O", |
key_frag || ""); |
|
if (key_frag) |
|
key_frag = replace (key_frag, "\0", "\0\1"); |
else key_frag = ""; |
key += "\0\0" + key_frag; |
}; |
} |
|
void init_from_variables( ) |
{ |
max_size = query( "data_cache_size" ) * 1024; |
max_file_size = query( "data_cache_file_max_size" ) * 1024; |
if( max_size < max_file_size ) |
max_size += max_file_size; |
int n; |
while( (current_size > max_size) && (n++<10)) |
clear_some_cache(); |
} |
|
protected void create() |
{ |
init_from_variables(); |
} |
} |
|
|
class DataCache { |
inherit DataCacheImpl; |
|
constant AUTH_PUBL = "AUTH |"; |
constant PUBL_ONLY = ""; |
|
void expire_entry(string key_prefix, RequestID|void id) |
{ |
|
::expire_entry(AUTH_PUBL + key_prefix, id); |
::expire_entry(PUBL_ONLY + key_prefix, id); |
} |
|
void set(string url, string data, mapping meta, int expire, RequestID id) |
{ |
if (id->rawauth) { |
|
|
|
|
|
|
|
::set(AUTH_PUBL + url, data, meta, expire, id); |
::set(PUBL_ONLY + url, data, meta, expire, id); |
} else { |
|
|
|
::set(PUBL_ONLY + url, data, meta, expire, id); |
} |
} |
|
array(string|mapping(string:mixed)) get(string url, RequestID id) |
{ |
|
|
|
|
mixed res = UNDEFINED; |
if (!id->rawauth) { |
res = ::get(PUBL_ONLY + url, id); |
if (!res) |
misses--; |
} |
|
|
return res || ::get(AUTH_PUBL + url, id); |
} |
} |
|
|
#include "rxml.pike"; |
constant store = roxen.store; |
constant retrieve = roxen.retrieve; |
constant remove = roxen.remove; |
|
int config_id; |
int get_config_id() |
{ |
if(config_id) return config_id; |
for(int i=sizeof(roxen->configurations); i;) |
if(roxen->configurations[--i]->name==name) return config_id=i; |
} |
|
string get_doc_for( string region, string variable ) |
{ |
RoxenModule module; |
if(variable[0] == '_') |
return 0; |
if((int)reverse(region)) |
return 0; |
if(module = find_module( region )) |
{ |
if(module->variables[variable]) |
return module->variables[variable]->name()+ |
"\n"+module->variables[ variable ]->doc(); |
} |
if(variables[ variable ]) |
return variables[variable]->name()+ |
"\n"+variables[ variable ]->doc(); |
} |
|
string query_internal_location(RoxenModule|void mod) |
{ |
string ret = internal_location+(mod?replace(otomod[mod]||"", "#", "!")+"/":""); |
if (has_suffix(ret, "!0/")) { |
|
ret = ret[..<sizeof("!0/")] + "/"; |
} |
return ret; |
} |
|
string query_name() |
{ |
if(strlen(query("name"))) |
return query("name"); |
return name; |
} |
|
string comment() |
{ |
return query("comment"); |
} |
|
private float cached_compat_level; |
|
float compat_level() |
{ |
if (cached_compat_level == 0.0) |
cached_compat_level = (float) query ("compat_level"); |
return cached_compat_level; |
} |
|
|
|
|
|
|
array(int) query_oid() |
{ |
return SNMP.RIS_OID_WEBSERVER + ({ 2 }); |
} |
|
|
|
|
|
|
|
|
array(int) generate_module_oid_segment(RoxenModule me) |
{ |
string s = otomod[me]; |
array(string) a = s/"#"; |
return ({ sizeof(a[0]), @((array(int))a[0]), ((int)a[1]) + 1 }); |
} |
|
ADT.Trie generate_module_mib(array(int) oid, |
array(int) oid_suffix, |
RoxenModule me, |
ModuleInfo moduleinfo, |
ModuleCopies module) |
{ |
array(int) segment = generate_module_oid_segment(me); |
return SNMP.SimpleMIB(oid, |
oid_suffix + segment, |
({ |
UNDEFINED, |
SNMP.Integer(segment[-1], "moduleCopy"), |
SNMP.String(otomod[me], |
"moduleIdentifier"), |
SNMP.Integer(moduleinfo->type, |
"moduleType"), |
SNMP.String(me->cvs_version || "", |
"moduleVersion"), |
})); |
} |
|
|
private int sub_req_limit = 30; |
private string internal_location = "/_internal/"; |
|
#ifdef HTTP_COMPRESSION |
int(0..1) http_compr_enabled; |
mapping(string:int) http_compr_main_mimes = ([]); |
mapping(string:int) http_compr_exact_mimes = ([]); |
int http_compr_minlen; |
int http_compr_maxlen; |
int(0..1) http_compr_dynamic_reqs; |
Thread.Local gz_file_pool = Thread.Local(); |
#endif |
|
int handler_queue_timeout; |
|
|
|
private mapping (int|string:string) log_format = ([]); |
|
|
private array(RoxenModule) sorted_modules = ({}); |
|
|
private array(int) sorted_module_types = ({}); |
|
mapping modules = ([]); |
|
|
|
mapping (RoxenModule:string) otomod = ([]); |
|
|
int module_set_counter = 1; |
|
|
|
mapping(string:int) counters = ([]); |
|
|
|
|
private array (function) url_module_cache, last_module_cache; |
private array (function) logger_module_cache, first_module_cache; |
private array (function) filter_module_cache; |
private array (array (string|function)) location_module_cache; |
private mapping (string:array (function)) file_extension_module_cache=([]); |
private mapping (string:array (RoxenModule)) provider_module_cache=([]); |
private array (RoxenModule) auth_module_cache, userdb_module_cache; |
|
private int module_sort_key(RoxenModule me) |
{ |
int pri = me->query("_priority"); |
array(int|string) info = me->register_module(); |
return (pri << 32) | (info[0] & MODULE_TYPE_MASK); |
} |
|
private void sort_modules() |
{ |
sorted_module_types = map(sorted_modules, module_sort_key); |
sort(sorted_module_types, sorted_modules); |
sorted_module_types = map(sorted_module_types, `&, MODULE_TYPE_MASK); |
invalidate_cache(); |
} |
|
|
private array(function|RoxenModule) low_module_lookup(int module_type_mask, |
string|void symbol) |
{ |
array(RoxenModule) modules = |
reverse(filter(sorted_modules, |
map(sorted_module_types, `&, module_type_mask))); |
if (!symbol) return modules; |
return modules[symbol] - ({ 0 }); |
} |
|
void unregister_urls() |
{ |
foreach( registered_urls + failed_urls, string url ) |
roxen.unregister_url(url, this_object()); |
registered_urls = ({}); |
} |
|
private void safe_stop_module (RoxenModule mod, string desc) |
{ |
if (mixed err = catch (mod && mod->stop && |
call_module_func_with_cbs (mod, "stop", 0))) |
report_error ("While stopping " + desc + ": " + describe_backtrace (err)); |
} |
|
private Thread.Mutex stop_all_modules_mutex = Thread.Mutex(); |
|
private void do_stop_all_modules (Thread.MutexKey stop_lock) |
{ |
foreach(sorted_modules, RoxenModule m) { |
safe_stop_module(m, "module"); |
} |
|
end_logger(); |
|
destruct (stop_lock); |
} |
|
void stop (void|int asynch) |
|
|
|
{ |
if (Thread.MutexKey lock = stop_all_modules_mutex->trylock()) { |
#ifdef SNMP_AGENT |
if(query("snmp_process") && objectp(roxen->snmpagent)) { |
roxen->snmpagent->vs_stop_trap(get_config_id()); |
roxen->snmpagent->del_virtserv(get_config_id()); |
} |
#endif |
|
unregister_urls(); |
|
if (roxen.handler_threads_on_hold()) |
|
|
do_stop_all_modules (lock); |
else |
|
|
|
|
roxen.handle (do_stop_all_modules, lock); |
} |
|
if (!asynch) stop_all_modules_mutex->lock (1); |
destruct(json_logger); |
} |
|
string|array(string) type_from_filename( string file, int|void to, |
string|void myext ) |
{ |
array(string)|string tmp; |
if(!types_fun) |
return to?({ "application/octet-stream", 0 }):"application/octet-stream"; |
|
string ext = lower_case(myext || Roxen.extension(file)); |
|
if(tmp = types_fun(ext)) |
{ |
|
if (tmp[0] == "strip") |
{ |
array(string) tmp2 = file/"."; |
string nx; |
if (sizeof(tmp2) > 2) |
nx = lower_case(tmp2[-2]); |
tmp[0] = (nx && types_fun(nx)) || types_fun("default") || |
"application/octet-stream"; |
} |
} else if (!(tmp = types_fun("default"))) { |
tmp = ({ "application/octet-stream", 0 }); |
} |
return to?tmp:tmp[0]; |
} |
|
array (RoxenModule) get_providers(string provides) |
|
{ |
|
if(!sizeof(provider_module_cache)) |
{ |
provider_module_cache[0] = 0; |
int prev_pri = -1; |
array(RoxenModule) modules = ({}); |
foreach(low_module_lookup(MODULE_PROVIDER), RoxenModule me) { |
if (!me->query_provides) continue; |
int pri = me->query("_priority"); |
if (pri != prev_pri) { |
sort(modules->module_identifier(), modules); |
foreach(modules, RoxenModule p) { |
mixed provs = p->query_provides(); |
if (stringp(provs)) { |
provs = (< provs >); |
} else if (arrayp(provs)) { |
provs = mkmultiset(provs); |
} |
if (provs) { |
foreach(provs; string provides;) { |
provider_module_cache[provides] += ({ p }); |
} |
} |
} |
modules = ({}); |
} |
prev_pri = pri; |
modules += ({ me }); |
} |
sort(modules->module_identifier(), modules); |
foreach(modules, RoxenModule p) { |
mixed provs = p->query_provides(); |
if (stringp(provs)) { |
provs = (< provs >); |
} else if (arrayp(provs)) { |
provs = mkmultiset(provs); |
} |
if (provs) { |
foreach(provs; string provides;) { |
provider_module_cache[provides] += ({ p }); |
} |
} |
} |
} |
return provider_module_cache[provides] || ({}); |
} |
|
RoxenModule get_provider(string provides) |
|
{ |
array (RoxenModule) prov = get_providers(provides); |
if(sizeof(prov)) |
return prov[0]; |
return 0; |
} |
|
array(mixed) map_providers(string provides, string fun, mixed ... args) |
|
{ |
array (RoxenModule) prov = get_providers(provides); |
mixed error; |
array a=({ }); |
mixed m; |
foreach(prov, RoxenModule mod) |
{ |
if(!objectp(mod)) |
continue; |
if(functionp(mod[fun])) |
error = catch(m=mod[fun](@args)); |
if(error) { |
report_debug("Error in map_providers(): " + describe_backtrace(error)); |
} |
else |
a += ({ m }); |
error = 0; |
} |
return a; |
} |
|
mixed call_provider(string provides, string fun, mixed ... args) |
|
|
{ |
foreach(get_providers(provides), RoxenModule mod) |
{ |
function f; |
if(objectp(mod) && functionp(f = mod[fun])) { |
mixed ret; |
if (ret = f(@args)) { |
return ret; |
} |
} |
} |
} |
|
array(function) file_extension_modules(string ext) |
{ |
if (!sizeof(file_extension_module_cache)) { |
file_extension_module_cache[0] = 0; |
foreach(low_module_lookup(MODULE_FILE_EXTENSION), RoxenModule me) { |
if (!me->handle_file_extension) continue; |
array(string) arr = me->query_file_extensions(); |
foreach(arr, string e) { |
file_extension_module_cache[e] += ({ me->handle_file_extension }); |
} |
} |
} |
return file_extension_module_cache[ext]; |
} |
|
array(function) url_modules() |
{ |
if(!url_module_cache) |
{ |
url_module_cache = low_module_lookup(MODULE_URL, "remap_url"); |
} |
return url_module_cache; |
} |
|
protected mapping api_module_cache = ([]); |
mapping api_functions(void|RequestID id) |
{ |
return api_module_cache+([]); |
} |
|
array (function) logger_modules() |
{ |
if(!logger_module_cache) |
{ |
logger_module_cache = low_module_lookup(MODULE_LOGGER, "log"); |
} |
return logger_module_cache; |
} |
|
array (function) last_modules() |
{ |
if(!last_module_cache) |
{ |
last_module_cache = low_module_lookup(MODULE_LAST, "last_resort"); |
} |
return last_module_cache; |
} |
|
protected mixed strip_fork_information(RequestID id) |
{ |
if (uname()->sysname == "Darwin") { |
|
|
|
|
|
|
|
if (has_value(id->not_query, "..namedfork/") || |
has_suffix(id->not_query, "/rsrc") || |
has_value(lower_case(id->not_query), ".ds_store")) |
|
|
return Roxen.http_status(404, "No such file."); |
} |
|
array a = id->not_query/"::"; |
|
|
|
id->not_query = a[0]; |
id->misc->fork_information = a[1..]; |
return 0; |
} |
|
array (function) first_modules() |
{ |
if(!first_module_cache) |
{ |
first_module_cache = ({ }); |
|
|
if ( |
#ifdef __NT__ |
1 || |
#endif |
uname()->sysname == "Darwin") { |
first_module_cache += ({ |
strip_fork_information, |
}); |
} |
|
first_module_cache += low_module_lookup(MODULE_FIRST, "first_try"); |
} |
|
return first_module_cache; |
} |
|
void set_userdb_module_cache( array to ) |
|
|
{ |
userdb_module_cache = to; |
} |
|
array(UserDB) user_databases() |
{ |
if( userdb_module_cache ) |
return userdb_module_cache; |
return userdb_module_cache = low_module_lookup(MODULE_USERDB); |
} |
|
array(AuthModule) auth_modules() |
{ |
if( auth_module_cache ) |
return auth_module_cache; |
return auth_module_cache = low_module_lookup(MODULE_AUTH); |
} |
|
array location_modules() |
|
|
{ |
if(!location_module_cache) |
{ |
array new_location_module_cache=({ }); |
int prev_pri = -1; |
array level_find_files = ({}); |
array(string) level_locations = ({}); |
foreach(low_module_lookup(MODULE_LOCATION), RoxenModule me) { |
int pri = me->query("_priority"); |
if (pri != prev_pri) { |
|
sort(map(level_locations, |
lambda(string loc) { return -sizeof(loc); }), |
level_locations, level_find_files); |
foreach(level_locations; int i; string path) { |
new_location_module_cache += ({ ({ path, level_find_files[i] }) }); |
} |
level_locations = ({}); |
level_find_files = ({}); |
} |
prev_pri = pri; |
|
string location = me->find_file && me->query_location(); |
if (!location) continue; |
level_find_files += ({ me->find_file }); |
level_locations += ({ location }); |
} |
|
sort(map(level_locations, |
lambda(string loc) { return -sizeof(loc); }), |
level_locations, level_find_files); |
foreach(level_locations; int i; string path) { |
new_location_module_cache += ({ ({ path, level_find_files[i] }) }); |
} |
location_module_cache = new_location_module_cache; |
} |
return location_module_cache; |
} |
|
array(function) filter_modules() |
{ |
if(!filter_module_cache) |
{ |
filter_module_cache = low_module_lookup(MODULE_FILTER, "filter"); |
} |
return filter_module_cache; |
} |
|
void end_logger() |
{ |
if (mixed err = catch { |
if (roxen.LogFile logger = |
log_function && function_object (log_function)) { |
logger->close(); |
} |
}) report_error ("While stopping the logger: " + describe_backtrace (err)); |
log_function = 0; |
} |
|
void init_log_file() |
{ |
end_logger(); |
|
if(query("Log")) |
{ |
string logfile = query("LogFile"); |
if(strlen(logfile)) |
log_function = roxen.LogFile(logfile, query("LogFileCompressor"))->write; |
} |
} |
|
private void parse_log_formats() |
{ |
array foo=query("LogFormat")/"\n"; |
log_format = ([]); |
foreach(foo; int i; string b) |
if(strlen(b) && b[0] != '#') { |
if (sscanf (b, "%d:%*[\t ]%s", int status, b)) |
log_format[status] = b; |
else if (sscanf (b, "*:%*[\t ]%s", b)) |
log_format[0] = b; |
else if (sscanf (b, "%[-_.#a-zA-Z0-9*]/%[-_.#a-zA-Z0-9*]:%*[\t ]%s", |
string facility, string action, b) >= 2) |
log_format[facility + "/" + action] = b; |
else |
|
|
report_warning ("Unrecognized format on line %d " |
"in log format setting: %O\n", i + 1, b); |
} |
} |
|
void log(mapping file, RequestID request_id) |
{ |
|
array(function) log_funs = logger_module_cache||logger_modules(); |
if (sizeof(log_funs)) { |
request_id->init_cookies(1); |
foreach(log_funs, function f) |
if( f( request_id, file ) ) |
return; |
} |
|
if( !log_function ) |
return; |
|
if(do_not_log_patterns && |
Roxen._match(request_id->remoteaddr, do_not_log_patterns)) |
return; |
|
string form; |
if(!(form=log_format[(int) file->error])) |
form = log_format[0]; |
if(!form) return; |
|
roxen.run_log_format( form, log_function, request_id, file ); |
} |
|
void log_event (string facility, string action, string resource, |
void|mapping(string:mixed) info) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
if( !log_function ) |
return; |
|
if(do_not_log_patterns && |
Roxen._match("0.0.0.0", do_not_log_patterns)) |
return; |
|
sscanf (facility, "%[^#]", string modname); |
|
if (string format = |
log_format[facility + "/" + action] || |
log_format[facility + "/*"] || |
|
|
modname != "" && (log_format[modname + "/" + action] || |
log_format[modname + "/*"]) || |
log_format["*/*"]) |
roxen.run_log_event_format (format, log_function, |
facility, action, resource || "-", info); |
} |
|
array(string) userinfo(string u, RequestID|void id) |
|
|
|
|
|
|
|
|
{ |
User uid; |
foreach( user_databases(), UserDB m ) |
if( uid = m->find_user( u ) ) |
return uid->compat_userinfo(); |
} |
|
array(string) userlist(RequestID|void id) |
|
|
|
|
|
|
|
|
|
{ |
array(string) list = ({}); |
foreach( user_databases(), UserDB m ) { |
if (!m->list_users) continue; |
list |= m->list_users(id); |
} |
return list; |
} |
|
array(string) user_from_uid(int u, RequestID|void id) |
|
|
|
|
|
|
|
|
|
{ |
User uid; |
foreach( user_databases(), UserDB m ) |
if( uid = m->find_user_from_uid( u,id ) ) |
return uid->compat_userinfo(); |
} |
|
UserDB find_user_database( string name ) |
|
|
{ |
foreach( user_databases(), UserDB m ) |
if( m->name == name ) |
return m; |
} |
|
AuthModule find_auth_module( string name ) |
|
|
{ |
foreach( auth_modules(), AuthModule m ) |
if( m->name == name ) |
return m; |
} |
|
User authenticate( RequestID id, UserDB|void database) |
|
|
|
|
|
|
{ |
User u; |
if (!zero_type (u = id->misc->authenticated_user)) |
return u; |
foreach( auth_modules(), AuthModule method ) |
if( u = method->authenticate( id, database ) ) |
return id->misc->authenticated_user = u; |
} |
|
mapping authenticate_throw( RequestID id, string realm, |
UserDB|void database) |
|
|
|
{ |
mapping m; |
foreach( auth_modules(), AuthModule method ) |
if( m = method->authenticate_throw( id, realm, database ) ) |
return m; |
} |
|
User find_user( string user, RequestID|void id ) |
|
|
|
|
|
|
|
|
{ |
User uid; |
|
if( id && id->misc->authenticated_user |
&& ( uid = id->misc->authenticated_user->database->find_user(user,id))) |
return uid; |
|
foreach( user_databases(), UserDB m ) |
if( uid = m->find_user( user,id ) ) |
return uid; |
} |
|
array(string) list_users(RequestID|void id) |
|
|
|
|
|
{ |
array(string) list = ({}); |
foreach( user_databases(), UserDB m ) |
list |= m->list_users(id); |
return list; |
} |
|
array(string) list_groups(RequestID|void id) |
|
|
|
|
|
{ |
array(string) list = ({}); |
foreach( user_databases(), UserDB m ) |
list |= m->list_groups(id); |
return list; |
} |
|
|
|
Group find_group( string group, RequestID|void id ) |
|
|
|
|
|
|
|
|
{ |
Group uid; |
|
if( id && id->misc->authenticated_user |
&& ( uid = id->misc->authenticated_user->database->find_group( group ) )) |
return uid; |
|
foreach( user_databases(), UserDB m ) |
if( uid = m->find_group( group,id ) ) |
return uid; |
} |
|
|
string last_modified_by(Stdio.File file, RequestID id) |
{ |
Stat s; |
int uid; |
array u; |
|
if(objectp(file)) s=file->stat(); |
if(!s || sizeof(s)<5) return "A. Nonymous"; |
uid=s[5]; |
u=user_from_uid(uid, id); |
if(u) return u[0]; |
return "A. Nonymous"; |
} |
|
|
|
|
|
|
private mapping internal_gopher_image(string from) |
{ |
sscanf(from, "%s.gif", from); |
sscanf(from, "%s.jpg", from); |
from -= "."; |
|
|
|
Stdio.File f = lopen("roxen-images/dir/"+from+".gif","r"); |
if (f) |
return (["file":f, "type":"image/gif", "stat":f->stat(),]); |
else |
return 0; |
|
} |
|
#ifdef MODULE_LEVEL_SECURITY |
private mapping(RoxenModule:array) security_level_cache = set_weak_flag (([]), 1); |
|
int|mapping check_security(function|RoxenModule a, RequestID id, |
void|int slevel) |
{ |
array seclevels; |
|
|
|
|
|
|
|
if (RoxenModule mod = Roxen.get_owning_module (a)) { |
|
|
if (!(seclevels = security_level_cache[mod])) { |
if(mod->query_seclevels) |
seclevels = ({ |
mod->query_seclevels(), |
mod->query("_seclvl"), |
}); |
else |
seclevels = ({0,0}); |
security_level_cache[mod] = seclevels; |
} |
} |
else |
seclevels = ({0,0}); |
|
if(slevel && (seclevels[1] > slevel)) |
|
|
return 1; |
|
mixed err; |
if( function(RequestID:int|mapping) f = seclevels[0] ) |
|
|
|
|
|
|
err=catch { return f( id ); }; |
else |
return 0; |
|
report_error("check_security(): %s:\n%s\n", |
LOC_M(39, "Error during module security check"), |
describe_backtrace(err)); |
|
return 1; |
} |
#endif |
|
void invalidate_cache() |
{ |
last_module_cache = 0; |
filter_module_cache = 0; |
userdb_module_cache = 0; |
auth_module_cache = 0; |
first_module_cache = 0; |
url_module_cache = 0; |
location_module_cache = 0; |
logger_module_cache = 0; |
file_extension_module_cache = ([]); |
provider_module_cache = ([]); |
#ifdef MODULE_LEVEL_SECURITY |
security_level_cache = set_weak_flag (([ ]), 1); |
#endif |
} |
|
|
void clear_memory_caches() |
{ |
invalidate_cache(); |
foreach(indices(otomod), RoxenModule m) |
if (m && m->clear_memory_caches) |
if (mixed err = catch( m->clear_memory_caches() )) |
report_error("clear_memory_caches() "+ |
LOC_M(40, "failed for module %O:\n%s\n"), |
otomod[m], describe_backtrace(err)); |
} |
|
|
protected array(string) draw_saturation_bar(int hue,int brightness, int where, |
int small_version) |
{ |
Image.Image bar = |
small_version ? Image.Image(16, 128) : Image.Image(30, 256); |
|
hue &= 0xff; |
brightness &= 0xff; |
|
for(int i=0;i<128;i++) |
{ |
int j = i * 2; |
array color = hsv_to_rgb(hue, 255 - j, brightness); |
if (small_version) { |
bar->line(0, i, 15, i, @color); |
} else { |
bar->line(0, j, 29, j, @color); |
bar->line(0, j + 1,29, j + 1, @color); |
} |
} |
|
if (where >= 0 && where <= 255) { |
where = 255 - where; |
int hilite = (brightness > 128) ? 0 : 255; |
if (small_version) |
bar->line(0, where / 2, 15, where / 2, hilite, hilite, hilite); |
else |
bar->line(0, where, 29, where, hilite, hilite, hilite); |
} |
|
#if constant(Image.JPEG) && constant(Image.JPEG.encode) |
return ({ Image.JPEG.encode(bar), "image/jpeg" }); |
#else |
return ({ Image.PNG.encode(bar), "image/png" }); |
#endif |
} |
|
|
#if constant(Image.GIF) && constant(Image.PNG) |
array(mapping) spinner_data = 0; |
|
|
protected array(string) draw_spinner(string bgcolor) |
{ |
|
array color = parse_color(bgcolor); |
|
|
if (!spinner_data) { |
array(mapping) temp_spinner_data = ({ }); |
for (int i = 0; i < 12; i++) { |
string src = lopen("roxen-images/spinner" + i + ".png", "r")->read(); |
temp_spinner_data += ({ Image.PNG._decode(src) }); |
} |
spinner_data = temp_spinner_data; |
} |
|
|
array(Image.Image) frames = ({ }); |
foreach(spinner_data, mapping data) { |
Image.Image frame = Image.Image(17, 17, @color); |
frame->paste_mask(data->image, data->alpha); |
frames += ({ frame }); |
} |
|
|
|
Image.Colortable colors = Image.Colortable(frames[0]); |
string res = Image.GIF.header_block(17, 17, colors); |
foreach(frames, Image.Image frame) |
res += Image.GIF.render_block(frame, colors, 0, 0, 0, 1); |
res += |
Image.GIF.netscape_loop_block(0) + |
Image.GIF.end_block(); |
|
return ({ res, "image/gif" }); |
} |
#endif |
|
|
|
|
private mapping internal_roxen_image( string from, RequestID id ) |
{ |
sscanf(from, "%s.gif", from); |
sscanf(from, "%s.jpg", from); |
sscanf(from, "%s.xcf", from); |
sscanf(from, "%s.png", from); |
|
#if constant(Image.GIF) && constant(Image.PNG) |
|
if (has_prefix(from, "spinner-")) { |
array(string) spinner = draw_spinner(from[8..]); |
return ([ "data" : spinner[0], |
"type" : spinner[1], |
"stat" : ({ 0, 0, 0, 900000000, 0, 0, 0 }) ]); |
} |
#endif |
|
|
int hue,bright,w; |
string colorbar; |
if(sscanf(from, "%s:%d,%d,%d", colorbar, hue, bright,w)==4) { |
array bar = draw_saturation_bar(hue, bright, w, |
colorbar == "colorbar-small"); |
return Roxen.http_string_answer(bar[0], bar[1]); |
} |
|
Stdio.File f; |
|
if( !id->misc->internal_get ) |
if(f = lopen("roxen-images/"+from+".png", "r")) |
return (["file":f, "type":"image/png", "stat":f->stat()]); |
|
if(f = lopen("roxen-images/"+from+".gif", "r")) |
return (["file":f, "type":"image/gif", "stat":f->stat()]); |
|
if(f = lopen("roxen-images/"+from+".jpg", "r")) |
return (["file":f, "type":"image/jpeg", "stat":f->stat()]); |
|
if(f = lopen("roxen-images/"+from+".xcf", "r")) |
return (["file":f, "type":"image/x-gimp-image", "stat":f->stat()]); |
|
if(f = lopen("roxen-images/"+from+".gif", "r")) |
return (["file":f, "type":"image/gif", "stat":f->stat()]); |
|
return 0; |
} |
|
|
mapping (mixed:function|int) locks = ([]); |
|
#ifdef THREADS |
|
|
mapping locked = ([]), thread_safe = ([]); |
|
mixed _lock(object|function f) |
{ |
Thread.MutexKey key; |
function|int l; |
TIMER_START(module_lock); |
if (functionp(f)) { |
f = function_object(f); |
} |
if (l = locks[f]) |
{ |
if (l != -1) |
{ |
|
catch{ |
|
locked[f]++; |
key = l(); |
}; |
} else |
thread_safe[f]++; |
} else if (f->thread_safe) { |
locks[f]=-1; |
thread_safe[f]++; |
} else { |
if (!locks[f]) |
{ |
|
l = Thread.Mutex()->lock; |
if (!locks[f]) { |
locks[f]=l; |
} |
} |
|
locked[f]++; |
key = l(); |
} |
TIMER_END(module_lock); |
return key; |
} |
|
#define LOCK(X) key=_lock(X) |
#define UNLOCK() do{key=0;}while(0) |
#else |
#define LOCK(X) |
#define UNLOCK() |
#endif |
|
string examine_return_mapping(mapping m) |
{ |
string res; |
|
if (m->extra_heads) |
m->extra_heads=mkmapping(Array.map(indices(m->extra_heads), |
lower_case), |
values(m->extra_heads)); |
else |
m->extra_heads=([]); |
|
switch (m->error||200) |
{ |
case 302: |
if (m->extra_heads && |
(m->extra_heads->location)) |
res = sprintf("Returned redirect to %O ", m->extra_heads->location); |
else |
res = "Returned redirect, but no location header. "; |
break; |
|
case 401: |
if (m->extra_heads["www-authenticate"]) |
res = sprintf("Returned authentication failed: %O ", |
m->extra_heads["www-authenticate"]); |
else |
res = "Returned authentication failed. "; |
break; |
|
case 200: |
|
if (sizeof(m) <= 1) { |
res = "Returned multi status. "; |
break; |
} |
res = "Returned ok. "; |
break; |
|
default: |
res = sprintf("Returned %O. ", m->error); |
} |
|
if (!zero_type(m->len)) |
if (m->len<0) |
res += "No data "; |
else |
res += sprintf("%O bytes ", m->len); |
else if (stringp(m->data)) |
res += sprintf("%d bytes ", strlen(m->data)); |
else if (objectp(m->file)) |
if (catch { |
Stat a=m->file->stat(); |
res += sprintf("%O bytes ", a[1]-m->file->tell()); |
}) |
res += "? bytes "; |
|
if (m->data) res += "(static)"; |
else if (m->file) res += "(open file)"; |
|
if (stringp(m->extra_heads["content-type"]) || |
stringp(m->type)) { |
res += sprintf(" of %O", m->type||m->extra_heads["content-type"]); |
} |
|
return res; |
} |
|
|
mapping(string:DAVLock) find_locks(string path, int(-1..1) recursive, |
int(0..1) exclude_shared, RequestID id) |
{ |
SIMPLE_TRACE_ENTER(0, "find_locks(%O, %O, %O, X)", |
path, recursive, exclude_shared); |
mapping(string:DAVLock) locks = ([]); |
|
foreach(location_module_cache||location_modules(), |
[string loc, function func]) |
{ |
SIMPLE_TRACE_ENTER(function_object(func), |
"Finding locks in %O.", loc); |
string subpath; |
if (has_prefix(path, loc)) { |
|
subpath = path[sizeof(loc)..]; |
} else if (recursive && has_prefix(loc, path)) { |
|
subpath = "/"; |
} else { |
|
TRACE_LEAVE("Skip this module."); |
continue; |
} |
TRACE_ENTER(sprintf("subpath: %O", subpath), |
function_object(func)->find_locks); |
mapping(string:DAVLock) sub_locks = |
function_object(func)->find_locks(subpath, recursive, |
exclude_shared, id); |
TRACE_LEAVE(""); |
if (sub_locks) { |
SIMPLE_TRACE_LEAVE("Got some locks: %O", sub_locks); |
locks |= sub_locks; |
} else { |
TRACE_LEAVE("Got no locks."); |
} |
} |
SIMPLE_TRACE_LEAVE("Returning %O", locks); |
return locks; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapping(string:mixed)|int(-1..0) check_locks(string path, |
int(0..1) recursive, |
RequestID id) |
{ |
TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this); |
|
mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id); |
|
if (!sizeof(locks)) { |
TRACE_LEAVE ("Got no locks."); |
return 0; |
} |
|
mapping(string:array(array(array(string)))) if_data = id->get_if_data(); |
if (if_data) { |
foreach(if_data[0], array(array(string)) tokens) { |
m_delete(locks, tokens[0][1]); |
} |
|
if (!sizeof(locks)) { |
TRACE_LEAVE ("All locks unlocked."); |
return 0; |
} |
} |
|
|
if (!has_suffix(path, "/")) path += "/"; |
mapping(string:mixed) ret = |
Roxen.http_dav_error(Protocols.HTTP.DAV_LOCKED, "lock-token-submitted"); |
foreach(locks;;DAVLock lock) { |
TRACE_ENTER(sprintf("Checking lock %O against %O.", lock, path), 0); |
|
|
|
|
if (sizeof(lock->path) <= sizeof(path)) { |
TRACE_LEAVE("Direct lock."); |
TRACE_LEAVE("Locked."); |
return ret; |
} |
if (lock->is_file) { |
id->set_status_for_path(lock->path[..<1], ret); |
} else { |
id->set_status_for_path(lock->path, ret); |
} |
TRACE_LEAVE("Added to multi status."); |
} |
TRACE_LEAVE("Multi status."); |
return ([]); |
} |
|
protected multiset(DAVLock) active_locks = (<>); |
|
|
|
|
|
mapping(string:mixed) unlock_file(string path, DAVLock lock, RequestID id) |
{ |
|
if (!has_suffix(path, "/")) path+="/"; |
|
foreach(location_module_cache||location_modules(), |
[string loc, function func]) |
{ |
if (has_prefix(path, loc)) { |
|
mapping(string:mixed) ret = |
function_object(func)->unlock_file(path[sizeof(loc)..], lock, id); |
|
|
if (ret) return ret; |
} else if (lock->recursive && has_prefix(loc, path)) { |
|
mapping(string:mixed) ret = |
function_object(func)->unlock_file("/", lock, id); |
|
|
if (ret) return ret; |
} |
if (function_object(func)->webdav_opaque) break; |
} |
active_locks[lock] = 0; |
|
return 0; |
} |
|
|
int expire_locks(RequestID id) |
{ |
int t = time(1); |
int min_time = 0x7fffffff; |
foreach(active_locks; DAVLock lock;) { |
if (lock->expiry_time) { |
if (lock->expiry_time < t) { |
unlock_file(lock->path, lock, id); |
} else if (lock->expiry_time < min_time) { |
min_time = lock->expiry_time; |
} |
} |
} |
return min_time - t; |
} |
|
mixed expire_lock_loop_handle; |
|
protected void expire_lock_loop() |
{ |
int t = expire_locks(0); |
|
if (sizeof(active_locks)) { |
t = max (t, 1); |
t = min (t, 3600); |
|
if (expire_lock_loop_handle) |
remove_call_out (expire_lock_loop_handle); |
|
expire_lock_loop_handle = roxen.background_run(t, expire_lock_loop); |
} |
} |
|
|
|
|
void refresh_lock(DAVLock lock) |
{ |
if (lock->expiry_delta) { |
|
|
|
lock->expiry_time = lock->expiry_delta + time(); |
} |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapping(string:mixed)|DAVLock lock_file(string path, |
int(0..1) recursive, |
string lockscope, |
string locktype, |
int(0..) expiry_delta, |
array(Parser.XML.Tree.Node) owner, |
RequestID id) |
{ |
TRACE_ENTER(sprintf("%O(%O, %O, %O, %O, %O, %O, %O)", |
this_function, path, recursive, lockscope, |
locktype, expiry_delta, owner, id), 0); |
|
int is_file; |
|
|
if (!has_suffix(path, "/")) { |
path+="/"; |
is_file = 1; |
} |
|
|
|
int fail; |
|
|
|
mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id); |
|
foreach(locks; string lock_token; DAVLock lock) { |
TRACE_ENTER(sprintf("Checking lock %O...\n", lock), 0); |
if ((lock->lockscope == "DAV:exclusive") || |
(lockscope == "DAV:exclusive")) { |
TRACE_LEAVE("Locked."); |
id->set_status_for_path(lock->path, 423, "Locked"); |
fail = 1; |
} |
TRACE_LEAVE("Shared."); |
} |
|
if (fail) { |
TRACE_LEAVE("Fail."); |
return ([]); |
} |
|
|
|
string locktoken = "urn:uuid:" + roxen->new_uuid_string(); |
DAVLock lock = DAVLock(locktoken, path, recursive, lockscope, locktype, |
expiry_delta, owner); |
lock->is_file = is_file; |
foreach(location_module_cache||location_modules(), |
[string loc, function func]) |
{ |
string subpath; |
if (has_prefix(path, loc)) { |
|
subpath = path[sizeof(loc)..]; |
} else if (recursive && has_prefix(loc, path)) { |
|
subpath = "/"; |
} else { |
|
continue; |
} |
|
TRACE_ENTER(sprintf("Calling %O->lock_file(%O, %O, %O)...", |
function_object(func), subpath, lock, id), 0); |
mapping(string:mixed) lock_error = |
function_object(func)->lock_file(subpath, lock, id); |
if (lock_error) { |
|
foreach(location_module_cache||location_modules(), |
[string loc2, function func2]) |
{ |
if (has_prefix(path, loc2)) { |
|
mapping(string:mixed) ret = |
function_object(func2)->unlock_file(path[sizeof(loc2)..], |
lock, id); |
} else if (recursive && has_prefix(loc2, path)) { |
|
mapping(string:mixed) ret = |
function_object(func2)->unlock_file("/", lock, id); |
} |
if (func == func2) break; |
} |
|
TRACE_LEAVE(sprintf("Lock error: %O", lock_error)); |
return lock_error; |
} |
TRACE_LEAVE("Ok."); |
if (function_object(func)->webdav_opaque) break; |
} |
|
if (expiry_delta) { |
|
|
if (!sizeof(active_locks)) { |
|
active_locks[lock] = 1; |
expire_lock_loop(); |
} else { |
active_locks[lock] = 1; |
} |
} |
|
|
TRACE_LEAVE("Success."); |
return lock; |
} |
|
|
|
|
|
|
|
|
|
|
string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) |
query_property(string path, string prop_name, RequestID id) |
{ |
foreach(location_module_cache||location_modules(), |
[string loc, function func]) |
{ |
if (!has_prefix(path, loc)) { |
|
continue; |
} |
|
|
string subpath = path[sizeof(loc)..]; |
|
string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) res = |
function_object(func)->query_property(subpath, prop_name, id); |
if (mappingp(res) && (res->error == 404)) { |
|
continue; |
} |
return res; |
} |
return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such property."); |
} |
|
mapping|int(-1..0) low_get_file(RequestID id, int|void no_magic) |
|
|
|
|
|
|
|
|
|
{ |
#ifdef MODULE_LEVEL_SECURITY |
int slevel; |
#endif |
|
#ifdef THREADS |
Thread.MutexKey key; |
#endif |
|
id->not_query = VFS.normalize_path( id->not_query ); |
|
TRACE_ENTER(sprintf("Request for %s", id->not_query), 0); |
|
string file=id->not_query; |
string loc; |
function funp; |
mixed tmp, tmp2; |
mapping|object(Stdio.File)|int fid; |
|
if(!no_magic) |
{ |
TIMER_START(internal_magic); |
#ifndef NO_INTERNAL_HACK |
|
|
|
string type; |
if(sizeof(file) > 17 && |
#if ROXEN_COMPAT <= 2.1 |
(file[0] == '/') && |
sscanf(file, "%*s/internal-%s-%[^/]", type, loc) == 3 |
#else |
sscanf(file, "/internal-%s-%[^/]", type, loc) == 2 |
#endif |
) { |
switch(type) { |
case "roxen": |
|
|
RAISE_CACHE(60 * 60 * 24 * 365); |
PROTO_CACHE(); |
id->set_response_header("Cache-Control", "public, max-age=31536000"); |
|
TRACE_LEAVE("Magic internal roxen image"); |
if(loc=="unit" || loc=="pixel-of-destiny") |
{ |
TIMER_END(internal_magic); |
return (["data":"GIF89a\1\0\1\0\200ÿ\0ÀÀÀ\0\0\0!ù\4\1\0\0\0\0," |
"\0\0\0\0\1\0\1\0\0\1\1""2\0;", |
"type":"image/gif", |
"stat": ({0, 0, 0, 900000000, 0, 0, 0})]); |
} |
if(has_prefix(loc, "pixel-")) |
{ |
TIMER_END(internal_magic); |
return (["data":sprintf("GIF89a\1\0\1\0\200\0\0\0\0\0%c%c%c,\0\0\0" |
"\0\1\0\1\0\0\2\2L\1\0;", |
@parse_color(loc[6..])), |
"type":"image/gif", |
"stat": ({0, 0, 0, 900000000, 0, 0, 0})]); |
} |
TIMER_END(internal_magic); |
return internal_roxen_image(loc, id); |
|
case "gopher": |
TRACE_LEAVE("Magic internal gopher image"); |
TIMER_END(internal_magic); |
return internal_gopher_image(loc); |
} |
} |
#endif |
|
|
if(has_prefix(file, internal_location)) |
{ |
TRACE_ENTER("Magic internal module location", 0); |
RoxenModule module; |
string name, rest; |
function find_internal; |
if(2==sscanf(file[strlen(internal_location)..], "%s/%s", name, rest) && |
(module = find_module(replace(name, "!", "#"))) && |
(find_internal = module->find_internal)) |
{ |
#ifdef MODULE_LEVEL_SECURITY |
if(tmp2 = check_security(find_internal, id, slevel)) |
if(intp(tmp2)) |
{ |
TRACE_LEAVE("Permission to access module denied."); |
find_internal = 0; |
} else { |
TRACE_LEAVE(""); |
TRACE_LEAVE("Request denied."); |
TIMER_END(internal_magic); |
return tmp2; |
} |
#endif |
if(find_internal) |
{ |
TRACE_ENTER("Calling find_internal()...", find_internal); |
PROF_ENTER("find_internal","location"); |
LOCK(find_internal); |
fid=find_internal( rest, id ); |
UNLOCK(); |
|
TRACE_LEAVE(""); |
PROF_LEAVE("find_internal","location"); |
if(fid) |
{ |
if(mappingp(fid)) |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE(examine_return_mapping(fid)); |
TIMER_END(internal_magic); |
return fid; |
} |
else |
{ |
#ifdef MODULE_LEVEL_SECURITY |
int oslevel = slevel; |
array slca; |
if(slca = security_level_cache[ Roxen.get_owning_module (find_internal) ]) |
slevel = slca[1]; |
|
|
id->misc->seclevel = slevel; |
#endif |
if(objectp(fid)) |
TRACE_LEAVE("Returned open filedescriptor. " |
#ifdef MODULE_LEVEL_SECURITY |
+(slevel != oslevel? |
sprintf(" The security level is now %d.", slevel):"") |
#endif |
); |
else |
TRACE_LEAVE("Returned directory indicator." |
#ifdef MODULE_LEVEL_SECURITY |
+(oslevel != slevel? |
sprintf(" The security level is now %d.", slevel):"") |
#endif |
); |
} |
} else |
TRACE_LEAVE(""); |
} else |
TRACE_LEAVE(""); |
} else |
TRACE_LEAVE(""); |
} |
TIMER_END(internal_magic); |
} |
|
|
|
if(!fid) |
{ |
#ifdef URL_MODULES |
|
TIMER_START(url_modules); |
foreach(url_module_cache||url_modules(), funp) |
{ |
PROF_ENTER(Roxen.get_owning_module(funp)->module_name,"url module"); |
LOCK(funp); |
TRACE_ENTER("URL module", funp); |
tmp=funp( id, file ); |
UNLOCK(); |
PROF_LEAVE(Roxen.get_owning_module(funp)->module_name,"url module"); |
|
if(mappingp(tmp)) |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returning data"); |
TIMER_END(url_modules); |
return tmp; |
} |
if(objectp( tmp )) |
{ |
mixed err; |
|
id->misc->get_file_nest++; |
err = catch { |
if( id->misc->get_file_nest < 20 ) |
tmp = (id->conf || this_object())->low_get_file( tmp, no_magic ); |
else |
{ |
TRACE_LEAVE("Too deep recursion"); |
error("Too deep recursion in roxen::get_file() while mapping " |
+file+".\n"); |
} |
}; |
id->misc->get_file_nest = 0; |
if(err) throw(err); |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returning data"); |
TIMER_END(url_modules); |
return tmp; |
} |
TRACE_LEAVE(""); |
TIMER_END(url_modules); |
} |
#endif |
|
TIMER_START(location_modules); |
foreach(location_module_cache||location_modules(), tmp) |
{ |
loc = tmp[0]; |
if(has_prefix(file, loc)) |
{ |
TRACE_ENTER(sprintf("Location module [%s] ", loc), tmp[1]); |
#ifdef MODULE_LEVEL_SECURITY |
if(tmp2 = check_security(tmp[1], id, slevel)) |
if(intp(tmp2)) |
{ |
TRACE_LEAVE("Permission to access module denied."); |
continue; |
} else { |
TRACE_LEAVE(""); |
TRACE_LEAVE("Request denied."); |
TIMER_END(location_modules); |
return tmp2; |
} |
#endif |
PROF_ENTER(Roxen.get_owning_module(tmp[1])->module_name,"location"); |
TRACE_ENTER("Calling find_file()...", 0); |
LOCK(tmp[1]); |
fid=tmp[1]( file[ strlen(loc) .. ] + id->extra_extension, id); |
UNLOCK(); |
TRACE_LEAVE(""); |
PROF_LEAVE(Roxen.get_owning_module(tmp[1])->module_name,"location"); |
if(fid) |
{ |
if (id) |
id->virtfile = loc; |
|
if(mappingp(fid)) |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE(examine_return_mapping(fid)); |
TIMER_END(location_modules); |
return fid; |
} |
else |
{ |
#ifdef MODULE_LEVEL_SECURITY |
int oslevel = slevel; |
array slca; |
if(slca = security_level_cache[ Roxen.get_owning_module (tmp[1]) ]) |
slevel = slca[1]; |
|
|
id->misc->seclevel = slevel; |
#endif |
if(objectp(fid)) |
TRACE_LEAVE("Returned open filedescriptor." |
#ifdef MODULE_LEVEL_SECURITY |
+(slevel != oslevel? |
sprintf(" The security level is now %d.", slevel):"") |
#endif |
|
); |
else |
TRACE_LEAVE("Returned directory indicator." |
#ifdef MODULE_LEVEL_SECURITY |
+(oslevel != slevel? |
sprintf(" The security level is now %d.", slevel):"") |
#endif |
); |
break; |
} |
} else |
TRACE_LEAVE(""); |
} else if(strlen(loc)-1==strlen(file) && file+"/" == loc) { |
|
|
|
TRACE_ENTER("Automatic redirect to location_module.", tmp[1]); |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returning data"); |
|
|
|
string new_query = Roxen.http_encode_invalids(id->not_query) + "/" + |
(id->query?("?"+id->query):""); |
new_query=Roxen.add_pre_state(new_query, id->prestate); |
|
TIMER_END(location_modules); |
return Roxen.http_redirect(new_query, id); |
} |
} |
TIMER_END(location_modules); |
} |
|
if(fid == -1) |
{ |
if(no_magic) |
{ |
TRACE_LEAVE("No magic requested. Returning -1."); |
return -1; |
} |
TIMER_START(directory_module); |
if(dir_module) |
{ |
PROF_ENTER(dir_module->module_name,"directory"); |
LOCK(dir_module); |
TRACE_ENTER("Directory module", dir_module); |
fid = dir_module->parse_directory(id); |
TRACE_LEAVE(""); |
UNLOCK(); |
PROF_LEAVE(dir_module->module_name,"directory"); |
} |
else |
{ |
TRACE_LEAVE("No directory module. Returning 'no such file'"); |
return 0; |
} |
TIMER_END(directory_module); |
if(mappingp(fid)) |
{ |
TRACE_LEAVE("Returning data"); |
return (mapping)fid; |
} |
} |
|
|
TIMER_START(extension_module); |
if(objectp(fid) && |
(tmp = file_extension_modules(loc = |
lower_case(Roxen.extension(id->not_query, |
id))))) |
{ |
foreach(tmp, funp) |
{ |
TRACE_ENTER(sprintf("Extension module [%s] ", loc), funp); |
#ifdef MODULE_LEVEL_SECURITY |
if(tmp=check_security(funp, id, slevel)) |
if(intp(tmp)) |
{ |
TRACE_LEAVE("Permission to access module denied."); |
continue; |
} |
else |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE("Permission denied"); |
TIMER_END(extension_module); |
return tmp; |
} |
#endif |
PROF_ENTER(Roxen.get_owning_module(funp)->module_name,"ext"); |
LOCK(funp); |
tmp=funp(fid, loc, id); |
UNLOCK(); |
PROF_LEAVE(Roxen.get_owning_module(funp)->module_name,"ext"); |
if(tmp) |
{ |
if(!objectp(tmp)) |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returning data"); |
TIMER_END(extension_module); |
return tmp; |
} |
if(fid && tmp != fid) |
destruct(fid); |
TRACE_LEAVE("Returned new open file"); |
fid = tmp; |
break; |
} else |
TRACE_LEAVE(""); |
} |
} |
TIMER_END(extension_module); |
|
if(objectp(fid)) |
{ |
TIMER_START(content_type_module); |
if(stringp(id->extension)) { |
id->not_query += id->extension; |
loc = lower_case(Roxen.extension(id->not_query, id)); |
} |
TRACE_ENTER("Content-type mapping module", types_module); |
tmp=type_from_filename(id->not_query, 1, loc); |
TRACE_LEAVE(tmp?sprintf("Returned type %O %s.", tmp[0], tmp[1]||"") |
: "Missing type."); |
if(tmp) |
{ |
TRACE_LEAVE(""); |
TIMER_END(content_type_module); |
return ([ "file":fid, "type":tmp[0], "encoding":tmp[1] ]); |
} |
TRACE_LEAVE(""); |
TIMER_END(content_type_module); |
return ([ "file":fid, ]); |
} |
|
if(!fid) |
TRACE_LEAVE("Returned 'no such file'."); |
else |
TRACE_LEAVE("Returning data"); |
return fid; |
} |
|
#define TRY_FIRST_MODULES(FILE, RECURSE_CALL) do { \ |
TIMER_START(first_modules); \ |
foreach(first_module_cache||first_modules(), function funp) \ |
{ \ |
TRACE_ENTER ("First try module", funp); \ |
if(FILE = funp( id )) { \ |
TRACE_LEAVE ("Got response"); \ |
break; \ |
} \ |
TRACE_LEAVE ("No response"); \ |
if(id->conf != this_object()) { \ |
TRACE_ENTER (sprintf ("Configuration changed to %O - " \ |
"redirecting", id->conf), 0); \ |
TRACE_LEAVE (""); \ |
TIMER_END (first_modules); \ |
TIMER_END (handle_request); \ |
return id->conf->RECURSE_CALL; \ |
} \ |
} \ |
TIMER_END(first_modules); \ |
} while (0) |
|
#define TRY_LAST_MODULES(FILE, RECURSE_CALL) do { \ |
mixed ret; \ |
TIMER_START(last_modules); \ |
foreach(last_module_cache||last_modules(), function funp) { \ |
TRACE_ENTER ("Last try module", funp); \ |
if(ret = funp(id)) { \ |
if (ret == 1) { \ |
TRACE_LEAVE ("Request rewritten - try again"); \ |
TIMER_END(last_modules); \ |
TIMER_END(handle_request); \ |
return RECURSE_CALL; \ |
} \ |
TRACE_LEAVE ("Got response"); \ |
break; \ |
} \ |
TRACE_LEAVE ("No response"); \ |
} \ |
FILE = ret; \ |
TIMER_END(last_modules); \ |
} while (0) |
|
mixed handle_request( RequestID id, void|int recurse_count) |
{ |
mixed file; |
REQUEST_WERR("handle_request()"); |
|
if (recurse_count > 50) { |
TRACE_ENTER ("Looped " + recurse_count + |
" times in internal redirects - giving up", 0); |
TRACE_LEAVE (""); |
return 0; |
} |
|
TIMER_START(handle_request); |
TRY_FIRST_MODULES (file, handle_request (id, recurse_count + 1)); |
if(!mappingp(file) && !mappingp(file = get_file(id))) |
TRY_LAST_MODULES (file, handle_request(id, recurse_count + 1)); |
TIMER_END(handle_request); |
|
REQUEST_WERR("handle_request(): Done"); |
MERGE_TIMERS(roxen); |
return file; |
} |
|
mapping|int get_file(RequestID id, int|void no_magic, int|void internal_get) |
|
|
|
{ |
TIMER_START(get_file); |
int orig_internal_get = id->misc->internal_get; |
id->misc->internal_get = internal_get; |
RequestID root_id = id->root_id || id; |
root_id->misc->_request_depth++; |
if(sub_req_limit && root_id->misc->_request_depth > sub_req_limit) |
error("Subrequest limit reached. (Possibly an insertion loop.)"); |
|
mapping|int res; |
mapping res2; |
function tmp; |
res = low_get_file(id, no_magic); |
TIMER_END(get_file); |
|
|
|
|
|
|
if (id && (!mappingp (res) || !res->pipe)) { |
|
|
|
TIMER_START(filter_modules); |
foreach(filter_module_cache||filter_modules(), tmp) |
{ |
TRACE_ENTER("Filter module", tmp); |
PROF_ENTER(Roxen.get_owning_module(tmp)->module_name,"filter"); |
if(res2=tmp(res,id)) |
{ |
if(mappingp(res) && res->file && (res2->file != res->file)) |
destruct(res->file); |
TRACE_LEAVE("Rewrote result."); |
res=res2; |
} else |
TRACE_LEAVE(""); |
PROF_LEAVE(Roxen.get_owning_module(tmp)->module_name,"filter"); |
} |
TIMER_END(filter_modules); |
} |
|
if (root_id) |
root_id->misc->_request_depth--; |
|
if (id) |
id->misc->internal_get = orig_internal_get; |
|
return res; |
} |
|
protected string combine_combiners(string s) |
{ |
if (String.width(s) <= 8) return s; |
return Unicode.normalize(s, "NFC"); |
} |
|
array(string) find_dir(string file, RequestID id, void|int(0..1) verbose) |
{ |
array dir; |
TRACE_ENTER(sprintf("List directory %O.", file), 0); |
|
if(!sizeof (file) || file[0] != '/') |
file = "/" + file; |
|
#ifdef URL_MODULES |
#ifdef THREADS |
Thread.MutexKey key; |
#endif |
|
foreach(url_modules(), function funp) |
{ |
string of = id->not_query; |
id->not_query = file; |
LOCK(funp); |
TRACE_ENTER("URL module", funp); |
mixed remap=funp( id, file ); |
UNLOCK(); |
|
if(mappingp( remap )) |
{ |
id->not_query=of; |
TRACE_LEAVE("Returned 'No thanks'."); |
TRACE_LEAVE(""); |
return 0; |
} |
if(objectp( remap )) |
{ |
mixed err; |
id->misc->find_dir_nest++; |
|
TRACE_LEAVE("Recursing"); |
file = id->not_query; |
err = catch { |
if( id->misc->find_dir_nest < 20 ) |
dir = (id->conf || this_object())->find_dir( file, id ); |
else |
error("Too deep recursion in roxen::find_dir() while mapping " |
+file+".\n"); |
}; |
id->misc->find_dir_nest = 0; |
TRACE_LEAVE(""); |
if(err) |
throw(err); |
|
if (arrayp(dir)) { |
return map(dir, combine_combiners); |
} |
return dir; |
} |
TRACE_LEAVE(""); |
id->not_query=of; |
} |
#endif /* URL_MODULES */ |
|
array | mapping d; |
array(string) locks=({}); |
RoxenModule mod; |
string loc; |
foreach(location_modules(), array tmp) |
{ |
loc = tmp[0]; |
if(!search(file, loc)) { |
|
TRACE_ENTER(sprintf("Location module [%s] ", loc), tmp[1]); |
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(tmp[1], id)) { |
TRACE_LEAVE("Permission denied"); |
continue; |
} |
#endif |
mod=function_object(tmp[1]); |
if(d=mod->find_dir(file[strlen(loc)..], id)) |
{ |
if(mappingp(d)) |
{ |
if(d->files) { |
TRACE_LEAVE("Got exclusive directory."); |
TRACE_LEAVE(sprintf("Returning list of %d files.", sizeof(d->files))); |
return map(d->files, combine_combiners); |
} else |
TRACE_LEAVE(""); |
} else { |
TRACE_LEAVE("Got files."); |
if(!dir) dir=({ }); |
dir |= d; |
} |
} |
else { |
if(verbose && mod->list_lock_files) |
locks |= mod->list_lock_files(); |
TRACE_LEAVE(""); |
} |
} else if((search(loc, file)==0) && (loc[strlen(file)-1]=='/') && |
(loc[0]==loc[-1]) && (loc[-1]=='/') && |
(function_object(tmp[1])->stat_file(".", id))) { |
|
|
|
TRACE_ENTER(sprintf("Location module [%s] ", loc), tmp[1]); |
loc=loc[strlen(file)..]; |
sscanf(loc, "%s/", loc); |
if (dir) { |
dir |= ({ loc }); |
} else { |
dir = ({ loc }); |
} |
TRACE_LEAVE("Added module mountpoint."); |
} |
} |
if(!dir) { |
TRACE_LEAVE("No directory contents.\n"); |
return verbose ? ({0})+locks : ([])[0]; |
} |
if(sizeof(dir)) |
{ |
TRACE_LEAVE(sprintf("Returning list of %d files.", sizeof(dir))); |
return map(dir, combine_combiners); |
} |
TRACE_LEAVE("Returning 'No such directory'."); |
return 0; |
} |
|
|
|
array(int)|Stat stat_file(string file, RequestID id) |
{ |
mixed s, tmp; |
#ifdef THREADS |
Thread.MutexKey key; |
#endif |
TRACE_ENTER(sprintf("Stat file %O.", file), 0); |
|
file=replace(file, "//", "/"); |
|
if (has_prefix(file, internal_location)) { |
TRACE_LEAVE(""); |
return 0; |
} |
|
#ifdef URL_MODULES |
|
string of = id->not_query; |
id->not_query = file; |
foreach(url_module_cache||url_modules(), function funp) |
{ |
TRACE_ENTER("URL module", funp); |
LOCK(funp); |
tmp=funp( id, file ); |
UNLOCK(); |
|
if (tmp) { |
if(mappingp( tmp )) { |
id->not_query = of; |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returned 'No thanks'."); |
return 0; |
} |
if(objectp( tmp )) |
{ |
mixed err; |
id->misc->stat_file_nest++; |
TRACE_LEAVE("Recursing"); |
err = catch { |
if( id->misc->stat_file_nest < 20 ) |
tmp = (id->conf || this_object())->stat_file(id->not_query, id ); |
else |
error("Too deep recursion in roxen::stat_file() while mapping " |
+file+".\n"); |
}; |
id->not_query = of; |
id->misc->stat_file_nest = 0; |
if(err) |
throw(err); |
TRACE_LEAVE(""); |
TRACE_LEAVE("Returning data"); |
return tmp; |
} |
} |
TRACE_LEAVE(""); |
} |
id->not_query = of; |
#endif |
|
|
foreach(location_module_cache||location_modules(), |
[string loc, function fun]) { |
if((file == loc) || ((file+"/")==loc)) |
{ |
TRACE_ENTER(sprintf("Location module [%s] ", loc), fun); |
TRACE_LEAVE("Exact match."); |
TRACE_LEAVE(""); |
return Stdio.Stat(({ 0775, -3, 0, 0, 0, 0, 0 })); |
} |
if(has_prefix(file, loc)) |
{ |
TRACE_ENTER(sprintf("Location module [%s] ", loc), fun); |
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(fun, id)) { |
TRACE_LEAVE(""); |
TRACE_LEAVE("Permission denied"); |
continue; |
} |
#endif |
if(s=function_object(fun)->stat_file(file[strlen(loc)..], id)) |
{ |
TRACE_LEAVE(""); |
TRACE_LEAVE("Stat ok."); |
return s; |
} |
TRACE_LEAVE(""); |
} |
} |
TRACE_LEAVE("Returned 'no such file'."); |
} |
|
mapping error_file( RequestID id ) |
{ |
mapping res; |
|
if (id->root_id->misc->generate_file_not_found || |
|
id->not_query == "/favicon.ico") { |
res = Roxen.http_string_answer("No such file", "text/plain"); |
res->error = 404; |
} else { |
id->root_id->misc->generate_file_not_found = 1; |
string data = "<return code='404' />" + query("ZNoSuchFile"); |
#if ROXEN_COMPAT <= 2.1 |
data = replace(data,({"$File", "$Me"}), |
({"&page.virtfile;", "&roxen.server;"})); |
#endif |
res = Roxen.http_rxml_answer( data, id, 0, "text/html" ); |
id->root_id->misc->generate_file_not_found = 0; |
} |
NOCACHE(); |
return res; |
} |
|
mapping auth_failed_file( RequestID id, string message ) |
{ |
|
|
if(id->root_id->misc->generate_auth_failed) |
return Roxen.http_low_answer(401, "<title>Access Denied</title>" |
"<h2 align=center>Access Denied</h2>"); |
id->root_id->misc->generate_auth_failed = 1; |
|
string data = "<return code='401' />" + query("ZAuthFailed"); |
NOCACHE(); |
mapping res = Roxen.http_rxml_answer( data, id, 0, "text/html" ); |
id->root_id->misc->generate_auth_failed = 0; |
return res; |
} |
|
|
array open_file(string fname, string mode, RequestID id, void|int internal_get, |
void|int recurse_count) |
{ |
mapping|int(0..1) file; |
string oq = id->not_query; |
|
if( id->conf && (id->conf != this_object()) ) |
return id->conf->open_file( fname, mode, id, internal_get, recurse_count ); |
|
if (recurse_count > 50) { |
TRACE_ENTER ("Looped " + recurse_count + |
" times in internal redirects - giving up", 0); |
TRACE_LEAVE (""); |
} |
|
else { |
Configuration oc = id->conf; |
id->not_query = fname; |
|
|
|
|
m_delete(id->misc, "defines"); |
m_delete(id->misc, "error_code"); |
|
TRY_FIRST_MODULES (file, open_file (fname, mode, id, |
internal_get, recurse_count + 1)); |
fname = id->not_query; |
|
if(search(mode, "R")!=-1) |
{ |
string f; |
mode -= "R"; |
if(f = real_file(fname, id)) |
{ |
|
return ({ open(f, mode), ([]) }); |
} |
|
} |
|
if(mode!="r") { |
id->not_query = oq; |
return ({ 0, (["error":501, "data":"Not implemented." ]) }); |
} |
|
if(!file) |
{ |
file = get_file( id, 0, internal_get ); |
if(!file) |
TRY_LAST_MODULES (file, open_file (id->not_query, mode, id, |
internal_get, recurse_count + 1)); |
} |
} |
|
if(!mappingp(file)) |
{ |
if(id->misc->error_code) |
file = Roxen.http_low_answer(id->misc->error_code, "Failed" ); |
else if(id->method!="GET"&&id->method != "HEAD"&&id->method!="POST") |
file = Roxen.http_low_answer(501, "Not implemented."); |
else |
file = error_file( id ); |
|
id->not_query = oq; |
|
return ({ 0, file }); |
} |
|
if( file->data ) |
{ |
file->file = StringFile(file->data); |
m_delete(file, "data"); |
} |
id->not_query = oq; |
return ({ file->file || StringFile(""), file }); |
} |
|
|
mapping(string:array(mixed)) find_dir_stat(string file, RequestID id) |
{ |
string loc; |
mapping(string:array(mixed)) dir = ([]); |
mixed d, tmp; |
|
|
file=replace(file, "//", "/"); |
|
if(!sizeof (file) || file[0] != '/') |
file = "/" + file; |
|
|
|
TRACE_ENTER(sprintf("Request for directory and stat's \"%s\".", file), 0); |
|
#ifdef URL_MODULES |
#ifdef THREADS |
Thread.MutexKey key; |
#endif |
|
foreach(url_modules(), function funp) |
{ |
string of = id->not_query; |
id->not_query = file; |
LOCK(funp); |
TRACE_ENTER("URL module", funp); |
tmp=funp( id, file ); |
UNLOCK(); |
|
if(mappingp( tmp )) |
{ |
id->not_query=of; |
#ifdef MODULE_DEBUG |
report_debug("conf->find_dir_stat(\"%s\"): url_module returned mapping:%O\n", |
file, tmp); |
#endif /* MODULE_DEBUG */ |
TRACE_LEAVE("Returned mapping."); |
TRACE_LEAVE(""); |
return 0; |
} |
if(objectp( tmp )) |
{ |
mixed err; |
id->misc->find_dir_stat_nest++; |
|
file = id->not_query; |
err = catch { |
if( id->misc->find_dir_stat_nest < 20 ) |
tmp = (id->conf || this_object())->find_dir_stat( file, id ); |
else { |
TRACE_LEAVE("Too deep recursion"); |
error("Too deep recursion in roxen::find_dir_stat() while mapping " |
+file+".\n"); |
} |
}; |
id->misc->find_dir_stat_nest = 0; |
if(err) |
throw(err); |
#ifdef MODULE_DEBUG |
report_debug("conf->find_dir_stat(\"%s\"): url_module returned object:\n", |
file); |
#endif /* MODULE_DEBUG */ |
TRACE_LEAVE("Returned object."); |
TRACE_LEAVE("Returning it."); |
return tmp; |
} |
id->not_query=of; |
TRACE_LEAVE(""); |
} |
#endif /* URL_MODULES */ |
|
foreach(location_modules(), tmp) |
{ |
loc = tmp[0]; |
|
TRACE_ENTER(sprintf("Location module [%s] ", loc), 0); |
|
if(!search(file, loc)) |
{ |
|
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(tmp[1], id)) { |
TRACE_LEAVE("Security check failed."); |
continue; |
} |
#endif |
RoxenModule c = function_object(tmp[1]); |
string f = file[strlen(loc)..]; |
if (c->find_dir_stat) { |
SIMPLE_TRACE_ENTER(c, "Calling find_dir_stat()."); |
if (d = c->find_dir_stat(f, id)) { |
SIMPLE_TRACE_LEAVE("Returned mapping with %d entries.", sizeof (d)); |
dir = d | dir; |
} |
else |
SIMPLE_TRACE_LEAVE("Returned zero."); |
} else { |
SIMPLE_TRACE_ENTER(c, "Calling find_dir()."); |
if(d = c->find_dir(f, id)) { |
SIMPLE_TRACE_LEAVE("Returned array with %d entries.", sizeof (d)); |
dir = mkmapping(d, Array.map(d, lambda(string fn) |
{ |
return c->stat_file(f + fn, id); |
})) | dir; |
} |
else |
SIMPLE_TRACE_LEAVE("Returned zero."); |
} |
} else if(search(loc, file)==0 && loc[strlen(file)-1]=='/' && |
(loc[0]==loc[-1]) && loc[-1]=='/' && |
(function_object(tmp[1])->stat_file(".", id))) { |
|
|
|
TRACE_ENTER(sprintf("The file %O is on the path to the mountpoint %O.", |
file, loc), 0); |
loc=loc[strlen(file)..]; |
sscanf(loc, "%s/", loc); |
if (!dir[loc]) { |
dir[loc] = ({ 0775, -3, 0, 0, 0, 0, 0 }); |
} |
TRACE_LEAVE(""); |
} |
TRACE_LEAVE(""); |
} |
if(sizeof(dir)) |
return dir; |
} |
|
|
|
|
array access(string file, RequestID id) |
{ |
string loc; |
array s, tmp; |
|
file=replace(file, "//", "/"); |
|
|
foreach(location_modules(), tmp) |
{ |
loc = tmp[0]; |
if((file+"/")==loc) { |
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(tmp[1], id)) continue; |
#endif |
if(s=function_object(tmp[1])->access("", id)) |
return s; |
} else if(!search(file, loc)) { |
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(tmp[1], id)) continue; |
#endif |
if(s=function_object(tmp[1])->access(file[strlen(loc)..], id)) |
return s; |
} |
} |
return 0; |
} |
|
string real_file(string file, RequestID id) |
|
{ |
string loc; |
string s; |
array tmp; |
file=replace(file, "//", "/"); |
|
if(!id) error("No id passed to real_file"); |
|
|
foreach(location_modules(), tmp) |
{ |
loc = tmp[0]; |
if(!search(file, loc)) |
{ |
#ifdef MODULE_LEVEL_SECURITY |
if(check_security(tmp[1], id)) continue; |
#endif |
if(s=function_object(tmp[1])->real_file(file[strlen(loc)..], id)) |
return s; |
} |
} |
} |
|
array(int)|Stat try_stat_file(string s, RequestID id, int|void not_internal) |
{ |
RequestID fake_id; |
array(int)|Stat res; |
|
if(!objectp(id)) |
error("No ID passed to 'try_stat_file'\n"); |
|
|
|
if ( !id->misc ) |
id->misc = ([]); |
|
fake_id = make_fake_id(s, id); |
|
fake_id->misc->internal_get = !not_internal; |
fake_id->method = "GET"; |
|
res = stat_file(fake_id->not_query, fake_id); |
|
destruct (fake_id); |
return res; |
} |
|
protected RequestID make_fake_id (string s, RequestID id) |
{ |
RequestID fake_id; |
|
|
|
if ( !id->misc->common ) |
id->misc->common = ([]); |
|
fake_id = id->clone_me(); |
|
fake_id->misc->common = id->misc->common; |
fake_id->conf = this_object(); |
|
|
|
s = map(string_to_utf8(s)/"/", Protocols.HTTP.percent_encode) * "/"; |
|
fake_id->raw_url = s; |
|
if (fake_id->scan_for_query) |
|
|
|
|
s = fake_id->scan_for_query (s); |
|
s = http_decode_string(s); |
|
catch { s = utf8_to_string(s); }; |
|
s = Roxen.fix_relative (s, id); |
|
|
if (search(s, "\0") != -1) |
sscanf(s, "%s\0", s); |
|
fake_id->not_query=s; |
|
return fake_id; |
} |
|
int|string try_get_file(string s, RequestID id, |
int|void stat_only, int|void nocache, |
int|void not_internal, |
mapping|void result_mapping) |
|
|
|
|
|
|
|
|
|
|
|
|
{ |
string res; |
RequestID fake_id = make_fake_id (s, id); |
mapping m; |
|
fake_id->misc->internal_get = !not_internal; |
fake_id->method = "GET"; |
|
array a = open_file( fake_id->not_query, "r", fake_id, !not_internal ); |
|
m = a[1]; |
|
|
id->propagate_vary_callbacks(fake_id); |
|
if (result_mapping) { |
foreach(indices(m), string i) |
result_mapping[i] = m[i]; |
if (string|function(string:string) charset = fake_id->get_output_charset()) |
|
|
|
|
|
|
result_mapping->charset = charset; |
result_mapping->last_modified = fake_id->misc->last_modified; |
} |
|
if(a[0]) { |
m->file = a[0]; |
} |
else { |
destruct (fake_id); |
return 0; |
} |
|
CACHE( fake_id->misc->cacheable ); |
destruct (fake_id); |
|
|
if (!(< 0,2,3 >)[m->error/100]) return 0; |
|
if(stat_only) return 1; |
|
if(m->data) |
res = m->data; |
else |
res=""; |
|
if( objectp(m->file) ) |
{ |
res += m->file->read(); |
if (m->file) { |
|
destruct(m->file); |
} |
} |
|
if(m->raw) { |
if (compat_level() > 5.0) |
res = Roxen.parse_http_response (res, result_mapping, 0, "from " + s); |
else { |
|
|
|
|
|
|
sscanf (res, "%*s\r\n\r\n%s", res) || |
sscanf (res, "%*s\n\n%s", res) || |
sscanf (res, "%*s\r\n%s", res) || |
sscanf (res, "%*s\n%s", res); |
} |
} |
|
return res; |
} |
|
mapping(string:string) try_get_headers(string s, RequestID id, |
int|void not_internal) |
|
|
|
|
{ |
RequestID fake_id = make_fake_id (s, id); |
mapping m; |
|
fake_id->misc->internal_get = !not_internal; |
fake_id->method = "HEAD"; |
|
array a = open_file( s, "r", fake_id, !not_internal ); |
if(a && a[1]) { |
if (a[0]) a[0]->close(); |
m = a[1]; |
} |
else { |
destruct (fake_id); |
return 0; |
} |
|
CACHE( fake_id->misc->cacheable ); |
|
if (!m->raw) |
m = fake_id->make_response_headers (m); |
|
else { |
Roxen.HeaderParser hp = Roxen.HeaderParser(); |
array res; |
|
if(m->data) |
res = hp->feed (m->data); |
|
if (!res && objectp(m->file)) |
{ |
hp->feed (m->file->read()); |
if (m->file) { |
|
destruct(m->file); |
} |
} |
|
m = res && res[2]; |
} |
|
destruct (fake_id); |
return m; |
} |
|
mapping(string:mixed) try_put_file(string path, string data, RequestID id) |
{ |
TIMER_START(try_put_file); |
|
|
|
if ( !id->misc ) |
id->misc = ([]); |
|
RequestID fake_id = make_fake_id(path, id); |
|
fake_id->root_id->misc->_request_depth++; |
if(sub_req_limit && fake_id->root_id->misc->_request_depth > sub_req_limit) |
error("Subrequest limit reached. (Possibly an insertion loop.)"); |
|
fake_id->method = "PUT"; |
fake_id->data = data; |
fake_id->misc->len = sizeof(data); |
fake_id->misc->internal_get = 1; |
|
mapping(string:mixed) res = low_get_file(fake_id, 1); |
TIMER_END(try_put_file); |
return res; |
} |
|
int(0..1) is_file(string virt_path, RequestID id, int(0..1)|void internal) |
|
|
{ |
if(internal) { |
int(0..1) was_internal = id->misc->internal_get; |
id->misc->internal_get = 1; |
int(0..1) res = !!stat_file(virt_path, id); |
if(!was_internal) |
m_delete(id->misc, "internal_get"); |
return res; |
} |
if(stat_file(virt_path, id) || |
has_suffix(virt_path, "/internal-roxen-unit")) |
return 1; |
string f = (virt_path/"/")[-1]; |
if( sscanf(f, "internal-roxen-%s", f) ) { |
if(internal_roxen_image(f, id) || |
has_prefix(f, "pixel-")) |
return 1; |
return 0; |
} |
if( sscanf(f, "internal-gopher-%s", f) && |
internal_gopher_image(f) ) |
return 1; |
return 0; |
} |
|
array registered_urls = ({}), failed_urls = ({ }); |
array do_not_log_patterns = 0; |
int start(int num) |
{ |
fix_my_url(); |
|
#if 0 |
report_debug(sprintf("configuration:start():\n" |
" registered_urls: ({ %{%O, %}})\n" |
" failed_urls: ({ %{%O, %}})\n" |
" URLs: ({ %{%O, %}})\n", |
registered_urls, |
failed_urls, |
query("URLs"))); |
#endif /* 0 */ |
|
|
foreach( (registered_urls-query("URLs"))+failed_urls, string url ) |
{ |
registered_urls -= ({ url }); |
roxen.unregister_url(url, this_object()); |
} |
|
failed_urls = ({ }); |
|
foreach( (query( "URLs" )-registered_urls), string url ) |
{ |
if( roxen.register_url( url, this_object() ) ) |
registered_urls += ({ url }); |
else |
failed_urls += ({ url }); |
} |
if( !datacache ) |
datacache = DataCache( ); |
else |
datacache->init_from_variables(); |
|
parse_log_formats(); |
init_log_file(); |
do_not_log_patterns = query("NoLog"); |
if(!sizeof(do_not_log_patterns)) |
do_not_log_patterns = 0; |
|
if( query("throttle") ) |
{ |
if( !throttler ) |
throttler=.throttler(); |
throttler->throttle(query("throttle_fill_rate"), |
query("throttle_bucket_depth"), |
query("throttle_min_grant"), |
query("throttle_max_grant")); |
} |
else if( throttler ) |
{ |
|
throttler->throttle( 1000000000, 1000000000, |
1024, 65536 ); |
|
throttler = 0; |
} |
|
#ifdef SNMP_AGENT |
if(query("snmp_process") && objectp(roxen->snmpagent)) |
roxen->snmpagent->add_virtserv(get_config_id()); |
#endif |
|
foreach(registered_urls, string url) { |
mapping(string:string|Configuration|Protocol) port_info = roxen.urls[url]; |
|
foreach((port_info && port_info->ports) || ({}), Protocol prot) { |
if ((prot->prot_name != "snmp") || (!prot->mib)) { |
continue; |
} |
|
string path = port_info->path || ""; |
if (has_prefix(path, "/")) { |
path = path[1..]; |
} |
if (has_suffix(path, "/")) { |
path = path[..sizeof(path)-2]; |
} |
|
array(int) oid_suffix = ({ sizeof(path), @((array(int))path) }); |
|
ADT.Trie mib = |
SNMP.SimpleMIB(query_oid(), oid_suffix, |
({ |
UNDEFINED, |
UNDEFINED, |
SNMP.String(query_name, "siteName"), |
SNMP.String(comment, "siteComment"), |
SNMP.Counter64(lambda() { return sent; }, |
"sent"), |
SNMP.Counter64(lambda() { return received; }, |
"received"), |
SNMP.Counter64(lambda() { return hsent; }, |
"sentHeaders"), |
SNMP.Counter64(lambda() { return requests; }, |
"numRequests"), |
UNDEFINED, |
({ |
UNDEFINED, |
({ |
UNDEFINED, |
({ |
UNDEFINED, |
SNMP.Counter(lambda() |
{ return request_acc_time/10000; }, |
"requestTime", |
"Accumulated total request time " |
"in centiseconds."), |
}), |
({ |
UNDEFINED, |
SNMP.Counter(lambda() { return requests; }, |
"requestNumRuns", |
"Total number of request runs."), |
SNMP.Counter(lambda() { return request_num_runs_001s; }, |
"requestNumRuns001s", |
"Number of request runs longer than 0.01 seconds."), |
SNMP.Counter(lambda() { return request_num_runs_005s; }, |
"requestNumRuns005s", |
"Number of request runs longer than 0.05 seconds."), |
SNMP.Counter(lambda() { return request_num_runs_015s; }, |
"requestNumRuns015s", |
"Number of request runs longer than 0.15 seconds."), |
SNMP.Counter(lambda() { return request_num_runs_05s; }, |
"requestNumRuns05s", |
"Number of request runs longer than 0.5 seconds."), |
SNMP.Counter(lambda() { return request_num_runs_1s; }, |
"requestNumRuns1s", |
"Number of request runs longer than 1 second."), |
SNMP.Counter(lambda() { return request_num_runs_5s; }, |
"requestNumRuns5s", |
"Number of request runs longer than 5 seconds."), |
SNMP.Counter(lambda() { return request_num_runs_15s; }, |
"requestNumRuns15s", |
"Number of request runs longer than 15 seconds."), |
}), |
}), |
({ |
UNDEFINED, |
({ |
UNDEFINED, |
SNMP.Counter(lambda() |
{ return handle_acc_time/10000; }, |
"handleTime", |
"Accumulated total handle time " |
"in centiseconds."), |
}), |
({ |
UNDEFINED, |
SNMP.Counter(lambda() { return requests; }, |
"handleNumRuns", |
"Total number of handle runs."), |
SNMP.Counter(lambda() { return handle_num_runs_001s; }, |
"handleNumRuns001s", |
"Number of handle runs longer than 0.01 seconds."), |
SNMP.Counter(lambda() { return handle_num_runs_005s; }, |
"handleNumRuns005s", |
"Number of handle runs longer than 0.05 seconds."), |
SNMP.Counter(lambda() { return handle_num_runs_015s; }, |
"handleNumRuns015s", |
"Number of handle runs longer than 0.15 seconds."), |
SNMP.Counter(lambda() { return handle_num_runs_05s; }, |
"handleNumRuns05s", |
"Number of handle runs longer than 0.5 seconds."), |
SNMP.Counter(lambda() { return handle_num_runs_1s; }, |
"handleNumRuns1s", |
"Number of handle runs longer than 1 second."), |
SNMP.Counter(lambda() { return handle_num_runs_5s; }, |
"handleNumRuns5s", |
"Number of handle runs longer than 5 seconds."), |
SNMP.Counter(lambda() { return handle_num_runs_15s; }, |
"handleNumRuns15s", |
"Number of handle runs longer than 15 seconds."), |
}), |
}), |
({ |
UNDEFINED, |
({ |
UNDEFINED, |
SNMP.Counter(lambda() |
{ return queue_acc_time/10000; }, |
"queueTime", |
"Accumulated total queue time " |
"in centiseconds."), |
}), |
({ |
UNDEFINED, |
SNMP.Counter(lambda() { return requests; }, |
"queueNumRuns", |
"Total number of queue runs."), |
SNMP.Counter(lambda() { return queue_num_runs_001s; }, |
"queueNumRuns001s", |
"Number of queue runs longer than 0.01 seconds."), |
SNMP.Counter(lambda() { return queue_num_runs_005s; }, |
"queueNumRuns005s", |
"Number of queue runs longer than 0.05 seconds."), |
SNMP.Counter(lambda() { return queue_num_runs_015s; }, |
"queueNumRuns015s", |
"Number of queue runs longer than 0.15 seconds."), |
SNMP.Counter(lambda() { return queue_num_runs_05s; }, |
"queueNumRuns05s", |
"Number of queue runs longer than 0.5 seconds."), |
SNMP.Counter(lambda() { return queue_num_runs_1s; }, |
"queueNumRuns1s", |
"Number of queue runs longer than 1 second."), |
SNMP.Counter(lambda() { return queue_num_runs_5s; }, |
"queueNumRuns5s", |
"Number of queue runs longer than 5 seconds."), |
SNMP.Counter(lambda() { return queue_num_runs_15s; }, |
"queueNumRuns15s", |
"Number of queue runs longer than 15 seconds."), |
}), |
}) |
}), |
({ |
UNDEFINED, |
SNMP.Counter(lambda() |
{ |
mapping stats = |
datacache->get_cache_stats(); |
return stats->hits + stats->misses; |
}, |
"protCacheLookups", |
"Number of protocol cache lookups."), |
SNMP.Counter(lambda() |
{ |
return |
datacache->get_cache_stats()->hits; |
}, |
"protCacheHits", |
"Number of protocol cache hits."), |
SNMP.Counter(lambda() |
{ |
return |
datacache->get_cache_stats()-> |
misses; |
}, |
"protCacheMisses", |
"Number of protocol cache misses."), |
SNMP.Gauge(lambda() |
{ |
return |
datacache->get_cache_stats()->entries; |
}, |
"protCacheEntries", |
"Number of protocol cache entries."), |
SNMP.Gauge(lambda() |
{ |
return |
datacache->get_cache_stats()-> |
max_size / 1024; |
}, |
"protCacheMaxSize", |
"Maximum size of protocol cache in KiB."), |
SNMP.Gauge(lambda() |
{ |
return |
datacache->get_cache_stats()-> |
current_size / 1024; |
}, |
"protCacheCurrSize", |
"Current size of protocol cache in KiB."), |
}) |
})); |
SNMP.set_owner(mib, this_object()); |
prot->mib->merge(mib); |
} |
} |
|
if (retrieve ("EnabledModules", this)["config_filesystem#0"]) |
return 1; |
return 0; |
} |
|
|
protected mapping(string: |
mapping(string: |
array(function(RoxenModule,mixed...:void)))) |
module_pre_callbacks = ([]), module_post_callbacks = ([]); |
|
void add_module_pre_callback (string mod_name, string func, |
function(RoxenModule,mixed...:void) cb) |
{ |
ASSERT_IF_DEBUG ((<"start", "stop">)[func]); |
mapping(string:array(function(RoxenModule,mixed...:void))) func_cbs = |
module_pre_callbacks[func] || (module_pre_callbacks[func] = ([])); |
if (func_cbs[mod_name] && has_value (func_cbs[mod_name], cb)) |
return; |
func_cbs[mod_name] += ({cb}); |
} |
|
void delete_module_pre_callback (string mod_name, string func, |
function(RoxenModule,mixed...:void) cb) |
{ |
if (mapping(string:array(function(RoxenModule,mixed...:void))) func_cbs = |
module_pre_callbacks[func]) |
if (func_cbs[mod_name]) |
func_cbs[mod_name] -= ({cb}); |
} |
|
void add_module_post_callback (string mod_name, string func, |
function(RoxenModule,mixed...:void) cb) |
{ |
ASSERT_IF_DEBUG ((<"start", "stop">)[func]); |
mapping(string:array(function(RoxenModule,mixed...:void))) func_cbs = |
module_post_callbacks[func] || (module_post_callbacks[func] = ([])); |
if (func_cbs[mod_name] && has_value (func_cbs[mod_name], cb)) |
return; |
func_cbs[mod_name] += ({cb}); |
} |
|
void delete_module_post_callback (string mod_name, string func, |
function(RoxenModule,mixed...:void) cb) |
{ |
if (mapping(string:array(function(RoxenModule,mixed...:void))) func_cbs = |
module_post_callbacks[func]) |
if (func_cbs[mod_name]) |
func_cbs[mod_name] -= ({cb}); |
} |
|
void call_module_func_with_cbs (RoxenModule mod, string func, mixed... args) |
{ |
string mod_name; |
|
if (mapping(string:array(function(RoxenModule,mixed...:void))) func_cbs = |
module_pre_callbacks[func]) { |
sscanf (mod->module_local_id(), "%[^#]", mod_name); |
array(function(RoxenModule,mixed...:void)) cbs; |
if (array(function(RoxenModule,mixed...:void)) a = func_cbs[mod_name]) { |
func_cbs[mod_name] = (a -= ({0})); |
cbs = a; |
} |
if (array(function(RoxenModule,mixed...:void)) a = func_cbs[0]) { |
func_cbs[0] = (a -= ({0})); |
if (cbs) cbs += a; else cbs = a; |
} |
if (cbs) { |
foreach (cbs, function(RoxenModule,mixed...:void) cb) { |
#ifdef MODULE_CB_DEBUG |
werror ("Calling callback before %O->%s: %O\n", mod, func, cb); |
#endif |
if (mixed err = catch (cb (mod, @args))) |
report_error ("Error calling callback %O before %O->%s:\n%s\n", |
cb, mod, func, describe_backtrace (err)); |
} |
} |
} |
|
|
#ifdef MODULE_CB_DEBUG |
werror ("Calling %O->%s (%s)\n", mod, func, |
map (args, lambda (mixed arg) |
{return sprintf ("%O", arg);}) * ", "); |
#endif |
mod[func |