/*
 * httpsvc.cxx
 *
 * Classes for service applications using HTTP as the user interface.
 *
 * Portable Windows Library
 *
 * Copyright (c) 1993-2002 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Contributor(s): ______________________________________.
 *
 * $Log: httpsvc.cxx,v $
 * Revision 1.96  2005/11/30 12:47:41  csoutheren
 * Removed tabs, reformatted some code, and changed tags for Doxygen
 *
 * Revision 1.95  2004/04/03 08:22:20  csoutheren
 * Remove pseudo-RTTI and replaced with real RTTI
 *
 * Revision 1.94  2004/04/03 06:54:24  rjongbloed
 * Many and various changes to support new Visual C++ 2003
 *
 * Revision 1.93  2004/03/23 04:41:05  csoutheren
 * Fixed compile problem on Linux
 *
 * Revision 1.92  2004/03/23 03:40:57  csoutheren
 * Change service process default to be more useful in some environments
 *
 * Revision 1.91  2004/01/17 17:44:54  csoutheren
 * Changed to use PString::MakeEmpty
 *
 * Revision 1.90  2003/09/17 09:02:13  csoutheren
 * Removed memory leak detection code
 *
 * Revision 1.89  2003/02/19 07:23:45  robertj
 * Changes to allow for single threaded HTTP service processes.
 *
 * Revision 1.88  2002/11/06 22:47:25  robertj
 * Fixed header comment (copyright etc)
 *
 * Revision 1.87  2002/10/10 04:43:44  robertj
 * VxWorks port, thanks Martijn Roest
 *
 * Revision 1.86  2002/08/14 00:43:40  robertj
 * Added ability to have fixed maximum length PStringStream's so does not do
 *   unwanted malloc()'s while outputing data.
 *
 * Revision 1.85  2002/08/13 05:39:17  robertj
 * Fixed GNU compatibility
 *
 * Revision 1.84  2002/08/13 01:57:15  robertj
 * Fixed "last dump object" position in Memory Dump macro.
 *
 * Revision 1.83  2002/08/13 01:30:27  robertj
 * Added UpTime macro for time service has been running.
 * Added IfQuery macro blcok to add chunks of HTML depending on the value
 *   of query parameters in the URL.
 * Added memory statistics dump and memory object dump macros to help in
 *   leak finding.
 *
 * Revision 1.82  2002/07/30 08:37:34  robertj
 * Removed peer host as bad DNS makes it useless due to huge timeout.
 *
 * Revision 1.81  2002/07/30 04:51:26  craigs
 * Added MonitorInfo macro
 *
 * Revision 1.80  2002/07/30 03:16:57  craigs
 * Added StartTime macro
 *
 * Revision 1.79  2002/07/17 09:18:00  robertj
 * made detection of gif file more intelligent for debug version.
 *
 * Revision 1.78  2002/07/17 08:03:45  robertj
 * Allowed for adjustable copyright holder.
 * Allowed for not having gif file for product name in default header.
 *
 * Revision 1.77  2001/10/10 08:06:49  robertj
 * Fixed problem with not shutting down threads when closing listener.
 *
 * Revision 1.76  2001/09/11 02:37:41  robertj
 * Fixed thread name for HTTP service connection handler.
 *
 * Revision 1.75  2001/08/28 06:44:45  craigs
 * Added ability to override PHTTPServer creation
 *
 * Revision 1.74  2001/06/30 06:59:06  yurik
 * Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
 *
 * Revision 1.73  2001/06/27 04:14:48  robertj
 * Added logging for listener thread open/close.
 *
 * Revision 1.72  2001/06/23 00:32:15  robertj
 * Added parameter to be able to set REUSEADDR on listener socket.
 *
 * Revision 1.71  2001/05/07 23:27:06  robertj
 * Added SO_LINGER setting to HTTP sockets to help with clearing up sockets
 *   when the application exits, which prevents new run of app as "port in use".
 *
 * Revision 1.70  2001/03/26 04:55:26  robertj
 * Made sure OnConfigChanged() is called from OnStart() function.
 *
 * Revision 1.69  2001/03/21 06:29:31  robertj
 * Fixed bug in calling OnConfigChanged after service macros are loaded,
 *   should be before so state can be changed before the macros translated.
 *
 * Revision 1.68  2001/03/19 02:41:53  robertj
 * Made sure HTTP listener thread is shut down in OnStop().
 *
 * Revision 1.67  2001/03/16 03:33:21  robertj
 * Fixed HTML signature code due to changes in encryption code.
 *
 * Revision 1.66  2001/03/04 02:24:44  robertj
 * Removed default OnControl() from http service as cannot get port number.
 *
 * Revision 1.65  2001/02/21 04:33:46  robertj
 * Fixed GNU warning.
 *
 * Revision 1.64  2001/02/20 02:32:41  robertj
 * Added PServiceMacro version that can do substitutions on blocks of HTML.
 *
 * Revision 1.63  2001/02/15 01:12:15  robertj
 * Moved some often repeated HTTP service code into PHTTPServiceProcess.
 *
 * Revision 1.62  2001/02/14 06:52:26  robertj
 * Fixed GNU compatibility with last change to PServiceMacro.
 *
 * Revision 1.61  2001/02/14 02:30:59  robertj
 * Moved HTTP Service Macro facility to public API so can be used by apps.
 * Added ability to specify the service macro keyword, defaults to "macro".
 * Added machine macro to get the OS version and hardware.
 *
 * Revision 1.60  2001/01/15 06:17:56  robertj
 * Set HTTP resource members to private to assure are not modified by
 *   dscendents in non-threadsafe manner.
 *
 * Revision 1.59  2001/01/08 22:53:34  craigs
 * Changed OnPOST to allow subtle usage of embedded commands
 *
 * Revision 1.58  2000/12/14 08:09:41  robertj
 * Fixed missing immediate expiry date on string and file service HTTP resourcer.
 *
 * Revision 1.57  2000/12/11 13:15:17  robertj
 * Added macro to include signed or unsigned chunks of HTML.
 * Added flag to globally ignore HTML signatures (useful for develeopment).
 *
 * Revision 1.56  2000/10/23 09:17:26  robertj
 * Fixed bug un Linux version where HTML macros didn't work correctly.
 *
 * Revision 1.55  2000/08/04 12:48:25  robertj
 * Added mechanism by which a service can get at new HTTP connections, eg to add SSL.
 *
 * Revision 1.54  2000/05/02 02:58:49  robertj
 * Fixed MSVC warning about unused parameters.
 *
 * Revision 1.53  2000/05/02 02:01:18  craigs
 * Changed stricmp and added implementation of PServiceMacro::Translate
 *
 * Revision 1.52  2000/05/02 01:50:37  robertj
 * Rewrite of PServiceMacro so does not use malloc (indirectly).
 *
 * Revision 1.51  2000/01/27 00:35:52  robertj
 * Fixed benign warning about uninitialised variables in MSVC optimised compile.
 *
 * Revision 1.50  1999/08/07 06:50:52  robertj
 * Removed silly (and incorrect) warning.
 *
 * Revision 1.49  1999/04/24 05:16:26  robertj
 * Fixed incorrect date in copyright notice.
 *
 * Revision 1.48  1998/11/30 05:37:46  robertj
 * New directory structure
 *
 * Revision 1.47  1998/11/24 23:05:14  robertj
 * Fixed extra *** in demo message
 *
 * Revision 1.46  1998/11/16 07:23:15  robertj
 * More PPC GNU compatibility.
 *
 * Revision 1.45  1998/11/16 06:50:40  robertj
 * Fixed PPC GNU compiler compatibility.
 *
 * Revision 1.44  1998/10/31 12:49:25  robertj
 * Added read/write mutex to the HTTP space variable to avoid thread crashes.
 *
 * Revision 1.43  1998/10/29 11:58:52  robertj
 * Added ability to configure the HTTP threads stack size.
 *
 * Revision 1.42  1998/10/29 11:31:57  robertj
 * Fixed default URL to have lower case and spaceless product name.
 * Increased HTTP stack size.
 *
 * Revision 1.41  1998/10/15 01:53:35  robertj
 * GNU compatibility.
 *
 * Revision 1.40  1998/10/13 14:06:24  robertj
 * Complete rewrite of memory leak detection code.
 *
 * Revision 1.39  1998/09/23 06:22:15  robertj
 * Added open source copyright license.
 *
 * Revision 1.38  1998/09/18 01:47:23  robertj
 * Fixed bug that made files with signature on first line fail on unix systems.
 *
 * Revision 1.37  1998/08/20 06:01:02  robertj
 * Improved internationalisation, registrationpage override.
 *
 * Revision 1.36  1998/04/21 02:43:40  robertj
 * Fixed conditional around wrong way for requiring signature on HTML files.
 *
 * Revision 1.35  1998/04/01 01:55:41  robertj
 * Fixed bug for automatically including GIF file in HTTP name space.
 *
 * Revision 1.34  1998/03/23 03:21:40  robertj
 * Fixed missing invalid case in register page.
 *
 * Revision 1.33  1998/03/20 03:18:15  robertj
 * Added special classes for specific sepahores, PMutex and PSyncPoint.
 *
 * Revision 1.32  1998/03/17 10:14:39  robertj
 * Rewrite of registration page to allow for HTML file override.
 *
 * Revision 1.31  1998/03/09 07:17:48  robertj
 * Added IP peer/local number macros.
 * Set GetPageGraphic reference to GIF file to be at lop level directory.
 *
 * Revision 1.30  1998/02/16 00:14:09  robertj
 * Added ProductName and BuildDate macros.
 * Major rewrite of application info passed in PHTTPServiceProcess constructor.
 *
 * Revision 1.29  1998/02/03 06:22:45  robertj
 * Allowed PHTTPServiceString to be overridden by html file after ';'.
 *
 * Revision 1.28  1998/01/26 02:49:19  robertj
 * GNU support.
 *
 * Revision 1.27  1998/01/26 02:12:14  robertj
 * GNU warnings.
 *
 * Revision 1.26  1998/01/26 00:45:44  robertj
 * Added option flags to ProcessMacros to automatically load from file etc.
 * Assured that all service HTTP resources are overidable with file, using ; URL field.
 * Added a number of extra #equival macros.
 * Added "Pty. Ltd." to company name.
 *
 * Revision 1.25  1997/11/10 12:40:05  robertj
 * Changed SustituteEquivalSequence so can override standard macros.
 *
 * Revision 1.24  1997/11/04 06:02:46  robertj
 * Allowed help gif file name to overridable in PServiceHTML, so can be in subdirectory.
 *
 * Revision 1.23  1997/10/30 10:21:26  robertj
 * Added ability to customise regisration text by application.
 *
 * Revision 1.22  1997/08/28 14:19:40  robertj
 * Fixed bug where HTTP directory was not processed for macros.
 *
 * Revision 1.20  1997/08/20 08:59:58  craigs
 * Changed macro handling to commonise #equival sequence
 *
 * Revision 1.19  1997/07/26 11:38:22  robertj
 * Support for overridable pages in HTTP service applications.
 *
 * Revision 1.18  1997/07/08 13:11:44  robertj
 * Added standard header and copyright macros to service HTML.
 *
 * Revision 1.17  1997/06/16 13:20:15  robertj
 * Fixed bug where PHTTPThread crashes on exit.
 *
 * Revision 1.16  1997/05/16 12:07:21  robertj
 * Added operating system and version to hidden fields on registration form.
 *
 * Revision 1.15  1997/03/02 03:40:59  robertj
 * Added error logging to standard HTTP Service HTTP Server.
 *
 * Revision 1.14  1997/02/05 11:54:54  robertj
 * Added support for order form page overridiing.
 *
 * Revision 1.13  1997/01/28 11:45:19  robertj
 * .
 *
 * Revision 1.13  1997/01/27 10:22:37  robertj
 * Numerous changes to support OEM versions of products.
 *
 * Revision 1.12  1997/01/03 06:33:23  robertj
 * Removed slash from operating system version string, so says Windows NT rather than Windows/NT
 *
 * Revision 1.11  1996/11/16 10:50:26  robertj
 * ??
 *
 * Revision 1.10  1996/11/04 03:58:23  robertj
 * Changed to accept separate copyright and manufacturer strings.
 *
 * Revision 1.8  1996/10/08 13:08:29  robertj
 * Changed standard graphic to use PHTML class.
 *
 * Revision 1.7  1996/09/14 13:09:33  robertj
 * Major upgrade:
 *   rearranged sockets to help support IPX.
 *   added indirect channel class and moved all protocols to descend from it,
 *   separating the protocol from the low level byte transport.
 *
 * Revision 1.6  1996/08/25 09:39:00  robertj
 * Prevented registration if no user etc entered.
 *
 * Revision 1.5  1996/08/19 13:39:55  robertj
 * Fixed race condition in system restart logic.
 *
 * Revision 1.4  1996/08/08 13:36:39  robertj
 * Fixed Registation page so no longer has static link, ie can be DLLed.
 *
 * Revision 1.3  1996/07/15 10:36:48  robertj
 * Added registration info to bottom of order form so can be faxed to us.
 *
 * Revision 1.2  1996/06/28 13:21:30  robertj
 * Fixed nesting problem in tables.
 * Fixed PConfig page always restarting.
 *
 * Revision 1.1  1996/06/13 13:33:34  robertj
 * Initial revision
 *
 */

