#!/home/richmit/bin/verGo.sh ruby
# -*- Mode:Ruby; Coding:us-ascii-unix; fill-column:160 -*-

################################################################################################################################################################
##
# @file      mpmfs.rb
# @author    Mitch Richling <https://www.mitchr.me/>
# @Copyright Copyright 2007 by Mitch Richling.  All rights reserved.
# @brief     mpmfs -- Mitch's Poor Man's File Server@EOL
# @Keywords  ruby file-server webrick mpmfs
# @Std       Ruby 1.8
#
# TODO (not prioritized)
# 
#      * FileBrowserService: Support links for dir=
#      * MultiFileActionService: Like FileActionService, but works on may files at one time.
#      * FileBrowserService: Add select boxes to the dir listings to feed MultiFileActionService
#      * FileBrowserService: Add option to compress downloaded files
#      * FileBrowserService: Make the directory components in the titles of /fs listings clickable
#      * FileBrowserService: Add clickable directory path components in title
#      * XXX: Ubersafe file-name packaging -- i.e. we need to come up with a canonical encoding that we can use to pass file names and path names around so that
#        we know with 100% assurance that we can have any chars in a file name - including ones that can not be expressed in HTML at all.  Note we need not do
#        this sort of thing for the shell service, as everything we do in the shell service is 'type-able' -- i.e. needs escaped if it is a funny char.
#      * XXX: Prefrences
#        * Figure out a way to make prefs persist between invocations
#        * FileBrowserService:
#             * size of hex dump & color in hex dump.
#             * option for ask=Y or ask=N for rm & rmdir
#             * Size threshold for download-only
#             * Display/not display: chmod, chown, touch, rm, hex, act, upload, mkdir
#        * HexdumpFileService
#             * size of default hex dump, color of default hex dump
#        * SysInfoService
#             * Refresh for top
#      * SysInfoService: Add better support for Windows XP.
#      * UnixFileActionService: Add display of known parameters when asking for cmd (i.e. file, cwd, ...)
#      * UnixFileActionService: Add diff (side-by-side and regular)
# 
# ANTI-TODO (i.e. Stuff people ask for that I'm just not going to implement)
# 
#      * Some options set the behavior for all future calls.  For example 'scol' sets the sort column for the current, and all future, listings produced by
#        FileBrowserService.  I like it this way.
#      * Make the directory listings not look so 'UNIX-ish'.
#      * Reformat the code to fit into 80 columns.  Sorry, I have a big screen. :)
#      * Make it faster!  Don't store two copies of stat for each directory entry!  Bah!
#      * Make it work with directories with 100K files. Bah!
#      * Fix the funny help text formatting.  No.  XEmacs goofs up the syntax highlighting when I fix it. :)
# 
# BUGS
# 
#      * Funny file names might still be able to cause some bad behaviour
#      * Not tested very well when run as root user
#      * Not tested very well on Windows XP (with or without cygwin)
#      * UnixFileActionService uses popen3 to capture STDOUT and STDERR, but it doesn't preserve exit code!
#            
################################################################################################################################################################

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
HELP_INTRODUCTION = "
        # INTRODUCTION                                                                                                             
        #                                                                                                                             
        #       mpmfs.rb (Mitch's Poor Man's File Server) implements a file-server (with a few extra bits) as a web service           
        #       (a specialized HTTP server) using Ruby.  Some of the things it can do:                                                
        #                                                                                                                             
        #             * Read/Write access to the current host's file-system.                                                          
        #             * Pure Ruby file manipulation (touch, chmod, etc.)                                                              
        #             * UNIX command/shell access                                                                                     
        #               * File manipuation (touch, chmod, etc..)                                                                      
        #               * System information (top, df, who, etc..)                                                                    
        #               * RPC-like access via HTTP GET to access raw shell command output.                                            
        #               * A usable interactive shell access with command history.                                                     
        #             * Supports SSL and HTTP based user/password authentication                                                      
        #             * Functions through HTTP proxies and over SSH tunnels                                                           
        #             * Works well with text browsers (Lynx & w3m)                                                                    
        #             * Isn't terribly ugly on GUI browsers (firefox, safari, IE)                                                     
        #             * Everything is a ONE FILE ruby script                                                                          
        #             * Decent support for Windows XP, and very good UNIX support                                                     
        #                                                                                                                             
        #       Normally MPMFS is bound to the host's loop-back interface (localhost or 127.0.0.1) so that it may be accessed by      
        #       any local processes.  If the local host is shared with other users, then HTTP authentication should be used to        
        #       prevent access.  To access a MPMFS server from a remote host, an SSH tunnel is the usual solution. For example,       
        #       we can provide my.host.com with easy file-system access to far.away.com a single terminal window (using the w3m       
        #       browser) like this:                                                                                                   
        #                                                                                                                             
        #          my.host.com$ ssh -L 127.0.0.1:8080:127.0.0.1:8080 far.away.com mpmfs.rb &                                          
        #          my.host.com$ w3m 'http://localhost:8080/'                                                                          
        #                                                                                                                             
        #       An alternate way of accessing an MPMFS remotely is to bind it to an externally visible interface.  When used in       
        #       this way, it is probably a good idea to turn on both SSL and HTTP authentication.  For the ultimately paranoid,       
        #       using SSL, HTTP authentication and an SSH tunnel in combination may be just the ticket.
"
HELP_USE_CASES = "
        # PERSONAL USE CASES
        # 
        #    MPMFS was originally designed to solve some of the problems I face on a day-to-day basis.  Probably the best way
        #    to determine if MPMFS is useful for you is to consider how I use the tool.
        # 
        #       * Browsing unfamiliar directory structures in a terminal window with w3m.
        # 
        #         The 'ls, look, cd', cycle used to explore a directory or directory structure on the command line can
        #         require a considerable amount of typing -- even with a modern shell like bash with great command line
        #         completion.  MPMFS provides a very efficient, visual interface for this kind of thing. Midnight Commander
        #         or 'dired mode' in Emacs are also solutions to this same problem if you have them around.
        # 
        #       * Moving files around over SSH tunnels in highly restricted environments without the typing effort of scp
        # 
        #         I frequently move files around my home network, but the only service I run is ssh.  I also find myself doing
        #         this at work on ssh-only, secure subnets. Running MPMFS via SSH tunnels is nice way to avoid the typing effort
        #         of scp -- I get very frustrated trying to type the 60 character names with spaces and special characters that
        #         my Windows using coworkers like to create.  FUSE and ssh/scp provide a nice file-system interface to solve
        #         this problem if you have FUSE, and the appropriate privileges, on the local system.
        # 
        #         This use-case is particularly common when one of the systems is a Windows machine setting on your desk and
        #         the other box is a UNIX host in a datacenter.  Using the SSH tunnel in this case avoids violating
        #         corporate policies against starting up network services or installing software on systems.
        # 
        #       * Getting a file from home through a corporate firewall (HTTP proxy)
        # 
        #         I ssh home via my smart phone, and start up MPMFS in a container (a sort of chroot-jail construct) with SSL
        #         and HTTP authentication turned on.  Then I connect to that 'web page' through the company firewall, and
        #         download my file.  Then I stop the MPMFS via my ssh connection on the phone.
        # 
        #       * Usable command line access to a remote host from a stupid host.
        # 
        #         I really need to work on a remote system, but I'm at gradma's house.  Grandma's computer doesn't have SSH,
        #         and the system is locked down by the assisted living IT guy.  I can ssh with my smart phone, but it is very
        #         painful.  Solution?  I ssh to the remote system with my phone, scp MPMFS to the remote system if required,
        #         and start up MPMFS on an external interface (using SSL and HTTP authentication). Then I use the browser on
        #         gradma's computer to access the remote system.
"
HELP_USAGE = "
        # 
        # USAGE
        # 
        #    USE: mpmfs.rb [OPTIONS] [[ADDRESS:]PORT]
        #         ADDRESS -- The IP address to listen on (default: localhost)
        #         PORT    -- Numeric port number to listen on (default: 8080)
        #         OPTIONS -- various options:
        #            -url=URL -- Provide an 'advertised' URL that will be used to construct links.  This is required when
        #                        the server is running on an address/port that is different from the one that clients
        #                        connect with (the server may listen on 127.0.0.1:8080 for example, but browsers may
        #                        connect via an SSH tunnel to somehost:8181).
        #            -b       -- browse only mode (only FileBrowserService available)
        #            -s       -- Turn on SSL if ruby >= 1.8.6. (Note: use https in the URL)
        #            -a       -- Turn on authentication if ruby >= 1.8.6.  The script has a hard  wired user name
        #                        of 'mpmfs' with a valid passwd.  This should be changed (account removed or at least
        #                        the password changed).
        #            -p=file  -- Specify a password file.  Turns on -a as well.  If you leave off the name
        #                        and equals sign, then ~/.mpmpw.txt is used.
        #                        Each line of the file looks like 'user_name:SHA1_hash_of_passwd' or
        #                        'user_name:clear_password'.  Clear passwords are recognized as not being 40 hex digits. :)
        #                        One can generate a simple file (user: mitch, passwd: mitch1) like this:
        #                           echo 'mitch:' > pw.txt; echo -n 'mitch1' | openssl sha1 >> pw.txt
        #            -up=u:p  -- Specify a user name and password on the command line.  Turns on -a as well.
        #                        This option may be present multiple times -- each time a new user & password will be added
        #                        to the list, or will update the password for an existing user. The -p & -up options are
        #                        processed in the order they appear on the command line. The value given to this option is
        #                        processed just like a single line of a password file given to -p
"
HELP_URLS = "
        # 
        # URL REFERENCE
        # 
        #    -----------------------------------------------------------------------------------------------
        #    CGI      | URL                                                            | Service
        #    ---------|----------------------------------------------------------------|--------------------
        #    NA       | /                                                              | RootService
        #    NA       | /favicon.ico                                                   | FaviconService
        #    GET      | /fs                                                            | FileBrowserService
        #             |   dir     -- the directory/file to download/display            |
        #             |   cols    -- comma separated columns to display                |
        #             |              If empty, set list to default                     |
        #             |   cdel    -- delete the given columns                          |
        #             |              deleting all cols, resets list to default         |
        #             |   cadd    -- add the given columns                             |
        #             |   scol    -- Sort by the given column                          |
        #             |   sdir    -- Sort order (inc=increasing & dec=decreasing)      |
        #             |   fcol    -- Filter Column                                     |
        #             |   fval    -- Regexp filter (if fcol is valid and fval is nil,  |
        #             |              then the filter for given column is removed)      |
        #             |   sfld    -- false turns off case insensitive file name sort   |
        #    GET      | /hexdump                                                       | HexdumpFileService
        #             |   file    -- file name to hexdump                              |
        #             |   max     -- maximum number of bytes to read (integer)         |
        #             |   color   -- false to turn of color                            |
        #    GET      | /ufa                                                           | UnixFileActionService
        #             |   cmd     -- Command to run                                    |
        #             |              rmRF, rmR, rm, rmdir, chmod, chown, mv, cp,       |
        #             |              zip, unzip, tar, untar, gzip, gunzip, bzip2,      |
        #             |              bunzip2, compress, uncompress, mkdir, file        |
        #             |              touchT, touchR, touch, ps2pdf, enscript, ls       |
        #             |   file    -- The file name to act on                           |
        #             |   arg1    -- Secondary argument                                |
        #             |   return  -- URL to which to jump                              |
        #             |   bt      -- Back Time -- time for redirect to 'return'        |
        #             |   ask     -- Y to ask for input before action                  |
        #    GET      | /rfa                                                           | RubyFileActionService
        #             |   cmd     -- Command to run                                    |
        #             |              rmRF, rmR, rm, rmdir, chmod,  touchT,             |
        #             |              chown, mv, cp, mkdir, touch                       |
        #             |   file    -- The file name to act on                           |
        #             |   arg1    -- Secondary argument                                |
        #             |   arg1    -- Secondary argument                                |
        #             |   return  -- URL to which to jump                              |
        #             |   bt      -- Back Time -- time for redirect to 'return'        |
        #             |   ask     -- Y to ask for input before action                  |
        #    GET      | /sysInfo                                                       | SysInfoService
        #             |   cmd     -- The command                                       |
        #             |   refresh -- Refresh time (integer, seconds)                   |
        #    GET      | /info                                                          | ServerInfoService
        #    GET      | /ss                                                            | ShellService
        #             |   cwd     -- Current working directory                         |
        #             |   cmd     -- The command to run                                |
        #             |   return  -- URL to which to jump                              |
        #             |   raw     -- Y to produce raw output                           |
        #             |   history -- Don't run the cmd, just update from history       |
        #    GET      | /log                                                           | LogReporterService
        #    GET/POST | /stest                                                         | ServiceTesterService
        #    POST     | /upload                                                        | FileUploadService
        #             |   uploadfile -- The file to upload                             |
        #             |   uptarget   -- The directory into which to put the file       |
        #             |   return     -- URL to which to jump                           |
        #    GET      | /srvctrl                                                       | ServiceControl
        #             |   action  -- 'stop' to shutdown MPMFS                          |
        #             |   ask     -- Y to ask for input before action                  |
        #    -----------------------------------------------------------------------------------------------
"

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
require 'webrick'
include WEBrick
require 'etc'
require 'cgi'
require 'timeout'
require 'digest/sha1'
require 'digest/md5'
require "open3"
include Open3
require 'fileutils'

if (RUBY_VERSION >= '1.8.6') then
require 'webrick/https'
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# We use this constantly, just make it global!
$hostName = if (FileTest.executable?('/usr/sbin/scutil')) then
              `/usr/sbin/scutil --get ComputerName`.chomp.strip
            else
              Socket.gethostname.sub(/\..*$/, '')
            end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# We use this variable to log things
