Roxen.git
/
server
/
plugins
/
protocols
/
http.pike
version
»
Context lines:
10
20
40
80
file
none
3
Roxen.git/server/plugins/protocols/http.pike:1:
+
// This is a roxen protocol module.
+
// Modified by Francesco Chemolli to add throttling capabilities.
+
// Copyright © 1996 - 2001, Roxen IS.
-
+
constant cvs_version = "$Id: http.pike,v 1.370 2002/06/03 21:43:10 nilsson Exp $";
+
// #define REQUEST_DEBUG
+
#define MAGIC_ERROR
+
+
#ifdef MAGIC_ERROR
+
inherit "highlight_pike";
+
#endif
+
+
// HTTP protocol module.
+
#include <config.h>
+
#define TIMER_PREFIX "http:"
+
#include <timers.h>
+
+
inherit RequestID;
+
+
#ifdef PROFILE
+
#define HRTIME() gethrtime()
+
#define HRSEC(X) ((int)((X)*1000000))
+
#define SECHR(X) ((X)/(float)1000000)
+
int req_time = HRTIME();
+
#endif
+
+
#ifdef ID_OBJ_DEBUG
+
RoxenDebug.ObjectMarker __marker = RoxenDebug.ObjectMarker (this_object());
+
#endif
+
+
#ifdef REQUEST_DEBUG
+
int footime, bartime;
+
#define REQUEST_WERR(X) bartime = gethrtime()-footime; werror("%s (%d)\n", (X), bartime);footime=gethrtime()
+
#else
+
#define REQUEST_WERR(X)
+
#endif
+
+
#ifdef FD_DEBUG
+
#define MARK_FD(X) \
+
catch{ \
+
REQUEST_WERR("FD " + my_fd->query_fd() + ": " + (X)); \
+
mark_fd(my_fd->query_fd(), (X)+" "+remoteaddr); \
+
}
+
#else
+
#define MARK_FD(X)
+
#endif
+
+
#ifdef THROTTLING_DEBUG
+
#undef THROTTLING_DEBUG
+
#define THROTTLING_DEBUG(X) werror("Throttling: "+X+"\n")
+
#else
+
#define THROTTLING_DEBUG(X)
+
#endif
+
+
constant decode = MIME.decode_base64;
+
constant find_supports_and_vars = roxen.find_supports_and_vars;
+
constant version = roxen.version;
+
constant _query = roxen.query;
+
+
private static array(string) cache;
+
private static int wanted_data, have_data;
+
+
#include <roxen.h>
+
#include <module.h>
+
#include <variables.h>
+
#include <request_trace.h>
+
+
#define MY_TRACE_ENTER(A, B) \
+
do {RequestID id = this_object(); TRACE_ENTER (A, B);} while (0)
+
#define MY_TRACE_LEAVE(A) \
+
do {RequestID id = this_object(); TRACE_LEAVE (A);} while (0)
+
+
mapping(string:array) real_variables = ([]);
+
mapping(string:mixed)|FakedVariables variables = FakedVariables( real_variables );
+
+
mapping (string:mixed) misc =
+
([
+
#if 0
+
#ifdef REQUEST_DEBUG
+
"trace_enter":lambda(mixed ...args) {
+
REQUEST_WERR(sprintf("TRACE_ENTER(%{%O,%})", args));
+
},
+
"trace_leave":lambda(mixed ...args) {
+
REQUEST_WERR(sprintf("TRACE_LEAVE(%{%O,%})", args));
+
}
+
#endif // REQUEST_DEBUG
+
#endif
+
]);
+
mapping (string:string) cookies = ([ ]);
+
mapping (string:string) request_headers = ([ ]);
+
mapping (string:string) client_var = ([ ]);
+
+
multiset (string) prestate = (< >);
+
multiset (string) config = (< >);
+
multiset (string) pragma = (< >);
+
+
mapping file;
+
+
string rest_query="";
+
string raw="";
+
string extra_extension = ""; // special hack for the language module
+
+
+
static mapping connection_stats = ([]);
+
+
class AuthEmulator
+
// Emulate the old (rather cumbersome) authentication API
+
{
+
mixed `[]( int i )
+
{
+
User u;
+
switch( i )
+
{
+
case 0:
+
return conf->authenticate( this_object() );
+
case 1:
+
if( u = conf->authenticate( this_object() ) )
+
return u->name();
+
if( realauth )
+
return (realauth/":")[0];
+
+
case 2:
+
if( u = conf->authenticate( this_object() ) )
+
return 0;
+
if( realauth )
+
return ((realauth/":")[1..])*":";
+
}
+
}
+
int `!( )
+
{
+
return !realauth;
+
}
+
}
+
+
AuthEmulator auth;
+
+
array(string) output_charset = ({});
+
string input_charset;
+
+
void set_output_charset( string|function to, int|void mode )
+
{
+
if( search( output_charset, to ) != -1 ) // Already done.
+
return;
+
+
switch( mode )
+
{
+
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;
+
}
+
}
+
+
string charset_name( function|string what )
+
{
+
switch( f )
+
{
+
case string_to_unicode: return "ISO10646-1";
+
case string_to_utf8: return "UTF-8";
+
default: return upper_case((string)what);
+
}
+
}
+
+
function charset_function( function|string what, int allow_entities )
+
{
+
switch( f )
+
{
+
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 {
+
// If current file is "text/html" or "text/xml" we'll use an entity
+
// encoding fallback instead of empty string subsitution.
+
function fallback_func =
+
allow_entities &&
+
(file->type[0..8] == "text/html" || file->type[0..7] == "text/xml") &&
+
lambda(string char) {
+
return sprintf("&#x%x;", char[0]);
+
};
+
return
+
Roxen._charset_decoder( 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 ) });
+
}
+
}
+
+
static array(string) output_encode( string what, int|void allow_entities,
+
string|void force_charset )
+
{
+
if( !force_charset )
+
{
+
string charset;
+
function encoder;
+
+
foreach( output_charset, string|function f )
+
[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()
+
});
+
}
+
+
void decode_map( mapping what, function decoder )
+
{
+
foreach( indices( what ), mixed q )
+
{
+
string ni;
+
mixed val;
+
if( stringp( q ) )
+
catch { ni = decoder( q ); };
+
val = what[q];
+
if( stringp( val ) )
+
catch { val = decoder( val ); };
+
else if( arrayp( val ) )
+
val = map( val, lambda( mixed q ) {
+
if( stringp( q ) )
+
catch { return decoder( q ); };
+
return q;
+
} );
+
else if( mappingp( val ) )
+
decode_map( val, decoder );
+
else if( multisetp( val ) )
+
val = mkmultiset( map( indices(val), lambda( mixed q ) {
+
if( stringp( q ) )
+
catch { return decoder( q ); };
+
return q;
+
} ));
+
what[ni] = val;
+
if( q != ni )
+
m_delete( what, q );
+
}
+
}
+
+
void decode_charset_encoding( string|function(string:string) decoder )
+
{
+
if(stringp(decoder))
+
decoder = Roxen._charset_decoder(Locale.Charset.decoder(decoder))->decode;
+
+
if( misc->request_charset_decoded )
+
return;
+
+
misc->request_charset_decoded = 1;
+
+
if( !decoder )
+
return;
+
+
string safe_decoder(string s) {
+
catch { return decoder(s); };
+
return s;
+
};
+
+
if( prot ) prot = safe_decoder( prot );
+
if( clientprot ) clientprot = safe_decoder( clientprot );
+
if( method ) method = safe_decoder( method );
+
if( rest_query ) rest_query = safe_decoder( rest_query );
+
if( query ) query = safe_decoder( query );
+
if( not_query ) not_query = safe_decoder( not_query );
+
if( realauth )
+
{
+
rawauth = safe_decoder( rawauth );
+
realauth = safe_decoder( realauth );
+
}
+
if( since )
+
since = safe_decoder( since );
+
+
decode_map( real_variables, decoder );
+
decode_map( misc, decoder );
+
decode_map( cookies, decoder );
+
decode_map( request_headers, decoder );
+
+
if( client )
+
client = map( client, safe_decoder );
+
if( referer )
+
referer = map( referer, safe_decoder );
+
prestate = mkmultiset( map( (array(string))indices( prestate ),
+
safe_decoder ) );
+
config = mkmultiset( map( (array(string))indices( config ),
+
safe_decoder ) );
+
pragma = mkmultiset( map( (array(string))indices( pragma ),
+
safe_decoder ) );
+
}
+
+
// Parse a HTTP/1.1 HTTP/1.0 or 0.9 request, including form data and
+
// state variables. Return 0 if more is expected, 1 if done, and -1
+
// if fatal error.
+
object pipe;
+
+
//used values: throttle->doit=0|1 to enable it
+
// throttle->rate the rate
+
// throttle->fixed if it's not to be touched again
+
mapping(string:mixed) throttle = ([]);
+
+
object throttler;//the inter-request throttling object.
+
+
/* Pipe-using send functions */
+
+
// FIXME:
+
//I'm choosing the pipe type upon setup. Thus I'm assuming that all headers
+
//have been defined before then. This is actually not true in case
+
//of throttling and keep-alive. We'll take care of that later.
+
private void setup_pipe()
+
{
+
if(!my_fd) {
+
end();
+
return;
+
}
+
if ( throttle->doit && conf->query("req_throttle") )
+
throttle->doit = 0;
+
if( throttle->doit || conf->throttler )
+
pipe=roxen.slowpipe();
+
else
+
pipe=roxen.fastpipe();
+
if (throttle->doit)
+
{
+
//we are sure that pipe is really a slowpipe.
+
throttle->rate=max(throttle->rate, conf->query("req_throttle_min"));
+
pipe->throttle(throttle->rate,
+
(int)(throttle->rate*conf->query("req_throttle_depth_mult")),
+
0);
+
THROTTLING_DEBUG("throtting request at "+throttle->rate);
+
}
+
if( pipe->set_status_mapping )
+
pipe->set_status_mapping( connection_stats );
+
if ( conf->throttler )
+
pipe->assign_throttler( conf->throttler );
+
}
+
+
+
void send (string|object what, int|void len)
+
{
+
if( len>0 && port_obj && port_obj->minimum_byterate )
+
call_out( end, len / port_obj->minimum_byterate );
+
+
if(!what) return;
+
if(!pipe) setup_pipe();
+
if(stringp(what)) {
+
REQUEST_WERR(sprintf("HTTP: Pipe string %O", what));
+
pipe->write(what);
+
}
+
else {
+
REQUEST_WERR(sprintf("HTTP: Pipe stream %O, length %O", what, len));
+
pipe->input(what,len);
+
}
+
}
+
+
void start_sender( )
+
{
+
if (pipe)
+
{
+
MARK_FD("HTTP really handled, piping response");
+
#ifdef FD_DEBUG
+
call_out(timer, 30, predef::time(1)); // Update FD with time...
+
#endif
+
pipe->set_done_callback( do_log );
+
pipe->output( my_fd );
+
} else {
+
MARK_FD("HTTP really handled, pipe done");
+
do_log();
+
}
+
}
+
+
string scan_for_query( string f )
+
{
+
query=0;
+
rest_query="";
+
if(sscanf(f,"%s?%s", f, query) == 2)
+
{
+
string v, a, b;
+
+
foreach(query / "&", v)
+
if(sscanf(v, "%s=%s", a, b) == 2)
+
{
+
a = http_decode_string(replace(a, "+", " "));
+
b = http_decode_string(replace(b, "+", " "));
+
real_variables[ a ] += ({ b });
+
} else
+
if(strlen( rest_query ))
+
rest_query += "&" + http_decode_string( v );
+
else
+
rest_query = http_decode_string( v );
+
rest_query=replace(rest_query, "+", "\000"); /* IDIOTIC STUPID STANDARD */
+
}
+
return f;
+
}
+
+
#define OLD_RXML_CONFIG
+
+
#ifdef OLD_RXML_CONFIG
+
private void really_set_config(array mod_config)
+
{
+
string url;
+
+
if(sscanf(replace(raw_url,({"%3c","%3e","%3C","%3E" }),
+
({"<",">","<",">"})),"/<%*s>/%s",url)!=2)
+
url = "/";
+
else
+
url = "/"+url;
+
+
multiset do_mod_config( multiset config )
+
{
+
if(!mod_config) return config;
+
foreach(mod_config, string m)
+
if(m[0]=='-')
+
config[m[1..]]=0;
+
else
+
config[m]=1;
+
return config;
+
};
+
+
void do_send_reply( string what, string url ) {
+
url = url_base() + url[1..];
+
my_fd->set_blocking();
+
my_fd->write( prot + " 302 Roxen config coming up\r\n"+
+
(what?what+"\r\n":"")+"Location: "+url+"\r\n"
+
"Connection: close\r\nDate: "+
+
Roxen.http_date(predef::time(1))+
+
"\r\nContent-Type: text/html\r\n"
+
"Content-Length: 1\r\n\r\nx" );
+
my_fd->close();
+
my_fd = 0;
+
end();
+
};
+
+
if(supports->cookies)
+
{
+
do_send_reply("Set-Cookie: "+
+
Roxen.http_roxen_config_cookie(indices(do_mod_config(config))*","),
+
url );
+
return;
+
}
+
if (sscanf(replace(url, ({ "%28", "%29" }), ({ "(", ")" })),
+
"/(%*s)/%s", url) == 2)
+
url = "/" + url;
+
+
do_send_reply(0,Roxen.add_pre_state( url, do_mod_config( prestate ) ));
+
}
+
#endif
+
+
private static mixed f, line;
+
private static int hstart;
+
+
class PrefLanguages {
+
+
int decoded=0;
+
int sorted=0;
+
array(string) subtags=({});
+
array(string) languages=({});
+
array(float) qualities=({});
+
+
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;
+
}
+
}
+
+
class CacheKey {
+
#if ID_CACHEKEY_DEBUG
+
constant __num = ({ 0 });
+
int _num;
+
string _sprintf() { return "CacheKey(#" + _num + ")"; }
+
void create() { _num = ++__num[0]; }
+
void destroy() { werror("CacheKey(#" + _num + "): --DESTROY--\n"
+
"%s\n\n", "" || describe_backtrace(backtrace())); }
+
#endif
+
}
+
+
//! Parse a cookie string.
+
//!
+
//! @param contents
+
//! HTTP transport-encoded cookie header value.
+
//!
+
//! @returns
+
//! Returns the resulting current cookie mapping.
+
mapping(string:string) parse_cookies( string contents )
+
{
+
if(!contents)
+
return cookies;
+
+
// misc->cookies += ({contents});
+
foreach(((contents/";") - ({""})), string c)
+
{
+
string name, value;
+
while(sizeof(c) && c[0]==' ') c=c[1..];
+
if(sscanf(c, "%s=%s", name, value) == 2)
+
{
+
value=http_decode_string(value);
+
name=http_decode_string(name);
+
cookies[ name ]=value;
+
#ifdef OLD_RXML_CONFIG
+
if( (name == "RoxenConfig") && strlen(value) )
+
config = mkmultiset( value/"," );
+
#endif
+
}
+
}
+
return cookies;
+
}
+
+
int things_to_do_when_not_sending_from_cache( )
+
{
+
#ifdef OLD_RXML_CONFIG
+
array mod_config;
+
int config_in_url;
+
#endif
+
array|string contents;
+
misc->pref_languages=PrefLanguages();
+
+
misc->cachekey = CacheKey();
+
misc->_cachecallbacks = ({});
+
if( contents = request_headers[ "accept-language" ] )
+
{
+
if( !arrayp( contents ) )
+
contents = (contents-" ")/",";
+
else
+
contents =
+
Array.flatten( map( map( contents, `-, " " ), `/, "," ))-({""});
+
misc->pref_languages->languages=contents;
+
misc["accept-language"] = contents;
+
}
+
+
if( contents = request_headers[ "cookie" ] )
+
{
+
// FIXME:
+
// "misc->cookies"? Shouldn't it be just "cookies"?
+
// /grubba 2002-03-22
+
misc->cookies = ({});
+
foreach( arrayp( contents )? contents : ({ contents }), contents )
+
{
+
parse_cookies(contents);
+
}
+
}
+
+
string f = raw_url;
+
+
+
f = scan_for_query( f );
+
f = http_decode_string( f );
+
+
// f is sent to Unix API's that take NUL-terminated strings...
+
if(search(f, "\0") != -1)
+
sscanf(f, "%s\0", f);
+
+
if( strlen( f ) > 5 )
+
{
+
string a;
+
switch( f[1] )
+
{
+
#ifdef OLD_RXML_CONFIG
+
case '<':
+
if (sscanf(f, "/<%s>/%s", a, f)==2)
+
{
+
config_in_url = 1;
+
mod_config = (a/",");
+
f = "/"+f;
+
}
+
#endif
+
// intentional fall-through
+
case '(':
+
if(strlen(f) && sscanf(f, "/(%s)/%s", a, f)==2)
+
{
+
prestate = (multiset)( a/","-({""}) );
+
f = "/"+f;
+
}
+
}
+
}
+
+
not_query = Roxen.simplify_path(f);
+
#ifndef DISABLE_SUPPORTS
+
if( !supports )
+
{
+
if( !client )
+
{
+
client = ({ "unknown" });
+
array s_and_v = find_supports_and_vars("", supports);
+
supports = s_and_v[0];
+
client_var = s_and_v[1];
+
}
+
else
+
{
+
if( !client_var->Fullname )
+
client_var->Fullname = "unknown";
+
client_var->fullname=lower_case(client_var->Fullname);
+
array s_and_v=find_supports_and_vars(client_var->fullname,supports,client_var);
+
supports = s_and_v[0];
+
client_var = s_and_v[1];
+
}
+
}
+
if ( client_var->charset && client_var->charset != "iso-8859-1" )
+
{
+
misc->cacheable = 0;
+
set_output_charset( client_var->charset );
+
input_charset = client_var->charset;
+
decode_charset_encoding( client_var->charset );
+
}
+
#else
+
supports = (< "images", "gifinline", "forms", "mailto">);
+
#endif
+
//REQUEST_WERR("HTTP: parse_got(): supports");
+
if(!referer) referer = ({ });
+
if(misc->proxyauth)
+
{
+
// The Proxy-authorization header should be removed... So there.
+
mixed tmp1,tmp2;
+
+
foreach(tmp2 = (raw / "\n"), tmp1) {
+
if(!search(lower_case(tmp1), "proxy-authorization:"))
+
tmp2 -= ({tmp1});
+
}
+
raw = tmp2 * "\n";
+
}
+
#ifdef OLD_RXML_CONFIG
+
if(config_in_url) {
+
//REQUEST_WERR("HTTP: parse_got(): config_in_url");
+
really_set_config( mod_config );
+
return 1;
+
}
+
#endif
+
if(!supports->cookies)
+
config = prestate;
+
else
+
if( port_obj->set_cookie
+
&& !cookies->RoxenUserID && strlen(not_query)
+
&& not_query[0]=='/' && method!="PUT")
+
{
+
if (!(port_obj->set_cookie_only_once &&
+
cache_lookup("hosts_for_cookie",remoteaddr)))
+
misc->moreheads = ([ "Set-Cookie":Roxen.http_roxen_id_cookie(), ]);
+
if (port_obj->set_cookie_only_once)
+
cache_set("hosts_for_cookie",remoteaddr,1);
+
}
+
+
if( mixed q = real_variables->magic_roxen_automatic_charset_variable )
+
decode_charset_encoding(Roxen.get_client_charset_decoder(q[0],this_object()));
+
}
+
+
static Roxen.HeaderParser hp = Roxen.HeaderParser();
+
static function(string:array(string|mapping)) hpf = hp->feed;
+
int last;
+
+
private int parse_got( string new_data )
+
{
+
TIMER_START(parse_got);
+
if( !method )
+
{
+
array res;
+
if( catch( res = hpf( new_data ) ) )
+
return 1;
+
if( !res )
+
{
+
TIMER_END(parse_got);
+
return 0; // Not enough data
+
}
+
data = res[0];
+
line = res[1];
+
request_headers = res[2];
+
}
+
return parse_got_2();
+
}
+
+
private final int parse_got_2( )
+
{
+
string trailer, trailer_trailer;
+
multiset (string) sup;
+
string a, b, s="", linename, contents;
+
array(string) sl = line / " ";
+
switch( sizeof( sl ) )
+
{
+
default:
+
sl = ({ sl[0], sl[1..sizeof(sl)-2]*" ", sl[-1] });
+
/* FALL_THROUGH */
+
+
case 3: /* HTTP/1.0 */
+
method = sl[0];
+
f = sl[1];
+
clientprot = sl[2];
+
prot = clientprot;
+
if(!(< "HTTP/1.0", "HTTP/1.1" >)[prot])
+
{
+
int maj,min;
+
if( sscanf(prot, "HTTP/%d.%d", maj, min) == 2 )
+
// Comply with the annoying weirdness of RFC 2616.
+
prot = "HTTP/" + maj + "." + min;
+
else
+
// We're nice here and assume HTTP even if the protocol
+
// is something very weird.
+
prot = "HTTP/1.1";
+
}
+
break;
+
+
case 2: // HTTP/0.9
+
case 1: // PING
+
method = sl[0];
+
f = sl[-1];
+
if( sizeof( sl ) == 1 )
+
sscanf( method, "%s%*[\r\n]", method );
+
+
clientprot = prot = "HTTP/0.9";
+
if(method != "PING")
+
method = "GET"; // 0.9 only supports get.
+
else
+
{
+
my_fd->write("PONG\r\n");
+
return 2;
+
}
+
s = data = ""; // no headers or extra data...
+
sscanf( f, "%s%*[\r\n]", f );
+
misc->cacheable = 0;
+
break;
+
+
case 0:
+
/* Not reached */
+
break;
+
}
+
REQUEST_WERR(sprintf("HTTP: request line %O", line));
+
REQUEST_WERR(sprintf("HTTP: headers %O", request_headers));
+
REQUEST_WERR(sprintf("HTTP: data (length %d) %O", strlen(data),data));
+
raw_url = f;
+
time = predef::time(1);
+
// if(!data) data = "";
+
//REQUEST_WERR(sprintf("HTTP: raw_url %O", raw_url));
+
+
if(!remoteaddr)
+
{
+
if(my_fd) {
+
remoteaddr = my_fd->query_address();
+
if(remoteaddr)
+
sscanf(remoteaddr, "%s %*s", remoteaddr);
+
}
+
if(!remoteaddr) {
+
REQUEST_WERR("HTTP: No remote address.");
+
TIMER_END(parse_got);
+
return 2;
+
}
+
}
+
+
foreach( (array)request_headers, [string linename, array|string contents] )
+
{
+
if( arrayp(contents) ) contents = contents[0];
+
switch (linename)
+
{
+
case "cache-control": // Opera sends "no-cache" here.
+
case "pragma": pragma|=(multiset)((contents-" ")/","); break;
+
+
case "content-length": misc->len = (int)contents; break;
+
case "authorization": rawauth = contents; break;
+
case "referer": referer = ({contents}); break;
+
case "if-modified-since": since=contents; break;
+
+
case "proxy-authorization":
+
array y;
+
y = contents / " ";
+
if(sizeof(y) < 2)
+
break;
+
y[1] = decode(y[1]);
+
misc->proxyauth=y;
+
break;
+
+
case "user-agent":
+
if( !client )
+
{
+
sscanf(contents, "%s via", contents);
+
client_var->Fullname=contents;
+
client = contents/" " - ({ "" });
+
}
+
break;
+
+
case "request-range":
+
contents = lower_case(contents-" ");
+
if(!search(contents, "bytes"))
+
// Only care about "byte" ranges.
+
misc->range = contents[6..];
+
break;
+
+
case "range":
+
contents = lower_case(contents-" ");
+
if(!misc->range && !search(contents, "bytes"))
+
// Only care about "byte" ranges. Also the Request-Range header
+
// has precedence since Stupid Netscape (TM) sends both but can't
+
// handle multipart/byteranges but only multipart/x-byteranges.
+
// Duh!!!
+
misc->range = contents[6..];
+
break;
+
+
+
case "host":
+
case "connection":
+
misc[linename] = lower_case(contents);
+
break;
+
case "content-type":
+
misc[linename] = contents;
+
break;
+
}
+
}
+
if(misc->len)
+
{
+
if(!data) data="";
+
int l = misc->len;
+
wanted_data=l;
+
have_data=strlen(data);
+
+
if(strlen(data) < l)
+
{
+
REQUEST_WERR(sprintf("HTTP: More data needed in %s.", method));
+
TIMER_END(parse_got);
+
return 0;
+
}
+
leftovers = data[l+2..];
+
data = data[..l+1];
+
+
if (method == "POST") {
+
switch(lower_case((((misc["content-type"]||"")+";")/";")[0]-" "))
+
{
+
default:
+
// Normal form data.
+
string v;
+
+
// Ok.. This might seem somewhat odd, but IE seems to add a
+
// (spurious) \r\n to the end of the data, and some versions of
+
// opera seem to add (spurious) \r\n to the start of the data.
+
//
+
// Oh, the joy of supporting all webbrowsers is endless.
+
data = String.trim_all_whites( data );
+
l = misc->len = strlen(data);
+
+
if(l < 200000)
+
foreach(replace(data,"+"," ")/"&", v)
+
if(sscanf(v, "%s=%s", a, b) == 2)
+
{
+
a = http_decode_string( a );
+
b = http_decode_string( b );
+
real_variables[ a ] += ({ b });
+
}
+
break;
+
+
case "multipart/form-data":
+
object messg = MIME.Message(data, request_headers);
+
if (!messg->body_parts) {
+
report_error("HTTP: Bad multipart/form-data.\n"
+
" headers:\n"
+
"%{ %O:%O\n%}"
+
" data:\n"
+
"%{ %O\"\\n\"\n%}",
+
(array)request_headers,
+
data/"\n");
+
/* FIXME: Should this be reported to the client? */
+
} else {
+
foreach(messg->body_parts, object part)
+
{
+
if(part->disp_params->filename)
+
{
+
real_variables[part->disp_params->name] += ({part->getdata()});
+
real_variables[part->disp_params->name+".filename"] +=
+
({part->disp_params->filename});
+
misc->files += ({ part->disp_params->name });
+
} else
+
real_variables[part->disp_params->name] += ({part->getdata()});
+
if(part->headers["content-type"])
+
real_variables[part->disp_params->name+".mimetype"] +=
+
({ part->headers["content-type"] });
+
}
+
}
+
break;
+
}
+
}
+
}
+
if (!(< "HTTP/1.0", "HTTP/0.9" >)[prot]) {
+
if (!misc->host) {
+
// RFC 2616 requires this behaviour.
+
REQUEST_WERR("HTTP: HTTP/1.1 request without a host header.");
+
my_fd->write((prot||"HTTP/1.1") +
+
" 400 Bad request (missing host header).\r\n"
+
"Content-Length: 0\r\n"
+
"Date: "+Roxen.http_date(predef::time())+"\r\n"
+
"\r\n");
+
return 2;
+
}
+
}
+
TIMER_END(parse_got);
+
return 3; // Done.
+
}
+
+
int get_max_cache()
+
{
+
return misc->cacheable;
+
}
+
+
int set_max_cache( int t )
+
{
+
int ot = misc->cacheable;
+
misc->cacheable = t;
+
return ot;
+
}
+
+
void disconnect()
+
{
+
file = 0;
+
conf && conf->connection_drop( this_object() );
+
#ifdef REQUEST_DEBUG
+
if (my_fd)
+
MARK_FD("HTTP my_fd in HTTP disconnected?");
+
#endif
+
MERGE_TIMERS(conf);
+
if(do_not_disconnect) return;
+
destruct();
+
}
+
+
void end(int|void keepit)
+
{
+
if( conf )
+
conf->connection_drop( this_object() );
+
if(keepit
+
&& !file->raw
+
&& (misc->connection == "keep-alive" ||
+
(prot == "HTTP/1.1" && misc->connection != "close"))
+
&& my_fd
+
&& !catch(my_fd->query_address()) )
+
{
+
// Now.. Transfer control to a new http-object. Reset all variables etc..
+
object o = object_program(this_object())(0, 0, 0);
+
o->remoteaddr = remoteaddr;
+
o->client = client;
+
o->supports = supports;
+
o->client_var = client_var;
+
o->host = host;
+
o->conf = conf;
+
o->pipe = pipe;
+
MARK_FD("HTTP kept alive");
+
object fd = my_fd;
+
my_fd=0;
+
o->chain(fd,port_obj,leftovers);
+
pipe = 0;
+
disconnect();
+
return;
+
}
+
+
pipe = 0;
+
if(objectp(my_fd))
+
{
+
MARK_FD("HTTP closed");
+
catch
+
{
+
// Don't set to blocking mode if SSL.
+
if (!my_fd->CipherSpec) {
+
my_fd->set_blocking();
+
}
+
my_fd->close();
+
destruct(my_fd);
+
};
+
catch {
+
my_fd = 0;
+
};
+
}
+
disconnect();
+
}
+
+
static void do_timeout()
+
{
+
int elapsed = predef::time(1)-time;
+
if(time && elapsed >= 30)
+
{
+
MARK_FD("HTTP timeout");
+
end();
+
} else {
+
// premature call_out... *¤#!"
+
call_out(do_timeout, 10);
+
MARK_FD("HTTP premature timeout");
+
}
+
}
+
+
string link_to(string file, int line, string fun, int eid, int qq)
+
{
+
if (!file || !line) return "<a>";
+
if(file[0]!='/') file = combine_path(getcwd(), file);
+
return ("<a href=\"/(old_error,find_file)/error/?"+
+
"file="+Roxen.http_encode_url(file)+
+
(fun ? "&fun="+Roxen.http_encode_url(fun) : "") +
+
"&off="+qq+
+
"&error="+eid+
+
(line ? "&line="+line+"#here" : "") +
+
"\">");
+
}
+
+
static string error_page_header (string title)
+
{
+
title = Roxen.html_encode_string (title);
+
return #"<html><head><title>" + title + #"</title></head>
+
<body bgcolor='white' text='black' link='#ce5c00' vlink='#ce5c00'>
+
<table width='100%'><tr>
+
<td><a href='http://www.roxen.com/'><img border='0' src='/internal-roxen-roxen-small'></a></td>
+
<td><b><font size='+1'>" + title + #"</font></b></td>
+
<td align='right'><font size='+1'>Roxen WebServer " + Roxen.html_encode_string (roxen_version()) + #"</font></td>
+
</tr></table>
+
+
";
+
}
+
+
string format_backtrace(int eid)
+
{
+
[string msg, array(string) rxml_bt, array(array) bt,
+
string raw_bt_descr, string raw_url, string raw] =
+
roxen.query_var ("errors")[eid];
+
+
string res = error_page_header ("Internal Server Error") +
+
"<h1>" + replace (Roxen.html_encode_string (msg), "\n", "<br />\n") + "</h1>\n";
+
+
if (rxml_bt && sizeof (rxml_bt)) {
+
res += "<h3>RXML frame backtrace</h3>\n<ul>\n";
+
foreach (rxml_bt, string line)
+
res += "<li>" + Roxen.html_encode_string (line) + "</li>\n";
+
res += "</ul>\n\n";
+
}
+
+
if (bt && sizeof (bt)) {
+
res += "<h3>Pike backtrace</h3>\n<ul>\n";
+
int q = sizeof (bt);
+
foreach(bt, [string file, int line, string func, string descr])
+
{
+
#if constant(PIKE_MODULE_RELOC)
+
file = file && master()->relocate_module(file);
+
#endif
+
res += "<li value="+(q--)+">" +
+
link_to (file, line, func, eid, q) +
+
(file ? Roxen.html_encode_string (file) : "<i>Unknown program</i>") +
+
(line ? ":" + line : "") +
+
"</a>" + (file ? Roxen.html_encode_string (get_cvs_id (file)) : "") + ":<br />\n" +
+
replace (Roxen.html_encode_string (descr),
+
({"(", ")", " "}), ({"<b>(</b>", "<b>)</b>", " "})) +
+
"</li>\n";
+
}
+
res += "</ul>\n\n";
+
}
+
+
res += ("<p><b><a href=\"/(old_error,plain)/error/?error="+eid+"\">"
+
"Generate text only version of this error message, for bug reports"+
+
"</a></b></p>\n\n");
+
return res+"</body></html>";
+
}
+
+
string generate_bugreport(string msg, array(string) rxml_bt, array(string) bt,
+
string raw_bt_descr, string raw_url, string raw)
+
{
+
return ("Roxen version: "+version()+
+
(roxen.real_version != version()?
+
" ("+roxen.real_version+")":"")+
+
"\nPike version: " + predef::version() +
+
"\nRequested URL: "+raw_url+"\n"
+
"\nError: " + raw_bt_descr +
+
"\nRequest data:\n"+raw);
+
}
+
+
string censor(string what)
+
{
+
string a, b, c;
+
if(!what)
+
return "No backtrace";
+
if(sscanf(what, "%suthorization:%s\n%s", a, b, c)==3)
+
return a+"uthorization: ################ (censored)\n"+c;
+
return what;
+
}
+
+
int store_error(mixed _err)
+
{
+
mixed err = _err;
+
_err = 0; // hide in backtrace, they are bad enough anyway...
+
mapping e = roxen.query_var("errors");
+
if(!e) roxen.set_var("errors", ([]));
+
e = roxen.query_var("errors"); /* threads... */
+
+
int id = ++e[0];
+
if(id>1024) id = 1;
+
+
string msg;
+
array(string) rxml_bt;
+
+
if (!err) msg = "Unknown error";
+
else {
+
msg = describe_error (err);
+
// Ugly, but it's hard to fix it better..
+
int i = search (msg, "\nRXML frame backtrace:\n");
+
if (i >= 0) {
+
rxml_bt = (msg[i + sizeof ("\nRXML frame backtrace:")..] / "\n | ")[1..];
+
if (sizeof (rxml_bt)) rxml_bt[-1] = rxml_bt[-1][..sizeof (rxml_bt[-1]) - 2];
+
msg = msg[..i - 1];
+
}
+
}
+
function dp = master()->describe_program;
+
+
string cwd = getcwd() + "/";
+
array bt;
+
if (arrayp (err) && sizeof (err) >= 2 && arrayp (err[1]) ||
+
objectp (err) && err->is_generic_error) {
+
+
object d = master()->Describer();
+
d->identify_parts(err[1]);
+
function dcl = d->describe_comma_list;
+
bt = ({});
+
+
foreach (reverse (err[1]), mixed ent) {
+
string file, func, descr;
+
int line;
+
if (arrayp (ent)) {
+
if (sizeof (ent) && stringp (ent[0]))
+
if (ent[0][..sizeof (cwd) - 1] == cwd)
+
file = ent[0] = ent[0][sizeof (cwd)..];
+
else
+
file = ent[0];
+
if (sizeof (ent) >= 2) line = ent[1];
+
if (sizeof (ent) >= 3)
+
if(functionp(ent[2])) {
+
func = "";
+
if (object o = function_object (ent[2])) {
+
string s;
+
if (!catch (s = sprintf ("%O",o)) && s != "object")
+
func = s + "->";
+
}
+
func += function_name(ent[2]);
+
if (!file)
+
catch {
+
file = dp(object_program( function_object( ent[2] ) ) );
+
if (file[..sizeof (cwd) - 1] == cwd) file = file[sizeof (cwd)..];
+
};
+
}
+
else if (stringp(ent[2])) func = ent[2];
+
else func ="<unknown function>";
+
if (sizeof (ent) >= 4)
+
descr = func + "(" +dcl(ent[3..],999999)+")";
+
else
+
descr = func + "()";
+
}
+
else if (stringp (ent)) descr = ent;
+
else if (catch (descr = sprintf ("%O", ent)))
+
descr = "???";
+
bt += ({({file, line, func, descr})});
+
}
+
}
+
+
add_cvs_ids (err);
+
e[id] = ({msg,rxml_bt,bt,describe_backtrace (err),raw_url,censor(raw)});
+
return id;
+
}
+
+
array get_error(string eid)
+
{
+
mapping e = roxen.query_var("errors");
+
if(e) return e[(int)eid];
+
return 0;
+
}
+
+
+
void internal_error(array _err)
+
{
+
misc->cacheable = 0;
+
mixed err = _err;
+
_err = 0; // hide in backtrace, they are bad enough anyway...
+
array err2;
+
if(port_obj && port_obj->query("show_internals"))
+
{
+
err2 = catch {
+
file = Roxen.http_low_answer(500, format_backtrace(store_error(err)));
+
};
+
if(err2) {
+
werror("Internal server error in internal_error():\n" +
+
describe_backtrace(err2)+"\n while processing \n"+
+
describe_backtrace(err));
+
file = Roxen.http_low_answer(500, "<h1>Error: The server failed to "
+
"fulfill your query, due to an "
+
"internal error in the internal error routine.</h1>");
+
}
+
} else {
+
file = Roxen.http_low_answer(500, "<h1>Error: The server failed to "
+
"fulfill your query, due to an internal error.</h1>");
+
}
+
report_error("Internal server error: " +
+
describe_backtrace(err) + "\n");
+
#ifdef INTERNAL_ERROR_DEBUG
+
report_error(sprintf("Raw backtrace:%O\n", err));
+
#endif /* INTERNAL_ERROR_DEBUG */
+
}
+
+
// This macro ensures that something gets reported even when the very
+
// call to internal_error() fails. That happens eg when this_object()
+
// has been destructed.
+
#define INTERNAL_ERROR(err) \
+
if (mixed __eRr = catch (internal_error (err))) \
+
report_error("Internal server error: " + describe_backtrace(err) + \
+
"internal_error() also failed: " + describe_backtrace(__eRr))
+
+
int wants_more()
+
{
+
return !!cache;
+
}
+
+
void do_log( int|void fsent )
+
{
+
MARK_FD("HTTP logging"); // fd can be closed here
+
TIMER_START(do_log);
+
if(conf)
+
{
+
int len;
+
if(!fsent && pipe)
+
file->len = pipe->bytes_sent();
+
else
+
file->len = fsent;
+
if(conf)
+
{
+
if(file->len > 0) conf->sent+=file->len;
+
file->len += misc->_log_cheat_addition;
+
conf->log(file, this_object());
+
}
+
}
+
if( !port_obj )
+
{
+
TIMER_END(do_log);
+
MERGE_TIMERS(conf);
+
if( conf )
+
conf->connection_drop( this_object() );
+
catch // paranoia
+
{
+
my_fd->close();
+
destruct( my_fd );
+
destruct( );
+
};
+
return;
+
}
+
TIMER_END(do_log);
+
end(1);
+
return;
+
}
+
+
#ifdef FD_DEBUG
+
void timer(int start)
+
{
+
if(pipe) {
+
// FIXME: Disconnect if no data has been sent for a long while
+
// (30min?)
+
MARK_FD(sprintf("HTTP piping %d %d %d %d (%s)",
+
pipe->sent,
+
stringp(pipe->current_input) ?
+
strlen(pipe->current_input) : -1,
+
pipe->last_called,
+
predef::time(1) - start,
+
not_query));
+
} else {
+
MARK_FD("HTTP piping, but no pipe for "+not_query);
+
}
+
call_out(timer, 30, start);
+
}
+
#endif
+
+
string handle_error_file_request (string msg, array(string) rxml_bt, array(array) bt,
+
string raw_bt_descr, string raw_url, string raw)
+
{
+
string data = Stdio.read_bytes(variables->file);
+
+
if(!data)
+
return error_page_header (variables->file) +
+
"<h3><i>Source file could not be read</i></h3>\n"
+
"</body></html>";
+
+
string down;
+
int next = (int) variables->off + 1;
+
+
if(next < sizeof (bt)) {
+
[string file, int line, string func, string descr] = bt[next];
+
down = link_to (file, line, func, (int) variables->error, next);
+
}
+
else
+
down = "<a>";
+
+
int off = 49;
+
array (string) lines = data/"\n";
+
int start = (int)variables->line-50;
+
if(start < 0)
+
{
+
off += start;
+
start = 0;
+
}
+
int end = (int)variables->line+50;
+
+
// The highlighting doesn't work well enough on recent pike code.
+
//lines=highlight_pike("foo", ([ "nopre":1 ]), lines[start..end]*"\n")/"\n";
+
lines = map (lines[start..end], Roxen.html_encode_string);
+
+
if(sizeof(lines)>off) {
+
sscanf (lines[off], "%[ \t]%s", string indent, string code);
+
if (!sizeof (code)) code = " ";
+
lines[off] = indent + "<font size='+1'><b>"+down+code+"</a></b></font></a>";
+
}
+
lines[max(off-20,0)] = "<a name=here>"+lines[max(off-20,0)]+"</a>";
+
+
return error_page_header (variables->file) +
+
"<font size='-1'><pre>" + lines*"\n" + "</pre></font>\n"
+
"</body></html>";
+
}
+
+
// The wrapper for multiple ranges (send a multipart/byteranges reply).
+
#define BOUND "Byte_Me_Now_Roxen"
+
+
class MultiRangeWrapper
+
{
+
object file;
+
function rcb;
+
int current_pos, len, separator;
+
array ranges;
+
array range_info = ({});
+
string type;
+
string stored_data = "";
+
void create(mapping _file, mapping heads, array _ranges, object id)
+
{
+
file = _file->file;
+
len = _file->len;
+
foreach(indices(heads), string h)
+
{
+
if(lower_case(h) == "content-type") {
+
type = heads[h];
+
m_delete(heads, h);
+
}
+
}
+
if(id->request_headers["request-range"])
+
heads["Content-Type"] = "multipart/x-byteranges; boundary=" BOUND;
+
else
+
heads["Content-Type"] = "multipart/byteranges; boundary=" BOUND;
+
ranges = _ranges;
+
int clen;
+
foreach(ranges, array range)
+
{
+
int rlen = 1+ range[1] - range[0];
+
string sep = sprintf("\r\n--" BOUND "\r\nContent-Type: %s\r\n"
+
"Content-Range: bytes %d-%d/%d\r\n\r\n",
+
type||"application/octet-stream",
+
@range, len);
+
clen += rlen + strlen(sep);
+
range_info += ({ ({ rlen, sep }) });
+
}
+
clen += strlen(BOUND) + 8; // End boundary length.
+
_file->len = clen;
+
}
+
+
string read(int num_bytes)
+
{
+
string out = stored_data;
+
int rlen, total = num_bytes;
+
num_bytes -= strlen(out);
+
stored_data = "";
+
foreach(ranges, array range)
+
{
+
rlen = range_info[0][0] - current_pos;
+
if(separator != 1) {
+
// New range, write new separator.
+
// write("Initiating new range %d -> %d.\n", @range);
+
out += range_info[0][1];
+
num_bytes -= strlen(range_info[0][1]);
+
file->seek(range[0]);
+
separator = 1;
+
}
+
if(num_bytes > 0) {
+
if(rlen <= num_bytes)
+
// Entire range fits.
+
{
+
out += file->read(rlen);
+
num_bytes -= rlen;
+
current_pos = separator = 0;
+
ranges = ranges[1..]; // One range done.
+
range_info = range_info[1..];
+
} else {
+
out += file->read(num_bytes);
+
current_pos += num_bytes;
+
num_bytes = 0;
+
}
+
}
+
if(num_bytes <= 0)
+
break; // Return data
+
}
+
if(!sizeof(ranges) && separator != 2) {
+
// End boundary. Only write once and only when
+
// no more ranges remain.
+
separator = 2;
+
out += "\r\n--" BOUND "--\r\n";
+
}
+
if(strlen(out) > total)
+
{
+
// Oops. too much data again. Write and store. Write and store.
+
stored_data = out[total..];
+
return out[..total-1];
+
}
+
return out ; // We are finally done.
+
}
+
+
mixed `->(string what)
+
{
+
switch(what) {
+
case "read":
+
return read;
+
+
case "set_nonblocking":
+
return 0;
+
+
case "query_fd":
+
return lambda() { return -1; };
+
+
default:
+
return file[what];
+
}
+
}
+
}
+
+
+
// Parse the range header into multiple ranges.
+
array parse_range_header(int len)
+
{
+
array ranges = ({});
+
foreach(misc->range / ",", string range)
+
{
+
int r1, r2;
+
if(range[0] == '-' ) {
+
// End of file request
+
r1 = (len - (int)range[1..]);
+
if(r1 < 0) {
+
// Entire file requested here.
+
r1 = 0;
+
}
+
ranges += ({ ({ len - (int)range[1..], len-1 }) });
+
} else if(range[-1] == '-') {
+
// Rest of file request
+
r1 = (int)range;
+
if(r1 >= len)
+
// Range beginning is after EOF.
+
continue;
+
ranges += ({ ({ r1, len-1 }) });
+
} else if(sscanf(range, "%d-%d", r1, r2)==2) {
+
// Standard range
+
if(r1 <= r2) {
+
if(r1 >= len)
+
// Range beginning is after EOF.
+
continue;
+
ranges += ({ ({ r1, r2 < len ? r2 : len -1 }) });
+
}
+
else
+
// A syntatically incorrect range should make the server
+
// ignore the header. Really.
+
return 0;
+
} else
+
// Invalid syntax again...
+
return 0;
+
}
+
return ranges;
+
}
+
+
// Tell the client that it can start sending some more data
+
void ready_to_receive()
+
{
+
if (clientprot == "HTTP/1.1" && request_headers->Expect &&
+
(request_headers->Expect == "100-continue" ||
+
has_value(request_headers->Expect, "100-continue" )))
+
my_fd->write("HTTP/1.1 100 Continue\r\n");
+
}
+
+
// Send the result.
+
void send_result(mapping|void result)
+
{
+
TIMER_START(send_result);
+
+
array err;
+
int tmp;
+
mapping heads;
+
string head_string="";
+
if (result)
+
file = result;
+
#ifdef PROFILE
+
float elapsed = SECHR(HRTIME()-req_time);
+
string nid =
+
#ifdef FILE_PROFILE
+
(raw_url/"?")[0]
+
#else
+
dirname((raw_url/"?")[0])
+
#endif
+
;
+
array p;
+
if(!(p=conf->profile_map[nid]))
+
p = conf->profile_map[nid] = ({0,0.0,0.0});
+
p[0]++;
+
p[1] += elapsed;
+
if(elapsed > p[2]) p[2]=elapsed;
+
#endif
+
+
REQUEST_WERR(sprintf("HTTP: response: prot %O, method %O, file %O",
+
prot, method, file));
+
+
if( prot == "HTTP/0.9" ) misc->cacheable = 0;
+
+
if(!leftovers)
+
leftovers = data||"";
+
+
if(!mappingp(file))
+
{
+
misc->cacheable = 0;
+
if(misc->error_code)
+
file = Roxen.http_low_answer(misc->error_code, errors[misc->error]);
+
else if(err = catch {
+
file = conf->error_file( this_object() );
+
})
+
INTERNAL_ERROR(err);
+
}
+
else
+
{
+
if((file->file == -1) || file->leave_me)
+
{
+
TIMER_END(send_result);
+
file = 0;
+
pipe = 0;
+
if(do_not_disconnect)
+
return;
+
my_fd = 0;
+
return;
+
}
+
+
if(file->type == "raw") file->raw = 1;
+
else if(!file->type) file->type="text/plain";
+
}
+
+
if(!file->raw)
+
{
+
heads = ([]);
+
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 )
+
{
+
if( !file->len )
+
file->len = fstat[1];
+
+
if ( fstat[ST_MTIME] > misc->last_modified )
+
misc->last_modified = fstat[ST_MTIME];
+
+
if( misc->cacheable < INITIAL_CACHEABLE )
+
heads["Expires"] = Roxen.http_date( predef::time(1)+misc->cacheable );
+
+
heads["Last-Modified"] = Roxen.http_date(misc->last_modified);
+
+
if(since)
+
{
+
/* ({ time, len }) */
+
array(int) since_info = Roxen.parse_since( since );
+
// werror("since: %{%O, %}\n"
+
// "lm: %O\n",
+
// since_info,
+
// misc->last_modified );
+
if ( ((since_info[0] >= misc->last_modified) &&
+
((since_info[1] == -1) || (since_info[1] == file->len)))
+
// actually ok, or...
+
// || ((misc->cacheable>0)
+
// && (since_info[0] + misc->cacheable<= predef::time(1))
+
// // cacheable, and not enough time has passed.
+
)
+
{
+
file->error = 304;
+
file->file = 0;
+
file->data="";
+
}
+
}
+
}
+
+
if(prot != "HTTP/0.9")
+
{
+
string h, charset="";
+
+
if( stringp(file->data) )
+
{
+
if (file["type"][0..4] == "text/")
+
{
+
[charset,file->data] = output_encode( file->data, 1 );
+
if( charset && (search(file["type"], "; charset=") == -1))
+
charset = "; charset="+charset;
+
else
+
charset = "";
+
}
+
file->len = strlen(file->data);
+
}
+
heads["Content-Type"] = file["type"]+charset;
+
heads["Accept-Ranges"] = "bytes";
+
heads["Server"] = replace(version(), " ", "·");
+
if( misc->connection )
+
heads["Connection"] = misc->connection;
+
+
if(file->encoding) heads["Content-Encoding"] = file->encoding;
+
+
if(!file->error)
+
file->error=200;
+
+
heads->Date = Roxen.http_date(predef::time(1));
+
if(file->expires)
+
heads->Expires = Roxen.http_date(file->expires);
+
+
if(mappingp(file->extra_heads))
+
heads |= file->extra_heads;
+
+
if(mappingp(misc->moreheads))
+
heads |= misc->moreheads;
+
+
if(misc->range && file->len && objectp(file->file) && !file->data &&
+
file->error == 200 && (method == "GET" || method == "HEAD"))
+
// Plain and simple file and a Range header. Let's play.
+
// Also we only bother with 200-requests. Anything else should be
+
// nicely and completely ignored. Also this is only used for GET and
+
// HEAD requests.
+
{
+
// split the range header. If no valid ranges are found, ignore it.
+
// If one is found, send that range. If many are found we need to
+
// use a wrapper and send a multi-part message.
+
array ranges = parse_range_header(file->len);
+
if(ranges) // No incorrect syntax...
+
{
+
misc->cacheable = 0;
+
if(sizeof(ranges)) // And we have valid ranges as well.
+
{
+
file->error = 206; // 206 Partial Content
+
if(sizeof(ranges) == 1)
+
{
+
heads["Content-Range"] = sprintf("bytes %d-%d/%d",
+
@ranges[0], file->len);
+
file->file->seek(ranges[0][0]);
+
if(ranges[0][1] == (file->len - 1) &&
+
GLOBVAR(RestoreConnLogFull))
+
// Log continuations (ie REST in FTP), 'range XXX-'
+
// using the entire length of the file, not just the
+
// "sent" part. Ie add the "start" byte location when logging
+
misc->_log_cheat_addition = ranges[0][0];
+
file->len = ranges[0][1] - ranges[0][0]+1;
+
} else {
+
// Multiple ranges. Multipart reply and stuff needed.
+
// We do this by replacing the file object with a wrapper.
+
// Nice and handy.
+
file->file = MultiRangeWrapper(file, heads, ranges, this_object());
+
}
+
} else {
+
// Got the header, but the specified ranges was out of bounds.
+
// Reply with a 416 Requested Range not satisfiable.
+
file->error = 416;
+
heads["Content-Range"] = "*/"+file->len;
+
if(method == "GET") {
+
file->data = "The requested byte range is out-of-bounds. Sorry.";
+
file->len = strlen(file->data);
+
file->file = 0;
+
}
+
}
+
}
+
}
+
head_string = sprintf("%s %d %s\r\n",
+
prot, file->error,
+
file->rettext ||errors[file->error]||"");
+
+
// if( file->len > 0 || (file->error != 200) )
+
heads["Content-Length"] = (string)file->len;
+
+
// Some browsers, e.g. Netscape 4.7, doesn't trust a zero
+
// content length when using keep-alive. So let's force a
+
// close in that case.
+
if( file->error/100 == 2 && file->len <= 0 )
+
{
+
heads->Connection = "close";
+
misc->connection = "close";
+
}
+
if( catch( head_string += Roxen.make_http_headers( heads ) ) )
+
{
+
foreach( indices( heads ), string x )
+
if( stringp( heads[x] ) )
+
head_string += x+": "+heads[x]+"\r\n";
+
else if( arrayp( heads[x] ) )
+
foreach( heads[x], string xx )
+
head_string += x+": "+xx+"\r\n";
+
else if( catch {
+
head_string += x+": "+(string)heads[x];
+
} )
+
error("Illegal value in headers array! "
+
"Expected string or array(string)\n");
+
head_string += "\r\n";
+
}
+
+
if( strlen( charset ) || String.width( head_string ) > 8 )
+
head_string = output_encode( head_string, 0, charset )[1];
+
conf->hsent += strlen(head_string);
+
}
+
}
+
#if 0
+
REQUEST_WERR(sprintf("HTTP: Sending result for prot:%O, method:%O, file:%O",
+
prot, method, file));
+
#endif
+
MARK_FD("HTTP handled");
+
+
if( (method!="HEAD") && (file->error!=304) )
+
// No data for these two...
+
{
+
#ifdef RAM_CACHE
+
if( (misc->cacheable > 0) && (file->data || file->file) &&
+
prot != "HTTP/0.9" )
+
{
+
if( file->len>0 && // known length.
+
((file->len + strlen( head_string )) <
+
conf->datacache->max_file_size)
+
&& misc->cachekey )
+
{
+
string data = "";
+
if( file->file ) data += file->file->read();
+
if( file->data ) data += file->data;
+
MY_TRACE_ENTER (sprintf ("Storing in ram cache, entry: %O", raw_url), 0);
+
MY_TRACE_LEAVE ("");
+
conf->datacache->set( raw_url, data,
+
([
+
// We have to handle the date header.
+
"hs":head_string,
+
"key":misc->cachekey,
+
"callbacks":misc->_cachecallbacks,
+
"len":file->len,
+
// fix non-keep-alive when sending from cache
+
"raw":file->raw,
+
"error":file->error,
+
"mtime":(file->stat && file->stat[ST_MTIME]),
+
"rf":realfile,
+
]),
+
misc->cacheable );
+
file = ([ "data":data, "raw":file->raw, "len":strlen(data) ]);
+
}
+
}
+
#endif
+
if( file->len > 0 && file->len < 4000 )
+
{
+
// Just do a blocking write().
+
int s;
+
TIMER_END(send_result);
+
TIMER_START(blocking_write);
+
string data = head_string +
+
(file->file?file->file->read(file->len):
+
(file->data[..file->len-1]));
+
REQUEST_WERR (sprintf ("HTTP: Send %O", data));
+
s = my_fd->write(data);
+
TIMER_END(blocking_write);
+
do_log( s );
+
return;
+
}
+
if(strlen(head_string)) send(head_string);
+
if(file->data && strlen(file->data)) send(file->data, file->len);
+
if(file->file) send(file->file, file->len);
+
}
+
else
+
{
+
if( strlen( head_string ) < 4000)
+
{
+
REQUEST_WERR (sprintf ("HTTP: Send %O", head_string));
+
do_log( my_fd->write( head_string ) );
+
return;
+
}
+
send(head_string);
+
file->len = 1; // Keep those alive, please...
+
}
+
TIMER_END(send_result);
+
start_sender();
+
}
+
+
+
// Execute the request
+
void handle_request( )
+
{
+
REQUEST_WERR("HTTP: handle_request()");
+
TIMER_START(handle_request);
+
#ifdef MAGIC_ERROR
+
if(prestate->old_error)
+
{
+
array err = get_error(variables->error);
+
if(err)
+
{
+
if(prestate->plain)
+
{
+
file = ([
+
"type":"text/plain",
+
"data":generate_bugreport( @err ),
+
]);
+
TIMER_END(handle_request);
+
send_result();
+
return;
+
} else {
+
if(prestate->find_file)
+
{
+
if (!roxen.configuration_authenticate (this_object(), "View Settings"))
+
file = Roxen.http_auth_required("admin");
+
else
+
file = ([
+
"type":"text/html",
+
"data":handle_error_file_request( @err ),
+
]);
+
TIMER_END(handle_request);
+
send_result();
+
return;
+
}
+
}
+
}
+
}
+
#endif /* MAGIC_ERROR */
+
+
MARK_FD("HTTP handling request");
+
+
array e;
+
if(e= catch(file = conf->handle_request( this_object() )))
+
INTERNAL_ERROR( e );
+
+
if( file )
+
if( file->try_again_later )
+
{
+
if( objectp( file->try_again_later ) )
+
;
+
else
+
call_out( roxen.handle, file->try_again_later, handle_request );
+
return;
+
}
+
else if( file->pipe )
+
return;
+
TIMER_END(handle_request);
+
send_result();
+
}
+
+
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;
+
}
+
+
string url_base()
+
// See the RequestID class for doc.
+
{
+
// Note: Code duplication in base_server/prototypes.pike.
+
+
if (!cached_url_base) {
+
// First look at the host header in the request.
+
if (string tmp = misc->host) {
+
int scanres = sscanf (tmp, "%[^:]:%d", string host, int port);
+
if (scanres < 2)
+
// Some clients don't send the port in the host header.
+
port = port_obj->port;
+
if (port_obj->default_port == port)
+
// Remove redundant port number.
+
cached_url_base = port_obj->prot_name + "://" + host;
+
else
+
if (scanres < 2)
+
cached_url_base = port_obj->prot_name + "://" + host + ":" + port;
+
else
+
cached_url_base = port_obj->prot_name + "://" + tmp;
+
}
+
+
// Then use the port object.
+
else {
+
string host = port_obj->conf_data[conf]->hostname;
+
if (host == "*")
+
if (conf && sizeof (host = conf->get_url()) &&
+
sscanf (host, "%*s://%[^:/]", host) == 2) {
+
// Use the hostname in the configuration url.
+
}
+
else
+
// Fall back to the numeric ip.
+
host = port_obj->ip;
+
cached_url_base = port_obj->prot_name + "://" + host;
+
if (port_obj->port != port_obj->default_port)
+
cached_url_base += ":" + port_obj->port;
+
}
+
+
if (string p = misc->site_prefix_path) cached_url_base += p;
+
cached_url_base += "/";
+
}
+
return cached_url_base;
+
}
+
+
/* We got some data on a socket.
+
* =================================================
+
*/
+
int processed;
+
// array ccd = ({});
+
void got_data(mixed fooid, string s)
+
{
+
REQUEST_WERR(sprintf("HTTP: Got %O", s));
+
+
if(wanted_data)
+
{
+
data += s;
+
if(strlen(s) + have_data < wanted_data)
+
{
+
have_data += strlen(s);
+
REQUEST_WERR("HTTP: We want more data.");
+
return;
+
}
+
}
+
+
if (mixed err = catch {
+
MARK_FD("HTTP got data");
+
raw += s;
+
+
// The port has been closed, but old (probably keep-alive)
+
// connections remain. Close those connections.
+
if( !port_obj )
+
{
+
if( conf )
+
conf->connection_drop( this_object() );
+
catch // paranoia
+
{
+
my_fd->set_blocking();
+
my_fd->close();
+
destruct( my_fd );
+
destruct( );
+
};
+
return;
+
}
+
+
switch( parse_got( s ) )
+
{
+
case 0:
+
REQUEST_WERR("HTTP: Request needs more data.");
+
return;
+
+
case 1:
+
REQUEST_WERR("HTTP: Stupid Client Error.");
+
my_fd->write((prot||"HTTP/1.0")+" 500 Illegal request\r\n"
+
"Content-Length: 0\r\n"+
+
"Date: "+Roxen.http_date(predef::time())+"\r\n"
+
"\r\n");
+
end();
+
return; // Stupid request.
+
+
case 2:
+
REQUEST_WERR("HTTP: Done.");
+
end();
+
return;
+
}
+
+
if( method == "GET" )
+
misc->cacheable = INITIAL_CACHEABLE; // FIXME: Make configurable.
+
+
TIMER_START(find_conf);
+
string path;
+
if( !conf || !(path = port_obj->path )
+
|| (sizeof( path )
+
&& raw_url[..sizeof(path) - 1] != path) )
+
+
{
+
// FIXME: port_obj->name & port_obj->default_port are constant
+
// consider caching them?
+
+
// RFC 2068 5.1.2:
+
//
+
// To allow for transition to absoluteURIs in all requests in future
+
// versions of HTTP, all HTTP/1.1 servers MUST accept the absoluteURI
+
// form in requests, even though HTTP/1.1 clients will only generate
+
// them in requests to proxies.
+
if (has_prefix(raw_url, port_obj->name+"://") &&
+
(conf = port_obj->find_configuration_for_url(raw_url,
+
this_object(), 1))) {
+
sscanf(raw_url[sizeof(port_obj->name+"://")..],
+
"%[^/]%s", misc->host, raw_url);
+
} else {
+
if (misc->host) {
+
conf =
+
port_obj->find_configuration_for_url(port_obj->name + "://" +
+
misc->host +
+
(search(misc->host, ":")<0?
+
(":"+port_obj->port):"") +
+
raw_url,
+
this_object());
+
} else {
+
conf =
+
port_obj->find_configuration_for_url(port_obj->name +
+
"://*:" + port_obj->port +
+
raw_url,
+
this_object());
+
}
+
}
+
}
+
else if( strlen(path) )
+
adjust_for_config_path( path );
+
+
TIMER_END(find_conf);
+
+
if (rawauth)
+
{
+
/* Need to authenticate with the configuration */
+
misc->cacheable = 0;
+
array(string) y = rawauth / " ";
+
realauth = 0;
+
auth = 0;
+
if (sizeof(y) >= 2)
+
{
+
y[1] = MIME.decode_base64(y[1]);
+
realauth = y[1];
+
}
+
}
+
+
+
if( misc->proxyauth )
+
{
+
/* Need to authenticate with the configuration */
+
misc->cacheable = 0;
+
if (sizeof(misc->proxyauth) >= 2)
+
{
+
// misc->proxyauth[1] = MIME.decode_base64(misc->proxyauth[1]);
+
if (conf->auth_module)
+
misc->proxyauth
+
= conf->auth_module->auth(misc->proxyauth,this_object() );
+
}
+
}
+
+
conf->connection_add( this_object(), connection_stats );
+
conf->received += strlen(raw);
+
conf->requests++;
+
my_fd->set_close_callback(0);
+
my_fd->set_read_callback(0);
+
processed=1;
+
+
remove_call_out(do_timeout);
+
#ifdef RAM_CACHE
+
TIMER_START(cache_lookup);
+
array cv;
+
if( prot != "HTTP/0.9" &&
+
misc->cacheable &&
+
!since &&
+
(cv = conf->datacache->get( raw_url )) )
+
{
+
MY_TRACE_ENTER (sprintf ("Found %O in ram cache - checking entry", raw_url), 0);
+
if( !cv[1]->key ) {
+
MY_TRACE_LEAVE ("Entry invalid due to zero key");
+
conf->datacache->expire_entry( raw_url );
+
}
+
else
+
{
+
int can_cache = 1;
+
if(!leftovers)
+
leftovers = data||"";
+
+
string d = cv[ 0 ];
+
file = cv[1];
+
+
if( sizeof(file->callbacks) )
+
{
+
if( mixed e = catch
+
{
+
foreach( file->callbacks, function f ) {
+
MY_TRACE_ENTER (sprintf ("Checking with %s",
+
master()->describe_function (f)), 0);
+
if( !f(this_object(), cv[1]->key ) )
+
{
+
MY_TRACE_LEAVE ("Entry invalid according to callback");
+
MY_TRACE_LEAVE ("");
+
can_cache = 0;
+
break;
+
}
+
MY_TRACE_LEAVE ("");
+
}
+
} )
+
{
+
INTERNAL_ERROR( e );
+
TIMER_END(cache_lookup);
+
send_result();
+
return;
+
}
+
}
+
if( !cv[1]->key )
+
{
+
MY_TRACE_LEAVE ("Entry invalid due to zero key");
+
conf->datacache->expire_entry( raw_url );
+
can_cache = 0;
+
}
+
if( can_cache )
+
{
+
#ifndef RAM_CACHE_ASUME_STATIC_CONTENT
+
Stat st;
+
if( !file->rf || !file->mtime ||
+
((st = file_stat( file->rf )) && st->mtime == file->mtime ))
+
#endif
+
{
+
string fix_date( string headers )
+
{
+
string a, b;
+
if( sscanf( headers, "%sDate: %*s\n%s", a, b ) == 3 )
+
return a+"Date: "+Roxen.http_date( predef::time(1) ) +"\r\n"+b;
+
return headers;
+
};
+
+
MY_TRACE_LEAVE ("Using entry from ram cache");
+
conf->hsent += strlen(file->hs);
+
cache_status["protcache"] = 1;
+
if( strlen( d ) < 4000 )
+
{
+
TIMER_END(cache_lookup);
+
do_log( my_fd->write( fix_date(file->hs)+d ) );
+
}
+
else
+
{
+
TIMER_END(cache_lookup);
+
send( fix_date(file->hs)+d );
+
start_sender( );
+
}
+
return;
+
}
+
#ifndef RAM_CACHE_ASUME_STATIC_CONTENT
+
else
+
MY_TRACE_LEAVE (
+
sprintf ("Entry out of date (disk: %s, cache: mtime %d)",
+
st ? "mtime " + st->mtime : "gone", file->mtime));
+
#endif
+
} else
+
misc->cacheable = 0; // Never cache in this case.
+
file = 0;
+
}
+
}
+
TIMER_END(cache_lookup);
+
#endif // RAM_CACHE
+
TIMER_START(parse_request);
+
if( things_to_do_when_not_sending_from_cache( ) )
+
return;
+
TIMER_END(parse_request);
+
+
#ifdef THREADS
+
REQUEST_WERR("HTTP: Calling roxen.handle().");
+
roxen.handle(handle_request);
+
#else
+
handle_request();
+
#endif
+
})
+
{
+
report_error("Internal server error: " + describe_backtrace(err));
+
my_fd->set_blocking();
+
my_fd->close();
+
disconnect();
+
}
+
}
+
+
/* Get a somewhat identical copy of this object, used when doing
+
* 'simulated' requests. */
+
+
object clone_me()
+
{
+
object c,t;
+
c=object_program(t=this_object())(0, port_obj, conf);
+
#ifdef ID_OBJ_DEBUG
+
werror ("clone %O -> %O\n", t, c);
+
#endif
+
+
c->port_obj = port_obj;
+
c->conf = conf;
+
c->root_id = root_id;
+
c->time = time;
+
c->raw_url = raw_url;
+
+
c->real_variables = copy_value( real_variables );
+
c->variables = FakedVariables( c->real_variables );
+
c->misc = copy_value( misc );
+
c->misc->orig = t;
+
+
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;
+
c->my_fd = 0;
+
c->prot = prot;
+
c->clientprot = clientprot;
+
c->method = method;
+
+
// realfile virtfile // Should not be copied.
+
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->auth = auth;
+
c->realauth = realauth;
+
c->rawauth = rawauth;
+
c->since = since;
+
return c;
+
}
+
+
void clean()
+
{
+
if(!(my_fd && objectp(my_fd)))
+
end();
+
else if((predef::time(1) - time) > 4800)
+
end();
+
}
+
+
static void create(object f, object c, object cc)
+
{
+
if(f)
+
{
+
f->set_read_callback(got_data);
+
f->set_close_callback(end);
+
my_fd = f;
+
MARK_FD("HTTP connection");
+
if( c ) port_obj = c;
+
if( cc ) conf = cc;
+
time = predef::time(1);
+
call_out(do_timeout, 90);
+
}
+
root_id = this_object();
+
}
+
+
void chain(object f, object c, string le)
+
{
+
my_fd = f;
+
f->set_read_callback(0);
+
f->set_close_callback(end);
+
port_obj = c;
+
processed = 0;
+
do_not_disconnect=-1; // Block destruction until we return.
+
MARK_FD("HTTP kept alive");
+
time = predef::time(1);
+
+
if ( strlen( le ) )
+
got_data( 0,le );
+
else
+
{
+
// If no pipelined data is available, call out...
+
remove_call_out(do_timeout);
+
call_out(do_timeout, 90);
+
}
+
+
if(!my_fd)
+
{
+
if(do_not_disconnect == -1)
+
{
+
do_not_disconnect=0;
+
disconnect();
+
}
+
}
+
else
+
{
+
if(do_not_disconnect == -1)
+
do_not_disconnect = 0;
+
if(!processed)
+
{
+
f->set_read_callback(got_data);
+
f->set_close_callback(end);
+
}
+
}
+
}
+
+
string _sprintf( )
+
{
+
return "RequestID(" + (raw_url||"") + ")"
+
#ifdef ID_OBJ_DEBUG
+
+ (__marker ? "[" + __marker->count + "]" : "")
+
#endif
+
;
+
}
+
+
Stdio.File connection( )
+
{
+
return my_fd;
+
}
+
+
Configuration configuration()
+
{
+
return conf;
+
}
Newline at end of file added.