Roxen.git/
server/
etc/
modules/
Roxen.pmod
Branch:
Tag:
Non-build tags
All tags
No tags
2000-12-11
2000-12-11 03:07:22 by Per Hedbor <ph@opera.com>
a97287382da69ce37e5b1a6c101b315b806dbeb7 (
1799
lines) (+
1797
/-
2
)
[
Show
|
Annotate
]
Branch:
5.2
Longer.
Rev: server/etc/modules/Roxen.pmod:1.56
1:
/*
-
* $Id: Roxen.pmod,v 1.
55
2000/12/
04
22
:
45
:
04
nilsson
Exp $
+
* $Id: Roxen.pmod,v 1.
56
2000/12/
11
03
:
07
:
22
per
Exp $
* * Various helper functions. * * Henrik Grubbström 1999-05-03 */
-
+
#include <roxen.h>
#include <config.h> #include <version.h> #include <module.h>
-
inherit
"roxenlib";
+
#include
<variables.h>
+
#include <stat.h>
-
+
#ifdef HTTP_DEBUG
+
# define HTTP_WERR(X) werror("HTTP: "+X+"\n");
+
#else
+
# define HTTP_WERR(X)
+
#endif
+
+
+
#define roxen roxenp()
+
// From the old 'http' file
+
string http_res_to_string( mapping file, RequestID id )
+
{
+
mapping(string:string|array(string)) heads=
+
([
+
"Content-type":[string]file["type"],
+
"Server":replace(roxen->version(), " ", "·"),
+
"Date":http_date([int]id->time)
+
]);
+
+
if(file->encoding)
+
heads["Content-Encoding"] = [string]file->encoding;
+
+
if(!file->error)
+
file->error=200;
+
+
if(file->expires)
+
heads->Expires = http_date([int]file->expires);
+
+
if(!file->len)
+
{
+
if(objectp(file->file))
+
if(!file->stat && !(file->stat=([mapping(string:mixed)]id->misc)->stat))
+
file->stat = (array(int))file->file->stat();
+
array fstat;
+
if(arrayp(fstat = file->stat))
+
{
+
if(file->file && !file->len)
+
file->len = fstat[1];
+
+
heads["Last-Modified"] = http_date([int]fstat[3]);
+
}
+
if(stringp(file->data))
+
file->len += strlen([string]file->data);
+
}
+
+
if(mappingp(file->extra_heads))
+
heads |= file->extra_heads;
+
+
if(mappingp(([mapping(string:mixed)]id->misc)->moreheads))
+
heads |= ([mapping(string:mixed)]id->misc)->moreheads;
+
+
array myheads=({id->prot+" "+(file->rettext||errors[file->error])});
+
foreach(indices(heads), string h)
+
if(arrayp(heads[h]))
+
foreach([array(string)]heads[h], string tmp)
+
myheads += ({ `+(h,": ", tmp)});
+
else
+
myheads += ({ `+(h, ": ", heads[h])});
+
+
+
if(file->len > -1)
+
myheads += ({"Content-length: " + file->len });
+
string head_string = (myheads+({"",""}))*"\r\n";
+
+
if(id->conf) {
+
id->conf->hsent+=strlen(head_string||"");
+
if(id->method != "HEAD")
+
id->conf->sent+=(file->len>0 ? file->len : 1000);
+
}
+
if(id->method != "HEAD")
+
head_string+=(file->data||"")+(file->file?file->file->read():"");
+
return head_string;
+
}
+
+
mapping http_low_answer( int errno, string data )
+
//! Return a result mapping with the error and data specified. The
+
//! error is infact the status response, so '200' is HTTP Document
+
//! follows, and 500 Internal Server error, etc.
+
{
+
if(!data) data="";
+
HTTP_WERR("Return code "+errno+" ("+data+")");
+
return
+
([
+
"error" : errno,
+
"data" : data,
+
"len" : strlen( data ),
+
"type" : "text/html",
+
]);
+
}
+
+
mapping http_pipe_in_progress()
+
{
+
HTTP_WERR("Pipe in progress");
+
return ([ "file":-1, "pipe":1, ]);
+
}
+
+
mapping http_rxml_answer( string rxml, RequestID id,
+
void|Stdio.File file,
+
void|string type )
+
//! Convenience functions to use in Roxen modules. When you just want
+
//! to return a string of data, with an optional type, this is the
+
//! easiest way to do it if you don't want to worry about the internal
+
//! roxen structures.
+
{
+
rxml =
+
([function(string,RequestID,Stdio.File:string)]id->conf->parse_rxml)
+
(rxml, id, file);
+
HTTP_WERR("RXML answer ("+(type||"text/html")+")");
+
return (["data":rxml,
+
"type":(type||"text/html"),
+
"stat":id->misc->defines[" _stat"],
+
"error":id->misc->defines[" _error"],
+
"rettext":id->misc->defines[" _rettext"],
+
"extra_heads":id->misc->defines[" _extra_heads"],
+
]);
+
}
+
+
+
mapping http_try_again( float delay )
+
//! Causes the request to be retried in delay seconds.
+
{
+
return ([ "try_again_later":delay ]);
+
}
+
+
class Delayer
+
{
+
RequestID id;
+
int resumed;
+
+
void resume( )
+
{
+
if( resumed )
+
return;
+
remove_call_out( resume );
+
resumed = 1;
+
if( !id )
+
error("Cannot resume request -- connection close\n");
+
roxenp()->handle( id->handle_request );
+
id = 0; // free the reference.
+
}
+
+
void create( RequestID _id, float max_delay )
+
{
+
id = _id;
+
if( max_delay && max_delay > 0.0 )
+
call_out( resume, max_delay );
+
}
+
}
+
+
array(object|mapping) http_try_resume( RequestID id, float|void max_delay )
+
//! Returns an object and a return mapping.
+
//! Call 'retry' in the object to resume the request.
+
//! Please note that this will cause your callback to be called again.
+
//! An optional maximum delay time can be specified.
+
//!
+
//! Can be used like this:
+
//!
+
//! void first_try( RequestID id )
+
//! {
+
//! if( !id->misc->has_logged_in )
+
//! {
+
//! [object key, mapping result] = Roxen.http_try_resume( id, 10.0 );
+
//! void do_the_work( )
+
//! {
+
//! id->misc->have_logged_in = "no";
+
//! if( connect_to_slow_id_host_and_get_login( id ) )
+
//! id->misc->have_logged_in = "yes";
+
//! key->resume();
+
//! };
+
//! thread_create( do_the_work, key );
+
//! return result;
+
//! }
+
//! }
+
{
+
Delayer delay = Delayer( id, max_delay );
+
return ({delay, ([ "try_again":delay ]) });
+
}
+
+
mapping http_string_answer(string text, string|void type)
+
//! Generates a result mapping with the given text as the request body
+
//! with a content type of `type' (or "text/html" if none was given).
+
{
+
HTTP_WERR("String answer ("+(type||"text/html")+")");
+
return ([ "data":text, "type":(type||"text/html") ]);
+
}
+
+
mapping http_file_answer(Stdio.File text, string|void type, void|int len)
+
{
+
HTTP_WERR("file answer ("+(type||"text/html")+")");
+
return ([ "file":text, "type":(type||"text/html"), "len":len ]);
+
}
+
+
constant months = ({ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" });
+
constant days = ({ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" });
+
+
+
+
static int chd_lt;
+
static string chd_lf;
+
string cern_http_date(int t)
+
//! Return a date, used in the common log format
+
{
+
if( t == chd_lt ) return chd_lf;
+
+
string c;
+
mapping(string:int) lt = localtime(t);
+
int tzh = lt->timezone/3600 - lt->isdst;
+
if(tzh > 0)
+
c="-";
+
else {
+
tzh = -tzh;
+
c="+";
+
}
+
chd_lt = t;
+
return(chd_lf=sprintf("%02d/%s/%04d:%02d:%02d:%02d %s%02d00",
+
lt->mday, months[lt->mon], 1900+lt->year,
+
lt->hour, lt->min, lt->sec, c, tzh));
+
}
+
+
string http_date(int t)
+
//! Returns a http_date, as specified by the HTTP-protocol standard.
+
//! This is used for logging as well as the Last-Modified and Time
+
//! heads in the reply.
+
{
+
mapping(string:int) l = gmtime( t );
+
return(sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
+
days[l->wday], l->mday, months[l->mon], 1900+l->year,
+
l->hour, l->min, l->sec));
+
}
+
+
string http_encode_string(string f)
+
{
+
return replace(f, ({ "\000", " ", "\t", "\n", "\r", "%", "'", "\"" }),
+
({"%00", "%20", "%09", "%0a", "%0d", "%25", "%27", "%22"}));
+
}
+
+
string http_encode_cookie(string f)
+
{
+
return replace(f, ({ "=", ",", ";", "%" }), ({ "%3d", "%2c", "%3b", "%25"}));
+
}
+
+
string http_encode_url (string f)
+
{
+
return replace (f, ({"\000", " ", "\t", "\n", "\r", "%", "'", "\"", "#",
+
"&", "?", "=", "/", ":"}),
+
({"%00", "%20", "%09", "%0a", "%0d", "%25", "%27", "%22", "%23",
+
"%26", "%3f", "%3d", "%2f", "%3a"}));
+
}
+
+
string http_roxen_config_cookie(string from)
+
{
+
return "RoxenConfig="+http_encode_cookie(from)
+
+"; expires=" + http_date (3600*24*365*2 + time (1)) + "; path=/";
+
}
+
+
string http_roxen_id_cookie()
+
{
+
return sprintf("RoxenUserID=0x%x; expires=" +
+
http_date (3600*24*365*2 + time (1)) + "; path=/",
+
roxen->increase_id());
+
}
+
+
string add_pre_state( string url, multiset state )
+
{
+
if(!url)
+
error("URL needed for add_pre_state()\n");
+
if(!state || !sizeof(state))
+
return url;
+
if(strlen(url)>5 && (url[1] == '(' || url[1] == '<'))
+
return url;
+
return "/(" + sort(indices(state)) * "," + ")" + url ;
+
}
+
+
mapping http_redirect( string url, RequestID|void id )
+
//! Simply returns a http-redirect message to the specified URL. If
+
//! the url parameter is just a virtual (possibly relative) path, the
+
//! current id object must be supplied to resolve the destination URL.
+
{
+
if(strlen(url) && url[0] == '/')
+
{
+
if(id)
+
{
+
if( id->misc->site_prefix_path )
+
url = replace( [string]id->misc->site_prefix_path + url, "//", "/" );
+
url = add_pre_state(url,id->prestate);
+
if(id->misc->host)
+
{
+
array(string) h;
+
HTTP_WERR(sprintf("(REDIR) id->port_obj:%O", id->port_obj));
+
string prot = id->port_obj->name + "://";
+
string p = ":" + id->port_obj->default_port;
+
+
h = [string]id->misc->host / p - ({""});
+
if(sizeof(h) == 1)
+
// Remove redundant port number.
+
url=prot+h[0]+url;
+
else
+
url=prot+[string]id->misc->host+url;
+
} else
+
url = [string]id->conf->query("MyWorldLocation") + url[1..];
+
}
+
}
+
HTTP_WERR("Redirect -> "+http_encode_string(url));
+
return http_low_answer( 302, "")
+
+ ([ "extra_heads":([ "Location":http_encode_string( url ) ]) ]);
+
}
+
+
mapping http_stream(Stdio.File from)
+
//! Returns a result mapping where the data returned to the client
+
//! will be streamed raw from the given Stdio.File object, instead of
+
//! being packaged by roxen. In other words, it's entirely up to you
+
//! to make sure what you send is HTTP data.
+
{
+
return ([ "raw":1, "file":from, "len":-1, ]);
+
}
+
+
mapping http_auth_required(string realm, string|void message)
+
//! Generates a result mapping that will instruct the web browser that
+
//! the user needs to authorize himself before being allowed access.
+
//! `realm' is the name of the realm on the server, which will
+
//! typically end up in the browser's prompt for a name and password
+
//! (e g "Enter username for <i>realm</i> at <i>hostname</i>:"). The
+
//! optional message is the message body that the client typically
+
//! shows the user, should he decide not to authenticate himself, but
+
//! rather refraim from trying to authenticate himself.
+
//!
+
//! In HTTP terms, this sends a <tt>401 Auth Required</tt> response
+
//! with the header <tt>WWW-Authenticate: basic realm="`realm'"</tt>.
+
//! For more info, see RFC 2617.
+
{
+
if(!message)
+
message = "<h1>Authentication failed.\n</h1>";
+
HTTP_WERR("Auth required ("+realm+")");
+
return http_low_answer(401, message)
+
+ ([ "extra_heads":([ "WWW-Authenticate":"basic realm=\""+realm+"\"",]),]);
+
}
+
+
mapping http_proxy_auth_required(string realm, void|string message)
+
//! Generates a result mapping that will instruct the client end that
+
//! it needs to authenticate itself before being allowed access.
+
//! `realm' is the name of the realm on the server, which will
+
//! typically end up in the browser's prompt for a name and password
+
//! (e g "Enter username for <i>realm</i> at <i>hostname</i>:"). The
+
//! optional message is the message body that the client typically
+
//! shows the user, should he decide not to authenticate himself, but
+
//! rather refraim from trying to authenticate himself.
+
//!
+
//! In HTTP terms, this sends a <tt>407 Proxy authentication
+
//! failed</tt> response with the header <tt>Proxy-Authenticate: basic
+
//! realm="`realm'"</tt>. For more info, see RFC 2617.
+
{
+
if(!message)
+
message = "<h1>Proxy authentication failed.\n</h1>";
+
return http_low_answer(407, message)
+
+ ([ "extra_heads":([ "Proxy-Authenticate":"basic realm=\""+realm+"\"",]),]);
+
}
+
+
mapping add_http_header(mapping to, string name, string value)
+
{
+
if(to[name]) {
+
if(arrayp(to[name]))
+
to[name] += ({ value });
+
else
+
to[name] = ({ to[name], value });
+
}
+
else
+
to[name] = value;
+
return to;
+
}
+
+
// From the old 'roxenlib' file
+
string extract_query(string from)
+
{
+
if(!from) return "";
+
if(sscanf(from, "%*s?%s%*[ \t\n]", from))
+
return (from/"\r")[0];
+
return "";
+
}
+
+
mapping build_env_vars(string f, RequestID id, string path_info)
+
{
+
string addr=id->remoteaddr || "Internal";
+
mapping(string:string) new = ([]);
+
RequestID tmpid;
+
+
if(id->query && strlen(id->query))
+
new->INDEX=id->query;
+
+
if(path_info && strlen(path_info))
+
{
+
string t, t2;
+
if(path_info[0] != '/')
+
path_info = "/" + path_info;
+
+
t = t2 = "";
+
+
// Kludge
+
if ( ([mapping(string:mixed)]id->misc)->path_info == path_info ) {
+
// Already extracted
+
new["SCRIPT_NAME"]=id->not_query;
+
} else {
+
new["SCRIPT_NAME"]=
+
id->not_query[0..strlen([string]id->not_query)-strlen(path_info)-1];
+
}
+
new["PATH_INFO"]=path_info;
+
+
+
while(1)
+
{
+
// Fix PATH_TRANSLATED correctly.
+
t2 = id->conf->real_file(path_info, id);
+
if(t2)
+
{
+
new["PATH_TRANSLATED"] = t2 + t;
+
break;
+
}
+
array(string) tmp = path_info/"/" - ({""});
+
if(!sizeof(tmp))
+
break;
+
path_info = "/" + (tmp[0..sizeof(tmp)-2]) * "/";
+
t = tmp[-1] +"/" + t;
+
}
+
} else
+
new["SCRIPT_NAME"]=id->not_query;
+
tmpid = id;
+
while(tmpid->misc->orig)
+
// internal get
+
tmpid = tmpid->misc->orig;
+
+
// Begin "SSI" vars.
+
array(string) tmps;
+
if(sizeof(tmps = tmpid->not_query/"/" - ({""})))
+
new["DOCUMENT_NAME"]=tmps[-1];
+
+
new["DOCUMENT_URI"]= tmpid->not_query;
+
+
Stat tmpi;
+
string real_file=tmpid->conf->real_file(tmpid->not_query||"", tmpid);
+
if (real_file) {
+
if(stringp(real_file)) {
+
if ((tmpi = file_stat(real_file)) &&
+
sizeof(tmpi)) {
+
new["LAST_MODIFIED"]=http_date(tmpi[3]);
+
}
+
} else {
+
// Extra paranoia.
+
report_error(sprintf("real_file(%O, %O) returned %O\n",
+
tmpid->not_query||"", tmpid, real_file));
+
}
+
}
+
+
// End SSI vars.
+
+
+
if(string tmp = id->conf->real_file(new["SCRIPT_NAME"], id))
+
new["SCRIPT_FILENAME"] = tmp;
+
+
if(string tmp = id->conf->real_file("/", id))
+
new["DOCUMENT_ROOT"] = tmp;
+
+
if(!new["PATH_TRANSLATED"])
+
m_delete(new, "PATH_TRANSLATED");
+
else if(new["PATH_INFO"][-1] != '/' && new["PATH_TRANSLATED"][-1] == '/')
+
new["PATH_TRANSLATED"] =
+
new["PATH_TRANSLATED"][0..strlen(new["PATH_TRANSLATED"])-2];
+
+
// HTTP_ style variables:
+
+
mapping hdrs;
+
+
if ((hdrs = id->request_headers)) {
+
foreach(indices(hdrs) - ({ "authorization", "proxy-authorization",
+
"security-scheme", }), string h) {
+
string hh = "HTTP_" + replace(upper_case(h),
+
({ " ", "-", "\0", "=" }),
+
({ "_", "_", "", "_" }));
+
+
new[hh] = replace(hdrs[h], ({ "\0" }), ({ "" }));
+
}
+
if (!new["HTTP_HOST"]) {
+
if(objectp(id->my_fd) && id->my_fd->query_address(1))
+
new["HTTP_HOST"] = replace(id->my_fd->query_address(1)," ",":");
+
}
+
} else {
+
if(id->misc->host)
+
new["HTTP_HOST"]=id->misc->host;
+
else if(objectp(id->my_fd) && id->my_fd->query_address(1))
+
new["HTTP_HOST"]=replace(id->my_fd->query_address(1)," ",":");
+
if(id->misc["proxy-connection"])
+
new["HTTP_PROXY_CONNECTION"]=id->misc["proxy-connection"];
+
if(id->misc->accept) {
+
if (arrayp(id->misc->accept)) {
+
new["HTTP_ACCEPT"]=id->misc->accept*", ";
+
} else {
+
new["HTTP_ACCEPT"]=(string)id->misc->accept;
+
}
+
}
+
+
if(id->misc->cookies)
+
new["HTTP_COOKIE"] = id->misc->cookies;
+
+
if(sizeof(id->pragma))
+
new["HTTP_PRAGMA"]=indices(id->pragma)*", ";
+
+
if(stringp(id->misc->connection))
+
new["HTTP_CONNECTION"]=id->misc->connection;
+
+
new["HTTP_USER_AGENT"] = id->client*" ";
+
+
if(id->referer && sizeof(id->referer))
+
new["HTTP_REFERER"] = id->referer*"";
+
}
+
+
new["REMOTE_ADDR"]=addr;
+
+
if(roxen->quick_ip_to_host(addr) != addr)
+
new["REMOTE_HOST"]=roxen->quick_ip_to_host(addr);
+
+
catch {
+
if(id->my_fd)
+
new["REMOTE_PORT"] = (id->my_fd->query_address()/" ")[1];
+
};
+
+
if (id->query && sizeof(id->query)) {
+
new["QUERY_STRING"] = id->query;
+
}
+
+
if(id->realauth)
+
new["REMOTE_USER"] = (id->realauth / ":")[0];
+
if(id->auth && id->auth[0])
+
new["ROXEN_AUTHENTICATED"] = "1"; // User is valid with the Roxen userdb.
+
if(id->data && strlen(id->data))
+
{
+
if(id->misc["content-type"])
+
new["CONTENT_TYPE"]=id->misc["content-type"];
+
else
+
new["CONTENT_TYPE"]="application/x-www-form-urlencoded";
+
new["CONTENT_LENGTH"]=(string)strlen(id->data);
+
}
+
+
if(id->query && strlen(id->query))
+
new["INDEX"]=id->query;
+
+
new["REQUEST_METHOD"]=id->method||"GET";
+
new["SERVER_PORT"] = id->my_fd?
+
((id->my_fd->query_address(1)||"foo unknown")/" ")[1]: "Internal";
+
+
return new;
+
}
+
+
mapping build_roxen_env_vars(RequestID id)
+
{
+
mapping(string:string) new = ([]);
+
string tmp;
+
+
if(id->cookies->RoxenUserID)
+
new["ROXEN_USER_ID"]=id->cookies->RoxenUserID;
+
+
new["COOKIES"] = "";
+
foreach(indices(id->cookies), tmp)
+
{
+
new["COOKIE_"+tmp] = id->cookies[tmp];
+
new["COOKIES"]+= tmp+" ";
+
}
+
+
foreach(indices(id->config), tmp)
+
{
+
new["WANTS_"+replace(tmp, " ", "_")]="true";
+
if(new["CONFIGS"])
+
new["CONFIGS"] += " " + replace(tmp, " ", "_");
+
else
+
new["CONFIGS"] = replace(tmp, " ", "_");
+
}
+
+
foreach(indices(id->variables), tmp)
+
{
+
string name = replace(tmp," ","_");
+
if (id->variables[tmp] && (sizeof(id->variables[tmp]) < 8192)) {
+
/* Some shells/OS's don't like LARGE environment variables */
+
new["QUERY_"+name] = replace(id->variables[tmp],"\000"," ");
+
new["VAR_"+name] = replace(id->variables[tmp],"\000","#");
+
}
+
if(new["VARIABLES"])
+
new["VARIABLES"]+= " " + name;
+
else
+
new["VARIABLES"]= name;
+
}
+
+
foreach(indices(id->prestate), tmp)
+
{
+
new["PRESTATE_"+replace(tmp, " ", "_")]="true";
+
if(new["PRESTATES"])
+
new["PRESTATES"] += " " + replace(tmp, " ", "_");
+
else
+
new["PRESTATES"] = replace(tmp, " ", "_");
+
}
+
+
foreach(indices(id->supports), tmp)
+
{
+
new["SUPPORTS_"+replace(tmp-",", " ", "_")]="true";
+
if (new["SUPPORTS"])
+
new["SUPPORTS"] += " " + replace(tmp, " ", "_");
+
else
+
new["SUPPORTS"] = replace(tmp, " ", "_");
+
}
+
return new;
+
}
+
+
+
+
string decode_mode(int m)
+
{
+
string s;
+
s="";
+
+
if(S_ISLNK(m)) s += "Symbolic link";
+
else if(S_ISREG(m)) s += "File";
+
else if(S_ISDIR(m)) s += "Dir";
+
else if(S_ISCHR(m)) s += "Special";
+
else if(S_ISBLK(m)) s += "Device";
+
else if(S_ISFIFO(m)) s += "FIFO";
+
else if(S_ISSOCK(m)) s += "Socket";
+
else if((m&0xf000)==0xd000) s+="Door";
+
else s+= "Unknown";
+
+
s+=", ";
+
+
if(S_ISREG(m) || S_ISDIR(m))
+
{
+
s+="<tt>";
+
if(m&S_IRUSR) s+="r"; else s+="-";
+
if(m&S_IWUSR) s+="w"; else s+="-";
+
if(m&S_IXUSR) s+="x"; else s+="-";
+
+
if(m&S_IRGRP) s+="r"; else s+="-";
+
if(m&S_IWGRP) s+="w"; else s+="-";
+
if(m&S_IXGRP) s+="x"; else s+="-";
+
+
if(m&S_IROTH) s+="r"; else s+="-";
+
if(m&S_IWOTH) s+="w"; else s+="-";
+
if(m&S_IXOTH) s+="x"; else s+="-";
+
s+="</tt>";
+
} else {
+
s+="--";
+
}
+
return s;
+
}
+
+
int _match(string w, array (string) a)
+
{
+
if(!stringp(w)) // Internal request..
+
return -1;
+
foreach(a, string q)
+
if(stringp(q) && strlen(q) && glob(q, w))
+
return 1;
+
}
+
+
string short_name(string long_name)
+
{
+
long_name = replace(long_name, " ", "_");
+
return lower_case(long_name);
+
}
+
+
string strip_config(string from)
+
{
+
sscanf(from, "/<%*s>%s", from);
+
return from;
+
}
+
+
string strip_prestate(string from)
+
{
+
sscanf(from, "/(%*s)%s", from);
+
return from;
+
}
+
+
#define _error defines[" _error"]
+
#define _extra_heads defines[" _extra_heads"]
+
#define _rettext defines[" _rettext"]
+
+
string parse_rxml(string what, RequestID id,
+
void|Stdio.File file,
+
void|mapping(string:mixed) defines)
+
{
+
if(!objectp(id)) error("No id passed to parse_rxml\n");
+
return id->conf->parse_rxml( what, id, file, defines );
+
}
+
+
constant iso88591
+
=([ " ": " ",
+
"¡": "¡",
+
"¢": "¢",
+
"£": "£",
+
"¤": "¤",
+
"¥": "¥",
+
"¦": "¦",
+
"§": "§",
+
"¨": "¨",
+
"©": "©",
+
"ª": "ª",
+
"«": "«",
+
"¬": "¬",
+
"­": "",
+
"®": "®",
+
"¯": "¯",
+
"°": "°",
+
"±": "±",
+
"²": "²",
+
"³": "³",
+
"´": "´",
+
"µ": "µ",
+
"¶": "¶",
+
"·": "·",
+
"¸": "¸",
+
"¹": "¹",
+
"º": "º",
+
"»": "»",
+
"¼": "¼",
+
"½": "½",
+
"¾": "¾",
+
"¿": "¿",
+
"À": "À",
+
"Á": "Á",
+
"Â": "Â",
+
"Ã": "Ã",
+
"Ä": "Ä",
+
"Å": "Å",
+
"Æ": "Æ",
+
"Ç": "Ç",
+
"È": "È",
+
"É": "É",
+
"Ê": "Ê",
+
"Ë": "Ë",
+
"Ì": "Ì",
+
"Í": "Í",
+
"Î": "Î",
+
"Ï": "Ï",
+
"Ð": "Ð",
+
"Ñ": "Ñ",
+
"Ò": "Ò",
+
"Ó": "Ó",
+
"Ô": "Ô",
+
"Õ": "Õ",
+
"Ö": "Ö",
+
"×": "×",
+
"Ø": "Ø",
+
"Ù": "Ù",
+
"Ú": "Ú",
+
"Û": "Û",
+
"Ü": "Ü",
+
"Ý": "Ý",
+
"Þ": "Þ",
+
"ß": "ß",
+
"à": "à",
+
"á": "á",
+
"â": "â",
+
"ã": "ã",
+
"ä": "ä",
+
"å": "å",
+
"æ": "æ",
+
"ç": "ç",
+
"è": "è",
+
"é": "é",
+
"ê": "ê",
+
"ë": "ë",
+
"ì": "ì",
+
"í": "í",
+
"î": "î",
+
"ï": "ï",
+
"ð": "ð",
+
"ñ": "ñ",
+
"ò": "ò",
+
"ó": "ó",
+
"ô": "ô",
+
"õ": "õ",
+
"ö": "ö",
+
"÷": "÷",
+
"ø": "ø",
+
"ù": "ù",
+
"ú": "ú",
+
"û": "û",
+
"ü": "ü",
+
"ý": "ý",
+
"þ": "þ",
+
"ÿ": "ÿ",
+
]);
+
+
constant international
+
=([ "Œ": "\x0152",
+
"œ": "\x0153",
+
"Š": "\x0160",
+
"š": "\x0161",
+
"Ÿ": "\x0178",
+
"ˆ": "\x02C6",
+
"˜": "\x02DC",
+
" ": "\x2002",
+
" ": "\x2003",
+
" ": "\x2009",
+
"‌": "\x200C",
+
"‍": "\x200D",
+
"‎": "\x200E",
+
"‏": "\x200F",
+
"–": "\x2013",
+
"—": "\x2014",
+
"‘": "\x2018",
+
"’": "\x2019",
+
"‚": "\x201A",
+
"“": "\x201C",
+
"”": "\x201D",
+
"„": "\x201E",
+
"†": "\x2020",
+
"‡": "\x2021",
+
"‰": "\x2030",
+
"‹": "\x2039",
+
"›": "\x203A",
+
"€": "\x20AC",
+
]);
+
+
constant symbols
+
=([ "ƒ": "\x0192",
+
"ϑ": "\x03D1",
+
"ϒ": "\x03D2",
+
"ϖ": "\x03D6",
+
"•": "\x2022",
+
"…": "\x2026",
+
"′": "\x2032",
+
"″": "\x2033",
+
"‾": "\x203E",
+
"⁄": "\x2044",
+
"℘": "\x2118",
+
"ℑ": "\x2111",
+
"ℜ": "\x211C",
+
"™": "\x2122",
+
"ℵ": "\x2135",
+
"←": "\x2190",
+
"↑": "\x2191",
+
"→": "\x2192",
+
"↓": "\x2193",
+
"↔": "\x2194",
+
"↵": "\x21B5",
+
"⇐": "\x21D0",
+
"⇑": "\x21D1",
+
"⇒": "\x21D2",
+
"⇓": "\x21D3",
+
"⇔": "\x21D4",
+
"∀": "\x2200",
+
"∂": "\x2202",
+
"∃": "\x2203",
+
"∅": "\x2205",
+
"∇": "\x2207",
+
"∈": "\x2208",
+
"∉": "\x2209",
+
"∋": "\x220B",
+
"∏": "\x220F",
+
"∑": "\x2211",
+
"−": "\x2212",
+
"∗": "\x2217",
+
"√": "\x221A",
+
"∝": "\x221D",
+
"∞": "\x221E",
+
"∠": "\x2220",
+
"∧": "\x2227",
+
"∨": "\x2228",
+
"∩": "\x2229",
+
"∪": "\x222A",
+
"∫": "\x222B",
+
"∴": "\x2234",
+
"∼": "\x223C",
+
"≅": "\x2245",
+
"≈": "\x2248",
+
"≠": "\x2260",
+
"≡": "\x2261",
+
"≤": "\x2264",
+
"≥": "\x2265",
+
"⊂": "\x2282",
+
"⊃": "\x2283",
+
"⊄": "\x2284",
+
"⊆": "\x2286",
+
"⊇": "\x2287",
+
"⊕": "\x2295",
+
"⊗": "\x2297",
+
"⊥": "\x22A5",
+
"⋅": "\x22C5",
+
"⌈": "\x2308",
+
"⌉": "\x2309",
+
"⌊": "\x230A",
+
"⌋": "\x230B",
+
"⟨": "\x2329",
+
"⟩": "\x232A",
+
"◊": "\x25CA",
+
"♠": "\x2660",
+
"♣": "\x2663",
+
"♥": "\x2665",
+
"♦": "\x2666",
+
]);
+
+
constant greek
+
= ([ "Α": "\x391",
+
"Β": "\x392",
+
"Γ": "\x393",
+
"Δ": "\x394",
+
"Ε": "\x395",
+
"Ζ": "\x396",
+
"Η": "\x397",
+
"Θ": "\x398",
+
"Ι": "\x399",
+
"Κ": "\x39A",
+
"Λ": "\x39B",
+
"Μ": "\x39C",
+
"Ν": "\x39D",
+
"Ξ": "\x39E",
+
"Ο": "\x39F",
+
"Π": "\x3A0",
+
"Ρ": "\x3A1",
+
"Σ": "\x3A3",
+
"Τ": "\x3A4",
+
"Υ": "\x3A5",
+
"Φ": "\x3A6",
+
"Χ": "\x3A7",
+
"Ψ": "\x3A8",
+
"Ω": "\x3A9",
+
"α": "\x3B1",
+
"β": "\x3B2",
+
"γ": "\x3B3",
+
"δ": "\x3B4",
+
"ε": "\x3B5",
+
"ζ": "\x3B6",
+
"η": "\x3B7",
+
"θ": "\x3B8",
+
"ι": "\x3B9",
+
"κ": "\x3BA",
+
"λ": "\x3BB",
+
"μ": "\x3BC",
+
"ν": "\x3BD",
+
"ξ": "\x3BE",
+
"ο": "\x3BF",
+
"π": "\x3C0",
+
"ρ": "\x3C1",
+
"ς": "\x3C2",
+
"σ": "\x3C3",
+
"τ": "\x3C4",
+
"υ": "\x3C5",
+
"φ": "\x3C6",
+
"χ": "\x3C7",
+
"ψ": "\x3C8",
+
"ω": "\x3C9",
+
]);
+
+
constant replace_entities=indices( iso88591 )+indices( international )+indices( symbols )+indices( greek )+({"<",">","&",""","'",""",""","'","�"});
+
constant replace_values =values( iso88591 )+values( international )+values( symbols )+values( greek )+({"<",">","&","\"","\'","\"","\"","\'","\000"});
+
+
constant safe_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"/"";
+
constant empty_strings = ({""})*sizeof(safe_characters);
+
+
int is_safe_string(string in)
+
{
+
return strlen(in) && !strlen(replace(in, safe_characters, empty_strings));
+
}
+
+
string make_entity( string q )
+
{
+
return "&"+q+";";
+
}
+
+
string make_tag_attributes(mapping(string:string) in)
+
{
+
if(!in || !sizeof(in)) return "";
+
string res="";
+
foreach(indices(in), string a)
+
res+=" "+a+"=\""+html_encode_string((string)in[a])+"\"";
+
return res;
+
}
+
+
string make_tag(string name, mapping(string:string) args, void|int xml)
+
//! Returns an empty element tag `name', with the tag arguments dictated
+
//! by the mapping `args'. If the flag xml is set, slash character will be
+
//! added in the end of the tag. Use RXML.t_xml->format_tag(name, args) instead.
+
{
+
return "<"+name+make_tag_attributes(args,xml)+(xml?" /":"")+">";
+
}
+
+
string make_container(string name, mapping(string:string) args, string content)
+
//! Returns a container tag `name' encasing the string `content', with
+
//! the tag arguments dictated by the mapping `args'. Use
+
//! RXML.t_xml->format_tag(name, args, content) instead.
+
{
+
if(args["/"]=="/") m_delete(args, "/");
+
return make_tag(name,args)+content+"</"+name+">";
+
}
+
+
string dirname( string file )
+
{
+
if(!file)
+
return "/";
+
if(file[-1] == '/')
+
if(strlen(file) > 1)
+
return file[0..strlen(file)-2];
+
else
+
return file;
+
array tmp=file/"/";
+
if(sizeof(tmp)==2 && tmp[0]=="")
+
return "/";
+
return tmp[0..sizeof(tmp)-2]*"/";
+
}
+
+
string conv_hex( int color )
+
{
+
return sprintf("#%06X", color);
+
}
+
+
string add_config( string url, array config, multiset prestate )
+
{
+
if(!sizeof(config))
+
return url;
+
if(strlen(url)>5 && (url[1] == '(' || url[1] == '<'))
+
return url;
+
return "/<" + config * "," + ">" + add_pre_state(url, prestate);
+
}
+
+
string msectos(int t)
+
{
+
if(t<1000) /* One sec. */
+
{
+
return sprintf("0.%02d sec", t/10);
+
} else if(t<6000) { /* One minute */
+
return sprintf("%d.%02d sec", t/1000, (t%1000 + 5) / 10);
+
} else if(t<3600000) { /* One hour */
+
return sprintf("%d:%02d m:s", t/60000, (t%60000)/1000);
+
}
+
return sprintf("%d:%02d h:m", t/3600000, (t%3600000)/60000);
+
}
+
+
string extension( string f, RequestID|void id)
+
{
+
string ext, key;
+
if(!f || !strlen(f)) return "";
+
if(!id || !(ext = [string]id->misc[key="_ext_"+f])) {
+
sscanf(reverse(f), "%s.%*s", ext);
+
if(!ext) ext = "";
+
else {
+
ext = lower_case(reverse(ext));
+
if(sizeof (ext) && (ext[-1] == '~' || ext[-1] == '#'))
+
ext = ext[..strlen(ext)-2];
+
}
+
if(id) id->misc[key]=ext;
+
}
+
return ext;
+
}
+
+
int(0..1) backup_extension( string f )
+
//! Determines if the provided filename indicates
+
//! that the file is a backup file.
+
{
+
if(!strlen(f))
+
return 1;
+
return (f[-1] == '#' || f[-1] == '~' || f[0..1]==".#"
+
|| (f[-1] == 'd' && sscanf(f, "%*s.old"))
+
|| (f[-1] == 'k' && sscanf(f, "%*s.bak")));
+
}
+
+
array(string) win_drive_prefix(string path)
+
//! Splits path into ({ prefix, path }) array. Prefix is "" for paths on
+
//! non-Windows systems or when no proper drive prefix is found.
+
{
+
#ifdef __NT__
+
string prefix;
+
if (sscanf(path, "\\\\%s%*[\\/]%s", prefix, string path_end) == 3) {
+
return ({ "\\\\" + prefix, "/" + path_end });
+
} else if (sscanf(path, "%1s:%s", prefix, path) == 2) {
+
return ({ prefix + ":", path });
+
}
+
#endif
+
return ({ "", path });
+
}
+
+
string simplify_path(string file)
+
//! This one will remove .././ etc. in the path. The returned value
+
//! will be a canonic representation of the given path.
+
{
+
// Faster for most cases since "//", "./" or "../" rarely exists.
+
if(!strlen(file) || (!has_value(file, "./") && (file[-1] != '.') &&
+
!has_value (file, "//")))
+
return file;
+
+
int t2,t1;
+
+
[string prefix, file] = win_drive_prefix(file);
+
+
if(file[0] != '/')
+
t2 = 1;
+
+
if(strlen(file) > 1
+
&& file[-2]=='/'
+
&& ((file[-1] == '/') || (file[-1]=='.'))
+
)
+
t1=1;
+
+
file=combine_path("/", file);
+
+
if(t1) file += "/.";
+
if(t2) return prefix + file[1..];
+
+
return prefix + file;
+
}
+
+
string short_date(int timestamp)
+
//! Returns a short date string from a time-int
+
{
+
int date = time(1);
+
+
if(ctime(date)[20..23] != ctime(timestamp)[20..23])
+
return ctime(timestamp)[4..9] +" "+ ctime(timestamp)[20..23];
+
+
return ctime(timestamp)[4..9] +" "+ ctime(timestamp)[11..15];
+
}
+
+
string int2roman(int m)
+
//! Converts the provided integer to a roman integer (i.e. a string).
+
{
+
string res="";
+
if (m>10000000||m<0) return "que";
+
while (m>999) { res+="M"; m-=1000; }
+
if (m>899) { res+="CM"; m-=900; }
+
else if (m>499) { res+="D"; m-=500; }
+
else if (m>399) { res+="CD"; m-=400; }
+
while (m>99) { res+="C"; m-=100; }
+
if (m>89) { res+="XC"; m-=90; }
+
else if (m>49) { res+="L"; m-=50; }
+
else if (m>39) { res+="XL"; m-=40; }
+
while (m>9) { res+="X"; m-=10; }
+
if (m>8) return res+"IX";
+
else if (m>4) { res+="V"; m-=5; }
+
else if (m>3) return res+"IV";
+
while (m) { res+="I"; m--; }
+
return res;
+
}
+
+
string number2string(int n, mapping m, array|function names)
+
{
+
string s;
+
switch (m->type)
+
{
+
case "string":
+
if (functionp(names)) {
+
s=([function(int:string)]names)(n);
+
break;
+
}
+
if (n<0 || n>=sizeof(names))
+
s="";
+
else
+
s=([array(string)]names)[n];
+
break;
+
case "roman":
+
s=int2roman(n);
+
break;
+
default:
+
return (string)n;
+
}
+
+
switch(m["case"]) {
+
case "lower": return lower_case(s);
+
case "upper": return upper_case(s);
+
case "capitalize": return capitalize(s);
+
}
+
+
#ifdef old_rxml_compat
+
if (m->lower) return lower_case(s);
+
if (m->upper) return upper_case(s);
+
if (m->cap||m->capitalize) return capitalize(s);
+
#endif
+
+
return s;
+
}
+
+
string image_from_type( string t )
+
//! Returns an internal-gopher icon link that corresponds to the
+
//! provided MIME-type, e.g. "internal-gopher-image" for "image/gif".
+
{
+
if(t)
+
{
+
sscanf(t, "%s/", t);
+
switch(t)
+
{
+
case "audio":
+
case "sound":
+
return "internal-gopher-sound";
+
case "image":
+
return "internal-gopher-image";
+
case "application":
+
return "internal-gopher-binary";
+
case "text":
+
return "internal-gopher-text";
+
}
+
}
+
return "internal-gopher-unknown";
+
}
+
+
#define PREFIX ({ "bytes", "kb", "Mb", "Gb", "Tb", "Hb" })
+
string sizetostring( int size )
+
//! Returns the size as a memory size string with suffix,
+
//! e.g. 43210 is converted into "42.2 kb.
+
{
+
if(size<0) return "--------";
+
float s = (float)size;
+
size=0;
+
+
if(s<1024.0) return (int)s+" bytes";
+
while( s > 1024.0 )
+
{
+
s /= 1024.0;
+
size ++;
+
}
+
return sprintf("%.1f %s", s, PREFIX[ size ]);
+
}
+
+
mapping proxy_auth_needed(RequestID id)
+
{
+
int|mapping res = id->conf->check_security(proxy_auth_needed, id);
+
if(res)
+
{
+
if(res==1) // Nope...
+
return http_low_answer(403, "You are not allowed to access this proxy");
+
if(!mappingp(res))
+
return 0; // Error, really.
+
res->error = 407;
+
return [mapping]res;
+
}
+
return 0;
+
}
+
+
// Please use __FILE__ if possible.
+
string program_filename()
+
{
+
return master()->program_name(this_object())||"";
+
}
+
+
string program_directory()
+
{
+
array(string) p = program_filename()/"/";
+
return (sizeof(p)>1? p[..sizeof(p)-2]*"/" : getcwd());
+
}
+
+
string html_encode_string(LocaleString str)
+
//! Encodes `str' for use as a literal in html text.
+
{
+
return replace((string)str, ({"&", "<", ">", "\"", "\'", "\000" }),
+
({"&", "<", ">", """, "'", "�"}));
+
}
+
+
string html_decode_string(LocaleString str)
+
//! Decodes `str', opposite to <ref>html_encode_string()</ref>
+
{
+
return replace((string)str, replace_entities, replace_values);
+
}
+
+
string html_encode_tag_value(LocaleString str)
+
//! Encodes `str' for use as a value in an html tag.
+
{
+
// '<' is not allowed in attribute values in XML 1.0.
+
return "\"" + replace((string)str, ({"&", "\"", "<"}), ({"&", """, "<"})) + "\"";
+
}
+
+
string strftime(string fmt, int t)
+
//! Encodes the time `t' according to the format string `fmt'.
+
{
+
if(!sizeof(fmt)) return "";
+
mapping lt = localtime(t);
+
fmt=replace(fmt, "%%", "\0");
+
array(string) a = fmt/"%";
+
string res = a[0];
+
+
foreach(a[1..], string key) {
+
if(key=="") continue;
+
switch(key[0]) {
+
case 'a': // Abbreviated weekday name
+
res += ({ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" })[lt->wday];
+
break;
+
case 'A': // Weekday name
+
res += ({ "Sunday", "Monday", "Tuesday", "Wednesday",
+
"Thursday", "Friday", "Saturday" })[lt->wday];
+
break;
+
case 'b': // Abbreviated month name
+
case 'h': // Abbreviated month name
+
res += ({ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" })[lt->mon];
+
break;
+
case 'B': // Month name
+
res += ({ "January", "February", "March", "April", "May", "June",
+
"July", "August", "September", "October", "November", "December" })[lt->mon];
+
break;
+
case 'c': // Date and time
+
res += strftime(sprintf("%%a %%b %02d %02d:%02d:%02d %04d",
+
lt->mday, lt->hour, lt->min, lt->sec, 1900 + lt->year), t);
+
break;
+
case 'C': // Century number; 0-prefix
+
res += sprintf("%02d", 19 + lt->year/100);
+
break;
+
case 'd': // Day of month [1,31]; 0-prefix
+
res += sprintf("%02d", lt->mday);
+
break;
+
case 'D': // Date as %m/%d/%y
+
res += strftime("%m/%d/%y", t);
+
break;
+
case 'e': // Day of month [1,31]; space-prefix
+
res += sprintf("%2d", lt->mday);
+
break;
+
case 'E':
+
case 'O':
+
key = key[1..]; // No support for E or O extension.
+
break;
+
case 'H': // Hour (24-hour clock) [0,23]; 0-prefix
+
res += sprintf("%02d", lt->hour);
+
break;
+
case 'I': // Hour (12-hour clock) [1,12]; 0-prefix
+
res += sprintf("%02d", 1 + (lt->hour + 11)%12);
+
break;
+
case 'j': // Day number of year [1,366]; 0-prefix
+
res += sprintf("%03d", lt->yday);
+
break;
+
case 'k': // Hour (24-hour clock) [0,23]; space-prefix
+
res += sprintf("%2d", lt->hour);
+
break;
+
case 'l': // Hour (12-hour clock) [1,12]; space-prefix
+
res += sprintf("%2d", 1 + (lt->hour + 11)%12);
+
break;
+
case 'm': // Month number [1,12]; 0-prefix
+
res += sprintf("%02d", lt->mon + 1);
+
break;
+
case 'M': // Minute [00,59]; 0-prefix
+
res += sprintf("%02d", lt->min);
+
break;
+
case 'n': // Newline
+
res += "\n";
+
break;
+
case 'p': // a.m. or p.m.
+
res += lt->hour<12 ? "a.m." : "p.m.";
+
break;
+
case 'r': // Time in 12-hour clock format with %p
+
res += strftime("%l:%M %p", t);
+
break;
+
case 'R': // Time as %H:%M
+
res += sprintf("%02d:%02d", lt->hour, lt->min);
+
break;
+
case 'S': // Seconds [00,61]; 0-prefix
+
res += sprintf("%02", lt->sec);
+
break;
+
case 't': // Tab
+
res += "\t";
+
break;
+
case 'T': // Time as %H:%M:%S
+
case 'X':
+
res += sprintf("%02d:%02d:%02d", lt->hour, lt->min, lt->sec);
+
break;
+
case 'u': // Weekday as a decimal number [1,7], Sunday == 1
+
res += sprintf("%d", lt->wday + 1);
+
break;
+
case 'w': // Weekday as a decimal number [0,6], Sunday == 0
+
res += sprintf("%d", lt->wday);
+
break;
+
case 'x': // Date
+
res += strftime("%a %b %d %Y", t);
+
break;
+
case 'y': // Year [00,99]; 0-prefix
+
res += sprintf("%02d", lt->year % 100);
+
break;
+
case 'Y': // Year [0000.9999]; 0-prefix
+
res += sprintf("%04d", 1900 + lt->year);
+
break;
+
+
case 'U': // Week number of year as a decimal number [00,53],
+
// with Sunday as the first day of week 1; 0-prefix
+
res += sprintf("%02d", ((lt->yday-1+lt->wday)/7));
+
break;
+
case 'V': // ISO week number of the year as a decimal number [01,53]; 0-prefix
+
res += sprintf("%02d", Calendar.ISO.Second(t)->week_no());
+
break;
+
case 'W': // Week number of year as a decimal number [00,53],
+
// with Monday as the first day of week 1; 0-prefix
+
res += sprintf("%02d", ((lt->yday+(5+lt->wday)%7)/7));
+
break;
+
case 'Z': /* FIXME: Time zone name or abbreviation, or no bytes if
+
* no time zone information exists
+
*/
+
}
+
res+=key[1..];
+
}
+
return replace(res, "\0", "%");
+
}
+
+
RoxenModule get_module (string modname)
+
//! Resolves a string as returned by get_modname to a module object if
+
//! one exists.
+
{
+
string cname, mname;
+
int mid = 0;
+
+
if (sscanf (modname, "%s/%s", cname, mname) != 2 ||
+
!sizeof (cname) || !sizeof(mname)) return 0;
+
sscanf (mname, "%s#%d", mname, mid);
+
+
if (Configuration conf = roxen->get_configuration (cname))
+
if (mapping moddata = conf->modules[mname])
+
return moddata->copies[mid];
+
+
return 0;
+
}
+
+
string get_modname (RoxenModule module)
+
//! Returns a string uniquely identifying the given module on the form
+
//! `<config name>/<module short name>#<copy>'.
+
{
+
if (!module) return 0;
+
+
if (Configuration conf = module->my_configuration())
+
if (string mname = conf->otomod[module])
+
return conf->name + "/" + mname;
+
+
return 0;
+
}
+
+
string get_modfullname (RoxenModule module)
+
//! This determines the full module (human-readable) name in
+
//! approximately the same way as the config UI. Note that the
+
//! returned string is text/html.
+
{
+
if (module) {
+
string|mapping(string:string) name = 0;
+
if (module->query)
+
catch {
+
mixed res = module->query ("_name");
+
if (res) name = (string) res;
+
};
+
if (!(name && sizeof (name)) && module->query_name)
+
name = module->query_name();
+
if (!(name && sizeof (name)))
+
name = [string]module->register_module()[1];
+
if (mappingp (name))
+
// FIXME: Use locale from an id object in some standard way.
+
name = name->standard;
+
return name;
+
}
+
else return 0;
+
}
+
+
string roxen_encode( string val, string encoding )
+
//! Quote strings in a multitude of ways. Used primarily by entity quoting.
+
//! The encoding string can be any of the following:
+
//! none - No encoding
+
//! http - HTTP encoding
+
//! cookie - HTTP cookie encoding
+
//! url - HTTP encoding, including special characters in URL:s
+
//! html - HTML encofing, for generic text in html documents.
+
//! pike - Pike string quoting, for use in e.g. the <pike></pike> tag.
+
//! js - Javascript string quoting.
+
//! mysql - MySQL quoting.
+
//! oracle - Oracle quoting.
+
//! mysql-pike - MySQL quoting followed by Pike string quoting.
+
{
+
switch (encoding) {
+
case "":
+
case "none":
+
return val;
+
+
case "http":
+
return http_encode_string (val);
+
+
case "cookie":
+
return http_encode_cookie (val);
+
+
case "url":
+
return http_encode_url (val);
+
+
case "html":
+
return html_encode_string (val);
+
+
case "dtag":
+
// This is left for compatibility...
+
return replace (val, "\"", "\"'\"'\"");
+
+
case "stag":
+
// This is left for compatibility
+
return replace(val, "'", "'\"'\"'");
+
+
case "pike":
+
return replace (val,
+
({ "\"", "\\", "\n" }),
+
({ "\\\"", "\\\\", "\\n" }));
+
+
case "js":
+
case "javascript":
+
return replace (val,
+
({ "\b", "\014", "\n", "\r", "\t", "\\", "'", "\"" }),
+
({ "\\b", "\\f", "\\n", "\\r", "\\t", "\\\\",
+
"\\'", "\\\"" }));
+
+
case "mysql":
+
return replace (val,
+
({ "\"", "'", "\\" }),
+
({ "\\\"" , "\\'", "\\\\" }) );
+
+
case "sql":
+
case "oracle":
+
return replace (val, "'", "''");
+
+
case "mysql-dtag":
+
// This is left for compatibility
+
return replace (val,
+
({ "\"", "'", "\\" }),
+
({ "\\\"'\"'\"", "\\'", "\\\\" }));
+
+
case "mysql-pike":
+
return replace (val,
+
({ "\"", "'", "\\", "\n" }),
+
({ "\\\\\\\"", "\\\\'",
+
"\\\\\\\\", "\\n" }) );
+
+
case "sql-dtag":
+
case "oracle-dtag":
+
// This is left for compatibility
+
return replace (val,
+
({ "'", "\"" }),
+
({ "''", "\"'\"'\"" }) );
+
+
default:
+
//! Unknown encoding. Let the caller decide what to do with it.
+
return 0;
+
}
+
}
+
+
string fix_relative( string file, RequestID id )
+
//! Turns a relative (or already absolute) virtual path into an
+
//! absolute virtual path, that is, one rooted at the virtual server's
+
//! root directory. The returned path is <ref>simplify_path()</ref>:ed.
+
{
+
string path = id->not_query;
+
if( !search( file, "http:" ) )
+
return file;
+
+
[string prefix, file] = win_drive_prefix(file);
+
+
// +(id->misc->path_info?id->misc->path_info:"");
+
if(file != "" && file[0] == '/')
+
;
+
else if(file != "" && file[0] == '#')
+
file = path + file;
+
else
+
file = dirname(path) + "/" + file;
+
return simplify_path(prefix + file);
+
}
+
+
Stdio.File open_log_file( string logfile )
+
//! Opens a log file with the provided name, but
+
//! with %y, %m, %d and %h replaced with year, month
+
//! day and hour.
+
{
+
mapping m = localtime(time(1));
+
m->year += 1900; // Adjust for years being counted since 1900
+
m->mon++; // Adjust for months being counted 0-11
+
if(m->mon < 10) m->mon = "0"+m->mon;
+
if(m->mday < 10) m->mday = "0"+m->mday;
+
if(m->hour < 10) m->hour = "0"+m->hour;
+
logfile = replace(logfile,({"%d","%m","%y","%h" }),
+
({ (string)m->mday, (string)(m->mon),
+
(string)(m->year),(string)m->hour,}));
+
if(strlen(logfile))
+
{
+
Stdio.File lf=Stdio.File( logfile, "wac");
+
if(!lf)
+
{
+
mkdirhier(logfile);
+
if(!(lf=Stdio.File( logfile, "wac")))
+
{
+
report_error("Failed to open logfile. ("+logfile+"): "
+
+ strerror( errno() )+"\n");
+
return 0;
+
}
+
}
+
return lf;
+
}
+
return Stdio.stderr;
+
}
+
+
string tagtime(int t, mapping(string:string) m, RequestID id,
+
function(string, string,
+
object:function(int, mapping(string:string):string)) language)
+
//! A rather complex function used as presentation function by
+
//! several RXML tags. It takes a unix-time integer and a mapping
+
//! with formating instructions and returns a string representation
+
//! of that time. See the documentation of the date tag.
+
{
+
string res;
+
+
if (m->adjust) t+=(int)m->adjust;
+
+
string lang;
+
if(id->misc->defines->theme_language) lang=id->misc->defines->theme_language;
+
if(m->lang) lang=m->lang;
+
+
if(m->strftime)
+
return strftime(m->strftime, t);
+
+
if (m->part)
+
{
+
string sp;
+
if(m->type == "ordered")
+
{
+
m->type="string";
+
sp = "ordered";
+
}
+
+
switch (m->part)
+
{
+
case "year":
+
return number2string(localtime(t)->year+1900,m,
+
language(lang, sp||"number",id));
+
case "month":
+
return number2string(localtime(t)->mon+1,m,
+
language(lang, sp||"month",id));
+
case "week":
+
return number2string(Calendar.ISO.Second(t)->week_no(),
+
m, language(lang, sp||"number",id));
+
case "beat":
+
//FIXME This should be done inside Calendar.
+
mapping lt=gmtime(t);
+
int secs=3600;
+
secs+=lt->hour*3600;
+
secs+=lt->min*60;
+
secs+=lt->sec;
+
secs%=24*3600;
+
float beats=secs/86.4;
+
if(!sp) return sprintf("@%03d",(int)beats);
+
return number2string((int)beats,m,
+
language(lang, sp||"number",id));
+
+
case "day":
+
case "wday":
+
return number2string(localtime(t)->wday+1,m,
+
language(lang, sp||"day",id));
+
case "date":
+
case "mday":
+
return number2string(localtime(t)->mday,m,
+
language(lang, sp||"number",id));
+
case "hour":
+
return number2string(localtime(t)->hour,m,
+
language(lang, sp||"number",id));
+
+
case "min": // Not part of RXML 2.0
+
case "minute":
+
return number2string(localtime(t)->min,m,
+
language(lang, sp||"number",id));
+
case "sec": // Not part of RXML 2.0
+
case "second":
+
return number2string(localtime(t)->sec,m,
+
language(lang, sp||"number",id));
+
case "seconds":
+
return number2string(t,m,
+
language(lang, sp||"number",id));
+
case "yday":
+
return number2string(localtime(t)->yday,m,
+
language(lang, sp||"number",id));
+
default: return "";
+
}
+
}
+
else if(m->type) {
+
switch(m->type)
+
{
+
case "iso":
+
mapping eris=localtime(t);
+
if(m->date)
+
return sprintf("%d-%02d-%02d",
+
(eris->year+1900), eris->mon+1, eris->mday);
+
if(m->time)
+
return sprintf("%02d:%02d:%02d", eris->hour, eris->min, eris->sec);
+
+
return sprintf("%d-%02d-%02dT%02d:%02d:%02d",
+
(eris->year+1900), eris->mon+1, eris->mday,
+
eris->hour, eris->min, eris->sec);
+
+
case "discordian":
+
#if efun(discdate)
+
array(string) not=discdate(t);
+
res=not[0];
+
if(m->year)
+
res += " in the YOLD of "+not[1];
+
if(m->holiday && not[2])
+
res += ". Celebrate "+not[2];
+
return res;
+
#else
+
return "Discordian date support disabled";
+
#endif
+
case "stardate":
+
#if efun(stardate)
+
return (string)stardate(t, (int)m->prec||1);
+
#else
+
return "Stardate support disabled";
+
#endif
+
}
+
}
+
+
res=language(lang, "date", id)(t,m);
+
+
if(m["case"])
+
switch(lower_case(m["case"]))
+
{
+
case "upper": return upper_case(res);
+
case "lower": return lower_case(res);
+
case "capitalize": return capitalize(res);
+
}
+
+
#ifdef old_rxml_compat
+
// Not part of RXML 2.0
+
if (m->upper) {
+
res=upper_case(res);
+
report_warning("Old RXML in "+(id->query||id->not_query)+
+
", contains upper attribute in a tag. Use case=\"upper\" instead.");
+
}
+
if (m->lower) {
+
res=lower_case(res);
+
report_warning("Old RXML in "+(id->query||id->not_query)+
+
", contains lower attribute in a tag. Use case=\"lower\" instead.");
+
}
+
if (m->cap||m->capitalize) {
+
res=capitalize(res);
+
report_warning("Old RXML in "+(id->query||id->not_query)+
+
", contains capitalize or cap attribute in a tag. Use case=\"capitalize\" instead.");
+
}
+
#endif
+
return res;
+
}
+
+
int time_dequantifier(mapping m)
+
//! Calculates an integer with how many seconds a mapping
+
//! that maps from time units to an integer can be collapsed to.
+
//! E.g. (["minutes":2]) results in 120.
+
//! Valid units are seconds, minutes, beats, hours, days, weeks,
+
//! months and years.
+
{
+
float t = 0.0;
+
if (m->seconds) t+=((float)(m->seconds));
+
if (m->minutes) t+=((float)(m->minutes))*60;
+
if (m->beats) t+=((float)(m->beats))*86.4;
+
if (m->hours) t+=((float)(m->hours))*3600;
+
if (m->days) t+=((float)(m->days))*86400;
+
if (m->weeks) t+=((float)(m->weeks))*604800;
+
if (m->months) t+=((float)(m->months))*(24*3600*30.436849);
+
if (m->years) t+=((float)(m->years))*(3600*24*365.242190);
+
return (int)t;
+
}
+
+
class _charset_decoder(object cs)
+
{
+
string decode(string what)
+
{
+
return cs->clear()->feed(what)->drain();
+
}
+
}
+
+
function get_client_charset_decoder( string åäö, RequestID|void id )
+
//! Returns a decoder for the clients charset, given the clients
+
//! encoding of the string "åäö". See the roxen-automatic-charset-variable
+
//! tag.
+
{
+
switch( (åäö/"\0")[0] )
+
{
+
case "edv":
+
report_notice( "Warning: Non 8-bit safe client detected (%s)",
+
(id?id->client*"":"unknown client"));
+
return 0;
+
+
case "åäö":
+
return 0;
+
+
case "\33-Aåäö":
+
id && id->set_output_charset && id->set_output_charset( "iso-2022" );
+
return _charset_decoder(Locale.Charset.decoder("iso-2022-jp"))->decode;
+
+
case "åäö":
+
id && id->set_output_charset && id->set_output_charset( "utf-8" );
+
return utf8_to_string;
+
+
case "\214\212\232":
+
id && id->set_output_charset && id->set_output_charset( "mac" );
+
return _charset_decoder( Locale.Charset.decoder( "mac" ) )->decode;
+
+
case "\0å\0ä\0ö":
+
id&&id->set_output_charset&&id->set_output_charset(string_to_unicode);
+
return unicode_to_string;
+
}
+
report_warning( "Unable to find charset decoder for åäö == "+åäö+"\n" );
+
}
+
+
+
// Low-level C-roxen optimization functions. #if constant( _Roxen ) inherit _Roxen;