$cLog = String.new
$aLog = String.new
def logAction(theServlet, theRequest)
  cTm = Time.now
  $cLog += sprintf("(%04d-%02d-%02d_%02d:%02d:%02d_%3s ", cTm.year, cTm.month, cTm.day, cTm.hour, cTm.min, cTm.sec, cTm.zone)
  $cLog += theServlet + ': '
  $cLog += theRequest.inspect
  $cLog += "\n"
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Parse arguments
$sysStartTime   = Time.now
$version        = '$Revision: 1.112 $ -- Distribution $Date: 2011/06/23 19:47:59 $'.gsub('$', '')
$bURL           = nil
osUsrAndPass    = Array.new
browseOnlyMode  = false
doAuth          = false
srvAndPort      = nil
doSSL           = false
while (ARGV[0]) do
  curOpt = ARGV.shift
  if (tmp = /^-+[hH](=(.+)){0,1}/.match(curOpt)) then
    helpOpt = tmp[2]
    if   (helpOpt == 'INTRODUCTION') then
      STDOUT.puts(HELP_INTRODUCTION)
    elsif(helpOpt == 'USE_CASES') then
      STDOUT.puts(HELP_USE_CASES)
    elsif(helpOpt == 'USE') then
      STDOUT.puts(HELP_USAGE)
    elsif(helpOpt =~ /URLS{0,1}/i) then
      STDOUT.puts(HELP_URLS)
    else
      STDOUT.puts("INFO(mpmfs): Use -h=OPT (where OPT is one of INTRODUCTION, USE, URLS, or USE_CASES for more)!")
    end
    STDOUT.puts("INFO(mpmfs): Read the comment at the top of the script for TODO, ANTI-TODO, and BUGS.")
    exit(1)
  elsif (/^-+a/i =~ curOpt) then
    if (RUBY_VERSION >= '1.8.6') then
      doAuth = true
    else
      STDOUT.puts("ERROR(mpmfs): HTTPAuth won't work with Ruby pre 1.8.6 -- ignoreing -a option")
      exit
    end
  elsif (/^-+s/i =~ curOpt) then
    if (RUBY_VERSION >= '1.8.6') then
      doSSL = true
    else
      STDOUT.puts("ERROR(mpmfs): SSL (HTTPS) won't work with Ruby pre 1.8.6 -- ignoreing -s option")
      exit
    end
  elsif (tmp = curOpt.match(/^-+up=(.+)$/i)) then
    upv = tmp[1]
    if (RUBY_VERSION >= '1.8.6') then
      doAuth = true
      if (tmp = upv.match(/^([^:]+):(.+)$/)) then
        osUsrAndPass.push( [ tmp[1], tmp[2] ] )
      else
        STDERR.puts("WARNING(mpmws): Value of -up was badly formatted: '#{upv}")
      end
    else
      STDOUT.puts("ERROR(mpmfs): HTTPAuth won't work with Ruby pre 1.8.6 -- ignoreing -p option")
      exit
    end
  elsif (tmp = curOpt.match(/^-+p(=(.+)){0,1}$/i)) then
    pwFile = tmp[2]
    if (pwFile.nil?) then
      if (ENV['HOME']) then
        pwFile = ENV['HOME'] + '/.mpmpw.txt'
      else
        pwFile = Etc.getpwnam(Etc.getlogin).dir + '/.mpmpw.txt'
      end
    end
    if (RUBY_VERSION >= '1.8.6') then
      doAuth = true
      open(pwFile) do |file|
        file.each_line do |line|
          if (tmp = line.strip.match(/^([^:]+):(.+)$/)) then
            osUsrAndPass.push( [ tmp[1], tmp[2] ] )
          else
            STDERR.puts("WARNING(mpmws): Password file had a bad line: '#{line}")
          end
        end
      end
    else
      STDOUT.puts("ERROR(mpmfs): HTTPAuth won't work with Ruby pre 1.8.6 -- ignoreing -p option")
      exit
    end

  elsif (tmp = curOpt.match(/^-+url=(.+)$/i)) then
    $bURL = tmp[1]
  elsif (/^-+b/i =~ curOpt) then
    browseOnlyMode = true
  else # Must be the [[ADDRESS:]PORT] argument
    srvAndPort = curOpt
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Figure out $bindAdd, $bindPort, & $bURL
$bindAdd    = 'localhost'
$bindPort   = 8080
if (srvAndPort) then
  if (/^\d+$/.match(srvAndPort)) then
    $bindPort = srvAndPort.to_i
  elsif(/^.+:\d+$/.match(srvAndPort)) then
    ($bindAdd, $bindPort) = srvAndPort.split(/:/)
    $bindPort = $bindPort.to_i
  else
    $bindAdd  = srvAndPort
  end
end
if ($bindAdd == 'external') then
  $bindAdd = Socket.gethostbyname(Socket.gethostname)[0]
end
if (($bindAdd == 'localhost') || ($bindAdd == 'loopback')) then
  $bindAdd = '127.0.0.1'
end

if ($bURL.nil?) then
  $bURL = "http://#{$bindAdd}:#{$bindPort}"
  if(doSSL) then
    $bURL = "https://#{$bindAdd}:#{$bindPort}"
  end
end

# Make sure port number is OK
if ( ($bindPort < 1024) && (Process.uid != 0) ) then
  STDERR.puts("ERROR(mpmp2pg):  Only root (uid=0) may open ports below 1024!")
  exit(6);
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Helper function to make unprintable strings printable.  wsKeep is a string of chars to NOT convert to oct codes
def octificateString(aString, wsKeep)
  octifedString = ''
  aString.each_byte do |theChar|
    if ((theChar <= 32) || (theChar >= 127) || (/\s/.match(theChar.chr)) || (theChar.chr == '\\')) then
      if (wsKeep.nil?) then
        octifedString += sprintf("\\%03o", theChar)
      else
        keepChar = false
        wsKeep.each_byte do |keepChar|
          if (theChar == keepChar) then
            keepChar = true
            break
          end
        end
        if (keepChar) then
          if (theChar.chr == '\t') then
            octifedString += (' ' * 8)
          else
            octifedString += theChar.chr
          end
        else
          octifedString += sprintf("\\%03o", theChar)
        end
      end
    else
      octifedString += theChar.chr
    end
  end
  return octifedString
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Link construction helper -- make nice links even in preformated  sections
# spcMode: 'out', 'zap', 'keep'
# prtSpec:  "%-Ns"
def mka(title, href, label, spcMode, prtSpec)
  newLabel = sprintf(prtSpec, label)
  newTitle = title.strip
  if (spcMode == 'keep') then
    return sprintf('<a title="%s" href="%s">%s</a>', newTitle, href, label)
  else
    labBits = newLabel.match(/^(\s*)(.*\S)(\s*)$/)
    lftSpc = nonSpc = endSpc = ''
    if(labBits) then
      lftSpc, nonSpc, endSpc = labBits[1], labBits[2], labBits[3]
    end
    if(spcMode == 'out') then
      return sprintf('%s<a title="%s" href="%s">%s</a>%s', lftSpc, newTitle, href, nonSpc, endSpc)
    else
      return sprintf('<a title="%s" href="%s">%s</a>', newTitle, href, nonSpc)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Helper function to produce simple HTML messages.
