835c6c2001-06-17Martin Nilsson // This file is part of Roxen WebServer. // Copyright © 2001, Roxen IS.
a896932001-01-03Per Hedbor #include <stat.h> #include <config.h>
f7ea0c2002-02-06Martin Stjernholm #include <module.h>
c8bfed2003-06-02Henrik Grubbström (Grubba) #include <variables.h>
3e3bab2001-01-19Per Hedbor #include <module_constants.h>
9750692004-06-01Martin Stjernholm constant cvs_version="$Id: prototypes.pike,v 1.138 2004/06/01 13:04:09 mast Exp $";
c2d7aa2004-04-13Martin Stjernholm  #ifdef DAV_DEBUG #define DAV_WERROR(X...) werror(X) #else /* !DAV_DEBUG */ #define DAV_WERROR(X...) #endif /* DAV_DEBUG */ // To avoid reference cycles. Set to the Roxen module object by // roxenloader.pike. object Roxen;
a896932001-01-03Per Hedbor 
119a552004-05-07Martin Stjernholm // Externally visible identifiers in this file that shouldn't be added // as global constants by roxenloader.pike.
8cedaa2004-05-07Martin Stjernholm constant ignore_identifiers = (<
119a552004-05-07Martin Stjernholm  "cvs_version", "Roxen", "ignore_identifiers"
8cedaa2004-05-07Martin Stjernholm >);
119a552004-05-07Martin Stjernholm static class Variable
a896932001-01-03Per Hedbor { constant is_variable = 1; constant type = "Basic"; string get_warnings(); int get_flags(); void set_flags( int flags ); int check_visibility( RequestID id, int more_mode, int expert_mode, int devel_mode, int initial, int|void variable_in_cfif ); void set_invisibility_check_callback( function(RequestID,Variable:int) cb ); function(Variable:void) get_changed_callback( ); void set_changed_callback( function(Variable:void) cb ); void add_changed_callback( function(Variable:void) cb ); function(RequestID,Variable:int) get_invisibility_check_callback() ; string doc( ); string name( ); string type_hint( ); mixed default_value(); void set_warning( string to ); int set( mixed to ); int low_set( mixed to ); mixed query(); int is_defaulted(); array(string|mixed) verify_set( mixed new_value ); mapping(string:string) get_form_vars( RequestID id ); mixed transform_from_form( string what ); void set_from_form( RequestID id ); string path(); void set_path( string to ); string render_form( RequestID id, void|mapping additional_args ); string render_view( RequestID id ); } class BasicDefvar { mapping(string:Variable) variables=([]); Variable getvar( string name ); int deflocaledoc( string locale, string variable, string name, string doc, mapping|void translate ); void set(string var, mixed value); int killvar(string var); void setvars( mapping (string:mixed) vars ); Variable defvar(string var, mixed value, mapping|string|void|object name, int|void type, mapping|string|void|object doc_str, mixed|void misc, int|function|void not_in_config, mapping|void option_translations); mixed query(string|void var, int|void ok); void definvisvar(string name, mixed value, int type, array|void misc); } class StringFile( string data, mixed|void _st ) { int offset; string _sprintf() { return "StringFile("+strlen(data)+","+offset+")"; } string read(int nbytes) { if(!nbytes) { offset = strlen(data); return data; } string d = data[offset..offset+nbytes-1]; offset += strlen(d); return d; } array stat() { if( _st ) return (array)_st; return ({ 0, strlen(data), time(), time(), time(), 0, 0, 0 }); } void write(mixed ... args) { throw( ({ "File not open for write\n", backtrace() }) ); } void seek(int to) { offset = to; } } class ModuleInfo { string sname; string filename; int last_checked; int type, multiple_copies;
ea262c2002-04-17Marcus Wellhardh  int|string locked; mapping(Configuration:int) config_locked;
a896932001-01-03Per Hedbor  string get_name(); string get_description(); RoxenModule instance( object conf, void|int silent ); void save(); void update_with( RoxenModule mod, string what ); int init_module( string what ); int rec_find_module( string what, string dir ); int find_module( string sn ); int check (void|int force); } class ModuleCopies { mapping copies = ([]); mixed `[](mixed q ) { return copies[q]; } mixed `[]=(mixed q,mixed w ) { return copies[q]=w; } array _indices() { return indices(copies); } array _values() { return values(copies); }
4d7e512001-01-16Martin Nilsson  string _sprintf( ) { return "ModuleCopies("+sizeof(copies)+")"; }
a896932001-01-03Per Hedbor }
b0c9b82004-04-30Henrik Grubbström (Grubba) // Simulate an import of useful stuff from Parser.XML.Tree.
a8e2b32004-05-07Henrik Grubbström (Grubba) static constant SimpleNode = Parser.XML.Tree.SimpleNode; static constant SimpleRootNode = Parser.XML.Tree.SimpleRootNode; static constant SimpleHeaderNode = Parser.XML.Tree.SimpleHeaderNode; static constant SimpleTextNode = Parser.XML.Tree.SimpleTextNode; static constant SimpleElementNode = Parser.XML.Tree.SimpleElementNode;
b0c9b82004-04-30Henrik Grubbström (Grubba) 
0ddcae2004-05-04Martin Stjernholm //! Container for information about an outstanding DAV lock. No field //! except @[owner] may change after the object has been created since //! filesystem modules might store this info persistently. //! //! @note //! @[DAVLock] objects might be shared between filesystems but not //! between configurations.
fcfa7b2004-05-14Henrik Grubbström (Grubba) class DAVLock { string locktoken;
8265772004-04-29Martin Stjernholm  //! The lock token given to the client. It may be zero in case there //! only is knowledge that a lock exists but the lock instance has //! been forgotten. This can happen in a filesystem that has some //! way of recording locks but doesn't store the DAV lock tokens.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  string path;
8265772004-04-29Martin Stjernholm  //! Canonical absolute path to the locked resource. Always ends with //! a @expr{"/"@}.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  int(0..1) recursive;
8265772004-04-29Martin Stjernholm  //! @expr{1@} if the lock applies to all resources under @[path], //! @expr{0@} if it applies to @[path] only.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  string|SimpleNode lockscope;
8265772004-04-29Martin Stjernholm  //! The lock scope (RFC 2518 12.7). As a special case, if it only is //! an empty element without attributes then the element name is //! stored as a string. //! //! @note //! RFC 2518 specifies the lock scopes @expr{"DAV:exclusive"@} and //! @expr{"DAV:shared"@}.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  string|SimpleNode locktype;
8265772004-04-29Martin Stjernholm  //! The lock type (RFC 2518 12.8). As a special case, if it only is //! an empty element without attributes then the element name is //! stored as a string. //! //! @note //! RFC 2518 only specifies the lock type @expr{"DAV:write"@}.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  int(0..) expiry_delta; //! Idle time before this lock expires. //! //! As a special case, if the value is @expr{0@} (zero), the lock //! has infinite duration. array(SimpleNode) owner;
8265772004-04-29Martin Stjernholm  //! The owner identification (RFC 2518 12.10), or zero if unknown.
071d472004-05-06Martin Stjernholm  //! More precisely, it's the children of the @expr{"DAV:owner"@} //! element.
4908322004-04-29Martin Stjernholm  //! //! @[RoxenModule.lock_file] may set this if it's zero, otherwise //! it shouldn't change.
fcfa7b2004-05-14Henrik Grubbström (Grubba)  int(0..) expiry_time; //! Absolute time when this lock expires. //! //! As a special case, if the value is @expr{0@} (zero), the lock //! has infinite duration. static void create(string locktoken, string path, int(0..1) recursive, string|SimpleNode lockscope, string|SimpleNode locktype, int(0..) expiry_delta, array(SimpleNode) owner) { DAVLock::locktoken = locktoken; DAVLock::path = path; DAVLock::recursive = recursive; DAVLock::lockscope = lockscope; DAVLock::locktype = locktype; DAVLock::expiry_delta = expiry_delta; DAVLock::owner = owner; if (expiry_delta) { if (expiry_delta < 0) error("Negative expiry delta!\n"); expiry_time = time(0) + expiry_delta; } }
b0c9b82004-04-30Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  //! Returns a DAV:activelock @[Parser.XML.Tree.SimpleNode] structure
b0c9b82004-04-30Henrik Grubbström (Grubba)  //! describing the lock.
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode get_xml()
b0c9b82004-04-30Henrik Grubbström (Grubba)  {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode res = SimpleElementNode("DAV:activelock", ([]))-> add_child(SimpleElementNode("DAV:locktype", ([]))-> add_child(stringp(locktype)? SimpleElementNode(locktype, ([])):locktype))-> add_child(SimpleElementNode("DAV:lockscope", ([]))-> add_child(stringp(lockscope)? SimpleElementNode(lockscope, ([])):lockscope))-> add_child(SimpleElementNode("DAV:depth", ([]))-> add_child(recursive? SimpleTextNode("Infinity"):SimpleTextNode("0")));
b0c9b82004-04-30Henrik Grubbström (Grubba)  if (owner) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode node; res->add_child(node = SimpleElementNode("DAV:owner", ([]))); node->replace_children(owner);
b0c9b82004-04-30Henrik Grubbström (Grubba)  }
fcfa7b2004-05-14Henrik Grubbström (Grubba)  if (expiry_delta) { res->add_child(SimpleElementNode("DAV:timeout", ([]))-> add_child(SimpleTextNode(sprintf("Second-%d", expiry_delta)))); } else { res->add_child(SimpleElementNode("DAV:timeout", ([]))-> add_child(SimpleTextNode("Infinite"))); }
b0c9b82004-04-30Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  res->add_child(SimpleElementNode("DAV:locktoken", ([]))-> add_child(SimpleElementNode("DAV:href", ([]))-> add_child(SimpleTextNode(locktoken))));
b0c9b82004-04-30Henrik Grubbström (Grubba)  return res; }
a720412004-05-14Martin Stjernholm  static string _sprintf (int flag) { return flag == 'O' && sprintf ("DAVLock(%O on %O, %s, %s%s)", locktoken, path, recursive ? "rec" : "norec", lockscope == "DAV:exclusive" ? "excl" : lockscope == "DAV:shared" ? "shared" : sprintf ("%O", lockscope), locktype == "DAV:write" ? "" : sprintf (", %O", locktype)); }
4c87da2004-04-28Henrik Grubbström (Grubba) }
091e702004-05-19Henrik Grubbström (Grubba) //! Configuration information for a site.
7f66d82001-08-24Martin Nilsson class Configuration
a896932001-01-03Per Hedbor { inherit BasicDefvar; constant is_configuration = 1; mapping enabled_modules = ([]); mapping(string:array(int)) error_log=([]); #ifdef PROFILE
091e702004-05-19Henrik Grubbström (Grubba)  mapping(string:array(int)) profile_map = ([]);
a896932001-01-03Per Hedbor #endif class Priority { string _sprintf() { return "Priority()"; } array (RoxenModule) url_modules = ({ }); array (RoxenModule) logger_modules = ({ }); array (RoxenModule) location_modules = ({ }); array (RoxenModule) filter_modules = ({ }); array (RoxenModule) last_modules = ({ }); array (RoxenModule) first_modules = ({ }); mapping (string:array(RoxenModule)) file_extension_modules = ([ ]); mapping (RoxenModule:multiset(string)) provider_modules = ([ ]); } class DataCache { int current_size, max_size, max_file_size; int hits, misses; void flush(); void expire_entry( string url ); void set( string url, string data, mapping meta, int expire ); array(string|mapping(string:mixed)) get( string url ); void init_from_variables( ); }; array(Priority) allocate_pris(); object throttler; RoxenModule types_module; RoxenModule dir_module; function types_fun; string name; int inited; // Protocol specific statistics. int requests, sent, hsent, received; function(string:int) log_function; DataCache datacache; int get_config_id(); string get_doc_for( string region, string variable ); string query_internal_location(RoxenModule|void mod); string query_name(); string comment();
0e15572001-02-23Martin Stjernholm  void unregister_urls(); void stop(void|int asynch);
c8bfed2003-06-02Henrik Grubbström (Grubba)  string|array(string) type_from_filename( string file, int|void to, string|void myext );
a896932001-01-03Per Hedbor 
c109fc2001-07-21Martin Stjernholm  string get_url();
a896932001-01-03Per Hedbor  array (RoxenModule) get_providers(string provides); RoxenModule get_provider(string provides); array(mixed) map_providers(string provides, string fun, mixed ... args); mixed call_provider(string provides, string fun, mixed ... args); array(function) file_extension_modules(string ext); array(function) url_modules(); mapping api_functions(void|RequestID id); array(function) logger_modules(); array(function) last_modules(); array(function) first_modules(); array location_modules(); array(function) filter_modules(); void init_log_file(); int|mapping check_security(function|object a, RequestID id, void|int slevel); void invalidate_cache(); void clear_memory_caches(); string examine_return_mapping(mapping m);
21d18b2004-05-14Henrik Grubbström (Grubba)  multiset(DAVLock) find_locks(string path, int(0..1) recursive, int(0..1) exclude_shared, RequestID id); DAVLock|LockFlag check_locks(string path, int(0..1) recursive, RequestID id);
a720412004-05-14Martin Stjernholm  mapping(string:mixed) unlock_file(string path, DAVLock lock, RequestID|int(0..0) id);
1894be2004-05-14Henrik Grubbström (Grubba)  int expire_locks(RequestID id); void refresh_lock(DAVLock lock);
21d18b2004-05-14Henrik Grubbström (Grubba)  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);
a896932001-01-03Per Hedbor  mapping|int(-1..0) low_get_file(RequestID id, int|void no_magic); mapping get_file(RequestID id, int|void no_magic, int|void internal_get); array(string) find_dir(string file, RequestID id, void|int(0..1) verbose);
ce64272001-01-14Martin Nilsson  array(int)|object(Stdio.Stat) stat_file(string file, RequestID id);
d720082004-04-19Martin Stjernholm  array open_file(string fname, string mode, RequestID id, void|int ig, void|int rc);
a896932001-01-03Per Hedbor  mapping(string:array(mixed)) find_dir_stat(string file, RequestID id); array access(string file, RequestID id); string real_file(string file, RequestID id); int|string try_get_file(string s, RequestID id, int|void status, int|void nocache,
6a613a2002-06-17Anders Johansson  int|void not_internal, mapping|void result_mapping);
2fbed12001-09-13Martin Nilsson  int(0..1) is_file(string virt_path, RequestID id, int(0..1)|void internal);
a896932001-01-03Per Hedbor  void start(int num); void save_me(); int save_one( RoxenModule o ); RoxenModule reload_module( string modname ); RoxenModule enable_module( string modname, RoxenModule|void me, ModuleInfo|void moduleinfo,
7902be2002-04-09Marcus Wellhardh  int|void nostart, int|void nosave );
a896932001-01-03Per Hedbor  void call_start_callbacks( RoxenModule me, ModuleInfo moduleinfo, ModuleCopies module ); void call_low_start_callbacks( RoxenModule me, ModuleInfo moduleinfo, ModuleCopies module ); int disable_module( string modname, int|void nodest ); int add_modules( array(string) mods, int|void now ); RoxenModule find_module(string name);
850c282001-01-10Per Hedbor #if ROXEN_COMPAT < 2.2 Sql.Sql sql_cache_get(string what); Sql.Sql sql_connect(string db); #endif
a896932001-01-03Per Hedbor  void enable_all_modules(); void low_init(void|int modules_already_enabled); string parse_rxml(string what, RequestID id, void|Stdio.File file, void|mapping defines ); void add_parse_module (RoxenModule mod); void remove_parse_module (RoxenModule mod); string real_file(string a, RequestID b);
6495292001-01-19Per Hedbor  mapping authenticate_throw( RequestID id, string realm, UserDB|void database, AuthModule|void method); User authenticate( RequestID id, UserDB|void database, AuthModule|void method ); array(AuthModule) auth_modules(); array(UserDB) user_databases();
e3f4662001-01-19Per Hedbor  AuthModule find_auth_module( string name ); UserDB find_user_database( string name );
a896932001-01-03Per Hedbor  static string _sprintf( ) { return "Configuration("+name+")"; } }
7f66d82001-08-24Martin Nilsson //! @appears Protocol
a896932001-01-03Per Hedbor class Protocol { inherit BasicDefvar;
a9fa0d2001-07-21Martin Stjernholm 
a896932001-01-03Per Hedbor  constant name = "unknown";
a9fa0d2001-07-21Martin Stjernholm  //! Name used for internal identification. constant prot_name = "unknown"; //! Name of the protocol as seen in the protocol part of a url.
a896932001-01-03Per Hedbor  constant supports_ipless = 0; constant requesthandlerfile = ""; constant default_port = 4711; int bound; int refs; program requesthandler; string path; int port; string ip; array(string) sorted_urls = ({}); mapping(string:mapping) urls = ([]);
ea65f72001-07-21Martin Stjernholm  mapping(Configuration:mapping) conf_data = ([]);
a896932001-01-03Per Hedbor  void ref(string url, mapping data); void unref(string url); Configuration find_configuration_for_url( string url, RequestID id, int|void no_default ); string get_key(); void save(); void restore(); };
e0ffe32001-02-05Per Hedbor class FakedVariables( mapping real_variables ) { static array _indices() { return indices( real_variables ); } static array _values() { return map( _indices(), `[] ); } static mixed fix_value( mixed what ) { if( !what ) return what; if( !arrayp(what) ) return what; // huh if( sizeof( what ) == 1 ) return what[0]; return what*"\0"; } static mixed `[]( string ind ) { return fix_value( real_variables[ ind ] ); } static mixed `->(string ind ) { return `[]( ind ); } static mixed `[]=( string ind, mixed what ) { real_variables[ ind ] = ({ what }); return what; } static mixed `->=(string ind, mixed what ) { return `[]=( ind,what ); } static mixed _m_delete( mixed what ) {
91d3c32001-03-12Martin Nilsson // report_debug(" _m_delete( %O )\n", what );
e0ffe32001-02-05Per Hedbor  return fix_value( m_delete( real_variables, what ) ); } static int _equal( mixed what ) { return `==(what); } static int `==( mixed what ) { if( mappingp( what ) && (real_variables == what) ) return 1; } static string _sprintf( int f ) { switch( f ) { case 'O': return sprintf( "FakedVariables(%O)", real_variables ); default: return sprintf( sprintf("%%%c", f ), real_variables ); } } static this_program `|( mapping what ) { foreach( indices(what), string q )`[]=( q,what[q] ); return this_object(); } static this_program `+=( mapping what ) { foreach( indices(what), string q )`[]=( q,what[q] ); return this_object(); } static this_program `+( mapping what ) { foreach( indices(what), string q )`[]=( q,what[q] ); return this_object(); }
e734d52001-02-18Mirar (Pontus Hagland)  static mapping cast(string to) { if (to[..6]=="mapping")
3ce5922001-02-18Mirar (Pontus Hagland)  { array v=indices(real_variables); return mkmapping(v,map(v,`[])); }
e734d52001-02-18Mirar (Pontus Hagland)  error("can't cast to %O\n",to); }
e0ffe32001-02-05Per Hedbor }
13b3532002-10-25Martin Stjernholm class PrefLanguages //! @appears PrefLanguages //! Support for language preferences. This object is typically //! accessed through @tt{id->misc->pref_languages@}. { int decoded=0; int sorted=0; array(string) subtags=({}); array(string) languages=({}); array(float) qualities=({});
8766282004-05-05Marcus Wellhardh  static string _sprintf(int c, mapping|void attrs) { return sprintf("PrefLanguages(%O)", get_languages()); }
13b3532002-10-25Martin Stjernholm  array(string) get_languages() { sort_lang(); return languages; } string get_language() { if(!languages || !sizeof(languages)) return 0; sort_lang(); return languages[0]; } array(float) get_qualities() { sort_lang(); return qualities; } float get_quality() { if(!qualities || !sizeof(qualities)) return 0.0; sort_lang(); return qualities[0]; } void set_sorted(array(string) lang, void|array(float) q) { languages=lang; if(q && sizeof(q)==sizeof(lang)) qualities=q; else qualities=({1.0})*sizeof(lang); sorted=1; decoded=1; } void sort_lang() { if(sorted && decoded) return; array(float) q; array(string) s=reverse(languages)-({""}), u=({}); if(!decoded) { q=({}); s=Array.map(s, lambda(string x) { float n=1.0; string sub=""; sscanf(lower_case(x), "%s;q=%f", x, n); if(n==0.0) return ""; sscanf(x, "%s-%s", x, sub); q+=({n}); u+=({sub}); return x; }); s-=({""}); decoded=1; } else q=reverse(qualities); sort(q,s,u); languages=reverse(s); qualities=reverse(q); subtags=reverse(u); sorted=1; } }
48b4112003-02-05Jonas Wallden 
b192532004-05-25Martin Stjernholm typedef function(CacheKey,mixed...:void) CacheActivationCB;
48b4112003-02-05Jonas Wallden 
b192532004-05-25Martin Stjernholm class CacheKey //! @appears CacheKey //! //! Used as @expr{id->misc->cachekey@}. Every request that might be //! cacheable has an instance, and the protocol cache which store the //! result of the request checks that this object still exists before //! using the cache entry. Thus other subsystems that provide data to //! the result can keep track of this object and destruct it whenever //! they change state in a way that invalidates the previous result. //! //! Those data providers should however not store this object //! directly, but instead call @[add_activation_cb]. That avoids //! unnecessary garbage (see below). //! //! A cache implementation must call @[activate] before storing the //! cache key. At that point the functions registered with //! @[add_activation_cb] are called, and the key gets added in the //! internal structures of the data providers. This avoids registering //! cache keys for results that never get cached, which would //! otherwise produce excessive amounts of garbage objects in those //! internal structures. Note that other code might need to call //! @[activate] to process the callbacks, so a key being active //! doesn't necessarily mean it's used in a cache. //! //! @note //! These objects can be destructed asynchronously; all accesses for //! things inside them have to rely on the interpreter lock, and code //! can never assume that @expr{id->misc->cachekey@} exists to begin //! with. The wrapper functions in @[RequestID] handles all this. {
3751a52004-05-18Martin Stjernholm #if ID_CACHEKEY_DEBUG
b192532004-05-25Martin Stjernholm  RoxenDebug.ObjectMarker __marker = RoxenDebug.ObjectMarker (this); #endif static array(array(CacheActivationCB|array)) activation_cbs; // Functions to call when the cache key is activated, i.e. stored // together with some result in a cache. Zero when the key already // is active. static void create (void|int activate_immediately) { if (!activate_immediately) activation_cbs = ({}); } void add_activation_cb (CacheActivationCB cb, mixed... args) //! Register a callback that will be called if and when this cache //! key is used in a cache, i.e. is activated. The callback gets //! this object followed by @[args] as arguments and should do //! whatever bookkeeping necessary to keep track of the cache key so //! that it can be destructed. //! //! If this cache key already is active then @[cb] is called right //! away. //! //! The registered callbacks will be called in the same order they //! are added. //! //! @note //! Cache keys can be destructed at any time, and @[cb] might get //! called with an already destructed object. //! //! @note //! Take care to avoid cyclic refs when the activation callback is //! registered. This object should e.g. not be among @[args], and
5c5d652004-05-25Martin Stjernholm  //! @[cb] should not be a lambda that contains references to this
b192532004-05-25Martin Stjernholm  //! object. { // Relying on the interpreter lock here. if (activation_cbs) // Relying on the interpreter lock here too. activation_cbs += ({({cb, args})}); else cb (this, @args); }
9750692004-06-01Martin Stjernholm  int activate()
b192532004-05-25Martin Stjernholm  //! Activate the cache key. This must be called when the key is
9750692004-06-01Martin Stjernholm  //! stored in a cache. Return nonzero if any callbacks got called.
b192532004-05-25Martin Stjernholm  { // Relying on the interpreter lock here. if (array(array(CacheActivationCB|array)) cbs = activation_cbs) { // Relying on the interpreter lock here too. activation_cbs = 0; foreach (cbs, [CacheActivationCB cb, array args]) cb (this, @args);
9750692004-06-01Martin Stjernholm  return sizeof (cbs);
b192532004-05-25Martin Stjernholm  }
9750692004-06-01Martin Stjernholm  return 0;
b192532004-05-25Martin Stjernholm  } int activated() //! Returns nonzero iff the key is activated. { // Relying on the interpreter lock here. return !activation_cbs; }
9750692004-06-01Martin Stjernholm  int activate_if_necessary() // Activate the key only if any activation cbs are installed. This // is a kludge to play safe in situations early in the request path // where we don't want to activate the key and where there aren't // any outstanding callbacks in the common case with a direct // request but might still be in the recursive case. Ignore if you // can.
b192532004-05-25Martin Stjernholm  { // Relying on the interpreter lock here. if (array(array(CacheActivationCB|array)) cbs = activation_cbs) {
9750692004-06-01Martin Stjernholm  if (sizeof (cbs)) { if (this) // Relying on the interpreter lock here. activation_cbs = 0; foreach (cbs, [CacheActivationCB cb, array args]) cb (this, @args); return 1; }
b192532004-05-25Martin Stjernholm  }
9750692004-06-01Martin Stjernholm  return 0;
b192532004-05-25Martin Stjernholm  } string _sprintf (int flag) { return flag == 'O' && ("CacheKey()" #ifdef ID_CACHEKEY_DEBUG + (__marker ? "[" + __marker->count + "]" : "")
3751a52004-05-18Martin Stjernholm #endif
b192532004-05-25Martin Stjernholm  ); }
3751a52004-05-18Martin Stjernholm }
b192532004-05-25Martin Stjernholm // Kludge for resolver problems static function _charset_decoder_func;
a896932001-01-03Per Hedbor class RequestID
7f66d82001-08-24Martin Nilsson //! @appears RequestID
a896932001-01-03Per Hedbor //! The request information object contains all request-local information and //! server as the vessel for most forms of intercommunication between modules, //! scripts, RXML and so on. It gets passed round to almost all API callbacks //! worth mentioning. A RequestID object is born when an incoming request is //! encountered, and its life expectancy is short, as it dies again when the //! request has passed through all levels of the <ref>module type calling //! sequence</ref>. {
b192532004-05-25Martin Stjernholm #ifdef ID_OBJ_DEBUG RoxenDebug.ObjectMarker __marker = RoxenDebug.ObjectMarker (this); #endif
a896932001-01-03Per Hedbor  Configuration conf; Protocol port_obj; //! The port object this request came from. int time; //! Time of the request, standard unix time (seconds since the epoch; 1970). string raw_url; //! The nonparsed, nontouched, non-* URL requested by the client. //! Hence, this path is unlike <ref>not_query</ref> and //! <ref>virtfile</ref> not relative to the server URL and must be //! used in conjunction with the former to generate absolute paths //! within the server. Be aware that this string will contain any //! URL variables present in the request as well as the file path. int do_not_disconnect; //! Typically 0, meaning the channel to the client will be disconnected upon //! finishing the request and the RequestID object destroyed with it.
e0ffe32001-02-05Per Hedbor  mapping (string:array) real_variables;
a896932001-01-03Per Hedbor  //! Form variables submitted by the client browser, as found in the //! <tt>form</tt> scope in RXML. Both query (as found in the query part of //! the URL) and POST (submitted in the request body) variables share this //! scope, with query variables having priority over POST ones. In other //! words, the query part of the URL overrides whatever variables are sent //! in the request body. //! //! The indices and values of this mapping map to the names and values of //! the variable names. All data (names and values) are decoded from their //! possible transport encoding.
e0ffe32001-02-05Per Hedbor  //! //! The value is always an array
a896932001-01-03Per Hedbor 
5aca9b2001-09-28Martin Stjernholm  mapping(string:mixed)|FakedVariables variables;
e0ffe32001-02-05Per Hedbor  //! @decl mapping(string:mixed) variables; //! //! The variables mapping is more or less identical to the //! real_variables maping, but each variable can only have one //! value, if the form variable was sent multiple times from the //! client (this happens, as an example, if you have checkbox //! variables with the same name but different values), the values //! will be separated with \0 (the null character) in this mapping.
9644732001-11-27Martin Stjernholm  mapping (string:mixed) misc;
a896932001-01-03Per Hedbor  //! This mapping contains miscellaneous non-standardized information, and //! is the typical location to store away your own request-local data for //! passing between modules et cetera. Be sure to use a key unique to your //! own application.
99e65a2003-06-18Tomas Nilsson  mapping (string:mixed) connection_misc; //! This mapping contains miscellaneous non-standardized information, and //! is the typical location to store away your own connection-local data //! for passing between requests on the same connection et cetera. Be sure //! to use a key unique to your own application.
a896932001-01-03Per Hedbor  mapping (string:string) cookies; //! The indices and values map to the names and values of the cookies sent //! by the client for the requested page. All data (names and values) are //! decoded from their possible transport encoding.
5588692001-04-22Per Hedbor  mapping (string:array(string)|string) request_headers;
a896932001-01-03Per Hedbor  //! Indices and values map to the names and values of all HTTP headers sent //! with the request; all data has been transport decoded, and the header //! names are canonized (lowercased) on top of that. Here is where you look //! for the "user-agent" header, the "referer" [sic!] header and similar //! interesting data provided by the client. mapping (string:mixed) throttle; // ? mapping (string:mixed) client_var; //! The client scope; a mapping of various client-related variables, indices //! being the entity names and the values being their values respectively. multiset(string) prestate; //! A multiset of all prestates harvested from the URL. Prestates are boolean //! flags, who are introduced in an extra leading path segment of the URL //! path put within parentheses, as in <a //! href="http://docs.roxen.com/(tables)/">docs://www.roxen.com/(tables)/</a>, //! this rendering a prestate multiset <pi>(&lt; "tables" &gt;)</pi>. //! //! Prestates are mostly useful for debugging purposes, since prestates //! generally lead to multiple URLs for identical documents resulting in //! poor usage of browser/proxy caches and the like. See <ref>config</ref>. multiset(string) config; //! Much like prestates, the id->config multiset is typically used for //! boolean information of state supplied by the client. The config state, //! however, is hidden in a client-side cookie treated specially by roxen, //! namely the <tt>RoxenConfig</tt> cookie. multiset(string) supports; //! All flags set by the supports system. multiset(string) pragma; //! All pragmas (lower-cased for canonization) sent with the request. For //! real-world applications typically only <pi>pragma["no-cache"]</pi> is of //! any particular interest, this being sent when the user does a forced //! reload of the page. array(string) client; array(string) referer; Stdio.File my_fd; // Don't touch; use the returned file descriptor from connection() instead. string prot; //! The protocol used for the request, e g "FTP", "HTTP/1.0", "HTTP/1.1". //! (Se also <ref>clientprot</ref>.) string clientprot; //! The protocol the client wanted to use in the request. This may //! not be the same as <ref>prot</ref>, if the client wanted to talk //! a higher protocol version than the server supports to date. string method; //! The method used by the client in this request, e g "GET", "POST". string realfile; //! When the the requested resource is an actual file in the real //! filesystem, this is its path. string virtfile; //! The mountpoint of the location module that provided the requested file. //! Note that this is not accessable from location modules; you need to keep //! track of your mountpoint on your own using <ref>defvar()</ref> and //! <ref>query()</ref>. This mountpoint is relative to the server URL. string rest_query; //! The scraps and leftovers of the requested URL's query part after //! removing all variables (that is, all key=value pairs) from it. string raw; //! The raw, untouched request in its entirety. string query; //! The entire raw query part (all characters after the first question mark, //! '?') of the requested URL. string not_query; //! The part of the path segment of the requested URL that is below //! the virtual server's mountpoint. For a typical server //! registering a URL with no ending path component, not_query will //! contain all characters from the leading '/' to, but not //! including, the first question mark ('?') of the URL. string extra_extension; string data; //! The raw request body, containing non-decoded post variables et cetera. string leftovers;
9689f12002-09-03Martin Stjernholm  string rawauth, realauth; // Used by many modules, so let's keep this.
a896932001-01-03Per Hedbor  string since; string remoteaddr; //! The client's IP address. string host; //! The client's hostname, if resolved.
6322612004-05-06Martin Stjernholm  multiset(string) cache_status = (<>);
46d4cb2001-08-22Martin Stjernholm  //! Contains the caches that was hit when the request was served. //! See the docstring for @tt{$cache-status@} in the @tt{LogFormat@} //! global variable for known values, but note that the multiset //! actually never contains the value "nocache"; it's only written //! when the multiset is empty.
7fafb82001-07-25Johan Sundström  object root_id; //! @decl RequestID root_id; //! The root id object directly associated with the request - remains //! the same for all id objects generated by <insert href> tags and //! similar conditions that invoke @[clone_me()].
a896932001-01-03Per Hedbor  static void create(Stdio.File fd, Protocol port, Configuration conf){} void send(string|object what, int|void len){}
a8e2b32004-05-07Henrik Grubbström (Grubba)  static SimpleNode xml_data; // XML data for the request.
24e9142003-04-22Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode get_xml_data()
24e9142003-04-22Henrik Grubbström (Grubba)  {
b105b52003-06-11Henrik Grubbström (Grubba)  if (!sizeof(data)) return 0;
24e9142003-04-22Henrik Grubbström (Grubba)  if (xml_data) return xml_data; // FIXME: Probably ought to check that the content-type for // the request is text/xml.
c2d7aa2004-04-13Martin Stjernholm  DAV_WERROR("Parsing XML data: %O\n", data);
a8e2b32004-05-07Henrik Grubbström (Grubba)  return xml_data = Parser.XML.Tree.simple_parse_input(data, 0, Parser.XML.Tree.PARSE_ENABLE_NAMESPACES);
24e9142003-04-22Henrik Grubbström (Grubba)  }
76d1192004-05-05Henrik Grubbström (Grubba)  // Parsed if-header for the request.
53cb8b2004-05-06Henrik Grubbström (Grubba)  static mapping(string:array(array(array(string)))) if_data;
76d1192004-05-05Henrik Grubbström (Grubba) 
2550a42004-05-17Martin Stjernholm #ifdef IF_HEADER_DEBUG #define IF_HDR_MSG(X...) werror (X) #else #define IF_HDR_MSG(X...) #endif
76d1192004-05-05Henrik Grubbström (Grubba)  //! Parse an RFC 2518 9.4 "If Header". //! //! @note //! For speed reasons the parsing is rather forgiving. //! //! @returns
1093e42004-05-05Henrik Grubbström (Grubba)  //! Returns @expr{0@} (zero) if there was no if header, or //! if parsing of the if header failed. //! Returns a mapping from resource name to condition on success.
76d1192004-05-05Henrik Grubbström (Grubba)  //! //! A condition is represented as an array of sub-conditions //! (@tt{List@} in RFC 2518), where each sub-condition is an array //! of tokens, and each token is an array of two elements, where //! the first is one of the strings @expr{"not"@}, @expr{"etag"@}, //! or @expr{"key"@}, and the second is the value.
1093e42004-05-05Henrik Grubbström (Grubba)  //! //! The resource @expr{0@} (zero) represents the default resource.
76d1192004-05-05Henrik Grubbström (Grubba)  mapping(string:array(array(array(string)))) get_if_data() {
2550a42004-05-17Martin Stjernholm  if (if_data) { IF_HDR_MSG ("get_if_data(): Returning cached result\n"); return sizeof(if_data) && if_data; }
76d1192004-05-05Henrik Grubbström (Grubba) 
53cb8b2004-05-06Henrik Grubbström (Grubba)  if_data = ([]); // Negative caching.
76d1192004-05-05Henrik Grubbström (Grubba)  string raw_header;
2550a42004-05-17Martin Stjernholm  if (!(raw_header = request_headers->if)) { IF_HDR_MSG ("get_if_data(): No if header\n"); return 0; }
76d1192004-05-05Henrik Grubbström (Grubba)  array(array(string|int|array(array(string)))) decoded_if = MIME.decode_words_tokenized_labled(raw_header);
2550a42004-05-17Martin Stjernholm #if 0 IF_HDR_MSG("get_if_data(): decoded_if: %O\n", decoded_if); #endif if (!sizeof(decoded_if)) { IF_HDR_MSG("Got only whitespace.\n"); return 0; }
76d1192004-05-05Henrik Grubbström (Grubba)  mapping(string:array(array(array(string)))) res = ([ 0: ({}) ]);
2a86b62004-05-08Henrik Grubbström (Grubba)  string tmp_resource;
76d1192004-05-05Henrik Grubbström (Grubba)  string resource; foreach(decoded_if, array(string|int|array(array(string))) symbol) { switch (symbol[0]) { case "special":
2a86b62004-05-08Henrik Grubbström (Grubba)  switch(symbol[1]) { case '<': tmp_resource = ""; break; case '>': resource = tmp_resource; tmp_resource = 0; // Normalize. // FIXME: Check that the protocol and server parts refer // to this server. // FIXME: Support for servers mounted on subpaths. catch { resource = Standards.URI(resource)->path; }; if (!sizeof(resource) || (resource[-1] != '/')) resource += "/"; if (!res[resource]) res[resource] = ({}); break; default: if (tmp_resource) tmp_resource += sprintf("%c", symbol[1]); break; }
76d1192004-05-05Henrik Grubbström (Grubba)  break; case "word":
2a86b62004-05-08Henrik Grubbström (Grubba)  case "domain-literal":
76d1192004-05-05Henrik Grubbström (Grubba)  // Resource
2a86b62004-05-08Henrik Grubbström (Grubba)  if (!tmp_resource) return 0; tmp_resource += symbol[1];
76d1192004-05-05Henrik Grubbström (Grubba)  break; case "comment": // Parenthesis expression.
2a86b62004-05-08Henrik Grubbström (Grubba)  if (tmp_resource) { // Inside a resource. tmp_resource += "(" + symbol[1][0][0] + ")"; break; }
76d1192004-05-05Henrik Grubbström (Grubba)  array(array(string|int|array(array(string)))) sub_expr = MIME.decode_words_tokenized_labled(symbol[1][0][0]); int i; array(array(string)) expr = ({});
2a86b62004-05-08Henrik Grubbström (Grubba)  string tmp_key;
76d1192004-05-05Henrik Grubbström (Grubba)  for (i = 0; i < sizeof(sub_expr); i++) { switch(sub_expr[i][0]) { case "special":
2a86b62004-05-08Henrik Grubbström (Grubba)  switch(sub_expr[i][1]) { case '<': tmp_key = ""; break; case '>':
2550a42004-05-17Martin Stjernholm  if (!tmp_key) { IF_HDR_MSG("No tmp_key.\n"); return 0; }
2a86b62004-05-08Henrik Grubbström (Grubba)  expr += ({ ({ "key", tmp_key }) }); tmp_key = 0; break; default: if (tmp_key) tmp_key += sprintf("%c", sub_expr[i][1]); break; }
76d1192004-05-05Henrik Grubbström (Grubba)  break; case "domain-literal":
2a86b62004-05-08Henrik Grubbström (Grubba)  if (tmp_key) { tmp_key += sub_expr[i][1]; break; }
76d1192004-05-05Henrik Grubbström (Grubba)  // entity-tag. string etag = sub_expr[i][1]; // etag is usually something like "[\"some etag\"]" here.
e589e52004-05-10Henrik Grubbström (Grubba)  sscanf(etag, "[%s]", etag); // Remove brackets
76d1192004-05-05Henrik Grubbström (Grubba)  expr += ({ ({ "etag", etag }) }); break; case "word": // State-token or Not.
2a86b62004-05-08Henrik Grubbström (Grubba)  if (tmp_key) { tmp_key += sub_expr[i][1]; break; } if (lower_case(sub_expr[i][1]) == "not") {
76d1192004-05-05Henrik Grubbström (Grubba)  // Not expr += ({ ({ "not", 0 }) });
2a86b62004-05-08Henrik Grubbström (Grubba)  break;
76d1192004-05-05Henrik Grubbström (Grubba)  }
2550a42004-05-17Martin Stjernholm  IF_HDR_MSG("Word outside key: %O\n", sub_expr[i][1]); report_debug("Syntax error in if-header: %O\n", raw_header);
2a86b62004-05-08Henrik Grubbström (Grubba)  return 0;
76d1192004-05-05Henrik Grubbström (Grubba)  } }
2550a42004-05-17Martin Stjernholm  if (tmp_key) { IF_HDR_MSG("Active tmp_key: %O\n", tmp_key); report_debug("Syntax error in if-header: %O\n", raw_header); return 0; }
76d1192004-05-05Henrik Grubbström (Grubba)  res[resource] += ({ expr }); break; default: report_debug("Syntax error in if-header: %O\n", raw_header); return 0; } }
2550a42004-05-17Martin Stjernholm  if (tmp_resource) { IF_HDR_MSG("Active tmp_resource: %O\n", tmp_resource); report_debug("Syntax error in if-header: %O\n", raw_header); return 0; } IF_HDR_MSG("get_if_data(): Parsed if header: %s:\n" "%O\n", raw_header, res);
76d1192004-05-05Henrik Grubbström (Grubba)  return if_data = res; }
24e9142003-04-22Henrik Grubbström (Grubba) 
5430602001-07-21Martin Stjernholm  static string cached_url_base; string url_base() //! Returns the base part of the URL, i.e. what should be added in //! front of a path in the virtual filesystem to get the absolute
3c49612001-07-21Martin Stjernholm  //! URL to the page. The returned string ends with a "/", or is "" //! if no server base could be found.
5430602001-07-21Martin Stjernholm  //! //! This function gets the correct host for protocols that handles //! IP-less hosts. { // Note: Code duplication in protocols/http.pike. if (!cached_url_base) { string tmp;
3c49612001-07-21Martin Stjernholm  // First consult the port object. if (port_obj) { string host = port_obj->conf_data[conf]->hostname; if (host == "*" && conf && sizeof (host = conf->get_url())) if (sscanf (host, "%*s://%[^:/]", host) < 2) host = port_obj->ip; cached_url_base = port_obj->prot_name + "://" + host;
5430602001-07-21Martin Stjernholm  if (port_obj->port != port_obj->default_port) cached_url_base += ":" + port_obj->port; }
3c49612001-07-21Martin Stjernholm  // Then try the configuration url. else if (conf && sizeof (tmp = conf->get_url())) cached_url_base = tmp[..sizeof (tmp) - 2]; // Remove trailing '/'.
5430602001-07-21Martin Stjernholm 
3c49612001-07-21Martin Stjernholm  // Lastly use a pathetic fallback. With this the produced urls // will still be relative, which has some chance of working. else return cached_url_base = "";
5430602001-07-21Martin Stjernholm  if (string p = misc->site_prefix_path) cached_url_base += p; cached_url_base += "/"; } return cached_url_base; }
f7ea0c2002-02-06Martin Stjernholm  void add_response_header (string name, string value)
b421932002-01-29Martin Stjernholm  //! Adds a header @[name] with the value @[value] to be sent in the //! http response. An existing header with the same name will not be //! overridden, instead another (duplicate) header line will be sent //! in the response. //! //! @note //! If used from within an RXML parse session, this function will //! ensure that the new header is registered properly in the RXML //! p-code cache. That's the primary reason to used it instead of //! adding the header directly to @tt{misc->moreheads@} or //! @tt{misc->defines[" _extra_heads"]@}.
f7ea0c2002-02-06Martin Stjernholm  { mapping hdrs = misc->defines && misc->defines[" _extra_heads"] || misc->moreheads; if (!hdrs) hdrs = misc->moreheads = ([]); // Essentially Roxen.add_http_header inlined. Can't refer to it // from here due to the recursive resolver problems in Pike. array|string cur_val = hdrs[name]; if(cur_val) { if(arrayp(cur_val)) { if (!has_value(cur_val, value)) cur_val += ({ value }); } else { if (cur_val != value) cur_val = ({ cur_val, value }); } } else cur_val = value; if (hdrs == misc->moreheads) hdrs[name] = cur_val; else if (object/*(RXML.Context)*/ ctx = RXML_CONTEXT) ctx->set_var (name, cur_val, "header"); else hdrs[name] = cur_val; }
b421932002-01-29Martin Stjernholm 
f7ea0c2002-02-06Martin Stjernholm  void set_response_header (string name, string value)
b421932002-01-29Martin Stjernholm  //! Sets the header @[name] to the value @[value] to be sent in the //! http response. If an existing header with the same name exists, //! its value(s) will be overridden. This is useful for headers like //! "Expire-Time", otherwise @[add_response_header] is typically a //! better choice. //! //! @note //! If used from within an RXML parse session, this function will //! ensure that the new header is registered properly in the RXML //! p-code cache. That's the primary reason to used it instead of //! adding the header directly to @tt{misc->moreheads@} or //! @tt{misc->defines[" _extra_heads"]@}.
f7ea0c2002-02-06Martin Stjernholm  { if (misc->defines && misc->defines[" _extra_heads"]) { misc->defines[" _extra_heads"][name] = value; if (object/*(RXML.Context)*/ ctx = RXML_CONTEXT) ctx->signal_var_change (name, "header"); } else { if (!misc->moreheads) misc->moreheads = ([]); misc->moreheads[name] = value; } }
b421932002-01-29Martin Stjernholm 
ae77c42004-05-05Martin Stjernholm  static MultiStatus multi_status; MultiStatus get_multi_status()
8cedaa2004-05-07Martin Stjernholm  //! Returns a @[MultiStatus] object that will be used to produce a //! 207 Multi-Status response (RFC 2518 10.2). It's only consultet //! if the result returned from @[RoxenModule.find_file] et al is an //! empty mapping.
ae77c42004-05-05Martin Stjernholm  //! //! @note //! It's not necessarily safe to assume that there aren't any //! multi-status responses stored here already on entry to
8cedaa2004-05-07Martin Stjernholm  //! @[find_file] et al. C.f. @[multi_status_size].
ae77c42004-05-05Martin Stjernholm  //! //! @seealso //! @[set_status_for_path] { if (!multi_status) multi_status = MultiStatus(); return multi_status; } int multi_status_size() //! Returns the number responses that have been added to the //! @[MultiStatus] object returned by @[get_multi_status]. Useful to //! see whether a (part of a) recursive operation added any errors //! or other results. { return multi_status && multi_status->num_responses(); } void set_status_for_path (string path, int status_code, string|void message, mixed... args) //! Register a status to be included in the response that applies //! only for the given path. This is used for recursive operations //! that can yield different results for different encountered files //! or directories. //! //! The status is stored in the @[MultiStatus] object returned by //! @[get_multi_status]. //! //! @param path //! Absolute path in the configuration to which the status //! applies. Note that filesystem modules need to prepend their //! location to their internal paths. //! //! @param status_code //! The HTTP status code. //! //! @param message //! If given, it's a message to include in the response. The //! message may contain line feeds ('\n') and ISO-8859-1
197e812004-05-06Martin Stjernholm  //! characters in the ranges 32..126 and 128..255. Line feeds are //! converted to spaces if the response format doesn't allow them.
ae77c42004-05-05Martin Stjernholm  //! //! @param args //! If there are more arguments after @[message] then @[message] //! is taken as an @[sprintf] style format string which is used to //! format @[args]. //! //! @seealso //! @[Roxen.http_status] { if (sizeof (args)) message = sprintf (message, @args); ASSERT_IF_DEBUG (has_prefix (path, "/")); get_multi_status()->add_status (url_base() + path[1..], status_code, message); } void set_status_for_url (string url, int status_code, string|void message, mixed... args) //! Register a status to be included in the response that applies //! only for the given URL. Similar to @[set_status_for_path], but //! takes a complete URL instead of an absolute path within the //! configuration. { if (sizeof (args)) message = sprintf (message, @args); get_multi_status()->add_status (url, status_code, message); }
48b4112003-02-05Jonas Wallden  // Charset handling
6322612004-05-06Martin Stjernholm  array(string) output_charset = ({});
9689f12002-09-03Martin Stjernholm  string input_charset; void set_output_charset( string|function to, int|void mode ) {
80f43b2003-03-24Martin Stjernholm  if (object/*(RXML.Context)*/ ctx = RXML_CONTEXT) ctx->add_p_code_callback ("set_output_charset", to, mode);
6322612004-05-06Martin Stjernholm  if( search( output_charset, to ) != -1 ) // Already done.
9689f12002-09-03Martin Stjernholm  return;
6322612004-05-06Martin Stjernholm  switch( mode )
9689f12002-09-03Martin Stjernholm  { case 0: // Really set. output_charset = ({ to }); break; case 1: // Only set if not already set. if( !sizeof( output_charset ) ) output_charset = ({ to }); break; case 2: // Join. output_charset |= ({ to }); break; } }
48b4112003-02-05Jonas Wallden  static string charset_name(function|string what) { switch (what) { case string_to_unicode: return "ISO10646-1"; case string_to_utf8: return "UTF-8"; default: return upper_case((string) what); } } static function charset_function(function|string what, int allow_entities) { switch (what) { case "ISO-10646-1": case "ISO10646-1": case string_to_unicode: return string_to_unicode; case "UTF-8": case string_to_utf8: return string_to_utf8; default: catch { // Use entity fallback if content type allows it function fallback_func = allow_entities && lambda(string char) { return sprintf("&#x%x;", char[0]); }; _charset_decoder_func =
c2d7aa2004-04-13Martin Stjernholm  _charset_decoder_func || Roxen->_charset_decoder;
48b4112003-02-05Jonas Wallden  return _charset_decoder_func(Locale.Charset.encoder((string) what, "", fallback_func)) ->decode; }; } return lambda(string what) { return what; }; } static array(string) join_charset(string old, function|string add, function oldcodec, int allow_entities) { switch (old && upper_case(old)) { case 0: return ({ charset_name(add), charset_function(add, allow_entities) }); case "ISO10646-1": case "UTF-8": return ({ old, oldcodec }); // Everything goes here. :-) case "ISO-2022": return ({ old, oldcodec }); // Not really true, but how to know this? default: // Not true, but there is no easy way to add charsets yet... return ({ charset_name(add), charset_function(add, allow_entities) }); } } array(string) output_encode(string what, int|void allow_entities, string|void force_charset) {
b038b12003-02-19Jonas Wallden  // Performance optimization for unneeded ISO-8859-1 recoding of // strings which already are narrow. if (String.width(what) == 8) { if (force_charset) { if (upper_case(force_charset) == "ISO-8859-1") return ({ "ISO-8859-1", what }); } else {
6322612004-05-06Martin Stjernholm  if (sizeof(output_charset) == 1 &&
b038b12003-02-19Jonas Wallden  upper_case(output_charset[0]) == "ISO-8859-1") return ({ "ISO-8859-1", what }); } }
48b4112003-02-05Jonas Wallden  if (!force_charset) { string charset; function encoder;
6322612004-05-06Martin Stjernholm  foreach( output_charset, string|function f )
48b4112003-02-05Jonas Wallden  [charset,encoder] = join_charset(charset, f, encoder, allow_entities); if (!encoder) if (String.width(what) > 8) { charset = "UTF-8"; encoder = string_to_utf8; } if (encoder) what = encoder(what); return ({ charset, what }); } else return ({ 0, Locale.Charset.encoder((force_charset / "=")[-1])->feed(what)->drain() }); }
a896932001-01-03Per Hedbor  string scan_for_query( string f ) { if(sscanf(f,"%s?%s", f, query) == 2) { string v, a, b; foreach(query / "&", v)
ccf8312001-08-15Per Hedbor  if(sscanf(v, "%s=%s", a, b) == 2) { a = _Roxen.http_decode_string(replace(a, "+", " ")); b = _Roxen.http_decode_string(replace(b, "+", " ")); real_variables[ a ] += ({ b }); } else if(strlen( rest_query )) rest_query += "&" + _Roxen.http_decode_string( v ); else rest_query = _Roxen.http_decode_string( v );
a896932001-01-03Per Hedbor  rest_query=replace(rest_query, "+", "\000"); } return f; }
c2d7aa2004-04-13Martin Stjernholm  mapping(string:string) make_response_headers (mapping(string:mixed) file)
2e9a312004-04-13Martin Stjernholm  //! Make the response headers from a response mapping for this //! request. The headers associated with transfer modifications of //! the response, e.g. 206 Partial Content and 304 Not Modified, are //! not calculated here. //! //! @note //! Is destructive on @[file] and on various data in the request; //! should only be called once for a @[RequestID] instance.
c2d7aa2004-04-13Martin Stjernholm  {
a7332e2004-04-20Martin Stjernholm  if (!file->stat) file->stat = misc->stat; if(objectp(file->file)) { if(!file->stat) file->stat = file->file->stat(); if (zero_type(misc->cacheable) && file->file->is_file) { // Assume a cacheablity on the order of the age of the file. misc->cacheable = (predef::time(1) - file->stat[ST_MTIME])/4; } } if( Stat fstat = file->stat ) {
2a86b62004-05-08Henrik Grubbström (Grubba)  if( !file->len && (fstat[1] >= 0) && file->file )
a7332e2004-04-20Martin Stjernholm  file->len = fstat[1]; if ( fstat[ST_MTIME] > misc->last_modified ) misc->last_modified = fstat[ST_MTIME]; } if (!file->error) file->error = Protocols.HTTP.HTTP_OK;
c2d7aa2004-04-13Martin Stjernholm 
de29ac2004-04-14Martin Stjernholm  if(!file->type) file->type="text/plain";
a7332e2004-04-20Martin Stjernholm  mapping(string:string) heads = ([]);
c2d7aa2004-04-13Martin Stjernholm  if( !zero_type(misc->cacheable) && (misc->cacheable != INITIAL_CACHEABLE) ) { if (!misc->cacheable) { // It expired a year ago. heads["Expires"] = Roxen->http_date( predef::time(1)-31557600 ); } else heads["Expires"] = Roxen->http_date( predef::time(1)+misc->cacheable ); if (misc->cacheable < INITIAL_CACHEABLE) { // Data with expiry is assumed to have been generated at the // same instant. misc->last_modified = predef::time(1); } } if (misc->last_modified) heads["Last-Modified"] = Roxen->http_date(misc->last_modified); { string charset=""; if( stringp(file->data) ) {
6322612004-05-06Martin Stjernholm  if (sizeof (output_charset) ||
c2d7aa2004-04-13Martin Stjernholm  has_prefix (file->type, "text/") || (String.width(file->data) > 8)) { int allow_entities = has_prefix(file->type, "text/xml") || has_prefix(file->type, "text/html"); [charset,file->data] = output_encode( file->data, allow_entities ); if( charset && (search(file["type"], "; charset=") == -1)) charset = "; charset="+charset; else charset = ""; } file->len = strlen(file->data); }
de29ac2004-04-14Martin Stjernholm  heads["Content-Type"] = file->type + charset;
c2d7aa2004-04-13Martin Stjernholm  } heads["Accept-Ranges"] = "bytes";
2284712004-04-25Henrik Grubbström (Grubba)  heads["Server"] = replace(roxenp()->version(), " ", "·");
2a86b62004-05-08Henrik Grubbström (Grubba)  if (file->error == 500) { // Internal server error. // Make sure the connection is closed to resync. heads["Connection"] = "close"; misc->connection = "close"; } else if( misc->connection )
c2d7aa2004-04-13Martin Stjernholm  heads["Connection"] = misc->connection; if(file->encoding) heads["Content-Encoding"] = file->encoding; heads->Date = Roxen->http_date(predef::time(1)); if(file->expires) heads->Expires = Roxen->http_date(file->expires); //if( file->len > 0 || (file->error != 200) ) heads["Content-Length"] = (string)file->len;
56b5852004-04-13Martin Stjernholm  if (misc->etag) heads->ETag = misc->etag;
c2d7aa2004-04-13Martin Stjernholm #ifdef RAM_CACHE
56b5852004-04-13Martin Stjernholm  if (!misc->etag && file->len &&
c2d7aa2004-04-13Martin Stjernholm  (file->data || file->file) &&
2e9a312004-04-13Martin Stjernholm  file->error == 200 && (<"HEAD", "GET">)[method] &&
c2d7aa2004-04-13Martin Stjernholm  (file->len < conf->datacache->max_file_size)) { string data = ""; if (file->file) { data = file->file->read(file->len); if (file->data && (sizeof(data) < file->len)) { data += file->data[..file->len - (sizeof(data)+1)]; }
2e9a312004-04-13Martin Stjernholm  m_delete(file, "file");
c2d7aa2004-04-13Martin Stjernholm  } else if (file->data) { data = file->data[..file->len - 1]; } file->data = data; heads->ETag = misc->etag =
b0838c2004-05-03Henrik Grubbström (Grubba)  sprintf("\"%s\"", Crypto.string_to_hex(Crypto.md5()->update(data)->digest()));
c2d7aa2004-04-13Martin Stjernholm  } #endif /* RAM_CACHE */
bb55552004-05-18Henrik Grubbström (Grubba)  if (misc->vary && sizeof(misc->vary)) {
c4596c2004-05-18Henrik Grubbström (Grubba)  heads->Vary = ((array)misc->vary)*", ";
bb55552004-05-18Henrik Grubbström (Grubba)  }
2e9a312004-04-13Martin Stjernholm  if(mappingp(file->extra_heads)) heads |= file->extra_heads; if(mappingp(misc->moreheads)) heads |= misc->moreheads;
c2d7aa2004-04-13Martin Stjernholm  return heads; }
9689f12002-09-03Martin Stjernholm  void adjust_for_config_path( string p ) { if( not_query ) not_query = not_query[ strlen(p).. ]; raw_url = raw_url[ strlen(p).. ]; misc->site_prefix_path = p; }
a896932001-01-03Per Hedbor  void end(string|void s, int|void keepit){} void ready_to_receive(){}
12bbbd2004-05-10Martin Stjernholm  void send_result(mapping|void result) { #ifdef DEBUG error ("send_result not overridden.\n"); #endif }
a896932001-01-03Per Hedbor  RequestID clone_me() { object c,t; c=object_program(t=this_object())(0, port_obj, conf); c->port_obj = port_obj; c->conf = conf;
7fafb82001-07-25Johan Sundström  c->root_id = root_id;
a896932001-01-03Per Hedbor  c->time = time; c->raw_url = raw_url;
ccf8312001-08-15Per Hedbor  c->real_variables = copy_value( real_variables ); c->variables = FakedVariables( c->real_variables );
a896932001-01-03Per Hedbor  c->misc = copy_value( misc ); c->misc->orig = t;
99e65a2003-06-18Tomas Nilsson  c->connection_misc = connection_misc;
a896932001-01-03Per Hedbor  c->prestate = prestate; c->supports = supports; c->config = config; c->client_var = client_var; c->remoteaddr = remoteaddr; c->host = host; c->client = client; c->referer = referer; c->pragma = pragma; c->cookies = cookies;
fcfa7b2004-05-14Henrik Grubbström (Grubba)  c->request_headers = request_headers + ([]);
a896932001-01-03Per Hedbor  c->my_fd = 0; c->prot = prot; c->clientprot = clientprot; c->method = method; c->rest_query = rest_query; c->raw = raw; c->query = query; c->not_query = not_query; c->data = data; c->extra_extension = extra_extension; c->realauth = realauth; c->rawauth = rawauth; c->since = since; return c; } Stdio.File connection( ) //! Returns the file descriptor used for the connection to the client. { return my_fd; } Configuration configuration() //! Returns the <ref>Configuration</ref> object of the virtual server that //! is handling the request. { return conf; }
b192532004-05-25Martin Stjernholm  string _sprintf (int flag) { return flag == 'O' && ("RequestID(" + (raw_url||"") + ")" #ifdef ID_OBJ_DEBUG + (__marker ? "[" + __marker->count + "]" : "") #endif ); }
a896932001-01-03Per Hedbor }
197e812004-05-06Martin Stjernholm class MultiStatusStatus (int http_code, void|string message)
c8bfed2003-06-02Henrik Grubbström (Grubba) {
197e812004-05-06Martin Stjernholm  constant is_status = 1;
a8e2b32004-05-07Henrik Grubbström (Grubba)  void build_response (SimpleElementNode response_node)
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode node = SimpleElementNode("DAV:status", ([]));
197e812004-05-06Martin Stjernholm  response_node->add_child (node); // No use wasting space on a good message in the status node since // we have it in the responsedescription instead.
a8e2b32004-05-07Henrik Grubbström (Grubba)  node->add_child(SimpleTextNode(sprintf("HTTP/1.1 %d ", http_code)));
197e812004-05-06Martin Stjernholm  if (message) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  node = SimpleElementNode ("DAV:responsedescription", ([]));
197e812004-05-06Martin Stjernholm  response_node->add_child (node);
a8e2b32004-05-07Henrik Grubbström (Grubba)  node->add_child (SimpleTextNode (message));
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
197e812004-05-06Martin Stjernholm  } int `== (mixed other) { return objectp (other) && object_program (other) == this_program && other->http_code == http_code && other->message == message; } int __hash() { return http_code + (message && hash (message)); } string _sprintf (int flag) { return flag == 'O' && sprintf ("MultiStatusStatus(%d,%O)", http_code, message);
c8bfed2003-06-02Henrik Grubbström (Grubba)  } }
a8e2b32004-05-07Henrik Grubbström (Grubba) private SimpleElementNode ok_status_node =
0662752004-05-13Martin Stjernholm  SimpleElementNode("DAV:status", ([]))->add_child(SimpleTextNode("HTTP/1.1 200 OK"));
c8bfed2003-06-02Henrik Grubbström (Grubba) 
197e812004-05-06Martin Stjernholm class MultiStatusPropStat { constant is_prop_stat = 1;
c8bfed2003-06-02Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  mapping(string:string|SimpleNode|array(SimpleNode)|MultiStatusStatus) properties = ([]);
071d472004-05-06Martin Stjernholm  //! The property settings. Indexed on property name (with complete //! XML namespace). Values are: //! //! @mixed
a8e2b32004-05-07Henrik Grubbström (Grubba)  //! @type array(SimpleNode)
071d472004-05-06Martin Stjernholm  //! The property value as a sequence of XML nodes. These nodes //! are used as children to the property nodes in the DAV //! protocol.
a8e2b32004-05-07Henrik Grubbström (Grubba)  //! @type SimpleNode
071d472004-05-06Martin Stjernholm  //! Same as an array containing only this node. //! @type string
a8e2b32004-05-07Henrik Grubbström (Grubba)  //! Same as a single @[Parser.XML.Tree.SimpleTextNode] with this value.
071d472004-05-06Martin Stjernholm  //! @type int(0..0) //! The property exists but has no value. //! @type MultiStatusStatus //! There was an error querying the property and this is the //! status it generated instead of a value. //! @endmixed
1904282003-06-17Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  void build_response (SimpleElementNode response_node)
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode ok_prop_node = SimpleElementNode("DAV:prop", ([])); mapping(MultiStatusStatus:SimpleNode) prop_nodes = ([]);
197e812004-05-06Martin Stjernholm  foreach (properties;
a8e2b32004-05-07Henrik Grubbström (Grubba)  string prop_name; string|SimpleNode|array(SimpleNode)|MultiStatusStatus value) {
197e812004-05-06Martin Stjernholm  if (objectp (value) && value->is_status) { // Group together failed properties according to status codes.
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode prop_node = prop_nodes[value];
197e812004-05-06Martin Stjernholm  if (!prop_node)
a8e2b32004-05-07Henrik Grubbström (Grubba)  prop_nodes[value] = prop_node = SimpleElementNode("DAV:prop", ([])); prop_node->add_child(SimpleElementNode(prop_name, ([])));
a7332e2004-04-20Martin Stjernholm  }
197e812004-05-06Martin Stjernholm  else { // The property is ok and has a value. string ms_type; // The DAV client in Windows XP Pro (at least) requires types // on the date fields to parse them correctly. The type system // is of course some MS goo. switch (prop_name) { case "DAV:creationdate": ms_type = "dateTime.tz"; break; case "DAV:getlastmodified": ms_type = "dateTime.rfc1123"; break; // MS header - format unknown. //case "DAV:lastaccessed": ms_type = "dateTime.tz"; break; }
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode node = SimpleElementNode(prop_name, ms_type ? ([ "urn:schemas-microsoft-com:datatypesdt": ms_type ]) : ([]));
197e812004-05-06Martin Stjernholm  ok_prop_node->add_child (node); if (arrayp (value)) node->replace_children (value); else if (stringp (value))
a8e2b32004-05-07Henrik Grubbström (Grubba)  node->add_child(SimpleTextNode(value));
197e812004-05-06Martin Stjernholm  else if (objectp (value)) node->add_child (value);
1904282003-06-17Henrik Grubbström (Grubba)  }
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
197e812004-05-06Martin Stjernholm  if (ok_prop_node->count_children()) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode propstat_node = SimpleElementNode("DAV:propstat", ([]));
197e812004-05-06Martin Stjernholm  response_node->add_child (propstat_node); propstat_node->add_child (ok_prop_node); propstat_node->add_child (ok_status_node);
1904282003-06-17Henrik Grubbström (Grubba)  }
c8bfed2003-06-02Henrik Grubbström (Grubba) 
a8e2b32004-05-07Henrik Grubbström (Grubba)  foreach (prop_nodes; MultiStatusStatus status; SimpleNode prop_node) { SimpleElementNode propstat_node = SimpleElementNode("DAV:propstat", ([]));
197e812004-05-06Martin Stjernholm  response_node->add_child (propstat_node); propstat_node->add_child (prop_node); status->build_response (propstat_node); } }
c8bfed2003-06-02Henrik Grubbström (Grubba) 
197e812004-05-06Martin Stjernholm  string _sprintf (int flag)
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
197e812004-05-06Martin Stjernholm  return flag == 'O' && sprintf ("MultiStatusPropStat(%O)", properties);
c8bfed2003-06-02Henrik Grubbström (Grubba)  } }
197e812004-05-06Martin Stjernholm typedef MultiStatusStatus|MultiStatusPropStat MultiStatusNode;
c8bfed2003-06-02Henrik Grubbström (Grubba) class MultiStatus {
197e812004-05-06Martin Stjernholm  static mapping(string:MultiStatusNode) status_set = ([]);
c8bfed2003-06-02Henrik Grubbström (Grubba) 
f8874d2004-04-29Martin Stjernholm  static mapping(string:string) args = ([ "xmlns:DAV": "DAV:", // MS namespace for data types; see comment in // XMLPropStatNode.add_property. Note: The XML parser in the // MS DAV client is broken and requires the break of the last // word "datatypesdt" to be exactly at this point. "xmlns:MS": "urn:schemas-microsoft-com:datatypes", ]);
ae77c42004-05-05Martin Stjernholm  int num_responses() //! Returns the number of responses that have been added to this //! object.
b303da2004-03-03Henrik Grubbström (Grubba)  {
197e812004-05-06Martin Stjernholm  return sizeof (status_set);
b303da2004-03-03Henrik Grubbström (Grubba)  }
197e812004-05-06Martin Stjernholm  MultiStatusNode get_response (string href) //! Returns the response stored for @[href] if there is any. //! //! @returns //! @mixed //! @type MultiStatusStatus //! There was some kind of error on @[href] and this is the //! stored status. This type is preferably checked by testing //! for a nonzero @expr{is_status@} member. //! @type MultiStatusPropStat //! A property query was successful on @[href] and the //! returned object contains the results for some or all //! properties. This type is preferably checked by testing for //! a nonzero @expr{is_prop_stat@} member. //! @endmixed
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
197e812004-05-06Martin Stjernholm  return status_set[href]; } mapping(string:MultiStatusNode) get_responses_by_prefix (string href_prefix) //! Returns the stored responses for all URI:s that got //! @[href_prefix] as prefix. See @[get_response] for details about //! the response value. //! //! Prefixes are only matched at @expr{"/"@} boundaries, so //! @expr{"http://x.se/foo"@} is considered a prefix of //! @expr{"http://x.se/foo/bar"@} but not //! @expr{"http://x.se/foobar"@}. As a special case, the empty //! string as @[href_prefix] returns all stored responses. { if (href_prefix == "") return status_set + ([]); mapping(string:MultiStatusNode) result = ([]); if (!has_suffix (href_prefix, "/")) { if (MultiStatusNode stat = status_set[href_prefix]) result[href_prefix] = stat; href_prefix += "/";
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
197e812004-05-06Martin Stjernholm  foreach (status_set; string href; MultiStatusNode stat) if (has_prefix (href, href_prefix)) result[href] = stat; return result;
c8bfed2003-06-02Henrik Grubbström (Grubba)  } //! Add DAV:propstat information about the property @[prop_name] for //! the resource @[href]. //! //! @param prop_value //! Optional property value. It can be one of the following: //! @mixed prop_value //! @type void|int(0..0) //! Operation performed ok, no value.
a8e2b32004-05-07Henrik Grubbström (Grubba)  //! @type string|SimpleNode|array(SimpleNode)
c8bfed2003-06-02Henrik Grubbström (Grubba)  //! Property has value @[prop_value].
197e812004-05-06Martin Stjernholm  //! @type MultiStatusStatus
c8bfed2003-06-02Henrik Grubbström (Grubba)  //! @type mapping(string:mixed) //! Operation failed as described by the mapping. //! @endmixed void add_property(string href, string prop_name,
a8e2b32004-05-07Henrik Grubbström (Grubba)  void|int(0..0)|string|array(SimpleNode)|SimpleNode|
197e812004-05-06Martin Stjernholm  MultiStatusStatus|mapping(string:mixed) prop_value)
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
197e812004-05-06Martin Stjernholm  MultiStatusPropStat prop_stat; if (MultiStatusNode stat = status_set[href]) if (stat->is_prop_stat) // This will cause override of an existing MultiStatusStatus. // Presumably it came from another file system that is now // being hidden. Or is it better to keep the status node and // do nothing here instead? prop_stat = stat; if (!prop_stat) prop_stat = status_set[href] = MultiStatusPropStat(); if (mappingp (prop_value)) prop_value = MultiStatusStatus (prop_value->error, prop_value->rettext); prop_stat->properties[prop_name] = prop_value;
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
ae77c42004-05-05Martin Stjernholm  void add_status (string href, int status_code, void|string message, mixed... args) //! Add a status for the specified url. The remaining arguments are //! the same as for @[Roxen.http_status]. {
197e812004-05-06Martin Stjernholm  if (sizeof (args)) message = sprintf (message, @args);
59ec342004-05-09Henrik Grubbström (Grubba)  if (!status_code) error("Bad status code!\n");
197e812004-05-06Martin Stjernholm  status_set[href] = MultiStatusStatus (status_code, message);
ae77c42004-05-05Martin Stjernholm  }
f8874d2004-04-29Martin Stjernholm  void add_namespace (string namespace) //! Add a namespace to the generated @tt{<multistatus>@} element. //! Useful if several properties share a namespace. { int ns_count = 0; string ns_name; while (args[ns_name = "xmlns:NS" + ns_count]) { if (args[ns_name] == namespace) return; ns_count++; } args[ns_name] = namespace; }
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode get_xml_node()
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode node; SimpleRootNode root = SimpleRootNode()-> add_child(SimpleHeaderNode((["version": "1.0", "encoding": "utf-8"])))-> add_child(node = SimpleElementNode("DAV:multistatus", args));
f8874d2004-04-29Martin Stjernholm 
a8e2b32004-05-07Henrik Grubbström (Grubba)  array(SimpleNode) response_xml = allocate(sizeof(status_set));
c8bfed2003-06-02Henrik Grubbström (Grubba)  int i;
ca04922003-12-22Henrik Grubbström (Grubba)  DAV_WERROR("Generating XML Nodes for status_set:%O\n", status_set);
c8bfed2003-06-02Henrik Grubbström (Grubba) 
8cedaa2004-05-07Martin Stjernholm  // Sort this because some client (which one?) requires collections // to come before the entries they contain.
bd5d4d2003-12-15Henrik Grubbström (Grubba)  foreach(sort(indices(status_set)), string href) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleElementNode response_node = SimpleElementNode("DAV:response", ([]))-> add_child(SimpleElementNode("DAV:href", ([]))-> add_child(SimpleTextNode(href)));
197e812004-05-06Martin Stjernholm  response_xml[i++] = response_node; status_set[href]->build_response (response_node);
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
f8874d2004-04-29Martin Stjernholm  node->replace_children(response_xml);
c8bfed2003-06-02Henrik Grubbström (Grubba)  return root; } mapping(string:mixed) http_answer() { string xml = get_xml_node()->render_xml(); return ([ "error": 207, "data": xml, "len": sizeof(xml), "type": "text/xml; charset=\"utf-8\"", ]); }
ae77c42004-05-05Martin Stjernholm  class Prefixed (static string href_prefix) { MultiStatus get_multi_status() {return MultiStatus::this;}
c8bfed2003-06-02Henrik Grubbström (Grubba)  void add_property(string path, string prop_name,
a8e2b32004-05-07Henrik Grubbström (Grubba)  void|int(0..0)|string|array(SimpleNode)|SimpleNode|
197e812004-05-06Martin Stjernholm  MultiStatusStatus|mapping(string:mixed) prop_value)
c8bfed2003-06-02Henrik Grubbström (Grubba)  { MultiStatus::add_property(href_prefix + path, prop_name, prop_value); }
ae77c42004-05-05Martin Stjernholm  void add_status (string path, int status_code, void|string message, mixed... args)
f8874d2004-04-29Martin Stjernholm  {
ae77c42004-05-05Martin Stjernholm  MultiStatus::add_status (href_prefix + path, status_code, message, @args);
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
ae77c42004-05-05Martin Stjernholm  void add_namespace (string namespace)
c8bfed2003-06-02Henrik Grubbström (Grubba)  {
ae77c42004-05-05Martin Stjernholm  MultiStatus::add_namespace (namespace);
c8bfed2003-06-02Henrik Grubbström (Grubba)  }
ae77c42004-05-05Martin Stjernholm  MultiStatus.Prefixed prefix(string href_prefix) { return this_program (this_program::href_prefix + href_prefix);
c8bfed2003-06-02Henrik Grubbström (Grubba)  } }
ae77c42004-05-05Martin Stjernholm  MultiStatus.Prefixed prefix (string href_prefix) //! Returns an object with the same @expr{add_*@} methods as this //! one except that @[href_prefix] is implicitly prepended to every //! path. { return Prefixed (href_prefix); }
c8bfed2003-06-02Henrik Grubbström (Grubba) }
c5dd9f2004-03-16Henrik Grubbström (Grubba) // Only for protyping and breaking of circularities. static class PropertySet { string path;
ae77c42004-05-05Martin Stjernholm  RequestID id; Stat get_stat(); mapping(string:string) get_response_headers();
c5dd9f2004-03-16Henrik Grubbström (Grubba)  multiset(string) query_all_properties();
a8e2b32004-05-07Henrik Grubbström (Grubba)  string|array(SimpleNode)|mapping(string:mixed)
c5dd9f2004-03-16Henrik Grubbström (Grubba)  query_property(string prop_name); mapping(string:mixed) start(); void unroll(); void commit(); mapping(string:mixed) set_property(string prop_name,
a8e2b32004-05-07Henrik Grubbström (Grubba)  string|array(SimpleNode) value);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) set_dead_property(string prop_name,
a8e2b32004-05-07Henrik Grubbström (Grubba)  array(SimpleNode) value);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) remove_property(string prop_name); mapping(string:mixed) find_properties(string mode,
ae77c42004-05-05Martin Stjernholm  MultiStatus.Prefixed result,
c5dd9f2004-03-16Henrik Grubbström (Grubba)  multiset(string)|void filt); }
0662752004-05-13Martin Stjernholm //! See @[RoxenModule.check_locks].
119a552004-05-07Martin Stjernholm enum LockFlag { LOCK_NONE = 0, LOCK_SHARED_BELOW = 2, LOCK_SHARED_AT = 3, LOCK_OWN_BELOW = 4, LOCK_EXCL_BELOW = 6, LOCK_EXCL_AT = 7 };
0662752004-05-13Martin Stjernholm //! How to handle an existing destination when files or directories //! are moved or copied in a filesystem.
cdaab02004-05-10Martin Stjernholm enum Overwrite {
0662752004-05-13Martin Stjernholm  NEVER_OVERWRITE = -1, //! Fail if the destination exists. Corresponds to an Overwrite //! header with the value "F" (RFC 2518 9.6). MAYBE_OVERWRITE = 0, //! If the source and destination are directories, overwrite the //! properties only. If the source and destination are files,
102dbe2004-05-13Martin Stjernholm  //! overwrite the file along with the properties. Otherwise fail if //! the destination exists.
0662752004-05-13Martin Stjernholm  DO_OVERWRITE = 1, //! If the destination exists then delete it recursively before //! writing the new content. Corresponds to an Overwrite header with //! the value "T" (RFC 2518 9.6).
cdaab02004-05-10Martin Stjernholm };
dfc1ad2004-05-13Henrik Grubbström (Grubba) //! State of the DAV:propertybehavior. //! //! @mixed //! @type int(0..0) //! DAV:omit. //! Failure to copy a property does not cause the entire copy //! to fail. //! @type int(1..1) //! DAV:keepalive "*". //! All live properties must be kept alive. //! @type multiset(string)
a720412004-05-14Martin Stjernholm //! Set of properties to keep alive. Properties not in the set //! should be copied according to best effort. The properties are //! listed with complete namespaces.
dfc1ad2004-05-13Henrik Grubbström (Grubba) //! @endmixed typedef int(0..1)|multiset(string) PropertyBehavior;
cdaab02004-05-10Martin Stjernholm 
a896932001-01-03Per Hedbor class RoxenModule { inherit BasicDefvar; constant is_module = 1; constant module_type = 0; constant module_unique = 1;
7787ac2001-07-31Per Hedbor  LocaleString module_name; LocaleString module_doc;
a896932001-01-03Per Hedbor 
0aa37d2001-08-23Martin Stjernholm  string module_identifier(); string module_local_id();
a896932001-01-03Per Hedbor  array(int|string|mapping) register_module(); string file_name_and_stuff(); void start(void|int num, void|object conf); string query_internal_location(); string query_location(); string query_provides();
bd5d4d2003-12-15Henrik Grubbström (Grubba)  function(RequestID:int|mapping) query_seclevels();
119a552004-05-07Martin Stjernholm  void set_status_for_path (string path, RequestID id, int status_code, string|void message, mixed... args);
ce64272001-01-14Martin Nilsson  array(int)|object(Stdio.Stat) stat_file(string f, RequestID id);
c8c1352003-01-13Henrik Grubbström (Grubba)  array(string) find_dir(string f, RequestID id);
a896932001-01-03Per Hedbor  mapping(string:array(mixed)) find_dir_stat(string f, RequestID id); string real_file(string f, RequestID id); void save(); mapping api_functions(); mapping query_tag_callers(); mapping query_container_callers(); string info(object conf); string comment();
c8bfed2003-06-02Henrik Grubbström (Grubba) 
c11ef72004-05-10Martin Stjernholm  PropertySet|mapping(string:mixed) query_property_set(string path, RequestID id);
a8e2b32004-05-07Henrik Grubbström (Grubba)  string|array(SimpleNode)|mapping(string:mixed)
c8bfed2003-06-02Henrik Grubbström (Grubba)  query_property(string path, string prop_name, RequestID id); void recurse_find_properties(string path, string mode, int depth,
cdaab02004-05-10Martin Stjernholm  RequestID id, multiset(string)|void filt);
a98f752004-03-03Henrik Grubbström (Grubba)  mapping(string:mixed) patch_properties(string path, array(PatchPropertyCommand) instructions,
cdaab02004-05-10Martin Stjernholm  RequestID id);
4c87da2004-04-28Henrik Grubbström (Grubba)  mapping(string:mixed) set_property (string path, string prop_name,
a8e2b32004-05-07Henrik Grubbström (Grubba)  string|array(SimpleNode) value,
4c87da2004-04-28Henrik Grubbström (Grubba)  RequestID id); mapping(string:mixed) remove_property (string path, string prop_name, RequestID id);
8cedaa2004-05-07Martin Stjernholm 
0ddcae2004-05-04Martin Stjernholm  string resource_id (string path, RequestID id);
de070f2004-05-04Martin Stjernholm  string|int authenticated_user_id (string path, RequestID id);
47fa212004-05-03Martin Stjernholm  multiset(DAVLock) find_locks(string path, int(0..1) recursive, int(0..1) exclude_shared, RequestID id);
119a552004-05-07Martin Stjernholm  DAVLock|LockFlag check_locks(string path, int(0..1) recursive, RequestID id);
0850612004-05-03Martin Stjernholm  mapping(string:mixed) lock_file(string path, DAVLock lock, RequestID id);
4908322004-04-29Martin Stjernholm  mapping(string:mixed) unlock_file (string path, DAVLock lock, RequestID id);
28c81a2004-05-12Martin Stjernholm  mapping(string:mixed)|int(0..1) check_if_header(string relative_path, int(0..1) recursive, RequestID id);
8cedaa2004-05-07Martin Stjernholm 
b303da2004-03-03Henrik Grubbström (Grubba)  mapping(string:mixed)|int(-1..0)|Stdio.File find_file(string path, RequestID id);
48d26d2004-05-10Henrik Grubbström (Grubba)  mapping(string:mixed) recurse_delete_files(string path, RequestID id);
cdaab02004-05-10Martin Stjernholm  mapping(string:mixed) make_collection(string path, RequestID id);
dfc1ad2004-05-13Henrik Grubbström (Grubba)  mapping(string:mixed) recurse_copy_files(string source, string destination, PropertyBehavior behavior,
cdaab02004-05-10Martin Stjernholm  Overwrite overwrite, RequestID id);
7d1c8e2004-05-13Henrik Grubbström (Grubba)  mapping(string:mixed) recurse_move_files(string source, string destination, PropertyBehavior behavior, Overwrite overwrite, RequestID id);
a896932001-01-03Per Hedbor }
0cb4c42004-03-15Martin Stjernholm class PatchPropertyCommand { constant command = ""; string property_name;
df04242004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) execute(PropertySet context);
0cb4c42004-03-15Martin Stjernholm }
a896932001-01-03Per Hedbor class _roxen { mapping(string:object) variables;
2284712004-04-25Henrik Grubbström (Grubba)  constant real_version = "";
a896932001-01-03Per Hedbor  object locale; int start_time; array(Configuration) configurations; mixed query(string a); void store(string a, mapping b, int c, object d); mapping(string:mixed) retrieve(string a, object b); void remove(string a, object b); string version(); void dump(string a); void nwrite(string a, int|void b, int|void c, void|mixed ... d); int main(int a, array(string) b); }
3e3bab2001-01-19Per Hedbor  class AuthModule
7f66d82001-08-24Martin Nilsson //! @appears AuthModule
3e3bab2001-01-19Per Hedbor //! The interface an authentication module must implement { inherit RoxenModule; constant module_type = MODULE_AUTH; constant thread_safe=1; constant name = "method name";
3342dd2001-01-19Per Hedbor  User authenticate( RequestID id, UserDB db );
3e3bab2001-01-19Per Hedbor  //! Try to authenticate the request with users from the specified user //! database. If no @[db] is specified, all datbases in the current //! configuration are searched in order, then the configuration user //! database.
3342dd2001-01-19Per Hedbor  mapping authenticate_throw( RequestID id, string realm, UserDB db );
3e3bab2001-01-19Per Hedbor  //! Returns a reply mapping, similar to @[Roxen.http_rxml_reply] with //! friends. If no @[db] is specified, all datbases in the current //! configuration are searched in order, then the configuration user //! database. }
e082212001-08-28Per Hedbor static mapping(string:function(void:void)) user_sql_inited = ([]);
a665382001-01-29Per Hedbor static Sql.Sql user_mysql;
69fa3e2001-01-30Per Hedbor static void init_user_sql(string table)
3342dd2001-01-19Per Hedbor {
e082212001-08-28Per Hedbor  string db = all_constants()->REPLICATE?"replicate":"local";
69fa3e2001-01-30Per Hedbor  if( !user_mysql )
e082212001-08-28Per Hedbor  user_mysql = master()->resolv("DBManager.get")( db );
c580392001-08-13Per Hedbor  if(catch(user_mysql->query( "SELECT module FROM "+ table+" WHERE module=''"))) {
69fa3e2001-01-30Per Hedbor  user_mysql->query( "CREATE TABLE "+table+" " " (module varchar(30) NOT NULL, "
3342dd2001-01-19Per Hedbor  " name varchar(30) NOT NULL, "
69fa3e2001-01-30Per Hedbor  " user varchar(30) NOT NULL, " " value blob, " " raw int not null, " " INDEX foo (module,name,user))" );
e082212001-08-28Per Hedbor  master()->resolv("DBManager.is_module_table")( 0, db, table, "Contains metadata about users. "
c580392001-08-13Per Hedbor  "Userdatabases can store information here " "at the request of other modules, or they " "can keep their own state in this table" ); }
e082212001-08-28Per Hedbor  user_sql_inited[ table ]= lambda(){user_mysql = master()->resolv("DBManager.get")( db );};
3342dd2001-01-19Per Hedbor }
7f66d82001-08-24Martin Nilsson //! @appears Group
3342dd2001-01-19Per Hedbor class Group( UserDB database )
3e3bab2001-01-19Per Hedbor { string name();
3342dd2001-01-19Per Hedbor  //! The group name
6533f22001-08-23Martin Nilsson 
9b94322001-01-29Per Hedbor  array(string) members()
3342dd2001-01-19Per Hedbor  //! All users that are members of this group. The default //! implementation loops over all users handled by the user database
3181852001-10-09Per Hedbor  //! and looks for users with the same gid as this group, or who is a //! member of it when the groups() method are called.
3342dd2001-01-19Per Hedbor  { array res = ({}); User uid; int id = gid(); foreach( database->list_users(), string u )
3181852001-10-09Per Hedbor  if( (uid = database->find_user( u )) && ((uid->gid() == id) || has_value(uid->groups(), name())))
9b94322001-01-29Per Hedbor  res += ({ u });
3342dd2001-01-19Per Hedbor  return res; } int gid(); //! A numerical GID, or -1 if not applicable
3181852001-10-09Per Hedbor  int set_name( string new_name ) { return 0; } int set_gid( int new_gid ) { return 0; } int set_members( array(string) members ) { return 0; } //! Returns 1 if it was possible to set the variable.
3342dd2001-01-19Per Hedbor }
e082212001-08-28Per Hedbor #ifdef THREADS
61e1cd2001-09-05Martin Nilsson static Thread.Mutex mutex = Thread.Mutex();
e082212001-08-28Per Hedbor #endif
7f66d82001-08-24Martin Nilsson //! @appears User
3342dd2001-01-19Per Hedbor class User( UserDB database ) {
69fa3e2001-01-30Per Hedbor  static string table;
3342dd2001-01-19Per Hedbor  string name(); //! The user (short) name
6533f22001-08-23Martin Nilsson 
3e3bab2001-01-19Per Hedbor  string real_name();
3342dd2001-01-19Per Hedbor  //! The real name of the user
0d1bcb2001-08-30Henrik Grubbström (Grubba)  int password_authenticate(string password)
3342dd2001-01-19Per Hedbor  //! Return 1 if the password is correct, 0 otherwise. The default //! implementation uses the crypted_password() method. {
0d1bcb2001-08-30Henrik Grubbström (Grubba)  string c = crypted_password(); return !sizeof(c) || crypt(password, c);
3342dd2001-01-19Per Hedbor  } int uid(); //! A numerical UID, or -1 if not applicable
6533f22001-08-23Martin Nilsson 
3342dd2001-01-19Per Hedbor  int gid(); //! A numerical GID, or -1 if not applicable
6533f22001-08-23Martin Nilsson 
3342dd2001-01-19Per Hedbor  string shell(); //! The shell, or 0 if not applicable string gecos() //! The gecos field, defaults to return the real name { return real_name(); } string homedir(); string crypted_password() { return "x"; } //! Used by compat_userinfo(). The default implementation returns "x"
9b94322001-01-29Per Hedbor  array(string) groups()
e3f4662001-01-19Per Hedbor  //! Return all groups this user is a member in. The default //! implementation returns ({}) { return ({}); }
3342dd2001-01-19Per Hedbor 
1d5c892001-01-19Per Hedbor  int set_name(string name) {} int set_real_name(string rname) {} int set_uid(int uid) {} int set_gid(int gid) {} int set_shell(string shell) {} int set_gecos(string gecos) {} int set_homedir(string hodir) {} int set_crypted_password(string passwd) {} int set_password(string passwd) {} //! Returns 1 if it was possible to set the variable.
3342dd2001-01-19Per Hedbor  array compat_userinfo( ) //! Return a unix passwd compatible array with user information. The //! defualt implementation uses the other methods to assemble this //! information. //! //! Basically: //! return ({ name(), crypted_password(), //! uid(), gid(), gecos(), homedir(), //! shell() }); { return ({name(),crypted_password(),uid(),gid(),gecos(),homedir(),shell()}); }
3e3bab2001-01-19Per Hedbor 
69fa3e2001-01-30Per Hedbor  #define INIT_SQL() do{ \ if(!table) table = replace(database->my_configuration()->name," ","_")+"_user_variables"; \
e082212001-08-28Per Hedbor  if(!user_sql_inited[ table ] )init_user_sql( table );else user_sql_inited[ table ](); \
69fa3e2001-01-30Per Hedbor  } while( 0 )
e082212001-08-28Per Hedbor #ifdef THREADS #define LOCK() mixed ___key = mutex->lock() #else #define LOCK() #endif
69fa3e2001-01-30Per Hedbor  static string module_name( RoxenModule module ) { if( !module ) // NULL does not work together with indexes, but this is // not a valid modulename, so it's not a problem. return "'0'"; else return replace("'"+user_mysql->quote(module->sname())+"'","%","%%"); }
3342dd2001-01-19Per Hedbor 
1d5c892001-01-19Per Hedbor  mixed set_var( RoxenModule module, string index, mixed value )
3342dd2001-01-19Per Hedbor  //! Set a specified variable in the user. If @[value] is a string, //! it's stored as is in the database, otherwise it's encoded using
e3f4662001-01-19Per Hedbor  //! encode_value before it's stored. Returns the value.
3342dd2001-01-19Per Hedbor  //! //! You can use 0 for the @[module] argument. //! //! The default implementation stores the value in a mysql table
69fa3e2001-01-30Per Hedbor  //! '*_user_data' in the 'shared' database.
3342dd2001-01-19Per Hedbor  //! //! Use @[get_var] to retrieve the value, and @[delete_var] to //! delete it. { delete_var( module, index );
69fa3e2001-01-30Per Hedbor  mixed oval = value;
e082212001-08-28Per Hedbor  LOCK();
69fa3e2001-01-30Per Hedbor  INIT_SQL();
3342dd2001-01-19Per Hedbor  int encoded;
69fa3e2001-01-30Per Hedbor 
3342dd2001-01-19Per Hedbor  if( stringp( value ) ) value = string_to_utf8( value ); else { value = encode_value( value ); encoded = 1; }
e082212001-08-28Per Hedbor 
3342dd2001-01-19Per Hedbor  user_mysql->query(
69fa3e2001-01-30Per Hedbor  "INSERT INTO "+table+" (module,name,user,value,raw) " "VALUES ("+module_name( module )+", %s, %s, %s, %d)", index, name(), value, encoded
3342dd2001-01-19Per Hedbor  );
69fa3e2001-01-30Per Hedbor  return oval;
3342dd2001-01-19Per Hedbor  } mixed get_var( RoxenModule module, string index ) //! Return the value of a variable previously set with @[set_var] { array rows;
e082212001-08-28Per Hedbor  LOCK();
69fa3e2001-01-30Per Hedbor  INIT_SQL(); rows = user_mysql->query( "SELECT * FROM "+table+
e082212001-08-28Per Hedbor  " WHERE module="+module_name( module ) +" AND name=%s AND user=%s",
3342dd2001-01-19Per Hedbor  index, name() ); if( !sizeof( rows ) ) return 0; mapping m = rows[0];
69fa3e2001-01-30Per Hedbor 
3342dd2001-01-19Per Hedbor  if( (int)m->raw ) return decode_value( m->value ); return utf8_to_string( m->value ); } void delete_var( RoxenModule module, string index ) //! Delete a variable previously created with @[set_var] {
e082212001-08-28Per Hedbor  LOCK();
69fa3e2001-01-30Per Hedbor  INIT_SQL(); user_mysql->query( "DELETE FROM "+table+" WHERE (module="+ module_name( module )+ " AND name=%s AND user=%s)", index, name() );
3342dd2001-01-19Per Hedbor  }
69fa3e2001-01-30Per Hedbor #undef INIT_SQL
e082212001-08-28Per Hedbor #undef LOCK
3e3bab2001-01-19Per Hedbor } class UserDB
7f66d82001-08-24Martin Nilsson //! @appears UserDB
3e3bab2001-01-19Per Hedbor //! The interface a UserDB module must implement. { inherit RoxenModule; constant module_type = MODULE_USERDB;
8ea2012001-01-29Per Hedbor  constant thread_safe=1;
3e3bab2001-01-19Per Hedbor 
e3f4662001-01-19Per Hedbor  constant name = "db name";
99a70c2001-08-30Henrik Grubbström (Grubba)  User find_user( string s, RequestID|void id );
3e3bab2001-01-19Per Hedbor  //! Find a user
bd5d4d2003-12-15Henrik Grubbström (Grubba)  User find_user_from_uid( int uid, RequestID|void id )
3342dd2001-01-19Per Hedbor  //! Find a user given a UID. The default implementation loops over //! list_users() and checks the uid() of each one. {
bd5d4d2003-12-15Henrik Grubbström (Grubba)  User user;
3342dd2001-01-19Per Hedbor  foreach( list_users(), string u )
bd5d4d2003-12-15Henrik Grubbström (Grubba)  if( (user = find_user( u )) && (user->uid() == uid) ) return user;
3342dd2001-01-19Per Hedbor  }
bd5d4d2003-12-15Henrik Grubbström (Grubba)  Group find_group( string group, RequestID|void id )
3342dd2001-01-19Per Hedbor  //! Find a group object given a group name. //! The default implementation returns 0. { }
bd5d4d2003-12-15Henrik Grubbström (Grubba)  Group find_group_from_gid( int gid, RequestID|void id )
3342dd2001-01-19Per Hedbor  //! Find a group given a GID. The default implementation loops over //! list_groups() and checks the gid() of each one. {
bd5d4d2003-12-15Henrik Grubbström (Grubba)  Group group;
3342dd2001-01-19Per Hedbor  foreach( list_groups(), string u )
bd5d4d2003-12-15Henrik Grubbström (Grubba)  if( (group = find_group( u )) && (group->gid() == gid) ) return group;
3342dd2001-01-19Per Hedbor  }
bd5d4d2003-12-15Henrik Grubbström (Grubba)  array(string) list_groups( RequestID|void id )
3342dd2001-01-19Per Hedbor  //! Return a list of all groups handled by this database module. //! The default implementation returns the empty array. { return ({}); }
bd5d4d2003-12-15Henrik Grubbström (Grubba)  array(string) list_users( RequestID|void id );
3e3bab2001-01-19Per Hedbor  //! Return a list of all users handled by this database module.
3342dd2001-01-19Per Hedbor  User create_user( string s )
788f082001-10-09Henrik Grubbström (Grubba)  //! Not necessarily implemented, as an example, it's not possible to
3e3bab2001-01-19Per Hedbor  //! create users in the system user database from Roxen WebServer.
3342dd2001-01-19Per Hedbor  //! The default implementation returns 0.
69fa3e2001-01-30Per Hedbor  { return 0; }
3181852001-10-09Per Hedbor  Group create_group( string s )
788f082001-10-09Henrik Grubbström (Grubba)  //! Not necessarily implemented, as an example, it's not possible to
3181852001-10-09Per Hedbor  //! create groups in the system user database from Roxen WebServer. //! The default implementation returns 0. { return 0; }
3e3bab2001-01-19Per Hedbor }