#ifdef __GNUC__
#pragma implementation "httpsvc.h"
#endif

#include <ptlib.h>
#include <ptclib/httpsvc.h>
#include <ptlib/sockets.h>


PSORTED_LIST(PServiceMacros_base, PServiceMacro);

class PServiceMacros_list : public PServiceMacros_base
{
  public:
    PServiceMacros_list();
};


#define new PNEW


#define HOME_PAGE   "http://www.equival.com"
#define EMAIL       "equival@equival.com.au"
#define EQUIVALENCE "Equivalence Pty. Ltd."


static const PTime ImmediateExpiryTime(0, 0, 0, 1, 1, 1980);


///////////////////////////////////////////////////////////////////////////////

PHTTPServiceProcess::PHTTPServiceProcess(const Info & inf)
  : PServiceProcess(inf.manufacturerName, inf.productName,
                    inf.majorVersion, inf.minorVersion, inf.buildStatus, inf.buildNumber),
    macroKeyword("macro"),
    productKey(inf.productKey),
    securedKeys(inf.securedKeyCount, inf.securedKeys),
    signatureKey(inf.signatureKey),
    compilationDate(inf.compilationDate),
    manufacturersHomePage(inf.manufHomePage != NULL ? inf.manufHomePage : HOME_PAGE),
    manufacturersEmail(inf.email != NULL ? inf.email : EMAIL),
    productNameHTML(inf.productHTML != NULL ? inf.productHTML : inf.productName),
    gifHTML(inf.gifHTML),
    copyrightHolder(inf.copyrightHolder != NULL ? inf.copyrightHolder : inf.manufacturerName),
    copyrightHomePage(inf.copyrightHomePage != NULL ? inf.copyrightHomePage : (const char *)manufacturersHomePage),
    copyrightEmail(inf.copyrightEmail != NULL ? inf.copyrightEmail : (const char *)manufacturersEmail)
{
  ignoreSignatures = FALSE;

  if (inf.gifFilename != NULL) {
    PDirectory exeDir = GetFile().GetDirectory();
#if defined(_WIN32) && defined(_DEBUG)
    // Special check to aid in using DevStudio for debugging.
    if (exeDir.Find("\\Debug\\") != P_MAX_INDEX)
      exeDir = exeDir.GetParent();
#endif
    httpNameSpace.AddResource(new PServiceHTTPFile(inf.gifFilename, exeDir+inf.gifFilename));
    if (gifHTML.IsEmpty()) {
      gifHTML = psprintf("<img src=\"/%s\" alt=\"%s!\"", inf.gifFilename, inf.productName);
      if (inf.gifWidth != 0 && inf.gifHeight != 0)
        gifHTML += psprintf(" width=%i height=%i", inf.gifWidth, inf.gifHeight);
      gifHTML += " align=absmiddle>";
    }
  }

  restartThread = NULL;
  httpListeningSocket = NULL;
  httpThreads.DisallowDeleteObjects();
}