def genSimpleMsgHTML(bodyt, meta, title1, title2, bodyHead, bodyPre, bodyFoot)
  return [
    '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">                                     ' + "\n" +
    '<HTML>                                                                                      ' + "\n" +
    '  <HEAD><TITLE>MPMFS(' + $hostName + '): ' + (title1 || title2 || '') + '</TITLE>           ' + "\n" +


'<style type="text/css">
  .tinyFormBox {
      margin:0px;
      border-width: 0px;
      padding: 0px;
  }
</style>' +



    (meta || '')                                                                                   + "\n" +
    '  </HEAD>                                                                                   ' + "\n" +
    "  <BODY #{bodyt || ''}>                                                                     " + "\n" +
    '    <H1>' + (title2 || title1 || '') + '</H1>                                               ' + "\n" +
    (bodyHead || '')                                                                               + "\n" +
    '    <PRE>' + "\n" + (bodyPre || '') + "\n" + '</PRE>                                        ' + "\n" +
    (bodyFoot || '')                                                                               + "\n" +
    "\n\n\n<br><br><br><hr>" + mka('Jump to the MPMFS root', $bURL, 'HOME', 'zap', '%s')           + "\n" +
    '  -- Generated by MPMFS (Mitch Richling\'s Poor Man\'s File Server) -- ' + $version + '<br> ' + "\n" +
    '  </BODY>                                                                                   ' + "\n" +
    '</HTML>                                                                                     ' + "\n",
    'text/html'
  ]
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Simple authentication
class MySimpleAuth

  # Password hashes are SHA1 hex dumps.  Use Digest::SHA1.hexdigest in irb or something like 'printf "foo" | openssl sha1' at
  # the command line (use printf because echo will add an \n at the end of the string).
  @@passwdDB = {
    'mpmfs'   => '2492fc7ff2801a9948c342f087444703259f5267'
  }

  @@doAuth = false

  def MySimpleAuth.setAuthOn(boolVal)
    @@doAuth = boolVal && (RUBY_VERSION >= '1.8.6')
    if (boolVal && !(@@doAuth)) then
      STDOUT.puts("WARNING(mpmfs): HTTPAuth won't work with Ruby pre 1.8.6 -- HTTPAuth disabled")
    end
  end

  # to lock an existing account, use '*LK*' as passwd and false for encPasswdForUse.
  def MySimpleAuth.addOrUpdateAccount(userName, passwd, encPasswdForUse)
    if (encPasswdForUse) then
      @@passwdDB[userName] = Digest::SHA1.hexdigest(passwd)
    else
      @@passwdDB[userName] = passwd
    end
  end

  def MySimpleAuth.doAuth(req, resp, realm)
    if (@@doAuth) then
      HTTPAuth.basic_auth(req, resp, realm) do |user, pass|
        (@@passwdDB.member?(user) && (Digest::SHA1.hexdigest(pass || '') == @@passwdDB[user]))
      end
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service provides access to the log files.
class LogReporterService < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    logAction('LogReporterService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    if (req.query.member?('log')) then
      if (req.query['log'] == 'a') then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'Access Log', nil, nil, CGI::escapeHTML(octificateString($aLog, ' \n')), nil)
      elsif(req.query['log'] == 'c') then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'Custom Log', nil, nil, CGI::escapeHTML(octificateString($cLog, ' \n')), nil)
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: LogReporterService', nil, nil, nil, nil)
      end
    else
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: LogReporterService', nil, nil, nil, nil)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service is a service tester.
class ServiceTesterService < WEBrick::HTTPServlet::AbstractServlet
  # POST
  def do_POST(req, resp)
    logAction('ServiceTesterService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    (resp.body, resp['Content-Type']) = do_X(req, resp, "POST")
  end
  # GET
  def do_GET(req, resp)
    logAction('ServiceTesterService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    (resp.body, resp['Content-Type']) = do_X(req, resp, "GET")
  end
  # This really performs all the work.
  def do_X(req, resp, rtype)
    if(req.query) then
      thep = (req.query.map { |key, val| "KEY: #{CGI::escapeHTML(key.inspect)} == VAL: #{CGI::escapeHTML(val.inspect)}\n" }).join +
        "\n\n\n" + 
        CGI::escapeHTML(req.inspect).gsub(',', ",\n")
      return genSimpleMsgHTML(nil, nil, "CGI #{rtype} TEST", nil, nil, thep, nil)
    else
      return genSimpleMsgHTML(nil, nil, "CGI #{rtype} TEST: ERROR", nil, nil, nil, nil)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service provides a way for the server to shut itself down.
class ServiceControl < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    logAction('ServiceControl', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    if (req.query.member?('ask')) then
      if (req.query.member?('action') && (req.query['action'] == 'stop')) then
        askHTML = mka('Shutdown the MPMFS server immediately', "#{$bURL}/srvctrl?action=stop", 'Shutdown Now!', 'zap', '%s')
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'MPMFS Shutdown Request Verification', nil, askHTML, nil, nil)
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'MPMFS Service Control', nil, '<h1>Unknown Request</h1>', nil, nil)
      end
    else
      if (req.query.member?('action') && (req.query['action'] == 'stop')) then
        exit!(1)
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'MPMFS Service Control', nil, '<h1>Unknown Request</h1>', nil, nil)
      end
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service provides access to various bits of system information.
class ServerInfoService < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    logAction('ServerInfoService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')

    timeString = sprintf("%04d-%02d-%02d_%02d:%02d:%02d_%3s ", $sysStartTime.year, $sysStartTime.month, $sysStartTime.day,
                         $sysStartTime.hour, $sysStartTime.min, $sysStartTime.sec, $sysStartTime.zone)

    theAnswer = ''
    theAnswer += '<b>MPMFS Version:</b> '   + $version.sub(/\s*--.*$/, '')                                                                 + "\n"
    theAnswer += '<b>MPMFS Date:</b> '      + $version.sub(/^.*--\s*/, '')                                                                 + "\n"
    theAnswer += '<b>MPMFS Author:</b> '    + mka('Jump to my homepage', 'https://www.mitchr.me', 'Mitch Richling', 'zap', '%s')            + "\n"
    theAnswer += "\n"
    theAnswer += '<b>Server Startup:</b> '  + CGI::escapeHTML(octificateString(timeString, ' '))                                           + "\n"
    theAnswer += '<b>Bound Address:</b> '   + CGI::escapeHTML(octificateString($bindAdd, ' '))                                             + "\n"
    theAnswer += '<b>Bound Port:</b> '      + CGI::escapeHTML(octificateString($bindPort.to_s, ' '))                                       + "\n"
    theAnswer += '<b>Base URL:</b> '        + CGI::escapeHTML(octificateString($bURL, ' '))                                                + "\n"
    theAnswer += "\n"
    theAnswer += '<b>Ruby Version:</b> '    + CGI::escapeHTML(octificateString(RUBY_VERSION, ' '))                                         + "\n"
    theAnswer += '<b>Ruby Platform:</b> '   + CGI::escapeHTML(octificateString(RUBY_PLATFORM, ' '))                                        + "\n"
    theAnswer += '<b>Ruby Rel Date:</b> '   + CGI::escapeHTML(octificateString(RUBY_RELEASE_DATE, ' '))                                    + "\n"
    theAnswer += "\n"
    theAnswer += '<b>Host (logical):</b> '  + CGI::escapeHTML(octificateString($hostName, ' '))                                            + "\n"
    if ( !(RUBY_PLATFORM =~ /mswin/)) then
    theAnswer += '<b>Host (hostname):</b> ' + CGI::escapeHTML(octificateString(`hostname`.chomp, ' '))                                     + "\n"
    theAnswer += '<b>Host (uname):</b> '    + CGI::escapeHTML(octificateString(`uname -a`.chomp, ' '))                                     + "\n"
    theAnswer += '<b>Host (uptime):</b> '   + CGI::escapeHTML(octificateString(`uptime`.chomp, ' '))                                       + "\n"
    end
    theAnswer += "\n"
    theAnswer += '<b>CWD:</b> '             + CGI::escapeHTML(octificateString(FileUtils.pwd, ' '))                                        + "\n"
    theAnswer += "\n"
    if ( !(RUBY_PLATFORM =~ /mswin/)) then
    theAnswer += '<b>User Name:</b> '       + CGI::escapeHTML(octificateString(Etc.getlogin, ' '))                                         + "\n"
    p=Etc.getpwnam(Etc.getlogin)
    theAnswer += '<b>User UID:</b> '        + CGI::escapeHTML(octificateString(p.uid.to_s, ' '))                                           + "\n"
    g=Etc.getgrgid(p.gid)
    theAnswer += '<b>Group Name:</b> '      + CGI::escapeHTML(octificateString(g.name, ' '))                                               + "\n"
    theAnswer += '<b>Group GID:</b> '       + CGI::escapeHTML(octificateString(p.gid.to_s, ' '))                                           + "\n"
    end

    (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'MPMFS Instance Information', nil, nil, theAnswer, nil)
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service provides a favicon
class FaviconService < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    #MySimpleAuth.doAuth(req, resp, 'mpmfs')
    resp['Content-Type'] = 'image/vnd.microsoft.icon' # or use: 'image/x-icon'
    resp.body =
      "\0\0\001\0\001\0\020\020\0\0\0\0\0\0h\005\0\0\026\0\0\0(\0\0\0\020\0\0\0 \0\0\0\001\0" +
      "\b\0\0\0\0\0@\001\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\0\0\0\377" +
      ("\0"*1014) +
      "\002\0\0\0\0\0\0\0\0\002\002\002\002\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\002\0\0\002" +
      "\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\002\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\002\002\002\002" +
      "\002\0\0\0\0\002\002\002\002\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0" +
      "\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\002\0\0\002\002\002\002\002\002" +
      "\0\0\0\002\002\002\002\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\0\002\0\002\0\0\0" +
      "\0\002\0\0\0\002\002\0\0\0\002\0\002\0\0\0\0\002\0\0\0\002\002\0\0\0\002\0\002\002"   +
      "\002\0\0\002\0\0\0\002\002\0\002\0\002\0\002\0\0\002\0\002\0\002\0\002\002\002\0\002" +
      "\002\0\002\0\0\002\0\002\002\0\002\002\002\0\0\0\002\0\002\002\002\0\0\002\0\0\0\002" +
      "\277\303\0\0\277\275\0\0\277\375\0\0\277\375\0\0\203\303\0\0\277\277\0\0\277\277\0\0" +
      "\277\275\0\0\201\303\0\0\377\377\0\0u\356\0\0u\356\0\0tn\0\0U\252\0\0%\244\0\0tn\0\0"
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# My personal little touch replacement --- should be in FileUtils!!
def mjrTouchT(tim, file)
  ttime = nil
  begin
    if   (tmp = tim.match(/^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\.(\d\d)){0,1}$/)) then
      ttime = Time.mktime(tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[7], 0)
    elsif(tmp = tim.match(/^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\.(\d\d)){0,1}{0,1}$/)) then
      ttime = Time.mktime(2000 + tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[7], 0)
    elsif(tmp = tim.match(/^(\d\d)(\d\d)(\d\d)(\d\d)(\.(\d\d)){0,1}$/)) then
      ttime = Time.mktime(Time.now.year, tmp[1], tmp[2], tmp[3], tmp[4], tmp[6], 0)
    end
  rescue
    return "ERROR: mjrTouchT failure: Time spec out of range ([CC]YY]MMDDhhmm[.SS])"
  end
  if (ttime) then
    begin
      if (File.utime(ttime, ttime, file) > 0) then
        return "SUCCESS: mjrTouchT"
      else
        return "ERROR: mjrTouchT failure: Bad time spec ([CC]YY]MMDDhhmm[.SS])"
      end
    rescue
      return "ERROR: mjrTouchT failure: utime failed (missing file?)"
    end
  else
    return "ERROR: mjrTouchT failure: Bad time spec ([CC]YY]MMDDhhmm[.SS])"
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# My personal little chmod replacement
def mjrChmod(daMode, file)
  mmode = nil
  if(daMode.match(/^[0-7]{3}$/)) then
    mmode = daMode.oct
  else
    return "ERROR: mjrChmod failure: Mode spec invalid (must be 3 octal digits)"
  end
  begin
    return FileUtils.chmod(mmode, file)
  rescue
    return "ERROR: mjrChmod: FileUtils.chmod failed"
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# My personal file digest replacement (think 'openssl md5' or 'openssl sha1'
def mjrDigest(file)
  begin
    open(file, "r") do |file|
      fileData = file.read()
      return "SUCCESS: mjrDigest: MD5: #{Digest::MD5.hexdigest(fileData)}  SHA1: #{Digest::SHA1.hexdigest(fileData)}"
    end
  rescue
    return "ERROR: mjrDigest failure: Duno why, but file was probably missing or not readable."
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Provide the hex dump service
class HexdumpFileService < WEBrick::HTTPServlet::AbstractServlet

  @@doColor     = true
  @@maxConvSize = 1024*64
  @@colCtr      = [ '<font color="#FF0000">', '</font>' ]
  @@colPrt      = [ '',                       ''        ]
  @@colOvr      = [ '<font color="#0000FF">', '</font>' ]

  def do_GET(req, resp)
    logAction('HexdumpFileService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')

    if ( !(req.query)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Hex Dump File (UNKNOWN REASON)', nil, nil, nil, nil)
      return
    end

    goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

    maxConvSizeNow = @@maxConvSize
    if (req.query.member?('max')) then
      maxConvSizeNow = req.query['max'].to_i
    end
    doColorNow = @@doColor
    if (req.query.member?('color')) then
      if (req.query['color'] == 'false') then
        doColorNow = false
      else
        doColorNow = true
      end
    end

    askHTML =
        '<FORM METHOD="get" ACTION="' + $bURL + '/hexdump?' + '"><table BORDER="1"><tr><td><table BORDER="0" CELLSPACING="0" CELLPADDING="2">                       '
    if ( (req.query.member?('return')) && (req.query['return'])) then
      askHTML +=
        '  <INPUT TYPE="hidden" NAME="return" VALUE="' + CGI::escapeHTML(req.query['return']) + '" />                                                               '
    end
    if ( !(req.query.member?('file')) || req.query['file'].empty? ) then
      askHTML +=
        '<tr><td align="right">file:</td><td align="left"><INPUT TYPE="text" SIZE="30" NAME="file" VALUE="" /></td></tr>                                            '
    else
      askHTML +=
        '<tr><td align="right">file:</td><td align="left"><INPUT TYPE="text" SIZE="30" NAME="file" VALUE="' + CGI::escapeHTML(req.query['file']) + '" /></td></tr>  '
    end
    askHTML +=
        '<tr><td align="right">max:</td><td align="left"><INPUT TYPE="text" SIZE="6" NAME="max" VALUE="' + maxConvSizeNow.to_s + '" /></td></tr>                    ' +
        '<tr><td align="right">color:</td><td align="left"><INPUT TYPE="text" SIZE="10" NAME="color" VALUE="' + doColorNow.inspect + '" /></td></tr>                '
    askHTML +=
        '<tr><td colspan="2" align="right"><INPUT TYPE="submit" NAME="added" VALUE="submit"></td></tr>                                                              ' +
        '</table></td></tr></table></FORM>                                                                                                                          '

    if ( !(req.query.member?('file')) || req.query['file'].empty? ) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'Hex Dump File', nil, askHTML, nil, goLink)
      return
    end

    theFile          = req.query['file']
    theFileHTMLable  = CGI::escapeHTML(octificateString(theFile, ' '))

    begin
      theFileStat = File.stat(theFile)
      if ( !(theFileStat)) then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Hex Dump File (Could not stat the file!)', nil, nil, nil, goLink)
        return
      end
      if ( !(theFileStat.file?)) then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Hex Dump File (File was not a file!)', nil, nil, nil, goLink)
        return
      end
      if ( !(theFileStat.readable?)) then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Hex Dump File (File was not readable!)', nil, nil, nil, goLink)
        return
      end
    rescue
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Hex Dump File (Could not stat the file!)', nil, nil, nil, goLink)
      return
    end

    theConv   = ''
    moreLeft  = false
    open(theFile) do |file|
      byteCount  = 0
      blockCount = 0
      cLineStr   = ''
      cLineAdd   = -1
      cLineHex   = ''
      file.each_byte do |b|
        if(cLineAdd < 0) then
          cLineAdd = byteCount
          cLineHex=''
          cLineStr=''
        end
        byteCount += 1
        if (doColorNow) then
          if (b<32) then
            cLineStr += @@colCtr[0] + CGI::escapeHTML((b+64).chr) + @@colCtr[1]
          elsif (b>126) then
            cLineStr += @@colOvr[0] + '#'                         + @@colOvr[1]
          else
            cLineStr += @@colPrt[0] + CGI::escapeHTML(b.chr)      + @@colPrt[1]
          end
        else
          if ((b<32) || (b>126)) then
            cLineStr += '.'
          else
            cLineStr += CGI::escapeHTML(b.chr)
          end
        end
        cLineHex += sprintf("%02x ", b)
        if ((byteCount % 8)==0) then
          blockCount += 1
          if ((blockCount % 4)==0) then
            theConv += sprintf("%010x : %-100s : %s\n", cLineAdd, cLineHex, cLineStr)
            cLineAdd = -1
          else
            cLineHex += sprintf(" ")
          end
        end
        if(byteCount>maxConvSizeNow) then
          moreLeft = true
          break
        end
      end
      if ( cLineAdd >= 0) then
        theConv += sprintf("%010x : %-100s : %s\n", cLineAdd, cLineHex, cLineStr)
      end
    end
    theConv += sprintf("\n")
    if (moreLeft) then
      theConv += sprintf("%s\n", 'Output Truncated')
    end

    (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "HexDump: #{theFileHTMLable}", nil, nil, theConv, askHTML + '<br>' + goLink)
    return
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Provide the filebrowser
class FileBrowserService < WEBrick::HTTPServlet::AbstractServlet

  @@showUnreadable                = true     # Directory listings show unreadable files
  @@showSimlinks                  = true     # Directory listings show simlinks
  @@cFactor                       = 1.5      # Factor for file size bucket (PB, TB, GB, MB) in listings
  @@doFileContentCheckForMimeType = true     # Read file content, and look for non-ASCII to determine Mime-Type
  @@okPercentESCnBSPinTextFile    = 10       # Percentage of text file that may be ESC/BSP and still be text/plain
  @@fileSizeDownloadThreshold     = 10       # Size (in MB) beyond which files will always be download only
  @@dfltSCol                      = 'name'   # Default column to sort
  @@dfltSDir                      = 'inc'    # Default sort order is acceding.
  @@foldCaseInSort                = true     # Do case insensitive sort
  @@showHelp                      = true     # Display the column link help in listings
  @@faSrv                         = 'ufa'    # The file action service URL (ufa or rfa)
  @@ignoreModeUploadLinks         = false    # Attempt file uploads into non-writable directories

  @@provideFUNCchmod              = true     # Directory listings have: chmod
  @@provideFUNCchown              = true     # Directory listings have: chown
  @@provideFUNCtouch              = true     # Directory listings have: touch
  @@provideFUNCshell              = true     # Directory listings have: shell (on . link)
  @@provideFUNCactA               = true     # Directory listings have: 'Act'
  @@provideFUNCactI               = true     # Directory listings have: 'act Inside"
  @@provideFUNCactH               = true     # Directory listings have: 'Hex view'
  @@provideFUNCactD               = true     # Directory listings have: 'Download only'
  @@provideFUNCactU               = true     # Directory listings have: 'Upload into'
  @@provideFUNCactF               = true     # Directory listings have: 'Form multi-select box'

  def FileBrowserService.roMode()
    @@provideFUNCchmod = false
    @@provideFUNCchown = false
    @@provideFUNCtouch = false
    @@provideFUNCshell = false
    @@provideFUNCactA  = false
    @@provideFUNCactI  = false
    @@provideFUNCactU  = false
    @@provideFUNCactF  = false;
  end

  @@ext2mimeType =
    begin
      tmpHash = Hash.new
      [
      [ 'application/postscript',       %w{ ps eps ai                     } ],
      [ 'application/pdf',              %w{ pdf                           } ],
      [ 'application/x-dvi',            %w{ dvi                           } ],
      [ 'text/plain',                   %w{ tcl
                                            c h cc cpp hpp hh hxx cxx
                                            f f77 f90 f95 f2k
                                            rb pl py
                                            js ls mocha jsc jsu
                                            java
                                            cgi
                                            tex latex texinfo texi
                                            t tr roff roff t tr man
                                            troff nroff
                                            txt
                                            tsv csv
                                            css
                                            sgm sgml
                                            xml xsl
                                            tcl
                                            ruby perl
                                            sh csh ksh bash tcsh
                                            python                        } ],  # Source code using #! lines
      [ 'text/html',                    %w{ htm html shtml                } ],
      [ 'application/x-download',       %w{ bin class exe
                                            Z bz2 gz zip tgz cpio gtar
                                            shartar uu uue jar bcpio      } ],
      [ 'image/bmp',                    %w{ bmp                           } ],
      [ 'image/gif',                    %w{ gif                           } ],
      [ 'image/jpeg',                   %w{ jpeg jpg jpe jfif pjpeg pjp   } ],
      [ 'image/png',                    %w{ png                           } ],
      [ 'image/tiff',                   %w{ tiff tif                      } ],
      [ 'image/x-cmu-raster',           %w{ ras                           } ],
      [ 'image/x-portable-anymap',      %w{ pnm                           } ],
      [ 'image/x-portable-bitmap',      %w{ pbm                           } ],
      [ 'image/x-portable-graymap',     %w{ pgm                           } ],
      [ 'image/x-portable-pixmap',      %w{ ppm                           } ],
      [ 'image/x-xbitmap',              %w{ xbm                           } ],
      [ 'image/x-xpixmap',              %w{ xpm                           } ],
      [ 'image/x-xwindowdump',          %w{ xwd                           } ],
      [ 'video/mpeg',                   %w{ mpeg mpg mpe mpv vbs mpegv    } ],
      [ 'video/msvideo',                %w{ avi wmv avi asf asx           } ],
      [ 'video/quicktime',              %w{ qt mov moov                   } ],
      [ 'application/x-excel',          %w{ xls xlw xla xlc xlm xlt xll   } ],
      [ 'application/mspowerpoint',     %w{ pot pps ppt ppz               } ],
      [ 'application/msword',           %w{ doc wiz rtf                   } ],
      [ 'application/msproject',        %w{ mpp                           } ]
      ].each do |type, extArr|
        extArr.each do |ext|
          tmpHash[ext.downcase] = type
        end
      end
      tmpHash
    end

  @@colInfo = {
    'mode'     =>  { 'pFmt' =>  '%-10s', 'lab' => 'Mode',  'dOrd' =>  0, 'win?' => true,  'cygwin?' => true,  'sortable' => true,  'filterable' => true  },
    'lnk'      =>  { 'pFmt' =>  '%3s',   'lab' => 'Lnk',   'dOrd' =>  1, 'win?' => false, 'cygwin?' => true,  'sortable' => true,  'filterable' => false },
    'owner'    =>  { 'pFmt' =>  '%-9s',  'lab' => 'Owner', 'dOrd' =>  2, 'win?' => false, 'cygwin?' => false, 'sortable' => true,  'filterable' => true  },
    'group'    =>  { 'pFmt' =>  '%-9s',  'lab' => 'Group', 'dOrd' =>  3, 'win?' => false, 'cygwin?' => false, 'sortable' => true,  'filterable' => true  },
    'size'     =>  { 'pFmt' =>  '%-10s', 'lab' => 'Size',  'dOrd' =>  4, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => true  },
    'ctime'    =>  { 'pFmt' =>  '%-19s', 'lab' => 'CTime', 'dOrd' =>  5, 'win?' => true,  'cygwin?' => true,  'sortable' => true,  'filterable' => true  },
    'mtime'    =>  { 'pFmt' =>  '%-19s', 'lab' => 'MTime', 'dOrd' =>  6, 'win?' => true,  'cygwin?' => true,  'sortable' => true,  'filterable' => true  },
    '1'        =>  { 'pFmt' =>  '%-1s',  'lab' => '1',     'dOrd' =>  7, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => false },
    '2'        =>  { 'pFmt' =>  '%-1s',  'lab' => '2',     'dOrd' =>  8, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => false },
    '3'        =>  { 'pFmt' =>  '%-1s',  'lab' => '3',     'dOrd' =>  8, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => false },
    '4'        =>  { 'pFmt' =>  '%-1s',  'lab' => '4',     'dOrd' =>  9, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => false },
    'name'     =>  { 'pFmt' =>  '%s',    'lab' => '',      'dOrd' => 10, 'win?' => true,  'cygwin?' => true,  'sortable' => true,  'filterable' => true  },
    'slink'    =>  { 'pFmt' =>  '%s',    'lab' => '',      'dOrd' => 11, 'win?' => true,  'cygwin?' => true,  'sortable' => false, 'filterable' => false }
  }
  @@theColsToPrint  = [ 'mode', 'lnk', 'owner', 'group', 'size', 'ctime', 'mtime', '1', '2', '3', '4', 'name', 'slink' ]

  @@theColsToFilter = Hash.new

  def do_GET(req, resp)
    logAction('FileBrowserService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    theFile = '/'
    if ((req.query) && (req.query.member?('dir'))) then
      theFile = CGI::unescape(req.query['dir'])
    end
    if (theFile.empty?) then
      theFile = '/'
    end

    theFile        = File.expand_path(theFile, theFile)
    curURL         = $bURL + '/fs?dir=' + theFile
    curEscURL      = CGI::escape(curURL)
    theFileOct     = octificateString(theFile, '')
    theFileForURLs = CGI::escape(theFile)

    theFileLStat = theFileStat = nil
    begin
      theFileLStat = File.lstat(theFile)
      theFileStat  = theFileLStat
      if (theFileLStat.symlink?) then
        theFileStat  = File.stat(theFile)
      end
    rescue
      STDOUT.puts("ERROR(mpmfs): Could not stat #{theFile.inspect}")
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File or directory not found', nil, nil, nil, nil)
      return
    end

    if (theFileLStat.nil?) then
      STDOUT.puts("ERROR(mpmfs): Could not stat #{theFile.inspect}")
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File or directory not found', nil, nil, nil, nil)
      return
    end

    if (theFileLStat.directory?) then

      if (RUBY_PLATFORM =~ /mswin/) then
        @@provideFUNCchmod      = false
        @@provideFUNCchown      = false
        @@provideFUNCshell      = false
        @@faSrv                 = 'rfa'
        @@ignoreModeUploadLinks = true
      end

      if (RUBY_PLATFORM =~ /cygwin/) then
        @@provideFUNCchmod     = false
        @@provideFUNCchown     = false
        @@ignoreModeUploadLinks = true
      end

      if (req.query['cols']) then
        @@theColsToPrint = req.query['cols'].to_s.split(',').uniq.delete_if { |x| !(@@colInfo.member?(x)) }
      end
      if (req.query['cdel']) then
        req.query['cdel'].to_s.split(',').map { |x| @@theColsToPrint.delete(x) }
      end
      if (req.query['cadd']) then
        @@theColsToPrint = (@@theColsToPrint + req.query['cadd'].to_s.split(',')).delete_if { |x| !(@@colInfo.member?(x)) }
      end
      if (@@theColsToPrint.empty?) then
        @@theColsToPrint = @@colInfo.keys.sort { |x,y| @@colInfo[x]['dOrd'] <=> @@colInfo[y]['dOrd'] }
      end
      @@theColsToPrint.sort! { |x,y| @@colInfo[x]['dOrd'] <=> @@colInfo[y]['dOrd'] }

      if (req.query['fcol']) then
        if (req.query['fval']) then
          tmp = req.query['fcol'].to_s.downcase
          if (@@colInfo.member?(tmp) && @@colInfo[tmp]['filterable']) then
            begin
              @@theColsToFilter[tmp] = Regexp.new(req.query['fval'])
            rescue
              STDOUT.puts("ERROR(mpmfs): Regexp was invalid: #{req.query['fval'].inspect}")
            end
          end
        else
          @@theColsToFilter.delete(req.query['fcol'].to_s)
        end
      end

      if (RUBY_PLATFORM =~ /mswin/) then
        @@theColsToPrint.delete_if { |x|    !(@@colInfo[x]['win?']) }
      end

      if (RUBY_PLATFORM =~ /cygwin/) then
        @@theColsToPrint.delete_if { |x|    !(@@colInfo[x]['cygwin?']) }
      end

      theColsToPrintNow  = @@theColsToPrint
      theColsToFilterNow = @@theColsToFilter

      if ( !(theFileLStat.writable?) || !(@@provideFUNCactA)) then
        theColsToPrintNow.delete('1')
      end
      if ( !(@@provideFUNCactF)) then
        theColsToPrintNow.delete('4')
      end
      if ( !(@@provideFUNCactI || @@provideFUNCactH)) then
        theColsToPrintNow.delete('2')
      end
      if ( !(@@provideFUNCactD || @@provideFUNCactU)) then
        theColsToPrintNow.delete('3')
      end

      if (req.query['scol']) then
        tmp = req.query['scol'].to_s.downcase
        if (@@colInfo.member?(tmp) && @@colInfo[tmp]['sortable']) then
          @@dfltSCol = tmp
        end
      end
      if (req.query['sdir']) then
        @@dfltSDir = req.query['sdir'].to_s.downcase
      end
      if (req.query['sfld']) then
        if (req.query['sfld'] == 'false') then
          @@foldCaseInSort = false
        else
          @@foldCaseInSort = true
        end
      end

      sortCol    = @@dfltSCol
      sortDir    = @@dfltSDir
      numSortDir =  ( sortDir == 'inc' ? 1 : -1 )

      dirList = ''

      if (@@provideFUNCactF && theColsToPrintNow.member?('4')) then
        dirList += "<form action=\"#{$bURL}/#{@@faSrv}\">"
      end
      
      dirList += (theColsToPrintNow.map { |x|
                    if (@@colInfo[x]['sortable']) then
                      if ((x == sortCol) && (sortDir == 'inc')) then
                        mka('Sort on this Column', "#{curURL}&sdir=dec&scol=#{x}", @@colInfo[x]['lab'], 'out', @@colInfo[x]['pFmt'])
                      else
                        mka('Sort on this Column', "#{curURL}&sdir=inc&scol=#{x}", @@colInfo[x]['lab'], 'out', @@colInfo[x]['pFmt'])
                      end
                    else
                      sprintf(@@colInfo[x]['pFmt'], @@colInfo[x]['lab'])
                    end
                  }).join(' ') + "\n"

      #excludeRE = ( req.query.member?('exclude') ? Regexp.new(req.query['exclude'].to_s) : nil )
      #includeRE = ( req.query.member?('include') ? Regexp.new(req.query['include'].to_s) : nil )
      excludeRE = nil
      includeRE = nil

      theDirData  = Array.new
      theDirDataU = Array.new  # "Unsortable Entries"
      Dir.entries(theFile).each do |d|
        if (excludeRE && excludeRE.match(d)) then
          next
        end
        if ( !(includeRE) || includeRE.match(d)) then
          begin
            dWithPath = (theFile + '/' + d).gsub(/\/+/, '/')
            curFileLStat = File.lstat(dWithPath)
            if (curFileLStat.nil?) then
              STDOUT.puts("ERROR(mpmfs): Could not stat #{d.inspect}")
              next
            end
            curFileStat = curFileLStat
            if (curFileLStat.symlink?) then
              curFileStat = File.stat(dWithPath)
              if (curFileStat.nil?) then
                STDOUT.puts("ERROR(mpmfs): Could not stat #{d.inspect}")
                next
              end
            end
            if ((d == '.') || (d == '..')) then
              theDirDataU.push([ d, dWithPath, curFileLStat, curFileStat ])
            else
              theDirData.push([ d, dWithPath, curFileLStat, curFileStat ])
            end
          rescue
            STDOUT.puts("ERROR(mpmfs): Could not stat #{d}")
          end
        end
      end

      if (sortCol == 'name')   then
        if(@@foldCaseInSort) then
          theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[0].downcase <=> y[0].downcase ) }
        else
          theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[0] <=> y[0] ) }
        end
      elsif (sortCol == 'mtime')  then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[2].mtime <=> y[2].mtime ) }
      elsif (sortCol == 'ctime')  then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[2].ctime <=> y[2].ctime ) }
      elsif (sortCol == 'size')   then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[3].size <=> y[3].size ) }
      elsif (sortCol == 'lnk')   then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[3].nlink <=> y[3].nlink ) }
      elsif (sortCol == 'group')  then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[2].gid <=> y[2].gid ) }
      elsif (sortCol == 'owner')  then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[2].uid <=> y[2].uid ) }
      elsif (sortCol == 'mode')   then
        theDirData = theDirDataU.sort { |x,y| x[0] <=> y[0] } + theDirData.sort { |x,y| numSortDir * ( x[2].mode <=> y[2].mode ) }
      end

      fileID = 0;
       theDirData.each do |d, dWithPath, curFileLStat, curFileStat|
        fileID += 1

        dWithPathForURLs = CGI::escape(dWithPath)
        if (d == '.') then
          dWithPathForURLs = theFileForURLs;
        end

        # Immediate Reasons To Skip
        if ( !(curFileLStat.symlink? || curFileLStat.directory? || curFileLStat.file?) )then
          next
        end
        if( !(@@showSimlinks) && curFileLStat.symlink?) then
          next
        end
        if( !(@@showUnreadable) && !(curFileLStat.readable?)) then
          next
        end

        daLnkData = Hash.new
        daRawData = Hash.new

        # File Name
        printableFileName = CGI::escapeHTML(octificateString(d, '') + ( curFileLStat.directory? ? '/' : '' ))

        if (curFileLStat.readable? && (curFileLStat.file? || (curFileLStat.directory? && curFileLStat.executable?))) then
          if ((d == '.') && @@provideFUNCshell) then
            daLnkData['name'] = mka('click to run shell in this directory', "#{$bURL}/ss?&cwd=#{theFileForURLs}&return=#{curEscURL}", printableFileName, 'zap', '%s')
          else
            daLnkData['name'] = mka('click to view/download', "#{$bURL}/fs?dir=#{dWithPathForURLs}", printableFileName, 'zap', '%s')
          end
        else
          daLnkData['name'] = printableFileName
        end
        daRawData['name'] = printableFileName
        # Symlink Names & Links to same
        if (curFileLStat.symlink?) then
          symlinkTarget   = File.readlink(dWithPath).gsub(/\/+/, '/')
          symlinkTargetFQ = File.expand_path(symlinkTarget, theFile).gsub(/\/+/, '/')
          symlinkTargetFQforURLs = CGI::escape((symlinkTargetFQ + ( curFileStat.directory? ? '/' : '' )).gsub(/\/+/, '/'))
          printableSymlinkTarget = octificateString(symlinkTarget, '')
          if (curFileStat.readable?) then
            daLnkData['slink'] = '-> ' + mka('click to view/download', "#{$bURL}/fs?dir=#{symlinkTargetFQforURLs}", printableSymlinkTarget, 'zap', '%s')
          else
            daLnkData['slink'] = sprintf("-> %s", printableSymlinkTarget)
          end
            daRawData['slink'] = printableSymlinkTarget
        end
        # Mode string
        fTypeChar = curFileLStat.ftype[0,1].sub('f', '-')
        fModStr =
          ( (curFileLStat.mode & 0000400).zero? ? '-' : 'r' ) +
          ( (curFileLStat.mode & 0000200).zero? ? '-' : 'w' ) +
          ( (curFileLStat.mode & 0000100).zero? ? '-' : ( (curFileLStat.mode & 0004000).zero? ? 'x' : 's') ) +
          ( (curFileLStat.mode & 0000040).zero? ? '-' : 'r' ) +
          ( (curFileLStat.mode & 0000020).zero? ? '-' : 'w' ) +
          ( (curFileLStat.mode & 0000010).zero? ? '-' : ( (curFileLStat.mode & 0002000).zero? ? 'x' : 's') ) +
          ( (curFileLStat.mode & 0000004).zero? ? '-' : 'r' ) +
          ( (curFileLStat.mode & 0000002).zero? ? '-' : 'w' ) +
          ( (curFileLStat.mode & 0000001).zero? ? '-' : 'x' )
        printableFileMode = CGI::escapeHTML(fTypeChar + fModStr)
        if (theFileLStat.writable? && @@provideFUNCchmod) then
          daLnkData['mode'] = mka('click to chmod', "#{$bURL}/#{@@faSrv}?cmd=chmod&ask=Y&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", printableFileMode, 'out', '%-10s')
        else
          daLnkData['mode'] = printableFileMode
        end
        daRawData['mode'] = printableFileMode
        # File Size (Carefull! We use curFileStat for this!)

        if (curFileStat.directory? || curFileStat.file?) then
          if (curFileStat.size > 1099511627776/@@cFactor) then
            daLnkData['size'] = sprintf("%7.2fPB", curFileStat.size / 1099511627776.to_f)
          elsif (curFileStat.size > 1073741824/@@cFactor) then
            daLnkData['size'] = sprintf("%7.2fGB", curFileStat.size / 1073741824.to_f)
          elsif (curFileStat.size > 1048576/@@cFactor) then
            daLnkData['size'] = sprintf("%7.2fMB", curFileStat.size / 1048576.to_f)
          elsif (curFileStat.size > 1024/@@cFactor) then
            daLnkData['size'] = sprintf("%7.2fKB", curFileStat.size / 1024.to_f)
          elsif (curFileStat.size >= 0) then
            daLnkData['size'] = sprintf("%4dB   ", curFileStat.size)
          else
            daLnkData['size'] = sprintf("%4d    ", -1) # Sometimes file sizes are negative on Windows XP.... Duno why.
          end
        else
          daLnkData['size'] = '-'
        end
        # File Ownership
        owner = begin Etc.getpwuid(curFileLStat.uid).name rescue curFileLStat.uid.to_s end
        group = begin Etc.getgrgid(curFileLStat.gid).name rescue curFileLStat.gid.to_s end
        printableOwner = CGI::escapeHTML(owner)
        printableGroup = CGI::escapeHTML(group)
        if ((Process.uid == 0) && @@provideFUNCchown) then
          daLnkData['owner'] = mka('click to chown', "#{$bURL}/#{@@faSrv}?cmd=chown&ask=Y&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", printableOwner, 'out', '%-9s')
          daLnkData['group'] = mka('click to chown', "#{$bURL}/#{@@faSrv}?cmd=chown&ask=Y&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", printableGroup, 'out', '%-9s')
        else
          daLnkData['owner'] = sprintf("%-9s", printableOwner)
          daLnkData['group'] = sprintf("%-9s", printableGroup)
        end
        daRawData['owner'] = printableOwner
        daRawData['group'] = printableGroup
        # Times
        printableCTime = CGI::escapeHTML(sprintf("%04d-%02d-%02d_%02d:%02d:%02d",
                                                 curFileLStat.ctime.year, curFileLStat.ctime.month, curFileLStat.ctime.day,
                                                 curFileLStat.ctime.hour, curFileLStat.ctime.min, curFileLStat.ctime.sec))
        daLnkData['ctime'] = printableCTime
        daRawData['ctime'] = printableCTime
        printableMTime = CGI::escapeHTML(sprintf("%04d-%02d-%02d_%02d:%02d:%02d",
                                                 curFileLStat.mtime.year, curFileLStat.mtime.month, curFileLStat.mtime.day,
                                                 curFileLStat.mtime.hour, curFileLStat.mtime.min, curFileLStat.mtime.sec))
        daLnkData['mtime'] = printableMTime
        if (theFileLStat.writable? && @@provideFUNCtouch) then
          daLnkData['mtime'] = mka('click to touch',"#{$bURL}/#{@@faSrv}?cmd=touchT&ask=Y&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", printableMTime, 'out', '%-19s')
        end
        daRawData['mtime'] = printableMTime

        # The 2nd action -- Act (A) on file/directory
        daLnkData['1'] = ''
        if (theFileLStat.writable? && @@provideFUNCactA) then
          daLnkData['1'] = mka('click to act on file', "#{$bURL}/#{@@faSrv}?cmd=ask&ask=Y&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", 'A', 'zap', '%s')
        end

        # The 2nd action -- Hex dump (H) or act-in (I)
        daLnkData['2'] = ''
        if (curFileStat.file? && curFileStat.readable? && @@provideFUNCactH) then  # Careful!  We use curFileStat for a reason here..
          daLnkData['2'] = mka('click to view hexdump', "#{$bURL}/hexdump?max=1024&color=true&cwd=#{theFileForURLs}&return=#{curEscURL}&file=#{dWithPathForURLs}", 'H', 'zap', '%s')
        elsif(curFileStat.directory? && curFileStat.writable? && curFileStat.executable? && @@provideFUNCactI) then
          daLnkData['2'] = mka('click to act in directory', "#{$bURL}/#{@@faSrv}?cmd=ask&ask=Y&cwd=#{dWithPathForURLs}&return=#{curEscURL}", 'I', 'zap', '%s')
        end

        # The 3rd action -- Download-Only (D) or Upload-into (U)
        daLnkData['3'] = ''
        if (curFileStat.file? && curFileStat.readable? && @@provideFUNCactD) then  # Careful!  We use curFileStat for a reason here..
          daLnkData['3'] = mka('click to download file', "#{$bURL}/fs?dmt=d&dir=#{dWithPathForURLs}", 'D', 'zap', '%s')
        elsif(curFileStat.directory? && ((curFileStat.executable? && curFileStat.writable?) || @@ignoreModeUploadLinks)  && @@provideFUNCactU) then
          daLnkData['3'] = mka('click to upload file to this directory', "#{$bURL}/upload?ask=Y&uptarget=#{dWithPathForURLs}&return=#{curEscURL}", 'U', 'zap', '%s')
        end

        # The 4rd action -- Download-Only (D) or Upload-into (U)
        daLnkData['4'] = ''
        if (@@provideFUNCactF && theColsToPrintNow.member?('4')) then  # We put the box on files even when we may not be able to act because of file perms.  TODO: FIX THIS!!!
          daLnkData['4'] = "<input class=\"tinyFormBox\" title=\"click to mark file\" type=\"checkbox\" name=\"multifile#{fileID}\" value=\"#{dWithPathForURLs}\" />"
        end

        # Hard link count
        if (curFileLStat.directory? || curFileLStat.file?) then
          daLnkData['lnk'] = sprintf("%3d", curFileLStat.nlink)
          daRawData['lnk'] = sprintf("%d",  curFileLStat.nlink)
        else
          daLnkData['lnk'] = '-'
          daRawData['lnk'] = '-'
        end

        # Finally, we update the directory list output
        if ((theColsToFilterNow.map { |x,y| y.match(daRawData[x]) }).all?)
          dirList += (theColsToPrintNow.map { |x| sprintf(@@colInfo[x]['pFmt'], daLnkData[x]) }).join(' ') + "\n"
        end

      end

      dirList += (theColsToPrintNow.map { |x|
                    mka('Remove this Column', "#{curURL}&cdel=#{x}", @@colInfo[x]['lab'], 'out', @@colInfo[x]['pFmt'])
                  }).join(' ') + "\n"
      dirList += "\n"

      # Done with the list, close off the form.
      if (@@provideFUNCactF && theColsToPrintNow.member?('4')) then
        dirList += 
          '<INPUT TYPE="hidden" NAME="cmd" VALUE="ask" />'                  +
          '<INPUT TYPE="hidden" NAME="ask" VALUE="Y" />'                    +
          '<INPUT TYPE="hidden" NAME="file" VALUE="/home/richmit/zogg" />'                    +
          '<INPUT TYPE="hidden" NAME="return" VALUE="' + curEscURL + '" />' +
          '<INPUT TYPE="hidden" NAME="dir" VALUE="' + theFile + '" />'      +
          '<INPUT TYPE="submit" NAME="submit" VALUE="Act on marked files">' +
          '</FORM>'
      end

      dirList += sprintf("Change Sort Order (on %s) to: %s\n",
                         @@colInfo[sortCol]['lab'],
                         if (sortDir == 'inc') then
                           mka('Set sort order to decreasing', "#{curURL}&sdir=dec", 'decreasing', 'zap', '%s')
                         else
                           mka('Set sort order to increasing', "#{curURL}&sdir=inc", 'increasing', 'zap', '%s')
                         end
                         )
      if (sortCol == 'name') then
        dirList += sprintf("Sort case sensitivity: %s\n",
                           if (@@foldCaseInSort) then
                             mka('Set sort to case sensitive', "#{curURL}&sfld=false", 'case sensitive', 'zap', '%s')
                           else
                             mka('Set sort to case insensitive', "#{curURL}&sfld=true", 'case insensitive', 'zap', '%s')
                           end
                           )
      end

      if (@@colInfo.keys.length != theColsToPrintNow.length)
        missingCols = @@colInfo.keys
        missingCols.delete_if { |x| theColsToPrintNow.member?(x) }
        dirList += "Display Column: "
        dirList += ((missingCols.sort { |x,y| @@colInfo[x]['dOrd'] <=> @@colInfo[y]['dOrd'] }).map { |x|
                      mka("Add Column #{x}", "#{curURL}&cadd=#{x}", @@colInfo[x]['lab'], 'zap', @@colInfo[x]['pFmt'])
                    }).join(' ') + "\n"
        dirList += sprintf("Columns: %s\n",mka('Reset displayed columns to defaults',
                                               "#{curURL}&cols=" + (@@colInfo.keys.sort { |x,y| @@colInfo[x]['dOrd'] <=> @@colInfo[y]['dOrd'] }).join(','),
                                               'show all', 'zap', '%s'))
      end
      if ( !(theColsToPrintNow.map { |x| x=='name' }).all? ) then
        dirList += sprintf("Remove all columns except 'Name': %s\n", mka('Show just the Name Column', "#{curURL}&cols=name", 'NamesOnly', 'zap', '%s'))
      end

      if (!(theColsToFilterNow.empty?)) then
        dirList += sprintf("Remove filter: %s\n",
                           theColsToFilterNow.keys.map { |x|
                             mka("Remove filter for column #{x}", "#{$bURL}/fs?dir=#{theFileForURLs}&fcol=#{x}", x, 'zap', '%s')
                           }.join(' '))
      end

      dirList +=
        "\n"                                                                            +
        '<FORM METHOD="GET" ACTION="' + $bURL + '/fs">                                ' +
        '  <INPUT TYPE="hidden" NAME="dir" VALUE="' + theFile + '" />                 ' +
        '  <TABLE BORDER="1"><TR><TD>                                                 ' +
        '      <SELECT NAME="fcol">                                                   ' +
        '        <OPTION VALUE="mode"         >mode</OPTION>                          ' +
        '        <OPTION VALUE="owner"        >owner</OPTION>                         ' +
        '        <OPTION VALUE="group"        >group</OPTION>                         ' +
        '        <OPTION VALUE="ctime"        >ctime</OPTION>                         ' +
        '        <OPTION VALUE="mtime"        >mtime</OPTION>                         ' +
        '        <OPTION VALUE="name" SELECTED>name</OPTION>                          ' +
        '      </SELECT>                                                              ' +
        '      </TD><TD>                                                              ' +
        '      <INPUT TYPE="text" NAME="fval" VALUE="" />                             ' +
        '      </TD><TD>                                                              ' +
        '      <INPUT TYPE="submit" NAME="submit" VALUE="Filter">                     ' +
        '  </TD></TR></TABLE>                                                         ' +
        '</FORM>                                                                      '

      if (@@showHelp) then
        dirList += "\nMagic links:\n"
        dirList += "  Delete a column:    Click a column title AT THE BOTTOM of the directory listing        \n"
        dirList += "  Change sort dir:    Click current sort column AT THE TOP of the directory listing      \n"
        dirList += "  Change sort col:    Click a column title AT THE TOP of the directory listing           \n"
        if (@@provideFUNCshell) then
          dirList += "  Run shell in CWD:   Click on the link for the directory named with a single period (.) \n"
        end
        dirList += "  Browse a directory: Click on the directory name                                        \n"
        dirList += "  View file:          Click on the file name                                             \n"
      end

      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "Directory Listing: #{theFile}", nil, nil, dirList, nil)

    elsif (theFileLStat.file?) then

      begin
        open(theFile, "r") do |file|
          resp.body = file.read()
        end
      rescue
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "ERROR: FileBrowserService: Could not read file: #{theFile.inspect}", nil, nil, nil, nil)
      end

      # Now we figure out if it is a download-only or forced-text.  Most browsers will do the "right thing" even when told the
      # thing is "octet-stream" if the browser thinks it knows better.  For example, jpeg images will get viewed by the
      # browser if it thinks it can display the thing even if it has Content-Type of 'application/octet-stream'.  This is
      # probably desirable.  OTOH, this will often keep us from downloading binaries and the like into the browser for it to
      # attempt to display. :)
      fileSize     = resp.body.size
      fileMimeType = ( req.query.member?('dmt') ? req.query['dmt'].to_s : nil )
      if (fileMimeType.nil?) then
        if(fileSize > 10*1048576) then # over 10MB, and it is a 'download only'
          fileMimeType = 'application/x-download'
        else
          if (tmp = /\.([A-Za-z0-9]+)$/.match(theFile)) then
            if (@@ext2mimeType.member?(tmp[1].downcase)) then
              fileMimeType = @@ext2mimeType[tmp[1].downcase]
            end
          end
          if( !(fileMimeType)) then
            if (/^#!.{0,100}(sh|csh|ksh|bash|tcsh|perl|ruby|python)/.match(resp.body)) then
              fileMimeType = 'text/plain'
            end
          end
          # If we don't have a mime type or if it is text, then we make sure we have only text or set the mime type to
          # octet-stream The logic here is tricky -- especially the state of fileMimeType -- careful.
          if( (fileMimeType.nil?) || (fileMimeType.match(/^text\//)) ) then
            numESCandBSP = 0
            numChars = 0
            if (@@doFileContentCheckForMimeType) then
              resp.body.each_byte do |theChar|
                numChars += 1
                if ( !(((theChar >= 32) && (theChar <= 126)) || (theChar == 9) || (theChar == 10) || (theChar == 13))) then
                  if (fileMimeType && ((theChar == 8) || (theChar == 27))) then
                    numESCandBSP += 1
                  else
                    STDOUT.puts("INFO(mpmfs): Found non-ASCII in suspected ASCII file: #{theChar}.")
                    fileMimeType = 'application/x-download'
                    break
                  end
                end
              end
            end
            if ( fileMimeType.nil? ) then
              fileMimeType = 'text/plain'
            end
            # If ESC and BSP make up more than 10% of a text file, it is a download-only
            if( (numESCandBSP * 100) > (numChars * @@okPercentESCnBSPinTextFile) && (/^text\//.match(fileMimeType)) ) then
              STDOUT.puts("INFO(mpmfs): Too many (#{numESCandBSP}:#{numChars})ESC||BSP in suspected ASCII file -- download only!")
              fileMimeType = 'application/x-download'
            end
          end
        end
      else
        if (fileMimeType.downcase == 'd') then
          fileMimeType = 'application/x-download'
        end
      end
      STDOUT.puts("INFO(mpmfs): Mime Type for '#{theFileOct}' is '#{fileMimeType}'")
      resp['Content-Type'] = fileMimeType
      # Now we set the file name for downloads.
      #resp['Content-Disposition']  = "attachment; filename=#{File.basename(theFile)}" # attachment makes doc "download only" with firefox
      resp['Content-Disposition']  = "filename=#{File.basename(theFile)}"
    else
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: FileBrowserService: Unsupported file type', nil, nil, nil, nil)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# The System Info Service
class SysInfoService < WEBrick::HTTPServlet::AbstractServlet

  @@commandsProvided =
    begin
      case
        when RUBY_PLATFORM =~ /darwin/i
         [[ 'hostname',       'hostname'                            ],
          [ 'ComputerName',   '/usr/sbin/scutil --get ComputerName' ],
          [ 'uname',          'uname -a'                            ],
          [ 'uptime',         'uptime'                              ],
          [ 'df',             'df -k'                               ],
          [ 'mount',          'mount'                               ],
          [ 'domainname',     'domainname'                          ],
          [ 'who',            'who'                                 ],
          [ 'last',           'last | head -40'                     ],
          [ 'top',            'top -l 1 -o cpu | head -50'          ]]
        when RUBY_PLATFORM =~ /solaris/i
         [[ 'hostname',       'hostname'                            ],
          [ 'uname',          'uname -a'                            ],
          [ 'uptime',         'uptime'                              ],
          [ 'df',             'df -k'                               ],
          [ 'mount',          'mount'                               ],
          [ 'who',            'who'                                 ],
          [ 'last',           'last | head -40'                     ],
          [ 'prstat (proc)',  'prstat -s cpu -n 50 1 1'             ],
          [ 'prstat (user)',  'prstat -s cpu -n 50 -t 1 1'          ],
          [ 'top',            'top | head -50'                      ]]
        when RUBY_PLATFORM =~ /linux/i
         [[ 'hostname',       'hostname'                            ],
          [ 'uname',          'uname -a'                            ],
          [ 'cpuinfo',        'cat /proc/cpuinfo'                   ],
          [ 'redhat-release', 'cat /etc/redhat-release'             ],
          [ 'uptime',         'uptime'                              ],
          [ 'df',             'df -k'                               ],
          [ 'mount',          'mount'                               ],
          [ 'who',            'who'                                 ],
          [ 'last',           'last | head -40'                     ],
          [ 'top',            'top -b | head -50'                   ]]
        else
         [[ 'hostname',       'hostname'                            ],
          [ 'uname',          'uname -a'                            ],
          [ 'uptime',         'uptime'                              ],
          [ 'df',             'df -k'                               ],
          [ 'mount',          'mount'                               ],
          [ 'who',            'who'                                 ],
          [ 'last',           'last | head -40'                     ],
          [ 'top',            'top | head -50'                      ]]
      end
    end

  def SysInfoService.commandsProvided
    return @@commandsProvided
  end

  def do_GET(req, resp)
    logAction('SysInfoService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')

    if ( !(req.query)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: SYSTEM INFO (UNKNOWN REASON)', nil, nil, nil, nil)
      return
    end
    
    goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

    refreshValue = 0
    if ( req.query.member?('refresh') && !(req.query['refresh'].empty?) && (/^[0-9]+$/.match(req.query['refresh'])) ) then
      refreshValue = req.query['refresh'].to_i
    end


    askHTML =
        '<FORM METHOD="get" ACTION="' + $bURL + '/sysInfo?' + '"><table BORDER="1"><tr><td><table BORDER="0" CELLSPACING="0" CELLPADDING="2">         '
    if ( (req.query.member?('return')) && (req.query['return'])) then                                                                                 
      askHTML +=                                                                                                                                      
        '  <INPUT TYPE="hidden" NAME="return" VALUE="' + CGI::escapeHTML(req.query['return']) + '" />                                                 '
    end                                                                                                                                               
    askHTML +=                                                                                                                                        
      '<tr><td align="right">command:</td><td align="left"><SELECT NAME="cmd" VALUE="" />                                                             '
    if ( !(req.query.member?('cmd')) || req.query['cmd'].empty? ) then                                                                                
      askHTML +=                                                                                                                                      
        '      <OPTION SELECTED>all</OPTION>                                                                                                          '
    else                                                                                                                                              
      '      <OPTION>all</OPTION>                                                                                                                     '
    end                                                                                                                                               
    @@commandsProvided.each do |commandURL, command|                                                                                                  
      if (req.query['cmd'].to_s == commandURL) then                                                                                                   
        askHTML +=                                                                                                                                    
          '      <OPTION SELECTED>' + commandURL + '</OPTION>                                                                                         '
      else                                                                                                                                            
        askHTML +=                                                                                                                                    
          '      <OPTION>' + commandURL + '</OPTION>                                                                                                  '
      end                                                                                                                                             
    end                                                                                                                                               
    askHTML +=                                                                                                                                        
      '        </SELECT>                                                                                                                              ' +
      '      </td></tr>                                                                                                                               ' +
      '<tr><td align="right">refresh:</td><td align="left"><INPUT TYPE="text" SIZE="5" NAME="refresh" VALUE="' + refreshValue.to_s + '" /></td></tr>  ' +
      '<tr><td colspan="2" align="right"><INPUT TYPE="submit"></td></tr>                                                                              ' +
      '</table></td></tr></table></FORM>                                                                                                              '

    metaInfo = ''
    if (refreshValue>0) then
      metaInfo = '<meta http-equiv="refresh" content="' + refreshValue.to_s + '">'
    end
    if ( !(req.query.member?('cmd')) || (req.query['cmd']=='all') ) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, metaInfo, "SYSTEM INFO", nil, askHTML, nil, goLink)
    elsif (@@commandsProvided.assoc(req.query['cmd'])) then
      commandURL = req.query['cmd']
      command    = (@@commandsProvided.assoc(commandURL))[1]
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, metaInfo, "SYSTEM INFO: #{commandURL}", "SYSTEM INFO: #{command}",
                                                           nil, CGI::escapeHTML(octificateString(`#{command}`, ' \t\n')),
                                                           askHTML + '<br>' + goLink)
    else
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "ERROR: SYSTEM INFO: UNKNOWN COMMAND", nil, nil, nil, goLink)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Provide the file upload service
class FileUploadService < WEBrick::HTTPServlet::AbstractServlet
  def do_POST(req, resp)
    logAction('FileUploadService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    if ( !(req.query)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (UNKNOWN REASON)', nil, nil, nil, nil)
      return
    end

    goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

    ['uploadfile', 'uptarget'].each do |parmName|
      if ( !(req.query.member?(parmName))) then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "ERROR: File Upload Failure (Missing Parameter: #{parmName})", nil, nil, nil, goLink)
        return
      end
    end
    if (req.query['uploadfile'].empty?) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (Empty or Missing File)', nil, nil, nil, goLink)
      return
    end
    if ( !(File.directory?(req.query['uptarget']))) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (Invalid Target Directory)', nil, nil, nil, goLink)
      return
    end
    if ( !(File.writable?(req.query['uptarget']))) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (Target Directory Not Writable)', nil, nil, nil, goLink)
      return
    end

    # Figure out the file name for the file we are getting uploaded to us.
    uploadFileName = nil
    uploadFileNameWonky = nil
    if ((req.query['uploadfile'].filename.class != String) || req.query['uploadfile'].filename.empty?) then
      nowTime   = Time.now
      uploadFileName = sprintf("mpmfsUpload_%04d%02d%02d%02d%02d%02d", nowTime.year, nowTime.month, nowTime.day,
                               nowTime.hour, nowTime.min, nowTime.sec, nowTime.zone)
    else
      uploadFileName = req.query['uploadfile'].filename
    end
    uploadTarget              = (req.query['uptarget'] + '/').gsub(/\/+/, '/')
    uploadFullPathAndFileName = uploadTarget + uploadFileName

    if (File.exists?(uploadFullPathAndFileName)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (Would clobber existing file!)', nil, nil, nil, goLink)
      return
    end

    begin
      open(uploadFullPathAndFileName, "w") do |file|
        file.write(req.query['uploadfile'])
      end
      if (File.exists?(uploadFullPathAndFileName)) then
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil,
                                                             "SUCCESS: File Upload Worked(#{uploadFileName})!" +
                                                             '<BR>' +
                                                             'MD5: ' + Digest::MD5.hexdigest(req.query['uploadfile']) +
                                                             '<BR>' +
                                                             'SHA1: ' + Digest::SHA1.hexdigest(req.query['uploadfile']),
                                                             nil,
                                                             mka('Browse the directory we just uploaded into',
                                                                 "#{$bURL}/fs?dir=#{CGI::escapeHTML(uploadTarget)}",
                                                                 "Browse: #{CGI::escapeHTML(uploadTarget)}", 'zap', '%s'),
                                                             nil, goLink)
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (Post File Write)', nil, nil, nil, goLink)
      end
    rescue
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (File Write)', nil, nil, nil, goLink)
    end
  end

  def do_GET(req, resp)
    logAction('FileUploadService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    if(req.query) then
      goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )
      uploadHTML =
        '<FORM METHOD="post" ENCTYPE="multipart/form-data" ACTION="' + $bURL + '/upload">                                       ' +
        '  <TABLE BORDER="1"><TR><TD>                                                                                           ' +
        '    Upload <INPUT TYPE="file" NAME="uploadfile" VALUE="File To Upload" />                                              ' +
        '    to the directory <INPUT TYPE="text" NAME="uptarget" VALUE="' + (req.query['uptarget'] || 'directory_name') + '" /> ' +
        '    <INPUT TYPE="submit" NAME="submit" VALUE="Upload File">                                                            ' +
        '  </TD></TR></TABLE>                                                                                                   ' +
        '</FORM>                                                                                                                '
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "File Upload", nil, uploadHTML, nil, goLink)
    else
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: File Upload Failure (UNKNOWN REASON)', nil, nil, nil, nil)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Ruby File Action Service
class RubyFileActionService < WEBrick::HTTPServlet::AbstractServlet

  @@cmdInfo = {
    'rmRF'       => { 'sCmd' => 'FileUtils.rm_rf', 'rParm' => [ 'file'                 ], 'win?' => true,  'sO' =>  1, 'des' => 'Remove File'              },
    'rmR'        => { 'sCmd' => 'FileUtils.rm_r',  'rParm' => [ 'file'                 ], 'win?' => true,  'sO' =>  2, 'des' => 'Remove File'              },
    'rm'         => { 'sCmd' => 'FileUtils.rm',    'rParm' => [ 'file'                 ], 'win?' => true,  'sO' =>  3, 'des' => 'Remove File'              },
    'rmdir'      => { 'sCmd' => 'FileUtils.rmdir', 'rParm' => [ 'file'                 ], 'win?' => true,  'sO' =>  4, 'des' => 'Remove Directory'         },
    'chmod'      => { 'sCmd' => 'mjrChmod',        'rParm' => [ 'arg1', 'file'         ], 'win?' => false, 'sO' =>  5, 'des' => 'Change Modes'             },
    'chown'      => { 'sCmd' => 'FileUtils.chown', 'rParm' => [ 'arg1', 'arg2', 'file' ], 'win?' => false, 'sO' =>  6, 'des' => 'Change Owner and Group'   },
    'mv'         => { 'sCmd' => 'FileUtils.mv',    'rParm' => [ 'file', 'arg1'         ], 'win?' => true,  'sO' =>  7, 'des' => 'Copy File'                },
    'cp'         => { 'sCmd' => 'FileUtils.cp',    'rParm' => [ 'file', 'arg1'         ], 'win?' => true,  'sO' =>  8, 'des' => 'Move File'                },
    'mkdir'      => { 'sCmd' => 'FileUtils.mkdir', 'rParm' => [ 'file'                 ], 'win?' => true,  'sO' =>  9, 'des' => 'Make Directory'           },
    'touch'      => { 'sCmd' => 'FileUtils.touch', 'rParm' => [ 'file'                 ], 'win?' => true,  'sO' => 10, 'des' => 'Touch File'               },
    'touchT'     => { 'sCmd' => 'mjrTouchT',       'rParm' => [ 'arg1', 'file'         ], 'win?' => true,  'sO' => 11, 'des' => 'Touch With Explicit Time' },
    'mjrDigest'  => { 'sCmd' => 'mjrDigest',       'rParm' => [ 'file'                 ], 'win?' => true,  'sO' => 12, 'des' => 'File Digest (MD5/SHA1)'   }
  }

  def RubyFileActionService.cmdInfo
    return @@cmdInfo
  end

  def do_GET(req, resp)
    logAction('RubyFileActionService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')

    if ( !(req.query)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: RubyFileActionService Failure (UNKNOWN REASON)', nil, nil, nil, nil)
      return
    end

    goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

    metaInfo = ''
    if ( !(goLink.empty?)) then
      if (req.query['bt']) then
        metaInfo = '<meta http-equiv="refresh" content="' + req.query['bt'] +';url=' + CGI::escapeHTML(req.query['return']) + '">'
      end
    end

    askCommand = false
    if ( !(req.query.member?('cmd'))) then
      if (req.query['ask'] == 'Y') then
        askCommand = true
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: RubyFileActionService Failure (Missing Parameter: cmd)', nil, nil, nil, goLink)
        return
      end
    end

    if ( !(@@cmdInfo.keys.member?(req.query['cmd']))) then
      if (req.query['ask'] == 'Y') then
        askCommand = true
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: RubyFileActionService Failure (UNKNOWN COMMAND)', nil, nil, nil, goLink)
        return
      end
    end

    if(askCommand) then
      askHTML = '<H2>Select A Ruby File Action</H2>' + "\n\n"
      (@@cmdInfo.keys.sort {|a,b| @@cmdInfo[a]['sO'] <=> @@cmdInfo[b]['sO'] }).each do |cmd|
        if ( (RUBY_PLATFORM =~ /mswin/) && !(@@cmdInfo[cmd]['win?']) ) then
          next
        end
        askHTML += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
        daLink = "#{$bURL}/rfa?cmd=#{cmd}"
        req.query.each do |pkey, pval|
          if (pkey != 'cmd') then
            daLink += "&#{pkey}=" + CGI::escape(pval)
          end
        end
        daLinkLab = @@cmdInfo[cmd]['des'] + sprintf("&nbsp;&nbsp;(<kbd>%s", @@cmdInfo[cmd]['sCmd']).strip + '</kbd>)'
        askHTML += mka(daLinkLab, daLink, daLinkLab, 'zap', '%s') + "<br>\n"
      end
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "RubyFileActionService", nil, askHTML, nil, goLink)
      return
    end

    if ( (req.query['ask'] == 'Y') || (@@cmdInfo[req.query['cmd']]['rParm'].map { |parm| !(req.query.member?(parm)) }).any? ) then
      askHTML =
        '<FORM METHOD="get" ACTION="' + $bURL + '/rfa' + '">                                                                 ' + "\n" +
        '  <TABLE BORDER="1" CELLSPACING="0" CELLPADDING="1"><TR><TD>                                                       ' + "\n" +
        '    <INPUT TYPE="hidden" NAME="cmd" VALUE="' + req.query['cmd'] + '" />                                            ' + "\n"
      if (req.query.member?('return')) then
        askHTML +=
          '    <INPUT TYPE="hidden" NAME="return" VALUE="' + CGI::escapeHTML(req.query['return']) + '" />                     ' + "\n"
      end
      askHTML +=
        '    <INPUT TYPE="hidden" NAME="ask" VALUE="N" />                                                                   ' + "\n" +
        @@cmdInfo[req.query['cmd']]['sCmd'] + ' '
      @@cmdInfo[req.query['cmd']]['rParm'].each do |parm|
        askHTML +=
          '    <INPUT TYPE="text" SIZE="30" NAME="' + parm + '" VALUE="' + CGI::escapeHTML(req.query[parm] || '') + '" />     ' + "\n"
      end
      askHTML +=
        '    <INPUT TYPE="submit" NAME="added" VALUE="submit">                                                              ' + "\n"
      if (req.query.member?('cwd')) then
        askHTML +=
          '  </TD></TR>                                                                                                       ' + "\n"  +
          '  <TR><TD align="right">                                                                                           ' + "\n"  +
          'cwd: <INPUT TYPE="text" NAME="cwd" SIZE="30" MAXLENGTH="200" VALUE="' + CGI::escapeHTML(req.query['cwd']) + '" />  ' + "\n"
      end
      askHTML +=
        '  </TD></TR></TABLE>                                                                                               ' + "\n"  +
        '</FORM>                                                                                                            ' + "\n"

      askTITLE = 'Ruby File Action: ' + @@cmdInfo[req.query['cmd']]['des'] +
        sprintf(" (<kbd>%s", @@cmdInfo[req.query['cmd']]['sCmd']).strip + ')'

      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, askTITLE, nil, askHTML, nil, goLink)
    else
      # Build command
      commandArgs = @@cmdInfo[req.query['cmd']]['rParm'].map { |x| req.query[x].to_s}

      retVal=nil
      begin
        eval("FileUtils.cd(req.query['cwd'] || '/') do retVal=#{@@cmdInfo[req.query['cmd']]['sCmd']}(*commandArgs); end")
      rescue
        retVal = 'FAILURE'
      end
      prdyCmd = "#{@@cmdInfo[req.query['cmd']]['sCmd']}(#{commandArgs.join(', ')})"
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, metaInfo,
                                                           "RubyFileActionService: Result", nil, nil,
                                                           "CWD: #{(req.query['cwd'] || '/').inspect}\nCMD: #{CGI::escapeHTML(prdyCmd.inspect)}\nResult: #{CGI::escapeHTML(retVal.inspect)}", goLink)
    end
  end

