Roxen.git / server / modules / proxies / relay2.pike

version» Context lines:

Roxen.git/server/modules/proxies/relay2.pike:1: - // This is a roxen module. Copyright © 2000 - 2009, Roxen IS. + // This is a roxen module. Copyright 2000 - 2018, Roxen IS.      #include <module.h> - constant cvs_version = "$Id: relay2.pike,v 1.42 2010/07/12 20:46:56 mast Exp $"; + constant cvs_version = "$Id$";      inherit "module";   constant module_type = MODULE_FIRST|MODULE_LAST;    -  +    constant module_name =   "Proxies: HTTP Relay module";      constant module_doc =   "Smart HTTP relay module. Can relay according to "   "regular expressions.";      array(Relayer) relays = ({});    -  +    class Relay   {    RequestID id;    string url;    multiset options;       string request_data;    string host;    int port;    string file;       Stdio.File fd;       mapping make_headers( RequestID from, int trim )    { -  mapping res = ([ +  string remoteaddr = from->remoteaddr; +  if (has_value(remoteaddr, ":")) { +  // IPv6. +  remoteaddr = "[" + remoteaddr + "]"; +  } +  +  string myip = from->port_obj->ip; +  if (!myip) { +  // Bound to IPv4 ANY. Resolve the IP that the client sonnected to. +  // +  // NB: query_address() shouldn't fail under any normal circumstances, +  // and the fall back to 127.0.0.1 is purely for paranoia reasons. +  catch { myip = from->my_fd->query_address(1); }; +  sscanf(myip || "127.0.0.1", "%s %*s", myip); +  } +  if (has_value(myip, ":")) { +  // IPv6. +  myip = "[" + myip + "]"; +  } +  if (from->port_obj && from->port_obj->port != from->port_obj->default_port) { +  myip += ":" + from->port_obj->port; +  } +  +  array(array(string|int)) forwarded = from->misc->forwarded || ({}); +  +  if (from->request_headers->host) { +  forwarded += ({ ({ +  "by", '=', myip, ';', +  "for", '=', remoteaddr, ';', +  "host", '=', from->request_headers->host, ';', +  "proto", '=', from->port_obj->prot_name, +  }) }); +  } else { +  forwarded += ({ ({ +  "by", '=', myip, ';', +  "for", '=', remoteaddr, ';', +  "proto", '=', from->port_obj->prot_name, +  }) }); +  } +  +  mapping res = ([]); +  if( !trim ) { +  foreach( from->request_headers; string i; string|array(string) v) +  { +  switch( i ) +  { +  case "accept-encoding": +  // We need to support the stuff we pass on in the proxy +  // otherwise we might end up with things like double +  // gzipped data. +  break; +  case "connection": /* We do not support keep-alive yet. */ +  res->Connection = "close"; +  break; +  default: +  res[Roxen.canonicalize_http_header (i) || String.capitalize (i)] = v; +  break; +  } +  } +  } +  +  res += ([    "Proxy-Software":roxen->version(), -  // These are set by Apaches mod_proxy and are more or less -  // defacto standard. -  "X-Forwarded-For": from->remoteaddr, -  "X-Forwarded-Host": from->request_headers->host, +  +  // RFC 7239 +  "Forwarded": map(forwarded, MIME.quote), +  +  "Host": host + ":" + port,    ]);       // Also try to model X-Forwarded-Server after Apaches mod_proxy.    // The following is ripped from RequestID.url_base.    string server_host;    if (Protocol port = from->port_obj) {    server_host = port->conf_data[from->conf]->hostname;    if (server_host == "*")    server_host = from->conf->get_host();    }    else    server_host = my_configuration()->get_host(); -  if (server_host) -  res["X-Forwarded-Server"] = server_host; +     -  if( trim ) return res; -  foreach( from->request_headers; string i; string v) -  { -  switch( i ) -  { -  case "connection": /* We do not support keep-alive yet. */ -  res->Connection = "close"; -  break; -  case "host": -  res->Host = host+":"+port; -  break; -  case "x-forwarded-for": -  res["X-Forwarded-For"] = v + "," + res["X-Forwarded-For"]; -  break; -  case "x-forwarded-host": -  res["X-Forwarded-Host"] = v + "," + res["X-Forwarded-Host"]; -  break; -  case "x-forwarded-server": -  if (server_host) -  res["X-Forwarded-Server"] = v + "," + server_host; -  else -  res["X-Forwarded-Server"] = v; -  break; -  default: -  res[Roxen.canonicalize_http_header (i) || String.capitalize (i)] = v; -  break; +  // These are set by Apaches mod_proxy and are more or less +  // defacto standard. +  foreach(([ "X-Forwarded-For": remoteaddr, +  "X-Forwarded-Host": from->request_headers->host, +  "X-Forwarded-Proto": from->port_obj->prot_name, +  "X-Forwarded-Server": server_host, +  +  // RFC 7230 5.7.1 +  "Via": from->clientprot + " " + server_host, +  ]); string field; string|array(string) value) { +  if (!value) continue; +  array(string)|string old_val = res[lower_case(field)]; +  if (arrayp(old_val)) { +  value = old_val + ({ value }); +  } else if (stringp(old_val)) { +  value = ({ old_val, value });    } -  +  res[field] = value;    } -  +     return res;    }       string encode_headers( mapping q )    {    string res = "";    string content_length="";    foreach( sort( indices( q ) ), string h )    if(lower_case(h)=="content-length")    content_length = (string)h+": "+(string)q[h]+"\r\n";
Roxen.git/server/modules/proxies/relay2.pike:107:    }       void done_with_data( )    {    destruct(fd);    string headers, data;    string type, charset, status;    int code;    mapping h = ([]);    +  if (additional_headers) { +  h = copy_value(additional_headers); +  } +     if(!id) {    destruct();    return;    }       string rewrite( string what )    {    // in what: URL.    if(!strlen(what))    return what; // local, is OK.
Roxen.git/server/modules/proxies/relay2.pike:136:    else if( search( what, url ) == 0 )    {    return replace( what, url, id->not_query );    }    return what;    };       string do_rewrite( string what )    {    Parser.HTML p = Parser.HTML(); +  p->xml_tag_syntax(1);    -  function rewrite_what( string elem ) -  { -  return lambda( string t, mapping a ) { -  if( a[elem] ) -  a[elem] = rewrite( a[elem] ); -  return ({Roxen.make_tag( p->tag_name(), a, 1 )}); -  }; -  }; -  p->add_tag( "a", rewrite_what("href") ); -  p->add_tag( "img", rewrite_what("src") ); -  p->add_tag( "form", rewrite_what("action") ); +  p->_set_tag_callback( lambda(object p, string s) { +  string tag_name = p->tag_name(); +  string rewrite_arg = 0; +  switch(tag_name) { +  case "a": +  rewrite_arg = "href"; +  break; +  case "img": +  rewrite_arg = "src"; +  break; +  case "form": +  rewrite_arg = "action"; +  break; +  } +  if(rewrite_arg) { +  mapping args = p->tag_args(); +  if(string val = args[rewrite_arg]) { +  string new_val = rewrite(val); +  if( val != new_val) { +  int is_closed = has_suffix(s,"/>"); +  args[rewrite_arg] = new_val; +  return ({ Roxen.make_tag(tag_name, args, is_closed) }); +  } +  } +  } +  }); +     return p->finish( what )->read();    };             if( !options->cache ) NO_PROTO_CACHE();       if( sscanf( buffer, "%s\r\n\r\n%s", headers, data ) != 2 )    sscanf( buffer, "%s\n\n%s", headers, data );   
Roxen.git/server/modules/proxies/relay2.pike:218:    }    if( options->rxml && code >= 200 && code < 300 &&    (lower_case(type) == "text/html" ||    lower_case(type) == "text/plain" ))    {    if( charset )    {    id->set_output_charset( charset );    catch    { -  data = Locale.Charset.decoder(charset)->feed(data)->drain(); +  data = Charset.decoder(charset)->feed(data)->drain();    };    }    if( options->rewrite )    do_rewrite( data );    id->misc->defines = ([]);    id->misc->defines[" _extra_heads"] = h;    id->misc->defines[" _error"] = code;   #ifdef RELAY_DEBUG    werror("RELAY: parsing rxml\n");   #endif
Roxen.git/server/modules/proxies/relay2.pike:408:    return 1;    }    }       void create( string p, string u, int _last, multiset o )    {    last = _last;    pattern = p;    options = o;    url = u; +  //FIXME: in Roxen7+ change default to PCRE and opt in to simple +  // using options->simple. See help text for "patterns". +  if(options->pcre) +  r = Regexp.PCRE( pattern ); +  else    r = Regexp( pattern );    }   }         /****** module callbacks **/      void create( Configuration c )   {    if( c )    {    defvar( "patterns", "", "Relay patterns", TYPE_TEXT,    "<p>Syntax:\n"    "<pre>\n"    "[LAST ]EXTENSION extension CALL url-prefix [rxml] [trimheaders] [raw] [utf8] [cache] [stream] [rewrite]\n"    "[LAST ]LOCATION location CALL url-prefix [rxml] [trimheaders] [raw] [utf8] [cache] [stream] [rewrite]\n" -  "[LAST ]MATCH regexp CALL url [rxml] [trimheaders] [raw] [utf8] [cache] [stream] [rewrite]\n" +  "[LAST ]MATCH regexp CALL url [rxml] [trimheaders] [raw] [utf8] [cache] [stream] [rewrite] [pcre] [simple]\n"    "</pre> \\1 to \\9 will be replaced with submatches from the "    "regexp.</p><p>"       "Rxml, trimheaders etc. are flags. If <b>rxml</b> is specified, "    "the result of the relay will be RXML-parsed. Trimheaders and raw "    "are mutually exclusive. If <b>trimheaders</b> is present, only "    "the most essential headers are sent to the remote server "    "(actually, no headers at all right now), if <b>raw</b> is "    "specified, the request is sent to the remote server exactly as it "    "arrived to Roxen, not even the Host: header is changed. If "    "<b>utf8</b> is specified the request is utf-8 encoded before it " -  "is sent to the remote server.</p><p>" +  "is sent to the remote server. If <b>pcre</b> is specified, " +  "the PCRE Regexp engine will be used for mathing. " +  "This will be default in the next major release. " +  "Specify <b>simple</b> to keep using simple Regexps after upgrade.</p><p>"       "Cache and stream alter the sending of data to the client. If "    "<b>cache</b> is specified, the data can end up in the roxen "    "data cache, if <b>stream</b> is specified, the data is streamed "    "directly from the server to the client. This disables logging, "    "headers will be exactly those sent by the remote server, and this "    "only works for http clients. Less memory is used, however.</p><p>"       "For <b>EXTENSION</b> and <b>LOCATION</b>, the URL path+query "    "components (<b>location</b> part trimmed off) is appended to the "
Roxen.git/server/modules/proxies/relay2.pike:472:    "LOCATION /&lt;path&gt;/ CALL http://&lt;domain&gt;/&lt;path&gt;/\n"    "</pre></p>");    defvar("pre-rxml", "",    "Header-RXML", TYPE_TEXT,    "Included before the page contents for redirectpatterns with "    "the 'rxml' attribute set if the content-type is text/*" );    defvar("post-rxml", "",    "Footer-RXML", TYPE_TEXT,    "Included after the page contents for redirectpatterns with "    "the 'rxml' attribute set if the content-type is text/*" ); +  defvar("additional-headers", "", +  "Additional response headers", TYPE_TEXT, +  "Additional headers to add to the response. Write in the format:<br/>" +  "<tt>Header-Name: header-value</tt><br/>One header per line.");    }   }      string status()   {    string res = "Relays per regexp:<p>\n"    "<table cellpadding=0 border=0>";    foreach( sort(indices(stats)), string s )    res += sprintf("<tr><td>%s</td><td align=right>%d</td></tr>\n",    s, stats[s]);    return res + "</table>\n";   }      int linux;    -  + mapping additional_headers = ([]); +    void start( int i, Configuration c )   {    if (uname()->sysname == "Linux")    linux = 1;    if( c )    {    relays = ({});    foreach( (query( "patterns" )-"\r") / "\n" - ({ "" }), string line )    {    if( strlen(line) && line[0] == '#' )
Roxen.git/server/modules/proxies/relay2.pike:547:    }       if(mixed e = catch ( relays += ({ Relayer( tokens[0], tokens[1],last,    (multiset)map(tokens[2..],    lower_case))    }) ) )    report_warning( "Syntax error in regular expression: "+    tokens[0]+": %s\n", ((array)e)[0] );    }    } +  +  additional_headers = ([]); +  +  foreach ((query("additional-headers")-"\r")/"\n", string line) { +  line = String.trim_all_whites(line); +  +  if (!sizeof(line) || line[0] == '#') { +  continue;    }    -  +  if (sscanf(line, "%s:%s", string name, string val) == 2) { +  string n = Roxen.canonicalize_http_header(name) || +  String.capitalize(name); +  additional_headers[n] = String.trim_all_whites(val);    } -  +  }    -  + #ifdef RELAY_DEBUG +  werror("Additional headers: %O\n", additional_headers); + #endif +  } + } +    mapping first_try( RequestID id )   {    foreach( relays, Relayer q )    if( !q->last && q->relay( id ) )    return Roxen.http_pipe_in_progress( );   }      mapping last_resort( RequestID id )   {    foreach( relays, Relayer q )    if( q->last && q->relay( id ) )    return Roxen.http_pipe_in_progress( );   }