PHTTPServiceProcess::~PHTTPServiceProcess()
{
  ShutdownListener();
}


PHTTPServiceProcess & PHTTPServiceProcess::Current() 
{
  PHTTPServiceProcess & process = (PHTTPServiceProcess &)PProcess::Current();
  PAssert(PIsDescendant(&process, PHTTPServiceProcess), "Not a HTTP service!");
  return process;
}


BOOL PHTTPServiceProcess::OnStart()
{
  if (!Initialise("Started"))
    return FALSE;

  OnConfigChanged();
  return TRUE;
}


void PHTTPServiceProcess::OnStop()
{
  ShutdownListener();
  PSYSTEMLOG(Warning, GetName() << " stopped.");
  PServiceProcess::OnStop();
}


BOOL PHTTPServiceProcess::OnPause()
{
  OnConfigChanged();
  return TRUE;
}


void PHTTPServiceProcess::OnContinue()
{
  if (Initialise("Restarted"))
    return;

  OnStop();
  Terminate();
}


#ifdef _WIN32
const char * PHTTPServiceProcess::GetServiceDependencies() const
{
  return "EventLog\0Tcpip\0";
}
#endif


BOOL PHTTPServiceProcess::ListenForHTTP(WORD port,
                                        PSocket::Reusability reuse,
                                        PINDEX stackSize)
{
  if (httpListeningSocket != NULL &&
      httpListeningSocket->GetPort() == port &&
      httpListeningSocket->IsOpen())
    return TRUE;

  return ListenForHTTP(new PTCPSocket(port), reuse, stackSize);
}


BOOL PHTTPServiceProcess::ListenForHTTP(PSocket * listener,
                                        PSocket::Reusability reuse,
                                        PINDEX stackSize)
{
  if (httpListeningSocket != NULL)
    ShutdownListener();

  httpListeningSocket = PAssertNULL(listener);
  if (!httpListeningSocket->Listen(5, 0, reuse)) {
    PSYSTEMLOG(Debug, "HTTPSVC\tListen on port " << httpListeningSocket->GetPort()
                   << " failed: " << httpListeningSocket->GetErrorText());
    return FALSE;
  }

  if (stackSize > 1000)
    new PHTTPServiceThread(stackSize, *this);

  return TRUE;
}


void PHTTPServiceProcess::ShutdownListener()
{
  if (httpListeningSocket == NULL)
    return;

  if (!httpListeningSocket->IsOpen())
    return;

  PSYSTEMLOG(Debug, "HTTPSVC\tClosing listener socket on port "
                 << httpListeningSocket->GetPort());

  httpListeningSocket->Close();

  httpThreadsMutex.Wait();
  for (PINDEX i = 0; i < httpThreads.GetSize(); i++)
    httpThreads[i].Close();

  while (httpThreads.GetSize() > 0) {
    httpThreadsMutex.Signal();
    Sleep(1);
    httpThreadsMutex.Wait();
  }

  httpThreadsMutex.Signal();

  delete httpListeningSocket;
  httpListeningSocket = NULL;
}


PString PHTTPServiceProcess::GetCopyrightText()
{
  PHTML html(PHTML::InBody);
  html << "Copyright &copy;"
       << compilationDate.AsString("yyyy") << " by "
       << PHTML::HotLink(copyrightHomePage)
       << copyrightHolder
       << PHTML::HotLink()
       << ", "
       << PHTML::HotLink("mailto:" + copyrightEmail)
       << copyrightEmail
       << PHTML::HotLink();
  return html;
}


PString PHTTPServiceProcess::GetPageGraphic()
{
  PFile header;
  if (header.Open("header.html", PFile::ReadOnly))
    return header.ReadString(header.GetLength());

  PHTML html(PHTML::InBody);
  html << PHTML::TableStart()
       << PHTML::TableRow()
       << PHTML::TableData();

  if (gifHTML.IsEmpty())
    html << PHTML::Heading(1) << productNameHTML << "&nbsp;" << PHTML::Heading(1);
  else
    html << gifHTML;

  html << PHTML::TableData()
       << GetOSClass() << ' ' << GetOSName()
       << " Version " << GetVersion(TRUE) << PHTML::BreakLine()
       << ' ' << GetCompilationDate().AsString("d MMMM yyyy")
       << PHTML::BreakLine()
       << "By "
       << PHTML::HotLink(manufacturersHomePage) << GetManufacturer() << PHTML::HotLink()
       << ", "
       << PHTML::HotLink("mailto:" + manufacturersEmail) << manufacturersEmail << PHTML::HotLink()
       << PHTML::TableEnd()
       << PHTML::HRule();

  return html;
}


void PHTTPServiceProcess::GetPageHeader(PHTML & html)
{
  GetPageHeader(html, GetName());
}


void PHTTPServiceProcess::GetPageHeader(PHTML & html, const PString & title)
{
  html << PHTML::Title(title) 
       << PHTML::Body()
       << GetPageGraphic();
}


PTCPSocket * PHTTPServiceProcess::AcceptHTTP()
{
  if (httpListeningSocket == NULL)
    return NULL;

  if (!httpListeningSocket->IsOpen())
    return NULL;

  // get a socket when a client connects
  PTCPSocket * socket = new PTCPSocket;
  if (socket->Accept(*httpListeningSocket))
    return socket;

  if (socket->GetErrorCode() != PChannel::Interrupted)
    PSYSTEMLOG(Error, "Accept failed for HTTP: " << socket->GetErrorText());

  if (httpListeningSocket != NULL && httpListeningSocket->IsOpen())
    return socket;

  delete socket;
  return NULL;
}


BOOL PHTTPServiceProcess::ProcessHTTP(PTCPSocket & socket)
{
  if (!socket.IsOpen())
    return TRUE;

  PHTTPServer * server = CreateHTTPServer(socket);
  if (server == NULL) {
    PSYSTEMLOG(Error, "HTTP server creation/open failed.");
    return TRUE;
  }

  // process requests
  while (server->ProcessCommand())
    ;

  // always close after the response has been sent
  delete server;

  // if a restart was requested, then do it, but only if we are not shutting down
  if (httpListeningSocket->IsOpen())
    CompleteRestartSystem();

  return TRUE;
}


void PHTTPServiceProcess::BeginRestartSystem()
{
  if (restartThread == NULL) {
    restartThread = PThread::Current();
    OnConfigChanged();
  }
}


void PHTTPServiceProcess::CompleteRestartSystem()
{
  if (restartThread == NULL)
    return;
  
  if (restartThread != PThread::Current())
    return;

  httpNameSpace.StartWrite();

  if (Initialise("Restart\tInitialisation"))
    restartThread = NULL;

  httpNameSpace.EndWrite();

  if (restartThread != NULL)
    Terminate();
}


void PHTTPServiceProcess::AddRegisteredText(PHTML &)
{
}


void PHTTPServiceProcess::AddUnregisteredText(PHTML &)
{
}


BOOL PHTTPServiceProcess::SubstituteEquivalSequence(PHTTPRequest &, const PString &, PString &)
{
  return FALSE;
}


PHTTPServer * PHTTPServiceProcess::CreateHTTPServer(PTCPSocket & socket)
{
#ifdef SO_LINGER
  const linger ling = { 1, 5 };
  socket.SetOption(SO_LINGER, &ling, sizeof(ling));
#endif

  PHTTPServer * server = OnCreateHTTPServer(httpNameSpace);

  if (server->Open(socket))
    return server;

  delete server;
  return NULL;
}


PHTTPServer * PHTTPServiceProcess::OnCreateHTTPServer(const PHTTPSpace & httpNameSpace)
{
  return new PHTTPServer(httpNameSpace);
}


//////////////////////////////////////////////////////////////