end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# File Action Service
class UnixFileActionService < WEBrick::HTTPServlet::AbstractServlet
  @@cmdInfo = {
    'rmRF'       => { 'sCmd' => 'rm',         'rParm' => [ 'file'         ], 'fArg' => '-rf',  'sO' =>  1, 'des' => 'Remove File'              },
    'rmR'        => { 'sCmd' => 'rm',         'rParm' => [ 'file'         ], 'fArg' => '-r',   'sO' =>  2, 'des' => 'Remove File'              },
    'rm'         => { 'sCmd' => 'rm',         'rParm' => [ 'file'         ], 'fArg' => '',     'sO' =>  3, 'des' => 'Remove File'              },
    'rmdir'      => { 'sCmd' => 'rmdir',      'rParm' => [ 'file'         ], 'fArg' => '',     'sO' =>  4, 'des' => 'Remove Directory'         },
    'chmod'      => { 'sCmd' => 'chmod',      'rParm' => [ 'arg1', 'file' ], 'fArg' => '',     'sO' =>  5, 'des' => 'Change Modes'             },
    'chown'      => { 'sCmd' => 'chown',      'rParm' => [ 'arg1', 'file' ], 'fArg' => '',     'sO' =>  6, 'des' => 'Change Owner'             },
    'mv'         => { 'sCmd' => 'mv',         'rParm' => [ 'file', 'arg1' ], 'fArg' => '',     'sO' =>  7, 'des' => 'Move File'                },
    'cp'         => { 'sCmd' => 'cp',         'rParm' => [ 'file', 'arg1' ], 'fArg' => '',     'sO' =>  7, 'des' => 'Copy File'                },
    'zip'        => { 'sCmd' => 'zip',        'rParm' => [ 'arg1', 'file' ], 'fArg' => '-r',   'sO' =>  8, 'des' => 'Zip File/Directory'       },
    'unzip'      => { 'sCmd' => 'unzip',      'rParm' => [ 'file'         ], 'fArg' => '',     'sO' =>  9, 'des' => 'UnZip File/Directory'     },
    'tar'        => { 'sCmd' => 'tar',        'rParm' => [ 'arg1', 'file' ], 'fArg' => 'cvf',  'sO' => 11, 'des' => 'Tar Directory'            },
    'untar'      => { 'sCmd' => 'tar',        'rParm' => [ 'file'         ], 'fArg' => 'xvf',  'sO' => 12, 'des' => 'UnTar File'               },
    'gzip'       => { 'sCmd' => 'gzip',       'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 13, 'des' => 'Gzip File'                },
    'gunzip'     => { 'sCmd' => 'gunzip',     'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 14, 'des' => 'Gunzip File'              },
    'bzip2'      => { 'sCmd' => 'bzip2',      'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 15, 'des' => 'Bzip2 File'               },
    'bunzip2'    => { 'sCmd' => 'bunzip2',    'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 16, 'des' => 'Bunzip2 File'             },
    'compress'   => { 'sCmd' => 'compress',   'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 17, 'des' => 'Compress File'            },
    'uncompress' => { 'sCmd' => 'uncompress', 'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 18, 'des' => 'Uncompress File'          },
    'mkdir'      => { 'sCmd' => 'mkdir',      'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 19, 'des' => 'Make Directory'           },
    'touchT'     => { 'sCmd' => 'touch',      'rParm' => [ 'arg1', 'file' ], 'fArg' => '-t',   'sO' => 21, 'des' => 'Touch with explicit time' },
    'touchR'     => { 'sCmd' => 'touch',      'rParm' => [ 'arg1', 'file' ], 'fArg' => '-r',   'sO' => 22, 'des' => 'Touch with refrence file' },
    'touch'      => { 'sCmd' => 'touch',      'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 23, 'des' => 'Touch File'               },
    'ps2pdf'     => { 'sCmd' => 'ps2pdf',     'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 24, 'des' => 'Convert to PDF'           },
    'enscript'   => { 'sCmd' => 'enscript',   'rParm' => [ 'arg1', 'file' ], 'fArg' => '-o',   'sO' => 25, 'des' => 'Convert to PS'            },
    'ls'         => { 'sCmd' => 'ls',         'rParm' => [ 'file'         ], 'fArg' => '-l',   'sO' => 26, 'des' => 'List a File/Directory'    },
    'file'       => { 'sCmd' => 'file',       'rParm' => [ 'file'         ], 'fArg' => '',     'sO' => 27, 'des' => 'Guess file type'          },
    'md5'        => { 'sCmd' => 'openssl',    'rParm' => [ 'file'         ], 'fArg' => 'md5',  'sO' => 28, 'des' => 'Compute file MD5'         },
    'sha1'       => { 'sCmd' => 'openssl',    'rParm' => [ 'file'         ], 'fArg' => 'sha1', 'sO' => 29, 'des' => 'Compute file SHA1'        }
  }

  def UnixFileActionService.cmdInfo
    return @@cmdInfo
  end

  def do_GET(req, resp)
    logAction('UnixFileActionService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')

    if ( !(req.query)) then
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: UnixFileActionService Failure (UNKNOWN REASON)', nil, nil, nil, nil)
      return
    end

    goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

    metaInfo = ''
    if ( !(goLink.empty?)) then
      if (req.query['bt']) then
        metaInfo = '<meta http-equiv="refresh" content="' + req.query['bt'] +';url=' + CGI::escapeHTML(req.query['return']) + '">'
      end
    end

    askCommand = false
    if ( !(req.query.member?('cmd'))) then
      if (req.query['ask'] == 'Y') then
        askCommand = true
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: UnixFileActionService Failure (Missing Parameter: cmd)', nil, nil, nil, goLink)
        return
      end
    end

    if ( !(@@cmdInfo.keys.member?(req.query['cmd']))) then
      if (req.query['ask'] == 'Y') then
        askCommand = true
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: UnixFileActionService Failure (UNKNOWN COMMAND)', nil, nil, nil, goLink)
        return
      end
    end

    if(askCommand) then
      askHTML = '<H2>Select A File Action</H2>' + "\n\n"
      (@@cmdInfo.keys.sort {|a,b| @@cmdInfo[a]['sO'] <=> @@cmdInfo[b]['sO'] }).each do |cmd|
        askHTML += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
        daLink = "#{$bURL}/ufa?cmd=#{cmd}"
        req.query.each do |pkey, pval|
          if (pkey != 'cmd') then
            daLink += "&#{pkey}=" + CGI::escape(pval)
          end
        end
        daLinkLab = @@cmdInfo[cmd]['des'] + sprintf("&nbsp;&nbsp;(<kbd>%s", "#{@@cmdInfo[cmd]['sCmd']} #{@@cmdInfo[cmd]['fArg']}").strip + '</kbd>)'
        askHTML += mka(daLinkLab, daLink, daLinkLab, 'zap', '%s') + "<br>\n"
      end
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, "File Action Service", nil, askHTML, nil, goLink)
      return
    end

    if ( (req.query['ask'] == 'Y') || (@@cmdInfo[req.query['cmd']]['rParm'].map { |parm| !(req.query.member?(parm)) }).any? ) then
      askHTML =
          '<FORM METHOD="get" ACTION="' + $bURL + '/ufa' + '">                                                                 ' + "\n" +
          '  <TABLE BORDER="1" CELLSPACING="0" CELLPADDING="1"><TR><TD>                                                       ' + "\n" +
          '    <INPUT TYPE="hidden" NAME="cmd" VALUE="' + req.query['cmd'] + '" />                                            ' + "\n"
      if (req.query.member?('return')) then
        askHTML +=
          '    <INPUT TYPE="hidden" NAME="return" VALUE="' + CGI::escapeHTML(req.query['return']) + '" />                     ' + "\n"
      end
      askHTML +=
          '    <INPUT TYPE="hidden" NAME="ask" VALUE="N" />                                                                   ' + "\n" +
          @@cmdInfo[req.query['cmd']]['sCmd'] + ' ' + @@cmdInfo[req.query['cmd']]['fArg']
      @@cmdInfo[req.query['cmd']]['rParm'].each do |parm|
        askHTML +=
          '    <INPUT TYPE="text" SIZE="30" NAME="' + parm + '" VALUE="' + CGI::escapeHTML(req.query[parm] || '') + '" />     ' + "\n"
      end
      askHTML +=
          '    <INPUT TYPE="submit" NAME="added" VALUE="submit">                                                              ' + "\n"
      if (req.query.member?('cwd')) then
        askHTML +=
          '  </TD></TR>                                                                                                       ' + "\n"  +
          '  <TR><TD align="right">                                                                                           ' + "\n"  +
          'cwd: <INPUT TYPE="text" NAME="cwd" SIZE="30" MAXLENGTH="200" VALUE="' + CGI::escapeHTML(req.query['cwd']) + '" />  ' + "\n"
      end
      askHTML +=
          '  </TD></TR></TABLE>                                                                                               ' + "\n"  +
          '</FORM>                                                                                                            ' + "\n"

      askTITLE = 'File Action: ' + @@cmdInfo[req.query['cmd']]['des'] +
                 sprintf(" (<kbd>%s", "#{@@cmdInfo[req.query['cmd']]['sCmd']} #{@@cmdInfo[req.query['cmd']]['fArg']}").strip + ')'

      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, askTITLE, nil, askHTML, nil, goLink)
    else
      # Build command
      commandArgs = [ @@cmdInfo[req.query['cmd']]['sCmd'] ]
      if ( !(@@cmdInfo[req.query['cmd']]['fArg'].empty?)) then
        commandArgs.push(@@cmdInfo[req.query['cmd']]['fArg'])
      end
      addArgs = @@cmdInfo[req.query['cmd']]['rParm'].map { |x| req.query[x]}
      if (addArgs.length > 0) then
        commandArgs += addArgs
      end
      # Fork and fire it off
      sysRet = 0
      sysOut = ''
      spReadme, spWriteme = IO.pipe
      aPID = Kernel.fork do
        $stdout = spWriteme
        $stderr = spWriteme
        spReadme.close
        puts("<b>META DATA</b>")
        puts("Command Array: " + CGI::escapeHTML(octificateString(commandArgs.inspect, ' \t')))
        if (req.query['cwd']) then
          puts("CWD Input: " + CGI::escapeHTML(octificateString(req.query['cwd'], ' \t')) + "\n\n")
        else
          puts("CWD Input: nil\n\n")
        end
        puts("<b>RUBY MSG</b>")
        if (req.query['cwd']) then
          if (Dir.chdir(req.query['cwd']) != 0) then
            exit(1)
          end
        end
        commandArgs = commandArgs.map { |x| x.to_s }
        si,so,se = popen3(*commandArgs)
        puts("\n\n")
        puts("<b>STDOUT:</b>\n" + CGI::escapeHTML(octificateString(so.read, ' \t\n\r')) + "\n\n")
        puts("<b>STDERR:</b>\n" + CGI::escapeHTML(octificateString(se.read, ' \t\n\r')) + "\n\n")
        exit($?.exitstatus)
      end
      # Wait for it to finish
      if aPID
        Process.wait(aPID)
        sysRet = $?.exitstatus
        spWriteme.close
        sysOut = spReadme.read
      else
        sysRet = -1
      end
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, metaInfo,
                                                           "UnixFileActionService: #{CGI::escapeHTML(octificateString(commandArgs[0], ' \t'))}",
                                                           nil, nil, sysOut, goLink)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
class ShellService < WEBrick::HTTPServlet::AbstractServlet
  @@cmdHistory = Array.new
  @@cmdHash    = Hash.new
  def do_GET(req, resp)
    logAction('ShellService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    if (req.query) then
      goLink = ( req.query['return'] ? mka('click to jump back to file browser', CGI::escapeHTML(req.query['return']), 'Return', 'zap', '%s') : '' )

      theCmd = (req.query['cmd'] || '')
      historyMode = (req.query.member?('history') && (req.query['history'] != '-- SHELL HISTORY --'))
      if (historyMode) then
        theCmd = req.query['history']
      end
      askHTML =
        '<FORM NAME="daform" METHOD="get" ACTION="' + $bURL + '/ss' + '">                                                          ' + "\n" +
        '  <TABLE BORDER="0"><TR><TD><b>CWD</b></td><td><b>CMD</b></td></tr>                                         ' + "\n" +
        '  <tr>                                                                                                      ' + "\n" +
        '    <td><INPUT TYPE="text" NAME="cwd" VALUE="' + CGI::escapeHTML(req.query['cwd'] || '/') + '" /><br></td>  ' + "\n" +
        '    <td><INPUT TYPE="text" NAME="cmd" VALUE="' + CGI::escapeHTML(theCmd)  +                 '" /><br></td>  ' + "\n" +
        '    <td><INPUT TYPE="submit" NAME="submit" VALUE="submit"></td>                                             ' + "\n" +
        '  </tr>                                                                                                     ' + "\n" +
        '  <tr><td colspan="2" align="right">                                                                        ' + "\n" +
        '    Raw Output: <INPUT TYPE="checkbox" NAME="raw" VALUE="Y">                                                ' + "\n" +
        '  </td></tr>                                                                                                ' + "\n"
      if ((theCmd.length>0) && (theCmd != '-- SHELL HISTORY --') && !(@@cmdHash.member?(theCmd))) then
        @@cmdHistory.push(theCmd)
        @@cmdHash[theCmd] = 1
      end
      if (@@cmdHistory.length > 0) then
        theOnchangeScript =
          "if(document.daform.history.selectedIndex != 0) {" +
          " cmd.value=history.options[history.selectedIndex].text;" +
          " document.getElementById('historywarning').innerHTML = 'Note: Some special characters may not be preserved when recalled from CMD history';" +
          " document.daform.history.selectedIndex = 0;" +
          "}"
        askHTML +=
          '  <tr><td align="right">CMD History:</td>                                                                     ' + "\n" +
          '    <td colspan="2" align="left">                                                                             ' + "\n" +
          '      <SELECT NAME="history" ONCHANGE="' + CGI::escapeHTML(theOnchangeScript) +            '">                ' + "\n" +
          '      <OPTION SELECTED>-- SHELL HISTORY --</OPTION>                                                           ' + "\n"
        @@cmdHistory.reverse_each do |hcmd|
          askHTML +=
            '      <OPTION>' + CGI::escapeHTML(hcmd) + '</OPTION>                                                        ' + "\n"
        end
        askHTML +=
          '        </SELECT>                                                                                             ' + "\n" +
          '      </td>                                                                                                   ' + "\n" +
          '    </tr>                                                                                                     ' + "\n"
      end
      askHTML +=
        '</table></FORM>                                                                                                 '
      if (@@cmdHistory.length > 0) then
        askHTML +=
          '<br><div id="historywarning">                                                                                 ' + "\n" +
          '  ' + CGI::escapeHTML('Without javascript, select from the CMD history and submit to populate CMD') + '       ' + "\n" +
          '</div><br>                                                                                                    ' + "\n"
      end
      if ( !(historyMode) && req.query.member?('cmd') && req.query.member?('cwd')) then
        theCommand = "cd \"#{( req.query['cwd'] ? req.query['cwd'] : '/' )}\" ; #{theCmd}"
        theOut     = `#{theCommand} 2>&1`
        askHTML += "<BR><H1>Result: #{CGI::escapeHTML(octificateString(theCommand, ' \t'))}</H1>\n"
        if (req.query && (req.query['raw'] == 'Y')) then
          resp.body = theOut # We do NOT CGI::escapeHTML here as we are sending raw text!!!
          resp['Content-Type'] = 'text/plain'
        else
          (resp.body, resp['Content-Type']) = genSimpleMsgHTML("onload=\"document.getElementById('historywarning').innerHTML = '&nbsp';\"",
                                                               nil, "Shell Service", nil, askHTML, CGI::escapeHTML(theOut), goLink)
        end
      else
        (resp.body, resp['Content-Type']) = genSimpleMsgHTML("onload=\"document.getElementById('historywarning').innerHTML = '&nbsp';\"",
                                                             nil, "Shell Service", nil, askHTML, nil, goLink)
      end
    else
      (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'ERROR: Shell Service (UNKNOWN REASON)', nil, nil, nil, nil)
    end
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# The root service (/) -- home page
class RootService < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    logAction('RootService', req.methods.member?('query') && req.query)
    MySimpleAuth.doAuth(req, resp, 'mpmfs')
    curUnameRE = Regexp.new(":#{Etc.getlogin}:")
    bodyHead =
          '    <HR>                                                                                                    ' + "\n"  +
          '    <H2>File Server Bookmarks</H2>                                                                          ' + "\n"

    quickFileSysLinks = [[ 'ROOT',                   '/',                 'all'               ],
                         [ '/home/richmit',          '/home/richmit',     ':richmit:'         ],
                         [ '/home/jrichli',          '/home/jrichli',     ':richmit:jrichli:' ],
                         [ '/Users/Shared',          '/Users/Shared',     'all'               ],
                         [ '/data/richmit',          '/data/richmit',     ':richmit:'         ],
                         [ '/data/lsf',              '/data/lsf',         ':richmit:'         ],
                         [ '/data/devlsf',           '/data/devlsf',      ':richmit:'         ],
                         [ '/data/cmetrics',         '/data/cmetrics',    ':richmit:'         ],
                         [ '/data/cmetrics2',        '/data/cmetrics2',   ':richmit:'         ],
                         [ '/data/smetrics',         '/data/smetrics',    ':richmit:'         ],
                         [ '/Volumes',               '/Volumes',          'all'               ],
                         [ '/Volumes/remote2',       '/Volumes/remote2',  ':richmit:'         ],
                         [ '/Volumes/remote7',       '/Volumes/remote7',  ':richmit:'         ],
                         [ '/Volumes/LAMB01',        '/Volumes/LAMB01',   ':richmit:'         ],
                         [ '/Volumes/richmit',       '/Volumes/richmit',  ':richmit:'         ],
                         [ '/etc',                   '/etc',              'all'               ],
                         [ '/tmp',                   '/tmp',              'all'               ],
                         [ '/var/logs',              '/var/logs',         'all'               ],
                         [ '/var/log/messages',      '/var/log/messages', 'all'               ],
                         [ '/var/logs',              '/var/logs',         'all'               ]]
    if (RUBY_PLATFORM =~ /mswin/) then
      quickFileSysLinks = Array.new
      'A'.upto('Z') do |dLet|
        quickFileSysLinks.push([ "Drive #{dLet}:",        "#{dLet}:\\",                   'all' ])
      end
    elsif (RUBY_PLATFORM =~ /cygwin/) then
      'A'.upto('Z') do |dLet|
        quickFileSysLinks.push([ "Drive #{dLet}:",        "/cygdrive/#{dLet.downcase}/",  'all' ])
      end
    end
    if (ENV.member?('HOME') && File.exists?(ENV['HOME']) && File.directory?(ENV['HOME'])) then
      quickFileSysLinks.push([   "Home Directory(env)",   ENV['HOME'],                   'all' ])
    end
    begin
      homeDir = Etc.getpwnam(Etc.getlogin).dir.to_s
      quickFileSysLinks.push([   "Home Directory(pwdb)",  homeDir,                       'all' ])
    rescue
      # Do nothing
    end

    unkQuickDirs = Hash.new
    quickFileSysLinks.each do |name, dir, meta|
      if ( !(unkQuickDirs.member?(dir))) then
        unkQuickDirs[dir] = true
        if (FileTest.directory?(dir)) then
          if ((meta == 'all') || curUnameRE.match(meta)) then
            bodyHead +=
              '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('Browse directory', "#{$bURL}/fs?dir=#{CGI::escape(dir)}", name, 'zap', '%s') + '</a><BR>' + "\n"
          end
        end
      end
    end
    if ( !(RUBY_PLATFORM =~ /mswin/)) then
      bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
        '    <HR>                                                                                                    ' + "\n"  +
        '    <H2>UNIX File Actions</H2>                                                                               ' + "\n"
      (UnixFileActionService.cmdInfo.keys.sort {|a,b| UnixFileActionService.cmdInfo[a]['sO'] <=> UnixFileActionService.cmdInfo[b]['sO'] }).each do |cmd|
        bodyHead += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
        daLink = "#{$bURL}/ufa?cmd=#{cmd}"
        daLinkLab = UnixFileActionService.cmdInfo[cmd]['des']
        daLinkLab += sprintf("&nbsp;&nbsp;(<kbd>%s", "#{UnixFileActionService.cmdInfo[cmd]['sCmd']} #{UnixFileActionService.cmdInfo[cmd]['fArg']}").strip + '</kbd>)'
        bodyHead += mka(daLinkLab, daLink, daLinkLab, 'zap', '%s') + "<br>\n"
      end
    end
    bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
          '    <HR>                                                                                                    ' + "\n"  +
          '    <H2>Ruby File Actions</H2>                                                                               ' + "\n"
    (RubyFileActionService.cmdInfo.keys.sort {|a,b| RubyFileActionService.cmdInfo[a]['sO'] <=> RubyFileActionService.cmdInfo[b]['sO'] }).each do |cmd|
      bodyHead += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
      daLink = "#{$bURL}/rfa?cmd=#{cmd}"
      daLinkLab = RubyFileActionService.cmdInfo[cmd]['des']
      daLinkLab += sprintf("&nbsp;&nbsp;(<kbd>%s", "#{RubyFileActionService.cmdInfo[cmd]['sCmd']} #{RubyFileActionService.cmdInfo[cmd]['fArg']}").strip + '</kbd>)'
      bodyHead += mka(daLinkLab, daLink, daLinkLab, 'zap', '%s') + "<br>\n"
    end

    bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
          '    <HR>                                                                                                    ' + "\n"  +
          '    <H2>File Uploader</H2>                                                                               ' + "\n"
    [[ 'Upload File',                   'upload'             ]
    ].each do |name, url|
      bodyHead +=
        '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka(name, "#{$bURL}/#{url}", name, 'zap', '%s') + '</a><BR>' + "\n"
    end

    if ( !(RUBY_PLATFORM =~ /mswin/)) then
      bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
        '    <HR>                                                                                                        ' + "\n"  +
        '    <H2>Shell Services</H2>                                                                                     ' + "\n" +
        '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('Shell Access', "#{$bURL}/ss", 'Interactive Shell', 'zap', '%s') + '<BR>' + "\n"
    end
    if ( !(RUBY_PLATFORM =~ /mswin/)) then
      bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
        '    <HR>                                                                                                            ' + "\n"  +
        '    <H2>System Info Commands Available</H2>                                                                         ' + "\n"  +
        '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('Everything', "#{$bURL}/sysInfo?cmd=all", 'EVERYTHING', 'zap', '%s') + '<BR>' + "\n"
      SysInfoService.commandsProvided.each do |cmdURL, cmd|
        bodyHead +=
          '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka(cmdURL, "#{$bURL}/sysInfo?cmd=#{cmdURL}", cmdURL, 'zap', '%s') + "\n"
        if (cmdURL =~ /(top|prstat)/) then
          bodyHead +=
            '  &nbsp;' + mka('Auto refresh top', "#{$bURL}/sysInfo?refresh=5&cmd=#{cmdURL}", '(auto refresh)', 'zap', '%s') + '<BR>' + "\n"
        else
          bodyHead +=
            '<BR>                                                                                                        ' + "\n"
        end
      end
    end
    bodyHead += # ------------------------------------------------------------------------------------------------------------------------------
          '    <HR>                                                                                                                                ' + "\n" +
          '    <H2>MPMFS Instance Information </H2>                                                                                                ' + "\n" +
          '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('MPMFS Access Logs', "#{$bURL}/log?log=a", 'Access Logs', 'zap', '%s') + '<BR>                  ' + "\n" +
          '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('MPMFS Custom Logs', "#{$bURL}/log?log=c", 'Custom Logs', 'zap', '%s') + '<BR>                  ' + "\n" +
          '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('MPMFS Info', "#{$bURL}/info", 'Info', 'zap', '%s') + '<BR>                                     ' + "\n" +
          '  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + mka('Shutdown MPMFS Server', "#{$bURL}/srvctrl?action=stop&ask=Y", 'Shutdown', 'zap', '%s') + '<BR> ' + "\n"
    (resp.body, resp['Content-Type']) = genSimpleMsgHTML(nil, nil, 'Mitch Richling\'s Poor Man\'s File Server', nil, bodyHead, nil, nil)
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# This service provides a favicon
class FaviconService < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    #MySimpleAuth.doAuth(req, resp, 'mpmfs')
    resp['Content-Type'] = 'image/vnd.microsoft.icon' # or use: 'image/x-icon'
    resp.body =
      "\0\0\001\0\001\0\020\020\0\0\0\0\0\0h\005\0\0\026\0\0\0(\0\0\0\020\0\0\0 \0\0\0\001\0" +
      "\b\0\0\0\0\0@\001\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\0\0\0\377" +
      ("\0"*1014) +
      "\002\0\0\0\0\0\0\0\0\002\002\002\002\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\002\0\0\002" +
      "\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\002\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\002\002\002\002" +
      "\002\0\0\0\0\002\002\002\002\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0" +
      "\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\0\0\0\002\0\0\0\0\002\0\0\002\002\002\002\002\002" +
      "\0\0\0\002\002\002\002\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\002\0\0\0\002\0\002\0\0\0" +
      "\0\002\0\0\0\002\002\0\0\0\002\0\002\0\0\0\0\002\0\0\0\002\002\0\0\0\002\0\002\002"   +
      "\002\0\0\002\0\0\0\002\002\0\002\0\002\0\002\0\0\002\0\002\0\002\0\002\002\002\0\002" +
      "\002\0\002\0\0\002\0\002\002\0\002\002\002\0\0\0\002\0\002\002\002\0\0\002\0\0\0\002" +
      "\277\303\0\0\277\275\0\0\277\375\0\0\277\375\0\0\203\303\0\0\277\277\0\0\277\277\0\0" +
      "\277\275\0\0\201\303\0\0\377\377\0\0u\356\0\0u\356\0\0tn\0\0U\252\0\0%\244\0\0tn\0\0"
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Create the server object, configure it, and fire it up.

STDOUT.puts("INFO(mpmfs): Requested File Server Information:\n")
STDOUT.puts("INFO(mpmfs):   Bind Address: #{$bindAdd}\n")
STDOUT.puts("INFO(mpmfs):   Bind Port:    #{$bindPort}\n")
STDOUT.puts("INFO(mpmfs):   Auth:         #{( doAuth ? 'Requested' : 'Not Requested' )}\n")
STDOUT.puts("INFO(mpmfs):   Mode:         #{( browseOnlyMode ? 'Browse Only' : 'Full Read/Write' )}\n")

STDOUT.puts("INFO(mpmfs)):  Starting up web server now...")
aServer = nil
if (doSSL) then
  aServer = HTTPServer.new(:Port => $bindPort,
                           :BindAddress => $bindAdd,
                           :SSLEnable       => true,
                           :SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
                           :SSLCertName => [ ["C","US"], ["O","127.0.0.1"], ["CN", "127.0.0.1"] ]
                           )
else
  aServer = HTTPServer.new(:Port => $bindPort, :BindAddress => $bindAdd,
                           :Logger => Log.new($stdout, Log::DEBUG),
                           :AccessLog => [ [ $aLog, AccessLog::COMBINED_LOG_FORMAT ] ]
                           )
end

if (!(osUsrAndPass.empty?)) then
  osUsrAndPass.each do |u, p|
    if (p =~ /^([a-z0-9]{40}|[A-Z0-9]{40})$/) then
      MySimpleAuth.addOrUpdateAccount(u, p, false)
    else
      MySimpleAuth.addOrUpdateAccount(u, p, true)
      STDERR.puts("WARNING(mpmws): Password didn't look like a SHA1 hash.  Hashed it: '#{u}:HIDDEN'")
    end
  end
end
MySimpleAuth.setAuthOn(doAuth)

if (browseOnlyMode) then
  FileBrowserService.roMode()
  aServer.mount("/",           FileBrowserService)
else
  aServer.mount("/",           RootService)
  aServer.mount("/upload",     FileUploadService)
  aServer.mount("/stest",      ServiceTesterService)
  aServer.mount("/rfa",        RubyFileActionService)
  if ( !(RUBY_PLATFORM =~ /mswin/)) then
    aServer.mount("/sysInfo",    SysInfoService)
    aServer.mount("/ufa",        UnixFileActionService)
    aServer.mount("/ss",         ShellService)
  end
end

aServer.mount("/fs",           FileBrowserService)
aServer.mount("/hexdump",      HexdumpFileService)
aServer.mount("/log",          LogReporterService)
aServer.mount("/info",         ServerInfoService)
aServer.mount("/favicon.ico",  FaviconService)
aServer.mount("/srvctrl",      ServiceControl)

# Trap server shutdown signals
if (RUBY_PLATFORM =~ /mswin/) then # cygwin is
  ['INT' , 'TERM'].each do |sig|
    trap(sig) { aServer.shutdown }
  end
else
  ['HUP', 'QUIT', 'INT', 'TERM', 'USR1', 'USR2'].each do |sig|
    trap(sig) { aServer.shutdown }
  end
end

# Start the server up
aServer.start