3db9192009-08-20Peter Bortas #pike __REAL_VERSION__ //#pragma strict_types
a7b80c2017-01-21Martin Nilsson constant version = sprintf(#"Pike httpserver %d.%d.%d ",(int)__REAL_VERSION__,__REAL_MINOR__,__REAL_BUILD__); constant description = "Minimal HTTP-server."; class Options { inherit Arg.Options; constant help_pre = #"Usage: httpserver [flags] [path]
3db9192009-08-20Peter Bortas Starts a simple HTTP server on port 8080 unless another port is specified. The server will present the contents of the current directory and it's children to
8921dc2009-08-20Henrik Grubbström (Grubba) the world without any authentication.
3db9192009-08-20Peter Bortas ";
a7b80c2017-01-21Martin Nilsson  constant port_help = "Port to use. Defaults to 8080."; constant version_help = "Displays version information.";
65e9962017-01-27Martin Nilsson  constant headers_help = "Set additional header and value, e.g. --header X-Content-Type-Options:nosniff";
c9f2162017-02-12Martin Nilsson  constant allow_help = "Limit access to a specific IP, IP-range or CIDR net.";
837fc62017-01-28Martin Nilsson  constant log_help = "Logs request in 'commonlog', 'raw' or 'string' format.";
3db9192009-08-20Peter Bortas 
a7b80c2017-01-21Martin Nilsson  Opt port = Int(HasOpt("--port")|Default(8080)); Opt version = NoOpt("--version");
65e9962017-01-27Martin Nilsson  Opt headers = Multiple(HasOpt("--header"));
c9f2162017-02-12Martin Nilsson  Opt allow = Multiple(HasOpt("--allow"));
837fc62017-01-28Martin Nilsson  Opt log = HasOpt("--log");
a7b80c2017-01-21Martin Nilsson } Options opt;
65e9962017-01-27Martin Nilsson mapping headers = ([]);
c9f2162017-02-12Martin Nilsson NetUtils.IpRangeLookup ip_whitelist;
833a172009-08-20Henrik Grubbström (Grubba) 
3db9192009-08-20Peter Bortas int main(int argc, array(string) argv) {
a7b80c2017-01-21Martin Nilsson  opt = Options(argv); if(opt->version) exit(0, version);
c9f2162017-02-12Martin Nilsson  if(opt->help) exit(0);
a7b80c2017-01-21Martin Nilsson  int port = opt->port; if(sizeof(argv=opt[Arg.REST])) { string home = combine_path(getcwd(), argv[-1]); if( Stdio.is_dir(home) ) cd(home); else if(port==8080 && (int)argv[-1]) port=(int)argv[-1]; }
65e9962017-01-27Martin Nilsson  if( opt->headers ) { foreach(opt->headers, string hdr) { array h = hdr/":"; if(sizeof(h)<2) error("Illegal header format %O.\n", hdr); headers[h[0]] = h[1..]*":"; } }
c9f2162017-02-12Martin Nilsson  if( opt->allow ) { ip_whitelist = NetUtils.IpRangeLookup( ([ 1 : opt->allow ]) ); }
a7b80c2017-01-21Martin Nilsson  Protocols.HTTP.Server.Port(handle_request, port, NetUtils.ANY); write("%s is now accessible on port %d through http, " "without password.\n", getcwd(), port); return -1;
3db9192009-08-20Peter Bortas } string dirlist( string dir ) {
3524712015-05-26Martin Nilsson  string res =
8921dc2009-08-20Henrik Grubbström (Grubba)  "<html><head>\n"
3db9192009-08-20Peter Bortas  "<style>a { text-decoration: none; }\n"
8921dc2009-08-20Henrik Grubbström (Grubba)  ".odd { background-color:#efefef; }\n" ".even { background-color:#fefefe; }\n" "</style>\n" "</head><body>\n" "<h1>"+Parser.encode_html_entities(dir[2..])+"</h1>" "<table cellspacing='0' cellpadding='2'>\n" "<tr><th align='left'>Filename</th>" "<th align='right'>Type</th>" "<th align='right'>Size</th></tr>\n";
3db9192009-08-20Peter Bortas 
8973902013-03-10Chris Angelico  foreach( sort( get_dir( dir ) ); int i; string fn )
3db9192009-08-20Peter Bortas  { Stdio.Stat s = file_stat( combine_path(dir, fn) );
3524712015-05-26Martin Nilsson  if( !s )
3db9192009-08-20Peter Bortas  continue; string t = s->isdir?"":Protocols.HTTP.Server.filename_to_type(fn); if( t == "application/octet-stream" ) t = "<span style='color:darkgrey'>unknown</span>";
3524712015-05-26Martin Nilsson  res +=
8921dc2009-08-20Henrik Grubbström (Grubba)  sprintf("<tr class='%s'><td><a href='%s%s'>%s%[2]s</a></td>" "<td align='right'>%s</td>"
3524712015-05-26Martin Nilsson  "<td align='right'>%s</td></tr>\n",
3db9192009-08-20Peter Bortas  (i&1?"odd":"even"),
3524712015-05-26Martin Nilsson  Protocols.HTTP.uri_encode(fn), s->isdir?"/":"",
dd167f2010-05-09Peter Bortas  Parser.encode_html_entities(fn), t,
3db9192009-08-20Peter Bortas  s->isdir?"":String.int2size(s->size)); }
8921dc2009-08-20Henrik Grubbström (Grubba)  return res+"</table></body></html>\n"; } string file_not_found(string fname) { return "<html><body><h1>File not found</h1>\n" "<tt>" + Parser.encode_html_entities(fname) + "</tt><br />\n" "</body></html>\n";
3db9192009-08-20Peter Bortas } void handle_request(Protocols.HTTP.Server.Request request) { string file = "."+combine_path("/",request->not_query);
dd167f2010-05-09Peter Bortas  file = Protocols.HTTP.uri_decode(file);
3db9192009-08-20Peter Bortas  Stdio.Stat s = file_stat( file );
c9f2162017-02-12Martin Nilsson  int ipblock = ip_whitelist && !ip_whitelist->lookup_range(request->my_fd->query_address());
b233022017-01-21Martin Nilsson 
837fc62017-01-28Martin Nilsson  switch(opt->log) { case 1: case "commonlog": { object now = Calendar.now();
c9f2162017-02-12Martin Nilsson  int code = 404; if( s ) code = 200; if( ipblock ) code = 401;
837fc62017-01-28Martin Nilsson  write("%s - - [%d/%s/%d:%02d:%02d:%02d %s] %O %d %d\n", (request->my_fd->query_address()/" ")[0], now->month_day(), now->month_name()[..2], now->year_no(), now->hour_no(), now->minute_no(), now->second_no(), now->tzname_utc_offset(),
c9f2162017-02-12Martin Nilsson  request->request_raw, code,
837fc62017-01-28Martin Nilsson  s && s->isreg && s->size); // Not showing generated data. } break; case "raw": write("%s\n\n", request->raw); break; case "string": write("%O\n\n", request->raw); break; default: break; }
c9f2162017-02-12Martin Nilsson  if( ipblock ) request->response_and_finish( (["data": "Permission denied for "+ (request->my_fd->query_address()/" ")[0], "type":"text/plain", "extra_heads" : headers, "error":401]) ); else if( !s )
8921dc2009-08-20Henrik Grubbström (Grubba)  request->response_and_finish( (["data": file_not_found(request->not_query), "type":"text/html",
65e9962017-01-27Martin Nilsson  "extra_heads" : headers,
b233022017-01-21Martin Nilsson  "error":404]) );
3db9192009-08-20Peter Bortas  else if( s->isdir )
8921dc2009-08-20Henrik Grubbström (Grubba)  request->response_and_finish( ([ "data":dirlist(file),
65e9962017-01-27Martin Nilsson  "extra_heads" : headers,
b233022017-01-21Martin Nilsson  "type":"text/html" ]) );
3db9192009-08-20Peter Bortas  else
b233022017-01-21Martin Nilsson  request->response_and_finish( ([ "file":Stdio.File(file),
65e9962017-01-27Martin Nilsson  "extra_heads" : headers,
b233022017-01-21Martin Nilsson  ]) );
3db9192009-08-20Peter Bortas }