PHTTPServiceThread::PHTTPServiceThread(PINDEX stackSize,
                                       PHTTPServiceProcess & app)
  : PThread(stackSize, AutoDeleteThread, NormalPriority, "HTTP Service:%x"),
    process(app)
{
  process.httpThreadsMutex.Wait();
  process.httpThreads.Append(this);
  process.httpThreadsMutex.Signal();

  myStackSize = stackSize;
  socket = NULL;
  Resume();
}


PHTTPServiceThread::~PHTTPServiceThread()
{
  process.httpThreadsMutex.Wait();
  process.httpThreads.Remove(this);
  process.httpThreadsMutex.Signal();
  delete socket;
}


void PHTTPServiceThread::Close()
{
  if (socket != NULL)
    socket->Close();
}


void PHTTPServiceThread::Main()
{
  PTCPSocket * socket = process.AcceptHTTP();
  if (socket != NULL) {
    new PHTTPServiceThread(myStackSize, process);
    process.ProcessHTTP(*socket);
  }
}


//////////////////////////////////////////////////////////////

PConfigPage::PConfigPage(PHTTPServiceProcess & app,
                         const PString & title,
                         const PString & section,
                         const PHTTPAuthority & auth)
  : PHTTPConfig(title, section, auth),
    process(app)
{
}


PConfigPage::PConfigPage(PHTTPServiceProcess & app,
                         const PString & section,
                         const PHTTPAuthority & auth)
  : PHTTPConfig(section.ToLower() + ".html", section, auth),
    process(app)
{
}


void PConfigPage::OnLoadedText(PHTTPRequest & request, PString & text)
{
  PServiceHTML::ProcessMacros(request, text,
                              GetURL().AsString(PURL::PathOnly).Mid(1),
                              PServiceHTML::LoadFromFile);
  PHTTPConfig::OnLoadedText(request, text);
  PServiceHTML::ProcessMacros(request, text, "", PServiceHTML::NoOptions);
}


BOOL PConfigPage::OnPOST(PHTTPServer & server,
                         const PURL & url,
                         const PMIMEInfo & info,
                         const PStringToString & data,
                         const PHTTPConnectionInfo & connectInfo)
{
  PHTTPConfig::OnPOST(server, url, info, data, connectInfo);
  return FALSE;    // Make sure we break any persistent connections
}


BOOL PConfigPage::Post(PHTTPRequest & request,
                       const PStringToString & data,
                       PHTML & reply)
{
  PSYSTEMLOG(Debug3, "Post to " << request.url << '\n' << data);
  BOOL retval = PHTTPConfig::Post(request, data, reply);

  if (request.code == PHTTP::RequestOK)
    process.BeginRestartSystem();

  PServiceHTML::ProcessMacros(request, reply,
                              GetURL().AsString(PURL::PathOnly).Mid(1),
                              PServiceHTML::LoadFromFile);
  OnLoadedText(request, reply);

  return retval;
}


BOOL PConfigPage::GetExpirationDate(PTime & when)
{
  // Well and truly before now....
  when = ImmediateExpiryTime;
  return TRUE;
}


//////////////////////////////////////////////////////////////

PConfigSectionsPage::PConfigSectionsPage(PHTTPServiceProcess & app,
                                         const PURL & url,
                                         const PHTTPAuthority & auth,
                                         const PString & prefix,
                                         const PString & valueName,
                                         const PURL & editSection,
                                         const PURL & newSection,
                                         const PString & newTitle,
                                         PHTML & heading)
  : PHTTPConfigSectionList(url, auth, prefix, valueName,
                           editSection, newSection, newTitle, heading),
    process(app)
{
}


void PConfigSectionsPage::OnLoadedText(PHTTPRequest & request, PString & text)
{
  PServiceHTML::ProcessMacros(request, text,
                              GetURL().AsString(PURL::PathOnly).Mid(1),
                              PServiceHTML::LoadFromFile);
  PHTTPConfigSectionList::OnLoadedText(request, text);
}


BOOL PConfigSectionsPage::OnPOST(PHTTPServer & server,
                                 const PURL & url,
                                 const PMIMEInfo & info,
                                 const PStringToString & data,
                                 const PHTTPConnectionInfo & connectInfo)
{
  PHTTPConfigSectionList::OnPOST(server, url, info, data, connectInfo);
  return FALSE;    // Make sure we break any persistent connections
}


BOOL PConfigSectionsPage::Post(PHTTPRequest & request,
                               const PStringToString & data,
                               PHTML & reply)
{
  BOOL retval = PHTTPConfigSectionList::Post(request, data, reply);
  if (request.code == PHTTP::RequestOK)
    process.BeginRestartSystem();
  return retval;
}


BOOL PConfigSectionsPage::GetExpirationDate(PTime & when)
{
  // Well and truly before now....
  when = ImmediateExpiryTime;
  return TRUE;
}


//////////////////////////////////////////////////////////////

PRegisterPage::PRegisterPage(PHTTPServiceProcess & app,
                             const PHTTPAuthority & auth)
  : PConfigPage(app, "register.html", "Secured Options", auth),
    process(app)
{
}


PString PRegisterPage::LoadText(PHTTPRequest & request)
{
  if (fields.GetSize() > 0)
    return PConfigPage::LoadText(request);

  PString mailURL = "mailto:" + process.GetEMailAddress();
  PString orderURL = mailURL;
  PString tempURL = mailURL;
  if (process.GetHomePage() == HOME_PAGE) {
    orderURL = "https://home.equival.com.au/purchase.html";
    tempURL = "http://www.equival.com/" + process.GetName().ToLower() + "/register.html";
    tempURL.Replace(" ", "", TRUE);
  }

  PServiceHTML regPage(process.GetName() & "Registration", NULL);
  regPage << "<!--#registration start Permanent-->"
             "Your registration key is permanent.<p>"
             "Do not change your registration details or your key will not "
             "operate correctly.<p>"
             "If you need to "
          << PHTML::HotLink(orderURL)
          << "upgrade"
          << PHTML::HotLink()
          << " or "
          << PHTML::HotLink(mailURL)
          << "change"
          << PHTML::HotLink()
          << " your registration, then you may enter the new values sent "
          << " to you from "
          << process.GetManufacturer()
          << " into the fields "
             "below, and then press the Accept button.<p>"
          << PHTML::HRule()
          << "<!--#registration end Permanent-->"
             "<!--#registration start Temporary-->"
             "Your registration key is temporary and will expire on "
             "<!--#registration ExpiryDate-->.<p>"
             "Do not change your registration details or your key will not "
             "operate correctly.<p>"
             "You may "
          << PHTML::HotLink(orderURL)
          << "order a permanent key"
          << PHTML::HotLink()
          << " and enter the new values sent to you from "
          << process.GetManufacturer()
          << " into the fields below, and then press the Accept button.<p>"
          << PHTML::HRule()
          << "<!--#registration end Temporary-->"
             "<!--#registration start Expired-->"
             "Your temporary registration key has expired.<p>"
             "You may "
          << PHTML::HotLink(orderURL)
          << "order a permanent key"
          << PHTML::HotLink()
          << " and enter the new values sent to you from "
          << process.GetManufacturer()
          << " into the fields below, and then press the Accept button.<P>"
          << PHTML::HRule()
          << "<!--#registration end Expired-->";

  PSecureConfig securedConf(process.GetProductKey(), process.GetSecuredKeys());
  PString prefix;
  if (securedConf.GetValidation() != PSecureConfig::IsValid) 
    prefix = securedConf.GetPendingPrefix();

  AddFields(prefix);

  Add(new PHTTPStringField("Validation", 40));
  BuildHTML(regPage, InsertIntoHTML);

  regPage << "<!--#registration start Invalid-->"
             "You have entered the values sent to you from "
          << process.GetManufacturer()
          << " incorrectly. Please enter them again. Note, "
          << PHTML::Emphasis() << PHTML::Strong() << "all" << PHTML::Strong() << PHTML::Emphasis()
          << "the fields must be entered "
          << PHTML::Emphasis() << PHTML::Strong() << "exactly" << PHTML::Strong() << PHTML::Emphasis()
          << " as they appear in the e-mail from "
          << process.GetManufacturer()
          << ". We strongly recommend using copy and paste of all the fields, and then "
             "press the Accept button."
             "<!--#registration end Invalid-->"
             "<!--#registration start Default-->"
             "You may "
          << PHTML::HotLink(orderURL)
          << "order a permanent key"
          << PHTML::HotLink()
          << " or "
          << PHTML::HotLink(tempURL)
          << "obtain a temporary key"
          << PHTML::HotLink()
          << " and enter the values sent to you from "
          << process.GetManufacturer()
          << " into the fields above, and then press the Accept button.<p>"
             "<!--#registration end Default-->"
          << PHTML::HRule()
          << PHTML::Heading(3) << "Disclaimer" << PHTML::Heading(3)
          << PHTML::Paragraph() << PHTML::Bold()
          << "The information and code herein is provided \"as is\" "
             "without warranty of any kind, either expressed or implied, "
             "including but not limited to the implied warrenties of "
             "merchantability and fitness for a particular purpose. In "
             "no event shall " << process.GetManufacturer() << " be liable "
             "for any damages whatsoever including direct, indirect, "
             "incidental, consequential, loss of business profits or special "
             "damages, even if " << process.GetManufacturer() << " has been "
             "advised of the possibility of such damages."
          << PHTML::Bold() << PHTML::Paragraph()
          << process.GetCopyrightText()
          << PHTML::Body();

  SetString(regPage);
  return PConfigPage::LoadText(request);
}


static BOOL FindSpliceBlock(const PRegularExpression & regex,
                            const PString & text,
                            PINDEX & pos,
                            PINDEX & len,
                            PINDEX & start,
                            PINDEX & finish)
{
  if (!text.FindRegEx(regex, pos, len, 0))
    return FALSE;

  PINDEX endpos, endlen;
  static PRegularExpression EndBlock("<?!--#registration[ \t\n]*end[ \t\n]*[a-z]*[ \t\n]*-->?",
                                     PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  if (text.FindRegEx(EndBlock, endpos, endlen, pos)) {
    start = pos+len;
    finish = endpos-1;
    len = endpos - pos + endlen;
  }

  return TRUE;
}



void PRegisterPage::OnLoadedText(PHTTPRequest & request, PString & text)
{
  PString block;
  PINDEX pos, len, start = 0, finish = 0;
  PSecureConfig securedConf(process.GetProductKey(), process.GetSecuredKeys());
  PTime expiry = securedConf.GetTime(securedConf.GetExpiryDateKey());

  static PRegularExpression Default("<?!--#registration[ \t\n]*start[ \t\n]*Default[ \t\n]*-->?",
                                    PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  static PRegularExpression Permanent("<?!--#registration[ \t\n]*start[ \t\n]*Permanent[ \t\n]*-->?",
                                      PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  static PRegularExpression Temporary("<?!--#registration[ \t\n]*start[ \t\n]*Temporary[ \t\n]*-->?",
                                      PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  static PRegularExpression Expired("<?!--#registration[ \t\n]*start[ \t\n]*Expired[ \t\n]*-->?",
                                    PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  static PRegularExpression Invalid("<?!--#registration[ \t\n]*start[ \t\n]*Invalid[ \t\n]*-->?",
                                    PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  static PRegularExpression Pending("name[ \t\n]*=[ \t\n]*\"" +
                                    securedConf.GetPendingPrefix() +
                                    "[^\"]+\"",
                                    PRegularExpression::Extended|PRegularExpression::IgnoreCase);

  PServiceHTML::ProcessMacros(request, text,
                              GetURL().AsString(PURL::PathOnly).Mid(1),
                              PServiceHTML::LoadFromFile);

  switch (securedConf.GetValidation()) {
    case PSecureConfig::Defaults :
      while (FindSpliceBlock(Default, text, pos, len, start, finish))
        text.Splice(text(start, finish), pos, len);
      while (FindSpliceBlock(Permanent, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Temporary, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Expired, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Invalid, text, pos, len, start, finish))
        text.Delete(pos, len);
      break;

    case PSecureConfig::Invalid :
    case PSecureConfig::Pending :
      while (FindSpliceBlock(Default, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Permanent, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Temporary, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Expired, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Invalid, text, pos, len, start, finish))
        text.Splice(text(start, finish), pos, len);
      break;

    case PSecureConfig::Expired :
      while (FindSpliceBlock(Default, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Permanent, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Temporary, text, pos, len, start, finish))
        text.Delete(pos, len);
      while (FindSpliceBlock(Expired, text, pos, len, start, finish))
        text.Splice(text(start, finish), pos, len);
      while (FindSpliceBlock(Invalid, text, pos, len, start, finish))
        text.Delete(pos, len);
      break;

    case PSecureConfig::IsValid :
      while (text.FindRegEx(Pending, pos, len)) {
        static PINDEX pendingLength = securedConf.GetPendingPrefix().GetLength();
        text.Delete(text.Find('"', pos)+1, pendingLength);
        start = pos + len - pendingLength;
      }
      if (expiry.GetYear() < 2011) {
        while (FindSpliceBlock(Default, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Permanent, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Temporary, text, pos, len, start, finish))
          text.Splice(text(start, finish), pos, len);
        while (FindSpliceBlock(Expired, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Invalid, text, pos, len, start, finish))
          text.Delete(pos, len);
      }
      else {
        while (FindSpliceBlock(Default, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Permanent, text, pos, len, start, finish))
          text.Splice(text(start, finish), pos, len);
        while (FindSpliceBlock(Temporary, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Expired, text, pos, len, start, finish))
          text.Delete(pos, len);
        while (FindSpliceBlock(Invalid, text, pos, len, start, finish))
          text.Delete(pos, len);
      }
  }

  static PRegularExpression ExpiryDate("<?!--#registration[ \t\n]*ExpiryDate[ \t\n]*-->?",
                                       PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  while (text.FindRegEx(ExpiryDate, pos, len, 0))
    text.Splice(expiry.AsString(PTime::LongDate), pos, len);

  PHTTPConfig::OnLoadedText(request, text);
  PServiceHTML::ProcessMacros(request, text, "", PServiceHTML::NoOptions);
}


BOOL PRegisterPage::Post(PHTTPRequest & request,
                         const PStringToString & data,
                         PHTML & reply)
{
  if (fields.GetSize() == 0)
    LoadText(request);

  BOOL retval = PHTTPConfig::Post(request, data, reply);
  if (request.code != PHTTP::RequestOK)
    return FALSE;

  PSecureConfig sconf(process.GetProductKey(), process.GetSecuredKeys());
  switch (sconf.GetValidation()) {
    case PSecureConfig::Defaults :
      sconf.ResetPending();
      break;

    case PSecureConfig::IsValid :
      break;

    case PSecureConfig::Pending :
      sconf.ValidatePending();
      break;

    default :
      sconf.ResetPending();
  }

  RemoveAllFields();
  LoadText(request);
  OnLoadedText(request, reply);

  return retval;
}


///////////////////////////////////////////////////////////////////

static void DigestSecuredKeys(PHTTPServiceProcess & process,
                              PString & reginfo,
                              PHTML * html)
{
  const PStringArray & securedKeys = process.GetSecuredKeys();
  PSecureConfig sconf(process.GetProductKey(), securedKeys);

  PString prefix;
  if (sconf.GetValidation() != PSecureConfig::IsValid) 
    prefix = sconf.GetPendingPrefix();

  PMessageDigest5 digestor;

  PStringStream info;
  info << '"' << process.GetName() << "\" ===";

  PINDEX i;
  for (i = 0; i < securedKeys.GetSize(); i++) {
    PString val = sconf.GetString(prefix + securedKeys[i]).Trim();
    info << " \"" << val << '"';
    if (html != NULL)
      *html << PHTML::HiddenField(securedKeys[i], val);
    digestor.Process(val);
  }

  PString digest = digestor.Complete();
  if (html != NULL)
    *html << PHTML::HiddenField("digest", digest);

  info.Replace("===", digest);
  reginfo = info;
}


///////////////////////////////////////////////////////////////////

PServiceHTML::PServiceHTML(const char * title, const char * help, const char * helpGif)
{
  PHTTPServiceProcess::Current().GetPageHeader(*this, title);

  ostream & this_stream = *this;
  this_stream << PHTML::Heading(1) << title;
  
  if (help != NULL)
    this_stream << "&nbsp;"
                << PHTML::HotLink(help)
                << PHTML::Image(helpGif, "Help", 48, 23, "align=absmiddle")
                << PHTML::HotLink();

  this_stream << PHTML::Heading(1) << PHTML::Paragraph();
}


PString PServiceHTML::ExtractSignature(PString & out)
{
  return ExtractSignature(*this, out);
}


PString PServiceHTML::ExtractSignature(const PString & html,
                                       PString & out,
                                       const char * keyword)
{
  out = html;

  PRegularExpression SignatureRegEx("<?!--" + PString(keyword) + "[ \t\r\n]+"
                                     "signature[ \t\r\n]+(-?[^-])+-->?",
                                     PRegularExpression::Extended|PRegularExpression::IgnoreCase);

  PINDEX pos, len;
  if (out.FindRegEx(SignatureRegEx, pos, len)) {
    PString tag = out.Mid(pos, len);
    out.Delete(pos, len);
    return tag(tag.Find("signature")+10, tag.FindLast('-')-2).Trim();
  }

  return PString::Empty();
}


PString PServiceHTML::CalculateSignature()
{
  return CalculateSignature(*this);
}


PString PServiceHTML::CalculateSignature(const PString & out)
{
  return CalculateSignature(out, PHTTPServiceProcess::Current().GetSignatureKey());
}


PString PServiceHTML::CalculateSignature(const PString & out,
                                         const PTEACypher::Key & sig)
{
  // calculate the MD5 digest of the HTML data
  PMessageDigest5 digestor;

  PINDEX p1 = 0;
  PINDEX p2;
  while ((p2 = out.FindOneOf("\r\n", p1)) != P_MAX_INDEX) {
    if (p2 > p1)
      digestor.Process(out(p1, p2-1));
    digestor.Process("\r\n", 2);
    p1 = p2 + 1;
    if (out[p2] == '\r' && out[p1] == '\n') // CR LF pair
      p1++;
  }
  digestor.Process(out(p1, P_MAX_INDEX));

  PMessageDigest5::Code md5;
  digestor.Complete(md5);

  // encode it
  PTEACypher cypher(sig);
  BYTE buf[sizeof(md5)+7];
  memcpy(buf, &md5, sizeof(md5));
  memset(&buf[sizeof(md5)], 0, sizeof(buf)-sizeof(md5));
  return cypher.Encode(buf, sizeof(buf));
}


BOOL PServiceHTML::CheckSignature()
{
  return CheckSignature(*this);
}


BOOL PServiceHTML::CheckSignature(const PString & html)
{
  if (PHTTPServiceProcess::Current().ShouldIgnoreSignatures())
    return TRUE;

  // extract the signature from the file
  PString out;
  PString signature = ExtractSignature(html, out);

  // calculate the signature on the data
  PString checkSignature = CalculateSignature(out);

  // return TRUE or FALSE
  return checkSignature == signature;
}


static BOOL FindBrackets(const PString & args, PINDEX & open, PINDEX & close)
{
  open = args.FindOneOf("[{(", close);
  if (open == P_MAX_INDEX)
    return FALSE;

  switch (args[open]) {
    case '[' :
      close = args.Find(']', open+1);
      break;
    case '{' :
      close = args.Find('}', open+1);
      break;
    case '(' :
      close = args.Find(')', open+1);
      break;
  }
  return close != P_MAX_INDEX;
}


static BOOL ExtractVariables(const PString & args,
                             PString & variable,
                             PString & value)
{
  PINDEX open;
  PINDEX close = 0;
  if (FindBrackets(args, open, close))
    variable = args(open+1, close-1);
  else {
    variable = args.Trim();
    close = P_MAX_INDEX-1;
  }
  if (variable.IsEmpty())
    return FALSE;

  if (FindBrackets(args, open, close))
    value = args(open+1, close-1);

  return TRUE;
}


///////////////////////////////////////////////////////////////////////////////

PServiceMacro * PServiceMacro::list;


PServiceMacro::PServiceMacro(const char * name, BOOL isBlock)
{
  macroName = name;
  isMacroBlock = isBlock;
  link = list;
  list = this;
}


PServiceMacro::PServiceMacro(const PCaselessString & name, BOOL isBlock)
{
  macroName = name;
  isMacroBlock = isBlock;
}


PObject::Comparison PServiceMacro::Compare(const PObject & obj) const
{
  PAssert(PIsDescendant(&obj, PServiceMacro), PInvalidCast);
  const PServiceMacro & other = (const PServiceMacro &)obj;

  if (isMacroBlock != other.isMacroBlock)
    return isMacroBlock ? GreaterThan : LessThan;

  int cmp = strcasecmp(macroName, other.macroName);
  if (cmp < 0)
    return LessThan;
  if (cmp > 0)
    return GreaterThan;
  return EqualTo;
}


PString PServiceMacro::Translate(PHTTPRequest &, const PString &, const PString &) const
{
  return PString::Empty();
};



PServiceMacros_list::PServiceMacros_list()
{
  DisallowDeleteObjects();
  PServiceMacro * macro = PServiceMacro::list;
  while (macro != NULL) {
    Append(macro);
    macro = macro->link;
  }
}


PCREATE_SERVICE_MACRO(Header,request,P_EMPTY)
{
  PString hdr = PHTTPServiceProcess::Current().GetPageGraphic();
  PServiceHTML::ProcessMacros(request, hdr, "header.html",
                PServiceHTML::LoadFromFile|PServiceHTML::NoURLOverride);
  return hdr;
}


PCREATE_SERVICE_MACRO(Copyright,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetCopyrightText();
}


PCREATE_SERVICE_MACRO(ProductName,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetProductName();
}


PCREATE_SERVICE_MACRO(Manufacturer,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetManufacturer();
}


PCREATE_SERVICE_MACRO(Version,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetVersion(TRUE);
}


PCREATE_SERVICE_MACRO(BuildDate,P_EMPTY,args)
{
  const PTime & date = PHTTPServiceProcess::Current().GetCompilationDate();
  if (args.IsEmpty())
    return date.AsString("d MMMM yyyy");

  return date.AsString(args);
}


PCREATE_SERVICE_MACRO(OS,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetOSClass() &
         PHTTPServiceProcess::Current().GetOSName();
}


PCREATE_SERVICE_MACRO(Machine,P_EMPTY,P_EMPTY)
{
  return PHTTPServiceProcess::Current().GetOSVersion() + '-' +
         PHTTPServiceProcess::Current().GetOSHardware();
}


PCREATE_SERVICE_MACRO(LongDateTime,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::LongDateTime);
}


PCREATE_SERVICE_MACRO(LongDate,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::LongDate);
}


PCREATE_SERVICE_MACRO(LongTime,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::LongTime);
}


PCREATE_SERVICE_MACRO(MediumDateTime,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::MediumDateTime);
}


PCREATE_SERVICE_MACRO(MediumDate,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::MediumDate);
}


PCREATE_SERVICE_MACRO(ShortDateTime,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::ShortDateTime);
}


PCREATE_SERVICE_MACRO(ShortDate,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::ShortDate);
}


PCREATE_SERVICE_MACRO(ShortTime,P_EMPTY,P_EMPTY)
{
  return PTime().AsString(PTime::ShortTime);
}


PCREATE_SERVICE_MACRO(Time,P_EMPTY,args)
{
  PTime now;
  if (args.IsEmpty())
    return now.AsString();

  return now.AsString(args);
}


PCREATE_SERVICE_MACRO(StartTime,P_EMPTY,P_EMPTY)
{
  return PProcess::Current().GetStartTime().AsString(PTime::MediumDateTime);
}


PCREATE_SERVICE_MACRO(UpTime,P_EMPTY,P_EMPTY)
{
  PTimeInterval upTime = PTime() - PProcess::Current().GetStartTime();
  return upTime.AsString(0, PTimeInterval::IncludeDays);
}


PCREATE_SERVICE_MACRO(LocalHost,request,P_EMPTY)
{
  if (request.localAddr != 0)
    return PIPSocket::GetHostName(request.localAddr);
  else
    return PIPSocket::GetHostName();
}


PCREATE_SERVICE_MACRO(LocalIP,request,P_EMPTY)
{
  if (request.localAddr != 0)
    return request.localAddr;
  else
    return "127.0.0.1";
}


PCREATE_SERVICE_MACRO(LocalPort,request,P_EMPTY)
{
  if (request.localPort != 0)
    return psprintf("%u", request.localPort);
  else
    return "80";
}


PCREATE_SERVICE_MACRO(PeerHost,request,P_EMPTY)
{
  if (request.origin != 0)
    return PIPSocket::GetHostName(request.origin);
  else
    return "N/A";
}


PCREATE_SERVICE_MACRO(PeerIP,request,P_EMPTY)
{
  if (request.origin != 0)
    return request.origin;
  else
    return "N/A";
}

PCREATE_SERVICE_MACRO(MonitorInfo,request,P_EMPTY)
{
  const PTime & compilationDate = PHTTPServiceProcess::Current().GetCompilationDate();

  PString peerAddr = "N/A";
  if (request.origin != 0)
    peerAddr = request.origin.AsString();

  PString localAddr = "127.0.0.1";
  if (request.localAddr != 0)
    localAddr = request.localAddr.AsString();

  WORD localPort = 80;
  if (request.localPort != 0)
    localPort = request.localPort;

  PString timeFormat = "yyyyMMdd hhmmss z";

  PTime now;
  PTimeInterval upTime = now - PProcess::Current().GetStartTime();

  PStringStream monitorText; 
  monitorText << "Program: "          << PHTTPServiceProcess::Current().GetProductName() << "\n"
              << "Version: "          << PHTTPServiceProcess::Current().GetVersion(TRUE) << "\n"
              << "Manufacturer: "     << PHTTPServiceProcess::Current().GetManufacturer() << "\n"
              << "OS: "               << PHTTPServiceProcess::Current().GetOSClass() << " " << PHTTPServiceProcess::Current().GetOSName() << "\n"
              << "OS Version: "       << PHTTPServiceProcess::Current().GetOSVersion() << "\n"
              << "Hardware: "         << PHTTPServiceProcess::Current().GetOSHardware() << "\n"
              << "Compilation date: " << compilationDate.AsString(timeFormat, PTime::GMT) << "\n"
              << "Start Date: "       << PProcess::Current().GetStartTime().AsString(timeFormat, PTime::GMT) << "\n"
              << "Current Date: "     << now.AsString(timeFormat, PTime::GMT) << "\n"
              << "Up time: "          << upTime << "\n"
              << "Peer Addr: "        << peerAddr << "\n"
              << "Local Host: "       << PIPSocket::GetHostName() << "\n"
              << "Local Addr: "       << localAddr << "\n"
              << "Local Port: "       << localPort << "\n"
       ;

  return monitorText;
}


PCREATE_SERVICE_MACRO(RegInfo,P_EMPTY,P_EMPTY)
{
  PString subs;
  DigestSecuredKeys(PHTTPServiceProcess::Current(), subs, NULL);
  return subs;
}


static PString GetRegInfo(const char * info)
{
  PHTTPServiceProcess & process = PHTTPServiceProcess::Current();
  PSecureConfig sconf(process.GetProductKey(), process.GetSecuredKeys());
  PString pending = sconf.GetPendingPrefix();
  return sconf.GetString(info, sconf.GetString(pending+info));
}

PCREATE_SERVICE_MACRO(RegUser,P_EMPTY,P_EMPTY)
{
  return GetRegInfo("Name");
}


PCREATE_SERVICE_MACRO(RegCompany,P_EMPTY,P_EMPTY)
{
  return GetRegInfo("Company");
}


PCREATE_SERVICE_MACRO(RegEmail,P_EMPTY,P_EMPTY)
{
  return GetRegInfo("EMail");
}


PCREATE_SERVICE_MACRO(Registration,P_EMPTY,args)
{
  PHTTPServiceProcess & process = PHTTPServiceProcess::Current();
  PSecureConfig sconf(process.GetProductKey(), process.GetSecuredKeys());
  PString pending = sconf.GetPendingPrefix();

  PString regNow = "Register Now!";
  PString viewReg = "View Registration";
  PString demoCopy = "Unregistered Demonstration Copy";
  PINDEX open;
  PINDEX close = 0;
  if (FindBrackets(args, open, close)) {
    regNow = args(open+1, close-1);
    if (FindBrackets(args, open, close)) {
      viewReg = args(open+1, close-1);
      if (FindBrackets(args, open, close))
        demoCopy = args(open+1, close-1);
    }
  }

  PHTML out(PHTML::InBody);
  out << "<font size=5>"
      << sconf.GetString("Name", sconf.GetString(pending+"Name", "*** "+demoCopy+" ***"))
      << PHTML::BreakLine()
      << "<font size=4>"
      << sconf.GetString("Company", sconf.GetString(pending+"Company"))
      << PHTML::BreakLine()
      << PHTML::BreakLine()
      << "<font size=3>";

  if (sconf.GetString("Name").IsEmpty())
    process.AddUnregisteredText(out);
  else
    process.AddRegisteredText(out);

  out << PHTML::HotLink("/register.html")
      << (sconf.GetString("Name").IsEmpty() ? regNow : viewReg)
      << PHTML::HotLink();
  return out;
}


PCREATE_SERVICE_MACRO(InputsFromQuery,request,P_EMPTY)
{
  PStringToString vars = request.url.GetQueryVars();
  PStringStream subs;
  for (PINDEX i = 0; i < vars.GetSize(); i++)
    subs << "<INPUT TYPE=hidden NAME=\"" << vars.GetKeyAt(i)
         << "\" VALUE=\"" << vars.GetDataAt(i) << "\">\r\n";
  return subs;
}


PCREATE_SERVICE_MACRO(Query,request,args)
{
  if (args.IsEmpty())
    return request.url.GetQuery();

  PString variable, value;
  if (ExtractVariables(args, variable, value)) {
    value = request.url.GetQueryVars()(variable, value);
    if (!value)
      return value;
  }
  return PString::Empty();
}


PCREATE_SERVICE_MACRO(Get,request,args)
{
  PString variable, value;
  if (ExtractVariables(args, variable, value)) {
    PString section = request.url.GetQueryVars()("section");
    PINDEX slash = variable.FindLast('\\');
    if (slash != P_MAX_INDEX) {
      section += variable.Left(slash);
      variable = variable.Mid(slash+1);
    }
    if (!section && !variable) {
      PConfig config(section);
      return config.GetString(variable, value);
    }
  }
  return PString::Empty();
}


PCREATE_SERVICE_MACRO(URL,request,P_EMPTY)
{
  return request.url.AsString();
}


PCREATE_SERVICE_MACRO(Include,P_EMPTY,args)
{
  PString text;

  if (!args) {
    PFile file;
    if (file.Open(args, PFile::ReadOnly))
      text = file.ReadString(file.GetLength());
  }

  return text;
}


PCREATE_SERVICE_MACRO(SignedInclude,P_EMPTY,args)
{
  PString text;

  if (!args) {
    PFile file;
    if (file.Open(args, PFile::ReadOnly)) {
      text = file.ReadString(file.GetLength());
      if (!PServiceHTML::CheckSignature(text)) {
        PHTTPServiceProcess & process = PHTTPServiceProcess::Current();
        PHTML html("Invalid OEM Signature");
        html << "The HTML file \""
             << args
             << "\" contains an invalid signature for \""
             << process.GetName()
             << "\" by \""
             << process.GetManufacturer()
             << '"'
             << PHTML::Body();
        text = html;
      }
    }
  }

  return text;
}

PCREATE_SERVICE_MACRO_BLOCK(IfQuery,request,args,block)
{
  PStringToString vars = request.url.GetQueryVars();

  PINDEX space = args.FindOneOf(" \t\r\n");
  PString var = args.Left(space);
  PString value = args.Mid(space).LeftTrim();

  BOOL ok;
  if (value.IsEmpty())
    ok = vars.Contains(var);
  else {
    PString operation;
    space = value.FindOneOf(" \t\r\n");
    if (space != P_MAX_INDEX) {
      operation = value.Left(space);
      value = value.Mid(space).LeftTrim();
    }

    PString query = vars(var);
    if (operation == "!=")
      ok = query != value;
    else if (operation == "<")
      ok = query < value;
    else if (operation == ">")
      ok = query > value;
    else if (operation == "<=")
      ok = query <= value;
    else if (operation == ">=")
      ok = query >= value;
    else if (operation == "*=")
      ok = (query *= value);
    else
      ok = query == value;
  }

  return ok ? block : PString::Empty();
}


PCREATE_SERVICE_MACRO_BLOCK(IfInURL,request,args,block)
{
  if (request.url.AsString().Find(args) != P_MAX_INDEX)
    return block;

  return PString::Empty();
}


PCREATE_SERVICE_MACRO_BLOCK(IfNotInURL,request,args,block)
{
  if (request.url.AsString().Find(args) == P_MAX_INDEX)
    return block;

  return PString::Empty();
}


static void SplitCmdAndArgs(const PString & text, PINDEX pos, PCaselessString & cmd, PString & args)
{
  static const char whitespace[] = " \t\r\n";
  PString macro = text(text.FindOneOf(whitespace, pos)+1, text.Find("--", pos+3)-1).Trim();
  PINDEX endCmd = macro.FindOneOf(whitespace);
  if (endCmd == P_MAX_INDEX) {
    cmd = macro;
    args.MakeEmpty();
  }
  else {
    cmd = macro.Left(endCmd);
    args = macro.Mid(endCmd+1).LeftTrim();
  }
}


BOOL PServiceHTML::ProcessMacros(PHTTPRequest & request,
                                 PString & text,
                                 const PString & defaultFile,
                                 unsigned options)
{
  PINDEX alreadyLoadedPrefixLength = 0;

  PString filename = defaultFile;
  if ((options&LoadFromFile) != 0) {
    if ((options&NoURLOverride) == 0) {
      filename = request.url.GetParameters();
      if (filename.IsEmpty())
        filename = defaultFile;
    }

    if (!filename) {
      PString alreadyLoaded = "<!--#loadedfrom " + filename + "-->\r\n";
      alreadyLoadedPrefixLength = alreadyLoaded.GetLength();

      if (text.Find(alreadyLoaded) != 0) {
        PFile file;
        if (file.Open(filename, PFile::ReadOnly)) {
          text = alreadyLoaded + file.ReadString(file.GetLength());
          if ((options&NoSignatureForFile) == 0)
            options |= NeedSignature;
        }
      }
    }
  }

  if ((options&NeedSignature) != 0) {
    if (!CheckSignature(text.Mid(alreadyLoadedPrefixLength))) {
      PHTTPServiceProcess & process = PHTTPServiceProcess::Current();
      PHTML html("Invalid OEM Signature");
      html << "The HTML file \""
           << filename
           << "\" contains an invalid signature for \""
           << process.GetName()
           << "\" by \""
           << process.GetManufacturer()
           << '"'
           << PHTML::Body();
      text = html;
      return FALSE;
    }
  }

  static PServiceMacros_list ServiceMacros;

  PHTTPServiceProcess & process = PHTTPServiceProcess::Current();

  PRegularExpression StartBlockRegEx("<?!--#(equival|" + process.GetMacroKeyword() + ")"
                                     "start[ \t\r\n]+(-?[^-])+-->?",
                                     PRegularExpression::Extended|PRegularExpression::IgnoreCase);

  PRegularExpression MacroRegEx("<?!--#(equival|" + process.GetMacroKeyword() + ")[ \t\r\n]+(-?[^-])+-->?",
                                PRegularExpression::Extended|PRegularExpression::IgnoreCase);

  BOOL substitedMacro;
  do {
    substitedMacro = FALSE;

    PINDEX pos = 0;
    PINDEX len;
    while (text.FindRegEx(StartBlockRegEx, pos, len, pos)) {
      PString substitution;

      PCaselessString cmd;
      PString args;
      SplitCmdAndArgs(text, pos, cmd, args);
      PINDEX idx = ServiceMacros.GetValuesIndex(PServiceMacro(cmd, TRUE));
      if (idx != P_MAX_INDEX) {
        PRegularExpression EndBlockRegEx("<?!--#(equival|" + process.GetMacroKeyword() + ")"
                                         "end[ \t\r\n]+" + cmd + "(-?[^-])*-->?",
                                         PRegularExpression::Extended|PRegularExpression::IgnoreCase);
        PINDEX endpos, endlen;
        if (text.FindRegEx(EndBlockRegEx, endpos, endlen, pos+len)) {
          PINDEX startpos = pos+len;
          len = endpos-pos + endlen;
          substitution = ServiceMacros[idx].Translate(request, args, text(startpos, endpos-1));
          substitedMacro = TRUE;
        }
      }

      text.Splice(substitution, pos, len);
    }

    pos = 0;
    while (text.FindRegEx(MacroRegEx, pos, len, pos)) {
      PCaselessString cmd;
      PString args;
      SplitCmdAndArgs(text, pos, cmd, args);

      PString substitution;
      if (!process.SubstituteEquivalSequence(request, cmd & args, substitution)) {
        PINDEX idx = ServiceMacros.GetValuesIndex(PServiceMacro(cmd, FALSE));
        if (idx != P_MAX_INDEX) {
          substitution = ServiceMacros[idx].Translate(request, args, PString::Empty());
          substitedMacro = TRUE;
        }
      }

      text.Splice(substitution, pos, len);
    }
  } while (substitedMacro);

  return TRUE;
}


///////////////////////////////////////////////////////////////////

static void ServiceOnLoadedText(PString & text)
{
  PHTTPServiceProcess & process = PHTTPServiceProcess::Current();

  PString manuf = "<!--Standard_" + process.GetManufacturer() + "_Header-->";
  if (text.Find(manuf) != P_MAX_INDEX)
    text.Replace(manuf, process.GetPageGraphic(), TRUE);

  static const char equiv[] = "<!--Standard_Equivalence_Header-->";
  if (text.Find(equiv) != P_MAX_INDEX)
    text.Replace(equiv, process.GetPageGraphic(), TRUE);

  static const char copy[] = "<!--Standard_Copyright_Header-->";
  if (text.Find(copy) != P_MAX_INDEX)
    text.Replace(copy, process.GetCopyrightText(), TRUE);
}


PString PServiceHTTPString::LoadText(PHTTPRequest & request)
{
  PString text = PHTTPString::LoadText(request);
  ServiceOnLoadedText(text);
  PServiceHTML::ProcessMacros(request, text, "", PServiceHTML::LoadFromFile);

  return text;
}

BOOL PServiceHTTPString::GetExpirationDate(PTime & when)
{
  // Well and truly before now....
  when = ImmediateExpiryTime;
  return TRUE;
}


void PServiceHTTPFile::OnLoadedText(PHTTPRequest & request, PString & text)
{
  ServiceOnLoadedText(text);
  PServiceHTML::ProcessMacros(request, text, GetURL().AsString(PURL::PathOnly),
          needSignature ? PServiceHTML::NeedSignature : PServiceHTML::NoOptions);
}

BOOL PServiceHTTPFile::GetExpirationDate(PTime & when)
{
  // Well and truly before now....
  when = ImmediateExpiryTime;
  return TRUE;
}


void PServiceHTTPDirectory::OnLoadedText(PHTTPRequest & request, PString & text)
{
  ServiceOnLoadedText(text);
  PServiceHTML::ProcessMacros(request, text, GetURL().AsString(PURL::PathOnly),
          needSignature ? PServiceHTML::NeedSignature : PServiceHTML::NoOptions);
}


BOOL PServiceHTTPDirectory::GetExpirationDate(PTime & when)
{
  // Well and truly before now....
  when = ImmediateExpiryTime;
  return TRUE;
}


///////////////////////////////////////////////////////////////////


syntax highlighted by Code2HTML, v. 0.9.1