/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                 DAVweb.c

    This package is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 3 of the License, or any later
    version.

>   This package is distributed in the hope that it will be useful,
>   but WITHOUT ANY WARRANTY; without even the implied warranty of
>   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>   GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Web-based Distributed Authoring and (not) Versioning for the WASD package.

Effective WASD WebDAV file-space (without significant naming constraints)
relies on being hosted on ODS-5 volumes.  Behaviour hosting file-space on ODS-2
volumes is untested (though possible provided file naming is constrained to
ODS-2 conventions).

WASD WebDAV methods and request headers, etc., are also propagated to the
scripting environment and so functionality may be implemented using CGI,
CGIplus or RTE based applications.

WASD proxy-serving supports WebDAV methods, header fields, etc.

Generally WebDAV clients are applications other than browsers and so response
bodies with human-readable error explanations are unnecessary and consume
bandwidth to no good purpose, and so not provided.

File-systems are notoriously latent components relative to the rest of the
system (more so with VMS).  Any operation to collections (directories) are not
going to be atomic and for large collections requiring many sub-operations the
potential for the process to be interrupted or otherwise disturbed are
enornmous.  File-systems are not databases amenable to extensive ACID
operations.

WASD takes out its own VMS DLM locks on resources (files and directories)
before beginning any WebDAV operation, and these prevent conflict with other
WASD WebDAV operations on the same system or cluster, but RMS does not use
these nor does WASD use RMS locks (except when actually modifying the
file-system of course), and so there is potential for interactions between the
two domains (in common with general file-system actvities).  WASD WebDAV
deliberately does not try to block file-system actions from other processing
(except where RMS locks/blocks).  It's own DLM locking is purely for internal
purposes.

In addition each file under WebDAV management has the potential for an
associated but independent META data file.  This of course means for every
DAV-specific resource file activity there is at least a file-system action to
check for a META data file and for some actions such as COPY the potential for
an associated but entirely independent file operation.

Of course WebDAV was not intended or designed as a general file-system protocol
but one for distributed management of somewhat restricted collections of
Web-related resources and so in context probably works well enough.

See sections below on file-system operation method restrictions.

If using WebDAV in any serious fashion the likes of $ HTTPD/DO=RESTART=NOW
during server WebDav file-system modifications is a recipe for inconsistency
and/or corruption!

In other words; ALL CARE BUT NO RESPONSIBILITY TAKEN!  See GPL above.


REFERENCES
----------
http://webdav.org/
http://en.wikipedia.org/wiki/Webdav
http://tools.ietf.org/html/rfc4918
http://tools.ietf.org/html/rfc4331 (quota)
http://tools.ietf.org/html/rfc2518 (obsolete)

WebDAV: Next-Generation Collaborative Web Authoring
        Lisa Dusseault, 2003  ISBN: 0130652083

Using Expat by Clark Cooper
http://en.wikipedia.org/wiki/Expat_(XML)
http://www.xml.com/pub/a/1999/09/expat/index.html
http://www.xml.com/lpt/a/47


DEVELOPMENT CLIENT TOOLS
------------------------
http://www.webdavsystem.com/server/access/

DAVExplorer - a Java-based GUI Explorer-style file navigation tool
http://www.davexplorer.org/

cadaver - a command-line WebDAV client for *x
http://www.webdav.org/cadaver/

davfs2 - a mountable WebDAV file-system for Linux
http://savannah.nongnu.org/projects/davfs2

The WebDAV URL handling of KDE 4.2 Dolphin (v1.2)
http://www.webdavsystem.com/server/access/konqueror  (yup, I know!)
(KDE+Dolphin is one of the best-behaved WebDAV clients I have encountered!)

The WebDAV URL handling of Gnome Nautilus (gvfs/1.2.2)
http://www.webdavsystem.com/server/access/gnome_nautilus
*** THE RECENT (08,09 at least) GNOME/GVFS/NAUTILUS HAVE A LOT OF ENTRIES IN
BUGZILLA - DO NOT EXPECT IT TO BEHAVE ITSELF - this has been my experience ***

Windows Explorer - and the associated mini-director, et.al., on XP (not Vista).
See "Microsoft Miscellanea" below.

*** AND PARTICULARLY EFFECTIVE FOR THE WINDOWS PLATFORM ***
WebDrive "FTP Client" for Windows XP (try before you buy)
http://www.webdrive.com/


METHODS SUPPORTED
-----------------
A list of WebDAV methods, what WASD does with them, and any limitations or
restrictions.  Issues of atomicity with the manipulation of file-system trees
containing numbers of individual files makes strict RFC 4918 compliance
difficult.

COPY  -  reproduces both single resources (files) and collections (directory
trees).  Currently will overwrite files (if specified by the request) but will
respond 209 (Conflict) if it would overwrite a tree.

DELETE  -  deletes files and directory trees.

GET  -  just the vanilla HTTP/1.1 behaviour.

HEAD  -  ditto.

LOCK  -  see WEBDAV LOCKING below.

MKCOL  -  create a directory.

MOVE  -  moves (rename or copy) a file or a directory tree.  Currently will
'overwrite' files (if specified by the request) but will respond 209 (Conflict)
if it would overwrite a tree.

OPTIONS  -  if WebDAV is enabled and available for the path this reports the
WebDAV extension methods.

PROPFIND  -  retrieves the requested file characteristics, DAV lock status and
'dead' properties for individual files, a directory and it's child files, or a
directory tree.

PROPPATCH  -  set and remove 'dead' meta-data properties (see DAVMETA.C).

PUT  -  against a WebDAV resource behaves a little differently to historical
WASD implementation of PUT.  See the PUT.C module.

UNLOCK  -  see WEBDAV LOCKING below.


COPY RESTRICTIONS
-----------------
Currently does not comply with the overwrite:T directive for collections (does
so for files).  Will not currently preemptively delete the existing tree.  It
returns a 209 (Conflict) response instead.

COPY does not maintain collection consistent URL namespace if a member resource
cannot be moved as required by RFC4918.  It should maintain the source subtree
completely uncopied.  Instead it is best-effort and continues copying resources
until exhausted.  This is consistent with file-system behaviour.  The RFC4918
requirement, while not impossible, is fraught with issues inside a file-system.


DELETE RESTRICTIONS
-------------------
Deletion of collections is particularly fraught with issues for a file-system. 
In userland it is almost impossible to predetermine if an individual file in a
directory tree is going to resist deletion (due to locking, protections, etc)
and in kernel land it's probably no easier.  It leaves the undeleted tree
hierachy (resource ancestors) intact. This is RFC4918 compliant however!

So, in the case of WASD WebDAV it's just best-effort and if something down the
tree won't disappear, it just reports the failure in the 207 response and
carries merrily on through the tree regardless.  This IS acceptable WebDAV
server behaviour!


MOVE RESTRICTIONS
-----------------
Currently does not comply with the overwrite:T directive for collections (does
so for files).  Will not currently pre-emptively delete the existing tree.  It
returns a 209 (Conflict) response instead.

MOVE first attempts to rename the file or directory.  This is reasonably
efficient, especially for directory trees but obviously only suitable for a
target on the same disk volume.  If a rename failure is due to a different
device it falls back to using a COPY then DELETE in two separate phases. 
Needless-to-say this is hardly atomic and can lead to inconsistencies between
source and target.

MOVE does not maintain collection consistent URL namespace if a member resource
cannot be moved as required by RFC4918.  It should maintain the source subtree
unmoved.  Instead it is best-effort and continues moving resources until
exhausted.  This is consistent with file-system behaviour.  The RFC4918
requirement, while not impossible, is fraught with issues inside a file-system.


IF: RESTRICTIONS
----------------
The conditional "If:" request header field does not have full RFC 4918 support. 
If currently implements lock token and etag token processing with parenthetical
OR and NOT processing.  For unsupported features WATCH reports that the header
was not understood and always returns an abort status.  WebDAV "If:" processing
is an extrodinarily complex kludge for on-the-fly decision making by the server
and much of what I have read indicates most clients only ever use extremely
simple conditions anyway.


GLOBAL CONFIGURATION
--------------------
WebDAV can be enabled and locking disabled with a WebDAV 1 compliance.

  # HTTPD$CONFIG
  [WebDAV]  enabled
  [WebDAVlocking]  enabled
  [WebDAVlockTimeoutDefault]  hh:mm:ss
  [WebDAVlockTimeoutMax]  hh:mm:ss
  [WebDAVlockCollectionDepth]  integer
  [WebDAVquota]  enabled
  [WebDAVmetaDir]  string


FILE-SYSTEM ACCESS
------------------
Is controlled using the mapping rules:

  WEBDAV=PROFILE    access using request SYSUAF security profile
  WEBDAV=WRITE      unconditional permission to read/write
  WEBDAV=READ       unconditional permission to read
  WEBDAV=ACCESS     access using server account permissions

All access by WebDAV operations MUST HAVE AT LEAST ONE SET against the path.
If access is permitted by one of the above settings SYSPRV is enabled to allow
that access using the server account.  Therefore files and directories should
have a SYSTEM:READ+WRITE+EXECUTE+DELETE protection or equivalent ACL
permissions, or the access may fail totally or in some part of a supposedly
atomic action.

These file-system access settings are applied in the order listed above.  That
is, if a path successively has one or more of the above settings applied during
rule processing, when it comes to applying those access controls, SYSUAF
profile is applied, then if no profile SETing access to read/write, then to
read-only, then access via the server account. 


FILE-SYSTEM AUTHORISATION
-------------------------
All access by WebDAV operations MUST HAVE AT LEAST ONE SET against the path.

All WebDAV access is a combination of HTTPD$MAP path setting and HTTPD$AUTH
authorisation permissions.  The least permissive of the two overrides the more.

These is the test-bench environment used during development:

  # HTTPD$MAP
  pass /dweb/* /dweb/* ods=5 webdav=write webdav=nowinprop
  redirect /dweb /dweb/

  # HTTPD$AUTH
  ["KLAATU"=WASD_VMS_RW=id]
  /dweb/* r+w

Note that WebDAV read/write access is a combination of the mapping and the
authorisation rule (mapping WEBDAV=READ overrides authorisation read+write). 
Expect complications with Microsoft environments.

For test-benching you could avoid authorisation issues completely with:

  # HTTPD$AUTH
  [world]
  /dweb/* r+w


SUMMARY OF WEBDAV SET RULES
---------------------------
WEBDAV=[NO]HIDDEN    WebDAV (and directory) listing do not display hidden files
WEBDAV=[NO]LOCK      allow/apply WebDAV locking to this path
WEBDAV=[NO]PROFILE   WebDAV access according to SYSUAF profile
WEBDAV=[NO]PROP      allow/apply WebDAV 'dead' property(ies) to this path
WEBDAV=[NO]PUT=LOCK  a resource must be locked before a PUT is allowed
WEBDAV=[NO]READ      WebDAV methods allowed read this tree
WEBDAV=[NO]SERVER    WebDAV access as server account (best effort) 
WEBDAV=[NO]WINPROP   when NOWINPROP windows properties are ignored and emulated
WEBDAV=[NO]WRITE     WebDAV methods allowed write to this path (implied read)
WEBDAV=LOCK=TIMEOUT=DEFAULT=      hh:mm:ss
WEBDAV=LOCK=TIMEOUT=MAX=          hh:mm:ss
WEBDAV=META=DIR=     subdirectory or full directory for metadata files


WEBDAV LOCKING
--------------
For efficiency and functionality considerations WebDAV locking may be enabled
and disabled (default) as global functionality using the HTTPD$CONFIG
[WebDAVlocking] directive.  Additionally the WEBVDAV=[NO]LOCKING path SETing
can configure this on a per-path basis.

In common with RFC 4918 WASD WebDAV locking controls only write access. Both
exclusive and shared locks are provided.  This includes methods such as DELETE,
LOCK, MKCOL, MOVE, PROPATCH, PUT, UNLOCK.

WASD WebDAV locking checks parent collections to a configurable depth. 
HTTPD$CONFIG directive [WebDAVlockCollectionDepth] where the default (0 or 1)
checks only WebDAV locking on files, 2 WebDAV locking on the parent directory,
3 on the grandparent, 4 the great-grandparent, etc.  Of course each level can
add significant latency (and expense) to some operations.

For more information on locking operation and implementation details see the
DAVLOCK.C module and for meta-data in general the DAVMETA.C module.


VMS DLM LOCKING
---------------
WASD uses VMS locking to queue and arbitrate access to WebDAV resources and
meta-files.

Two lock modes are employed; 'exclusive', when changes are to be made to the
resource or it's meta-data, and 'concurrent read', when resource and/or
meta-data are only to be read.  Concurrent read locks are compatible, but an
exclusive queued against a resource currently being read waits, as does a read
against a current exclusive.


MUNGING TYPELESS DIRECTORIES
----------------------------
Microsoft WebDAV mini-redirector (and others) insists on requesting directories
without a trailing slash thereby making them difficult to distinguish from
type-less files.  With WASD WebDAV a typeless file with the same name as a
directory will be hidden by the directory for such a client because the
directory will always be checked for first.

An added complication is a directory such as

  /whatever/This%20is.a.Test

which on ODS-5 will map to

  WHATEVER:[000000]This^_is^.a.Test

cannot simply be transformed into

  WHATEVER:[000000]This^_is^.a.Test.DIR

to check it's existance, it must be completely 'remapped' via

  /whatever/This%20is.a.Test/

into a directory specification

  WHATEVER:[This^_is^.a^.Test]

and from that the underlying directory file

  WHATEVER:[000000]This^_is^.a^.Test.DIR

may be derived.  Because of these complications it is always desirable for a
client to request directories using a trailing slash!  Sigh.


DREAMWEAVER SILLINESS
---------------------
Dreamwever 8 (at least, the only version I have access to) insists on using a
URI with a trailing "/./" occasionally (I'm guessing to specify the "current"
directory - c.f.  "/../", or "parent" syntax).  Just absorb this internally
using a mapping internal redirect along the lines of

  redirect /webdav/**/./ /webdav/*/


MICROSOFT WEBDAV MINI-REDIR
---------------------------
Microsoft approach WebDAV in their own inimitable fashion.  Hence Microsoft
agents, considering their ubiquity, including their mini-redirector are
specifically looked for and functionality modified to accomodate them.


MICROSOFT MISCELLANEA
---------------------
A cornucopia of of minor and major annoyances!

Some general references:

  http://greenbytes.de/tech/webdav/webdav-redirector-list.html
  http://greenbytes.de/tech/webdav/webfolder-client-list.html
  http://www.zorched.net/2006/03/01/more-webdav-tips-tricks-and-bugs/
  http://www.webdavsystem.com/server/documentation/troubleshooting
  http://www.webdavsystem.com/documentation/troubleshooting
  http://code.google.com/p/sabredav/wiki/Windows
  http://ulihansen.kicks-ass.net/aero/webdav/
  http://chapters.marssociety.org/webdav/

Command-line network configuration:

  C:\whatever> NET USE Z: http://the.host.name/folder/
  C:\whatever> NET USE Z: /DELETE

1) Mapping

Microsoft agents (at least) seem to request the server OPTIONS of the server
root regardless of any path provided with the NET USE or other network drive
mapping employed.  To selectively map such a request into a path that has
WebDAV enabled on it (and will therefore respond with the DAV-related options)
use a conditional redirect rule.  For example

  if (webdav:)
     if (request-method:OPTIONS) redirect / /dav-path/
  endif

or if only required for MS agents then something more specific

  if (webdav:MSagent)
     if (request-method:OPTIONS) redirect / /dav-path/
  endif

Subsequent rules will probably be required to map typeless directory requests
to the actual directory required.

  redirect /dav-path /dav-path/
  pass /dav-path/* /dav_root/* webdav=read

2) FrontPage Extensions

Requests containing paths /_vti_inf.html and /_vti_bin/* are related to
FrontPage protocol discovery probing.  They can be adequately handled using a
mapping rule lsuch as the following:

  pass /_vti_* "404 Not an MS platform!"

3) OPTIONS header "MS-Author-Via: DAV"

  http://msdn2.microsoft.com/en-us/library/ms691698.aspx

If the server's response does not contain an MS-Author-Via header, the OLE DB
Provider for Internet Publishing loads the WEC and WebDAV protocol drivers one
at a time (WEC first, WebDAV second) and asks them, "Do you know how to handle
this URL?", specifying the exact URL passed in by the client. The first
protocol which responds "yes" is selected. If neither protocol driver responds
"yes" then the method which triggered the automatic driver selection (usually
IBindResource::Bind) fails with an OLE DB Provider for Internet Publishing
specific error code IPP_E_SERVERTYPE_NOT_SUPPORTED.

4) Repairing broken XP Web Folders

  http://chapters.marssociety.org/webdav/

Some Windows XP machines have a broken Web Folders installation. Microsoft
includes a Web Folders repair utility built in to Windows to correct the
problem. Use the following steps to fix the problem:

   1. Click on the "Start" menu in the lower left corner, and select "Run..."
   2. Type in "webfldrs.msi" and click the "OK" button.
   3. Click on the "Select reinstall mode" button.
   4. Select *ALL* of the checkboxes *except* for the second one
      ("Reinstall only if file is missing").
   5. Click on the "OK" button.
   6. Click on the "Reinstall" button.
   7. After the reinstallation is complete, reboot the computer. 

5) Adding a port number to the webfolder-address

Attach the port-number (80 by default) to the http-address you enter into the
field of the "My Network Places"-assistant.  As you can see in the following
image and the linked screenshot, this will force Windows XP to use the
"Microsoft Data Access Internet Publishing Provider DAV 1.1" mechanism instead
of "Microsoft-WebDAV-MiniRedir/5.1.2600".

6) Adding a number-sign ("#") to the webfolder-address

It is also possible to add the number sign # to the http-address you enter into
the field of the "My Network Places"-assistant.  As you can see in the following
image and the linked screenshot, this will also force Windows XP to use the
"Microsoft Data Access Internet Publishing Provider DAV 1.1" mechanism instead
of "Microsoft-WebDAV-MiniRedir/5.1.2600".

  http://the.host.name/folder#

7) Force Windows XP to use Basic Authentication

There is a third way to get this working from the client-site.  As described in
the Microsoft Knowledge Base, Article ID: 841215, Windows XP disables "Basic
Auth" in his "Microsoft-WebDAV-MiniRedir/5.1.2600"-mechanism by default for
security reasons.  See description below.

8) Avoiding Microsoft Property Clutter

The MS WebDAV implementation (XP at least) sets properties corresponding to
Win32 file characteristics against all files it writes.  Much of these are
timestamps already corresponding to existing live data.

  <NS:Win32CreationTime xmlns:NS="urn:schemas-microsoft-com:">
  Sat, 29 Sep 2007 13:06:33 GMT</NS:Win32CreationTime>
  <NS:Win32LastAccessTime xmlns:NS="urn:schemas-microsoft-com:">
  Fri, 19 Jun 2009 20:28:48 GMT</NS:Win32LastAccessTime>
  <NS:Win32LastModifiedTime xmlns:NS="urn:schemas-microsoft-com:">
  Fri, 19 Jun 2009 20:28:48 GMT</NS:Win32LastModifiedTime>
  <NS:Win32FileAttributes xmlns:NS="urn:schemas-microsoft-com:">
  00000020</NS:Win32FileAttributes>

To avoid this *.*__wasdav; meta-data file clutter map the path with a
WEBDAV=NOWINPROP setting.  WASD WebDAV absorbs the associated PROPATCH
and emulates these from existing live data in PROPFINDs.

9) Error 0x800700DF: The file size exceeds the limit allowed and cannot be
saved (per JPP)

"In my case I try to copy file over WEBDAV to WEB Client connection e.g. I have
mapped drive to web site. file is about 70MB I can copy small files from the
same WEBDav folder."

   HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters

   1. Right click on the FileSizeLimitInBytes and click Modify
   2. Click on Decimal
   3. In the Value data box, type 4294967295, and then click OK. Note this 
      sets the maximum you can download from the Webdav to 4 gig at one time,
      I havent figured out how to make it unlimited so if you want to download
      more you need to split it up.

  http://social.answers.microsoft.com/Forums/en/xphardware/thread/d208bba6-920c-4639-bd45-f345f462934f


MICROSOFT XP EXPLORER BASIC AUTHENTICATION
------------------------------------------

  http://www.microsoft.com/technet/prodtechnol/winxppro/maintain/sp2netwk.mspx

  http://support.microsoft.com/kb/841215 (Vista & XP SP2)


You can enable BasicAuth by adding the following registry key and setting it to
a non-zero value:

  HKEY_LOCAL_MACHINE\SYSTEM
  \CurrentControlSet\Services\WebClient\Parameters\UseBasicAuth (DWORD)

If you delete the registry key or set it to 0, the behavior reverts to the
default, or disabling the use of BasicAuth.

Disabling Basic Authentication over a clear channel:

Because the DAVRdr is part of the remote file system stack, a computer is open
to attack whenever an attempt is made to remotely access files. Although the
threat to other applications that use the Internet APIs is less severe than it
is for the DAVRdr, a similar attack is possible whenever an application (or the
user) attempts to access a URL. For this reason, WinInet is exposing the
mechanism by which the DAVRdr disables BasicAuth to other users of the Internet
APIs.

With Windows XP Service Pack 2, there are two ways to block the use of Basic
Authentication over clear (or unencrypted) channels:
	
Create the following registry key and set it to a non-zero value.

  HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion
  \InternetSettings\DisableBasicOverClearChannel (DWORD)

This prevents WININET from attempting to use BasicAuth unless the channel is
secured (HTTPS or SSL).
	
The application can disable the use of BasicAuth for its connections by setting
the AUTH_FLAG_DISABLE_BASIC_CLEARCHANNEL flag (0x4) in the value supplied in
the call to InternetSetOption using INTERNET_OPTION_AUTH_FLAGS.

*** AND THEN RESTART WINDOWS ***


VERSION HISTORY
---------------
09-SEP-2020  MGD  DavWebGetHead..() specifically handle WebDAV GET and HEAD
01-AUG-2020  MGD  DavWebRequest() remove after RequestParseExecute() fix
14-MAY-2020  MGD  DavWebRequest() remove requirement for logical name
                    WASD_HTTP2_WEBDAV after WebDAV over HTTP/2 tested
12-SEP-2018  MGD  bugfix; significant refactor of locking
04-AUG-2018  MGD  bugfix; DavWebMicrosoftDetect() before ->WebDavTaskPtr
13-APR-2018  MGD  DavWebRundown() explicitly aborts WebDAV processing
                  bugfix; DavWebRequest() no DavWebEnd() until task set up
19-NOV-2017  MGD  (almost) always DavWebEnd() not RequestEnd()
06-MAY-2015  MGD  bugfix; DavWebDestination() URI and URL (Total Commander)
29-APR-2015  MGD  DavWebRequest() allow bodies with any and no Content-Type:
                    then in DavWebRequest2() check for XML in the body content
28-JUN-2014  MGD  metadata subdirectory or independent directory
                    e.g. [.^.dav] and DKA0:[WASDDAV] (see DAVMAETA.C)
                    via WASD_CONFIG_GLOBAL [WebDAVmetaDir] directive
                     or WASD_CONFIG_MAP webdav=meta=dir=<string> path SETing
                  DavLockUrnUuidToken() move from opaque to urn:uuid token
                    (OS X 10.9 started having issues with "opaquetoken"s)
                  PutWebDavBegin() migrate into DavWebPutBegin()
                  bugfix; DavWebCreateDir() set SYSPRV access, propagate rest
29-MAR-2012  MGD  bugfix; DavWebMicrosoftMunge2() just the header field
09-JUN-2010  MGD  bugfix; DavWebDestination() et al '^[', '^.' and '^]'
15-MAY-2010  JPP  bugfix; DavWebSlashlessMunge() enable SYSPRV while
                  calling  OdsFileExists()
02-SEP-2009  MGD  bugfix; scanning backwards through extended file spec
29-AUG-2009  MGD  DavWebMultiStatus() simplify HREF generation
24-JUN-2009  MGD  bugfix; DavWebEnqueue() EnqName memcpy() offset
31-DEC-2006  MGD  initial
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

#include <stdio.h>
#include <ctype.h>
#include <errno.h>

#include <lckdef.h>
#include <libdef.h>
#include <lib$routines.h>

#include "wasd.h"
#include "davweb.h"

#define WASD_MODULE "DAVWEB"

#ifndef WASD_WEBDAV
#define WASD_WEBDAV 1
#endif

/******************/
/* global storage */
/******************/

BOOL  WebDavEnabled,
      WebDavHttp2Enabled,
      WebDavLockingEnabled,
      WebDavQuotaEnabled;

int  WebDavLockCollectionDepth;
     WebDavLockTimeoutDefaultSeconds,
     WebDavLockTimeoutMaxSeconds;

/********************/
/* external storage */
/********************/

extern int  EfnWait,
            EfnNoWait;

extern unsigned long  SysLckMask[],
                      SysPrvMask[];

extern char  ErrorSanityCheck[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern LIST_HEAD  RequestList;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
*/

DavWebInit ()

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebInit()");

   if (Config.cfWebDav.DavEnabled)
   {
      WebDavLockTimeoutDefaultSeconds =
         Config.cfWebDav.LockTimeoutDefaultSeconds;
      if (!WebDavLockTimeoutDefaultSeconds)
         WebDavLockTimeoutDefaultSeconds = WEBDAV_LOCK_TIMEOUT_DEFAULT;

      WebDavLockTimeoutMaxSeconds = Config.cfWebDav.LockTimeoutMaxSeconds;
      if (!WebDavLockTimeoutMaxSeconds)
         WebDavLockTimeoutMaxSeconds = WEBDAV_LOCK_TIMEOUT_MAX;

      WebDavLockCollectionDepth = Config.cfWebDav.LockCollectionDepth;

      if (!WebDavLockCollectionDepth) WebDavLockCollectionDepth = 1;

      WebDavEnabled = true;
      WebDavLockingEnabled = Config.cfWebDav.LockingEnabled;
      WebDavQuotaEnabled = Config.cfWebDav.QuotaEnabled;

      InstanceGblSecSetLong (&AccountingPtr->WebDavEnabled, true);

      WebDavHttp2Enabled = (SysTrnLnm(WASD_HTTP2_WEBDAV) != NULL);
      
      FaoToStdout ("%HTTPD-I-WEBDAV, enabled; locking !AZ; quota !AZ\n",
                   WebDavLockingEnabled ? "enabled" : "disabled",
                   WebDavQuotaEnabled ? "enabled" : "disabled");

      DavXmlInit ();

      return;
   }

   InstanceGblSecSetLong (&AccountingPtr->WebDavEnabled, false);

   FaoToStdout ("%HTTPD-I-WEBDAV, disabled\n");
}

/*****************************************************************************/
/*
Begin processing WebDAV-specific request.

This function can be called twice.  The second call is after any request body
("text/xml" properties for the WebDAV request) has been completely read (see
BodyReadBegin() below).
*/

DavWebRequest (REQUEST_STRUCT *rqptr)

{
   int  status;
   char *cptr, *sptr, *zptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebRequest() !AZ nam$m_wildcard:!&B",
                rqptr->ParseOds.ExpFileName, 
                (rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD));

   if (!(tkptr = rqptr->WebDavTaskPtr))
   {
      InstanceGblSecIncrLong (&AccountingPtr->DoWebDavCount);

      /* ensure WebDAV is mapped to access this path */
      if (!(rqptr->rqPathSet.WebDavServer ||
            rqptr->rqPathSet.WebDavProfile ||
            rqptr->rqPathSet.WebDavRead || 
            rqptr->rqPathSet.WebDavWrite))
      {
         rqptr->WebDavMethod = rqptr->WebDavRequest =
            rqptr->WhiffOfWebDav = false;
         DavWebResponse (rqptr, 403, 0,
                         "PATH is not mapped for WebDAV access", FI_LI);
         RequestEnd (rqptr);
         return;
      }

      if (DavMetaDir (rqptr, &rqptr->ParseOds))
      {
         ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI);
         RequestEnd (rqptr);
         return;
      }

      /* set up the task structure */
      tkptr = rqptr->WebDavTaskPtr = VmGetHeap (rqptr, sizeof(WEBDAV_TASK));

      OdsStructInit (&tkptr->DestOds, false);
      OdsStructInit (&tkptr->SearchOds, false);

      /* if globally enabled and not disabled for the path */
      if (Config.cfWebDav.LockingEnabled && !rqptr->rqPathSet.WebDavNoLock)
         tkptr->LockingEnabled = true;
      /* just in case we decide to introduce a SETing for this */
      tkptr->QuotaEnabled = Config.cfWebDav.LockingEnabled;

      if (WATCHING (rqptr, WATCH_WEBDAV))
      {
         if (rqptr->rqPathSet.WebDavAll && !(rqptr->WebDavMethod ||
                                             rqptr->WebDavRequest ||
                                             rqptr->WhiffOfWebDav))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "WEBDAV=ALL");
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "AGENT !AZ",
                    rqptr->rqHeader.UserAgentPtr ?
                    rqptr->rqHeader.UserAgentPtr : "?");
      }

      if (rqptr->rqPathSet.WebDavMetaDirPtr)
         tkptr->MetaFileDirPtr = rqptr->rqPathSet.WebDavMetaDirPtr;
      else
      if (Config.cfWebDav.MetaFileDirectoryLength)
         tkptr->MetaFileDirPtr = Config.cfWebDav.MetaFileDirectory;
      else
         tkptr->MetaFileDirPtr = NULL;
   }

   if (rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD)
   {
      DavWebResponse (rqptr, 400, RMS$_WLD, NULL, FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   /* see if it corresponds to an actual directory name */
   status = DavWebSlashlessMunge (rqptr, true);
   if (VMSnok (status))
   {
      DavWebResponse (rqptr, 500, status, NULL, FI_LI);
      DavWebEnd (rqptr);
      return;
   }
   if (status == SS$_SUPERSEDE)
      tkptr->SlashlessDir = tkptr->IsDirectory = true;
   else
   if (status == SS$_NOTMODIFIED)
      tkptr->IsDirectory = true;

   if (rqptr->rqHeader.Method == HTTP_METHOD_PUT)
   {
      /* PUT handles it's own body processing */
      if (tkptr->IsDirectory)
      {
         DavWebResponse (rqptr, 405, 0, "cannot PUT a collection", FI_LI);
         DavWebEnd (rqptr);
      }
      else
         PutBegin (rqptr, &DavWebEnd);
      return;
   }

   if (rqptr->rqHeader.ContentLength64)
   {
      /* read all of request body */
      BodyReadBegin (rqptr, &DavWebRequest2, &BodyProcessReadAll);
      return;
   }

   DavWebRequest2 (rqptr);
}

/*****************************************************************************/
/*
Continue with initiating a WebDAV request.
Called directly or asynchronously after beginning a PUT request.
*/

DavWebRequest2 (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *zptr;
   STR_DSC_AUTO (XmlTextDsc);
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebRequest2()");

   tkptr = rqptr->WebDavTaskPtr;

   if (rqptr->rqBody.DataPtr)
   {
      if (rqptr->rqBody.DataStatus != SS$_ENDOFFILE)
      {
         DavWebEnd (rqptr);
         return;
      }

      /* examine the content for what looks like XML */
      zptr = (cptr = rqptr->rqBody.DataPtr) + rqptr->rqBody.DataCount;
      while (isspace(*cptr) && cptr < zptr) cptr++;
      if (cptr >= zptr-6 || !MATCH6 (cptr, "<?xml "))
      {
         DavWebResponse (rqptr, 400, 0, "not <?xml content", FI_LI);
         DavWebEnd (rqptr);
         return;
      }

      if (WATCHING (rqptr, WATCH_WEBDAV))
      {
         /* just tidy the WATCH for any trailing newline(s) */
         int  len;
         if (len = rqptr->rqBody.DataCount) len--;
         while (rqptr->rqBody.DataPtr[len] == '\n' && len) len--;
         if (len) len++;
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "REQUEST XML");
         WatchDataFormatted ("!#AZ\n", len, rqptr->rqBody.DataPtr);
      }

      StrDscThis (NULL, &XmlTextDsc, rqptr->rqBody.DataPtr,
                                     rqptr->rqBody.DataCount);
      status = DavXmlParseText (rqptr, &XmlTextDsc);
      if (VMSnok (status))
      {
         /* "out of memory" is a fatal error */
         if (status == LIB$_INSVIRMEM)
            ErrorExitVmsStatus (status, "DavWebEnd()", FI_LI);
         DavWebEnd (rqptr);
         return;
      }
   }

   if (rqptr->rqHeader.WebDavDepthPtr)
   {
      /* sanity check the depth field */
      if (rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_ZERO &&
          rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_ONE &&
          rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_INFINITY)
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DEPTH? !UL",
                       rqptr->rqHeader.WebDavDepth);
         rqptr->rqResponse.HttpStatus = 500;
         ErrorVmsStatus (rqptr, SS$_ABORT, FI_LI);
         DavWebEnd (rqptr);
         return;
      }

      tkptr->ToDepth = rqptr->rqHeader.WebDavDepth;
   }
   else
      tkptr->ToDepth = WEBDAV_DEPTH_INFINITY;

   if (rqptr->rqHeader.WebDavOverwritePtr)
      tkptr->CopyData.MoveOverwrite = rqptr->rqHeader.WebDavOverwrite;
   else
      tkptr->CopyData.MoveOverwrite = true;

   if (DavMetaDir (rqptr, &rqptr->ParseOds))
   {
      ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   switch (rqptr->rqHeader.Method)
   {
      case HTTP_METHOD_GET :
      case HTTP_METHOD_HEAD :

          if (rqptr->ParseOds.NamNameLength ||
              rqptr->ParseOds.NamTypeLength)
           {
              /* just another file request */
              DavWebGetHead (rqptr);
           }
           else
           {
              /* collection ("Index of") processed by non-WebDAV functions */
              tkptr->NotWebDavFun = true;
              SysDclAst (RequestExecutePostCache3, rqptr);
           }
           return;

      case HTTP_METHOD_POST :
           /* processed by non-WebDAV module functions */
           tkptr->NotWebDavFun = true;
           SysDclAst (RequestExecutePostCache3, rqptr);
           return;

      case HTTP_METHOD_WEBDAV_COPY :
           DavCopyBegin (rqptr);
           return;

      case HTTP_METHOD_DELETE :
           DavDeleteBegin (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_LOCK :
           if (!tkptr->LockingEnabled)
           {
              /* if it gets this far the path is SET disabled  */
              DavWebResponse (rqptr, 501, 0, "Locking disabled", FI_LI);
              DavWebEnd (rqptr);
              return;
           }
           DavLockBegin (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_MKCOL :
           DavWebMkCol (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_MOVE :
           DavMoveBegin (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_PROPFIND :
           DavPropBegin (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_PROPPATCH :
           DavPropPatchBegin (rqptr);
           return;

      case HTTP_METHOD_WEBDAV_UNLOCK :
           if (!tkptr->LockingEnabled)
           {
              /* if it gets this far the path is SET disabled  */
              DavWebResponse (rqptr, 501, 0, "Locking disabled", FI_LI);
              DavWebEnd (rqptr);
              return;
           }
           DavUnLockBegin (rqptr);
           return;

      default :
           DavWebResponse (rqptr, 501, 0, "unknown WebDAV method", FI_LI);
           DavWebEnd (rqptr);
   }
}

/*****************************************************************************/
/*
WebDAV GET and HEAD doesn't need all the convolutions of general HTTP file and
directory processing and for authorisation needs to be in the WebDAV fold.
*/

void DavWebGetHead (REQUEST_STRUCT *rqptr)

{
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebGetHead()");

   FileBegin (rqptr, DavWebGetHeadEnd, DavWebGetHead404,
              &rqptr->Md5HashPath,
              rqptr->ParseOds.ExpFileName,
              rqptr->rqContentInfo.ContentTypePtr);
}

/*****************************************************************************/
/*
End WebDAV file GET/HEAD.
*/

void DavWebGetHeadEnd (REQUEST_STRUCT *rqptr)

{
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebEndGetHead()");

   rqptr->WebDavTaskPtr = NULL;
   RequestEnd (rqptr);
}

/*****************************************************************************/
/*
Generate a 404 (not found) response for WebDAV file GET/HEAD and end.
*/

void DavWebGetHead404 (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse404()");

   ResponseHeader (rqptr, 404, NULL, 0, NULL, NULL);
   rqptr->WebDavTaskPtr = NULL;
   RequestEnd (rqptr);
}

/*****************************************************************************/
/*
Begin WebDAV-specific PUT processing.  This function will be called multiple
times as asynchronous testing of the DLM and meta locking occurs.
*/ 
 
void DavWebPutBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_PUT))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "DavWebPutBegin() !UL",
                 rqptr->WebDavTaskPtr->TestLockState);

   tkptr = rqptr->WebDavTaskPtr;

   /***********/
   /* locking */
   /***********/

   switch (tkptr->TestLockState)
   {
      case 0 :

         /* establish an exclusive VMS DLM lock on the resource */
         tkptr->TestLockState++;
         DavWebDlmEnqueue (rqptr, &tkptr->DlmSource,
                           rqptr->ParseOds.ExpFileName,
                           NULL, true, false, DavWebPutBegin, rqptr);
         return;

      case 1 :

         if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            PutEnd (rqptr);
            return;
         }

         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavWebPutBegin, rqptr);
         return;

      case 2 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "target locked", FI_LI);
            PutEnd (rqptr);
            return;
         }

         /*********************/
         /* webdav=put=locked */
         /*********************/

         if (rqptr->rqPathSet.WebDavPutLock)
         {
            /* test for no lock at all */
            if (!DavLockFindToken (rqptr))
            {
               DavWebResponse (rqptr, 412, 0,
"target NOT locked (path set WEBDAV=PUT=LOCKED)",
                               FI_LI);
               PutEnd (rqptr);
               return;
            }
         }
   }

   /************/
   /* continue */
   /************/

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                 "PUT !AZ", rqptr->ParseOds.NamDevicePtr);

   rqptr->PutTaskPtr->ContentLengthFixed = true;

   BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessByVirtualBlock);
}

/*****************************************************************************/
/*
End processing of WebDAV-specific request.  Any necessary clean-up.
*/

void DavWebEnd (REQUEST_STRUCT *rqptr)

{
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebEnd()");

   tkptr = rqptr->WebDavTaskPtr;

   /* dequeue any VMS DLM locks that *may* be extant */
   DavWebDequeue (&tkptr->DlmSource);
   DavWebDequeue (&tkptr->DlmDestin);
   DavWebDequeue (&tkptr->MetaData.DlmData);
   DavWebDequeue (&tkptr->MetaData.DlmDest);

   /* belt and braces */
   OdsParseRelease (&tkptr->DestOds);
   OdsParseRelease (&tkptr->SearchOds);

   /* indicate the task has successfully concluded */
   rqptr->WebDavTaskPtr = NULL;

   RequestEnd (rqptr);
}

/*****************************************************************************/
/*
Provide a response (with a little traceability, particularly useful when 
troubleshooting via WATCH).  Generally WebDAV clients are applications other
than browsers and so response bodies with human-readable error explanations are
unnecessary and so only headers are provided.  If a response body is required
then ResponseHeader() should be used instead.
*/

DavWebResponse
(
REQUEST_STRUCT *rqptr,
int HttpStatus,
int VmsStatus,
char *WatchExplanation,
char *SourceModuleName,
int SourceLineNumber
)
{
   char  ExplanBuf [256],
         StatusBuf [256];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse()");

   if (!HttpStatus) HttpStatus = VmsToHttpStatus (VmsStatus);

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (WatchExplanation)
         FaoToBuffer (ExplanBuf, sizeof(ExplanBuf), NULL,
                      " !AZ", WatchExplanation); 
      else
         ExplanBuf[0] = '\0';

      if (VmsStatus)
         FaoToBuffer (StatusBuf, sizeof(StatusBuf), NULL, " !&S", VmsStatus); 
      else
         StatusBuf[0] = '\0';

      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                 "STATUS !UL (!AZ)!AZ!AZ (!AZ:!UL)",
                 HttpStatus, HttpStatusCodeText(HttpStatus),
                 ExplanBuf, StatusBuf,
                 SourceModuleName, SourceLineNumber);
   }

   ResponseHeader (rqptr, HttpStatus, NULL, 0, NULL, NULL);
}

/*****************************************************************************/
/*
Generate a 201 (created) response complete with "Location:.." field.
DaveWebHref() must be called prior to set up URL components.
*/

DavWebResponse201 (REQUEST_STRUCT *rqptr)

{
   char  LocationField [ODS_MAX_FILE_NAME_LENGTH+128+32];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse201()");

   FaoToBuffer (LocationField, sizeof(LocationField), NULL,
                "Location: !AZ!&%AZ\r\n",
                rqptr->WebDavTaskPtr->HrefHost,
                rqptr->WebDavTaskPtr->HrefPath);

   ResponseHeader (rqptr, 201, NULL, 0, NULL, LocationField);
}

/*****************************************************************************/
/*
Provide a multistatus response header and leading XML of the response body.
*/

DavWebResponse207 (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse207()");

   if (rqptr->rqResponse.HttpStatus) return;

   ResponseHeader (rqptr, 207, "text/xml; charset=\"utf-8\"", -1, NULL, NULL);

   FaoToNet (rqptr,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\
<D:multistatus xmlns:D=\"DAV:\">\n");

}

/*****************************************************************************/
/*
Write a WebDAV multistatus XML entry.
*/ 
 
DavWebMultiStatus
(
REQUEST_STRUCT *rqptr,
int VmsStatus,
char *FileName
)
{
   int  HttpStatus;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebMultiStatus() !&S !&Z !UL",
                 VmsStatus, FileName, rqptr->WebDavTaskPtr->ToDepth);

   tkptr = rqptr->WebDavTaskPtr;

   if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr);

   HttpStatus = VmsToHttpStatus (VmsStatus);

   DavWebHref (rqptr, FileName, 0);

   FaoToNet (rqptr,
" <D:response>\n\
  <D:href>!AZ!&%AZ</D:href>\n\
  <D:status>HTTP/1.1 !UL !AZ</D:status>\n\
 </D:response>\n",
                tkptr->HrefHost, tkptr->HrefPath,
                HttpStatus, HttpStatusCodeText(HttpStatus));
}

/*****************************************************************************/
/*
Generate an <href> URL from the specified file name.  Place the path into the
tkptr->HrefPath buffer (not URL-escaped).  On the first (and perhaps only) time
through also generate an appropriate scheme://host:port and place into
tkptr->HrefHost. Use both in conjuction as required.
*/ 
 
DavWebHref
(
REQUEST_STRUCT *rqptr,
char *FileName,
int ReallyADirStatus
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   char  SpecBuffer [ODS_MAX_FILE_NAME_LENGTH+1];
   ODS_STRUCT  SpecOds;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebHref() !&Z !&S", FileName, ReallyADirStatus);

   tkptr = rqptr->WebDavTaskPtr;

   if (!tkptr->HrefHost[0])
   {
      /* first (perhaps only) time through */
      zptr = (sptr = tkptr->HrefHost) + sizeof(tkptr->HrefHost)-1;
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         cptr = "http://";
      else
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
         cptr = "https://";
      else
         cptr = NULL;

      if (cptr)
      {
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0])
            cptr = rqptr->rqHeader.HostPtr;
         else
            cptr = rqptr->ServicePtr->ServerHostPort;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
#if WATCH_MOD
      else
         ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); 
#endif /* WATCH_MOD */
      *sptr = '\0';
   }

   zptr = (sptr = SpecBuffer) + sizeof(SpecBuffer)-1;
   for (cptr = FileName; *cptr && sptr < zptr; cptr++)
   {
      if (*cptr & 0x80)
      {
         if (!rqptr->rqPathSet.OdsName ||
             rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT)
         {
            /* convert to UTF-8 */
            *sptr++ = ((*cptr & 0xc0) >> 6) | 0xc0;
            if (sptr < zptr) *sptr++ = (*cptr & 0x3f) | 0x80;
         }
         else
            *sptr++ = *cptr;
      }
      else
         *sptr++ = *cptr;
   }
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return ("");
   }
   *sptr = '\0';
   while (sptr > SpecBuffer &&
          (*sptr != '.' || SAME2(sptr-1,'^.')) &&
          (*sptr != ';' || SAME2(sptr-1,'^;')) &&
          (*sptr != ']' || SAME2(sptr-1,'^]')) &&
          (*sptr != '[' || SAME2(sptr-1,'^[' || SAME2(sptr-1,']['))))
       sptr--;
   if (*sptr == ';')
   {
      /* eliminate the version component */
      *sptr = '\0';
      for (sptr--;
           sptr > SpecBuffer &&
              (*sptr != '.' || SAME2(sptr-1,'^.')) &&
              (*sptr != ']' || SAME2(sptr-1,'^]')) &&
              (*sptr != '[' || SAME2(sptr-1,'^[') || SAME2(sptr-1,']['));
           sptr--);
   }
   if (MATCH5 (sptr, ".DIR\0"))
   {
      if (!ReallyADirStatus)
      {
         OdsStructInit (&SpecOds, true);
         AuthAccessEnable (rqptr, SpecBuffer, AUTH_ACCESS_READ);
         status = OdsParse (&SpecOds, SpecBuffer, sptr-SpecBuffer+4,
                            NULL, 0, 0, NULL, rqptr);
         AuthAccessEnable (rqptr, NULL, 0);
         if (VMSnok(status)) ErrorNoticed (rqptr, status, SpecBuffer, FI_LI); 
         ReallyADirStatus = OdsReallyADir (rqptr, &SpecOds);
         OdsParseRelease (&SpecOds);
      }
      if (VMSok (ReallyADirStatus))
      {
         /* munge into a directory specification */
         SET2(sptr,']\0');
         for (sptr--;
              sptr > SpecBuffer &&
                 (*sptr != ']' || SAME2(sptr-1,'^]'));
              sptr--);
         if (*sptr == ']') *sptr = '.';
      }
   }
   while (*sptr) sptr++;

   /* map the file-system name into a URL-compliant format */
   tkptr->HrefPath[0] = '\0';
   sptr = MapUrl_Map (tkptr->HrefPath, sizeof(tkptr->HrefPath),
                      SpecBuffer, sptr-SpecBuffer,
                      NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL);
   if (!sptr[0] && sptr[1])
      ErrorNoticed (rqptr, SS$_BUGCHECK, sptr+1, FI_LI); 

   /*
      Nautilus (gvfs/1.2.2), at least, considers it an invalid response
      if the leading href= path portions do not case-match.
   */
   cptr = rqptr->rqHeader.PathInfoPtr;
   sptr = tkptr->HrefPath;
   while (*cptr && *sptr)
   {
      if (*cptr != *sptr && to_lower(*cptr) == to_lower(*sptr)) *sptr = *cptr;
      cptr++;
      sptr++;
   }
}

/*****************************************************************************/
/*
Copy the file specification into the buffer upper-casing it, removing any
redundant MFD, and trimming any trailing version and empty file type.  This
produces a standardised file specification.  Then generate the hash used to
represent the resource.
*/

DavWebNameHash 
(
char *FileSpec,
unsigned long *HashPtr
)
{
   char  *cptr, *sptr, *zptr;
   char  Buffer [ODS_MAX_FILE_NAME_LENGTH+1];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                 "DavWebNameHash() !&Z", FileSpec);

   zptr = (sptr = Buffer) + sizeof(Buffer)-1;
   cptr = FileSpec;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '[' && MATCH8 (cptr, "[000000."))
      {
         /* eliminate any redundant MFD component */
         cptr += 8;
         *sptr++ = '[';
      }
      else
      {
         /* the hash must be case-insensitive */
         *sptr++ = to_lower(*cptr++);
      }
   }
   *sptr = '\0';
   /* eliminate any trailing version component delimiter */
   for (cptr = sptr;
        cptr > Buffer && *cptr != ';' && *cptr != '.' && *cptr != ']' &&
           !SAME2(cptr-1,'^;');
        cptr--);
   if (*cptr == ';')
   {
      sptr = cptr;
      *sptr = '\0';
   }
   /* eliminate any trailing empty type */
   if (sptr > Buffer && *(sptr-1) == '.' && !SAME2(sptr-2,'^.'))
      sptr--;
   *sptr = '\0';

   Md5Digest (Buffer, sptr - Buffer, HashPtr);

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "!&Z !8XL!8XL!8XL!8XL",
                 Buffer, HashPtr[0], HashPtr[1], HashPtr[2], HashPtr[3]);
}

/*****************************************************************************/
/*
Create a directory name from a directory filename, i.e. DEVICE:[DIRECTORY] from
DEVICE:[000000]DIRECTORY.DIR and DEVICE:[DIR1.DIR2] from DEVICE:[DIR1]DIR2.DIR. 
Return the length of the directory name.
*/

int DavWebDirFromName 
(
char *FileSpec,
char *Buffer,
int SizeOfBuffer
)
{
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                 "DavWebDirFromName() !&Z", FileSpec);

   zptr = (sptr = Buffer) + SizeOfBuffer-1;
   cptr = FileSpec;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '[' && MATCH8 (cptr, "[000000."))
      {
         /* eliminate any redundant MFD component */
         cptr += 8;
         *sptr++ = '[';
      }
      else
      if (*cptr == ']' && *(cptr+1) == '[')
      {
         /* eliminate concealed device component */
         cptr += 2;
      }
      else
      if (*cptr == ']' && *(sptr-1) == ':')
      {
         /* top-level directory */
         cptr++;
         *sptr++ = '[';
      }
      else
      if (*cptr == ']' && *(sptr-1) != ':')
      {
         /* end of directory spec */
         cptr++;
         *sptr++ = '.';
      }
      else
         *sptr++ = *cptr++;
   }
   *sptr = '\0';
   /* eliminate the trailing type (and version) */
   for (cptr = sptr;
        sptr > Buffer && (*sptr != '.' || SAME2(sptr-1,'^.'));
        sptr--);
   if (sptr < zptr) *sptr++ = ']';
   *sptr = '\0';

   return (sptr - Buffer);
}

/*****************************************************************************/
/*
Initialise the specified DLM structure, including generating the resource name
and hash from the supplied file name.  Then enqueue a VMS lock on the resource
delivering a completion AST as specified if appropriate, or if NOQUEUE then
returning the enqueue status (which must be checked).  When the AST is
delivered the routine must check 'dlmptr->LockSb.lksb$w_status' for success or
failure.  For convenience (to the calling routine) if 'FileName' is NULL just
initialise the DLM structure and directly call the AST function.
*/

int DavWebDlmEnqueue
(
REQUEST_STRUCT *rqptr,
WEBDAV_DLM *dlmptr,
char *FileName,
unsigned long *HashPtr,
BOOL ExMode,
BOOL NoQueue,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   int  status;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebDlmEnqueue() !8XL", dlmptr);

   memset (dlmptr, 0, sizeof(WEBDAV_DLM));

   dlmptr->RequestPtr = rqptr;
   dlmptr->TaskPtr = rqptr->WebDavTaskPtr;

   if (HashPtr)
      memcpy (dlmptr->ResourceHash, HashPtr, sizeof(dlmptr->ResourceHash));
   else
      DavWebNameHash (FileName, dlmptr->ResourceHash);

   zptr = (sptr = dlmptr->ResourceName) + sizeof(dlmptr->ResourceName)-1;
   for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = to_lower(*cptr++));
   *sptr = '\0';

   status = DavWebEnqueue (dlmptr, ExMode, NoQueue, AstFunction, AstParam);

   return (status);
}

/*****************************************************************************/
/*
Enqueue a VMS DLM lock on the name hash.  Resource name is
"WASDDAV_bbbbbbbbbbbbbbbb" where the 'b' represents a byte of the file name
hash.  When the AST is delivered the routine must check
'dlmptr->LockSb.lksb$w_status' for success or failure.
*/

int DavWebEnqueue
(
WEBDAV_DLM *dlmptr,
BOOL ExMode,
BOOL NoQueue,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   static char EnqName [7+16] = "WASDAV_";
   static $DESCRIPTOR (EnqNameDsc, EnqName);

   int  status;
   unsigned long  EnqFlags,
                  EnqMode;
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   rqptr = dlmptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
"DavWebEnqueue() !8XL exmode:!&B noqueue:!&B !8XL!8XL!8XL!8XL !AZ",
                 dlmptr, ExMode, NoQueue,
                 dlmptr->ResourceHash[0], dlmptr->ResourceHash[1],
                 dlmptr->ResourceHash[2], dlmptr->ResourceHash[3],
                 dlmptr->ResourceName);

   memcpy (EnqName+7, &dlmptr->ResourceHash, sizeof(dlmptr->ResourceHash));

   if (NoQueue)
      EnqFlags = LCK$M_SYSTEM | LCK$M_NOQUEUE;
   else
      EnqFlags = LCK$M_SYSTEM;

   if (dlmptr->ExMode = ExMode)
      EnqMode = LCK$K_EXMODE;
   else
      EnqMode = LCK$K_CRMODE;

   sys$setprv (1, &SysLckMask, 0, 0);

   status = sys$enq (EfnNoWait, EnqMode, &dlmptr->LockSb, EnqFlags,
                     &EnqNameDsc, 0, AstFunction, AstParam, 0, 2, 0);

   sys$setprv (0, &SysLckMask, 0, 0);

   if (VMSnok (status) && status != SS$_NOTQUEUED)
      ErrorExitVmsStatus (status, "sys$enq()", FI_LI);

   return (status);
}

/*****************************************************************************/
/*
If a VMS DLM lock exists on the meta-data then dequeue it and return true
indicating to the calling routine it must let the AST routine finish
processing (which shows an SS$_ABORT status).  If the AST has already been
delivered return false.
*/

BOOL DavWebDequeue (WEBDAV_DLM *dlmptr)

{
   int  status;
   char  *cptr;
   REQUEST_STRUCT  *rqptr;
   struct lksb  *lksbptr;

   /*********/
   /* begin */
   /*********/

   if (!(rqptr = dlmptr->RequestPtr)) return (true);

   lksbptr = &dlmptr->LockSb;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebDequeue() !8XL !&B !&S !8XL!8XL!8XL!8XL !&Z",
                 dlmptr, lksbptr->lksb$l_lkid, lksbptr->lksb$w_status,
                 dlmptr->ResourceHash[0], dlmptr->ResourceHash[1],
                 dlmptr->ResourceHash[2], dlmptr->ResourceHash[3],
                 dlmptr->ResourceName);

   if (!lksbptr->lksb$l_lkid) return (false);

   if (VMSnok (lksbptr->lksb$w_status))
      ErrorNoticed (rqptr, lksbptr->lksb$w_status, NULL, FI_LI); 

   sys$setprv (1, &SysLckMask, 0, 0);

   status = sys$deq (lksbptr->lksb$l_lkid, 0, 0, LCK$M_CANCEL);
   if (status == SS$_CANCELGRANT)
      status = sys$deq (lksbptr->lksb$l_lkid, 0, 0, 0);
   else
   if (VMSok (status))
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "CANCELLED");

   sys$setprv (0, &SysLckMask, 0, 0);

   lksbptr->lksb$l_lkid = 0;

   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); 

   return (true);
}

/*****************************************************************************/
/*
Used via the CLI to DLM lock a resource for testing purposes.  Used to DLM
(b)lock access to a meta-data file or a resource file/directory.

'M' neta-data or 'R' resource, 'E' exclusive or 'C' for concurrent read, and
'Q' for queue or 'N' for no queue, and then the file name; as with:

$ HTTPD /TEST "webdav=dlm=MEQdevice:[directory]file.ext"
*/ 
 
int DavWebDlmTest (char *CliParam)

{
#if WATCH_CAT

   int  status;
   static WEBDAV_DLM  DlmData;
   static REQUEST_STRUCT  DummyRequest;

   /*********/
   /* begin */
   /*********/

   if (!CliParam)
   {
      fprintf (stdout, "AST lksb$w_status: %%X%08.08X\n",
               DlmData.LockSb.lksb$w_status);
      sys$hiber();
      return (DlmData.LockSb.lksb$w_status);
   }

   Watch.Module |= WATCH_MOD_WEBDAV;
   Watch.LogFile = 1;
   strzcpy (Watch.LogFileName, WATCH_NAME_DEFAULT, sizeof(Watch.LogFileName));
   DummyRequest.WatchItem = 1;

   status = DavWebDlmEnqueue (&DummyRequest, &DlmData,
                              CliParam+3, NULL,
                              to_upper(CliParam[1]) == 'E',
                              to_upper(CliParam[2]) == 'N',
                              &DavWebDlmTest, NULL);

   fprintf (stdout, "DavWebDlmEnqueue: %%X%08.08X\n", status);
   if (VMSok(status)) sys$hiber();
   return (status);

#else
   return (SS$_ABORT);
#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Create a collection (directory)!
*/ 
 
DavWebMkCol (REQUEST_STRUCT *rqptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMkCol()");

   if (rqptr->rqBody.DataPtr)
   {
      DavWebResponse (rqptr, 415, 0,
         "request body not defined for MKCOL", FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   if (rqptr->rqHeader.ContentLength64)
   {
      /* any body at all in fact! */
      DavWebResponse (rqptr, 415, 0, "request entity not supported", FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   status = DavWebSlashlessMunge (rqptr, false);
   if (VMSnok (status))
   {
      DavWebResponse (rqptr, 500, status, NULL, FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   DavWebMkCol2 (rqptr);
}

/*****************************************************************************/
/*
Test if it is locked and continue with the directory creation if not.  This
function will be called multiple times as asynchronous testing of the DLM and
meta locking occurs.
*/ 
 
DavWebMkCol2 (REQUEST_STRUCT *rqptr)

{
   int  len, status;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebMkCol2() !UL", tkptr->TestLockState);

   /***********/
   /* locking */
   /***********/

   switch (tkptr->TestLockState)
   {
      case 0 :

         /* establish a request (overall) VMS DLM lock on source */
         tkptr->TestLockState++;
         DavWebDlmEnqueue (rqptr, &tkptr->DlmSource,
                           rqptr->ParseOds.ExpFileName,
                           false, true, false,
                           DavWebMkCol2, rqptr);
         return;

      case 1 :

         if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavWebEnd (rqptr);
            return;
         }

         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavWebMkCol2, rqptr);
         return;

      case 2 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "source locked", FI_LI);
            DavWebEnd (rqptr);
            return;
         }
   }

   /**************/
   /* not locked */
   /**************/

   if (VMSnok (DavWebParentExists (NULL, rqptr->ParseOds.ExpFileName)))
   {
      DavWebResponse (rqptr, 412, 0, "parent does not exist", FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   len = rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.NamDevicePtr;

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "MKCOL !#AZ",
                 len, rqptr->ParseOds.NamDevicePtr);

   status = DavWebCreateDir (rqptr, rqptr->ParseOds.NamDevicePtr, len);

   if (status == SS$_CREATED)
   {
      DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0);
      DavWebResponse201 (rqptr);
   }
   else
   if (status == SS$_NORMAL)
      DavWebResponse (rqptr, 405, 0, "directory existed", FI_LI);
   else
   if (status == SS$_NOPRIV ||
       status == SS$_NORAD50)
      DavWebResponse (rqptr, 403, status, NULL, FI_LI);
   else
      DavWebResponse (rqptr, 500, status, NULL, FI_LI);

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Create a directory using lib$create_dir()!
Used by the above and MOVE and COPY when reproducing trees..
*/ 
 
int DavWebCreateDir
(
REQUEST_STRUCT *rqptr,
char *DirName,
int DirNameLength
)
{
   /* ensure SYSPRV can control the directory */
   static unsigned short  ProtEnable = 0x000f,
                          ProtValue = 0x0000;  /* S:RWED */
   static $DESCRIPTOR (DirNameDsc, "");

   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebCreateDir()");

   if (DirNameLength <= 0) DirNameLength = strlen(DirName);

   DirNameDsc.dsc$a_pointer = DirName;
   DirNameDsc.dsc$w_length = DirNameLength;

   AuthAccessEnable (rqptr, DirName, AUTH_ACCESS_WRITE);

   status = lib$create_dir (&DirNameDsc, 0, &ProtEnable, &ProtValue, 0, 0);

   if (WATCHING (rqptr, WATCH_WEBDAV))
      if (VMSok (status))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "CREATED !#AZ",
                    DirNameLength, DirName);
      else
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "CREATE !#AZ !&S",
                    DirNameLength, DirName, status);

   AuthAccessEnable (rqptr, 0, 0);

   return (status);
}

/*****************************************************************************/
/*
Check if the parent directory exists.  Return a VMS status;
*/ 
 
int DavWebParentExists
(
REQUEST_STRUCT *rqptr,
char *DirectoryName
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   char  FileSpec [ODS_MAX_FILE_NAME_LENGTH+1];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebParentExists() !AZ", DirectoryName);

   zptr = (sptr = FileSpec) + sizeof(FileSpec)-1;
   for (cptr = DirectoryName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr-- = '\0';
   if (sptr > FileSpec && *sptr == ']' && !SAME2(sptr-1,'^]'))
   {
      /* passed a directory specification */
      while (sptr > FileSpec &&
             (*sptr != '.' || SAME2(sptr-1,'^.')) &&
             (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--;
      if (*sptr == '.')
         SET2(sptr,']\0');
      else
      {
         while (*sptr) sptr++;
         sptr--;
      }
   }
   while (sptr > FileSpec &&
          (*sptr != ']' || SAME2(sptr-1,'^]')) &&
          (*sptr != '.' || SAME2(sptr-1,'^.')) &&
          (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--;
   if (*sptr == ']')
      while (sptr > FileSpec &&
             (*sptr != '.' || SAME2(sptr-1,'^.')) &&
             (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--;

   /* if at the MFD then implicitly a parent directory */
   if (*sptr == '[') return (SS$_NORMAL);

   /* still a subdirectory */
   *sptr++ = ']';
   while (*sptr && (*sptr != ']' || SAME2(sptr-1,'^]')))
      sptr++;
   for (cptr = ".DIR;"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   sys$setprv (1, &SysPrvMask, 0, 0);
   status = OdsFileExists (NULL, FileSpec);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ", status, FileSpec);

   return (status);
}

/*****************************************************************************/
/*
Simply return a string representing the path WebDAV SETing.
*/

char* DavWebPathAccess (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   /* same order as in AuthAccessEnable() */
   if (rqptr->rqPathSet.WebDavWrite)
      return ("WRITE");
   else
   if (rqptr->rqPathSet.WebDavProfile)
      return ("PROFILE");
   else
   if (rqptr->rqPathSet.WebDavRead)
      return ("READ");
   else
   if (rqptr->rqPathSet.WebDavServer)
      return ("SERVER");
   else
      return ("BUGCHECK");
}

/*****************************************************************************/
/*
Maps and parses the URL specified in the request "Destination:" header into the
destination ODS structure.  Returns a VMS status to indicate the success or
otherwise of the function.  Return SS$_ABORT if no destination field.
*/

int DavWebDestination (REQUEST_STRUCT *rqptr)

{
   BOOL  IsURL;
   int  status,
        DirNameLength;
   char  *cptr, *sptr, *zptr,
         *PathPtr;
   char  DestPath [ODS_MAX_FILE_NAME_LENGTH+1],
         DestSpec [ODS_MAX_FILE_NAME_LENGTH+1],
         DirName [ODS_MAX_FILE_NAME_LENGTH+1],
         HostPort [128+16];
   REQUEST_PATHSET  PathSet;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebDestination() !&Z",
                  rqptr->rqHeader.WebDavDestinationPtr);

   tkptr = rqptr->WebDavTaskPtr;

   if (!(cptr = rqptr->rqHeader.WebDavDestinationPtr)) return (SS$_ABORT);

   IsURL = true;
   if (MATCH7 (cptr, "http://"))
      cptr += 7;
   else
   if (MATCH8 (cptr, "https://"))
      cptr += 8;
   else
   {
      for (sptr = cptr; *sptr && *sptr != ':' && *sptr != '/'; sptr++);
      if (MATCH3 (sptr, "://")) return (SS$_BADPARAM);
      IsURL = false;
   }

   if (IsURL)
   {
      /* some WebDAV include a username in the host specification, eliminate */
      for (sptr = cptr; *sptr && *sptr != '@' && *sptr != '/'; sptr++);
      if (*sptr == '@')
      {
         while (*cptr && *cptr != '@') cptr++;
         cptr++;
      }

      /* only local destinations supported */
      zptr = (sptr = HostPort) + sizeof(HostPort)-1;
      while (*cptr && *cptr != ':' && *cptr != '/' && sptr < zptr)
         *sptr++ = *cptr++;
      if (*cptr == ':')
      {
         /* compare host components only if both contain them */
         if (strchr (rqptr->rqHeader.HostPtr, ':'))
            while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         else
            while (*cptr && *cptr != '/') cptr++;
      }
      if (!*cptr) return (SS$_BADPARAM);
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         return (SS$_RESULTOVF);
      }
      *sptr = '\0';
      if (rqptr->rqHeader.HostPtr &&
          !strsame (rqptr->rqHeader.HostPtr, HostPort, -1))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "DESTINATION (!AZ) not this host (!AZ)",
                       HostPort, rqptr->rqHeader.HostPtr);
         return (SS$_BADPARAM);
      }
   }

   PathPtr = cptr;

   zptr = (sptr = DestPath) + sizeof(DestPath)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return (SS$_RESULTOVF);
   }
   *sptr = '\0';

   if (StringUrlDecode (DestPath) <= 0)
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "DESTINATION decode !AZ", DestPath);
      return (SS$_BADPARAM);
   }

   if (!rqptr->rqPathSet.OdsName ||
       rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT)
   {
      /* is there a potentially UTF-8 bit pattern here? */
      for (cptr = DestPath; *cptr && ((*cptr & 0xe0) != 0xc0); cptr++);
      if (*cptr)
      {
         /* some UTF-8 encode (e.g. KDE), others (e.g. MS WebFolders) do not */
         if (ConvertFromUtf8 (DestPath, -1, '$') > 0)
         {
            if (WATCHING (rqptr, WATCH_WEBDAV))
               WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                          "UTF-8 to native !AZ", DestPath);
         }
         else
         {
            /* we know from above it fits and decodes */
            sptr = DestPath;
            for (cptr = PathPtr; *cptr; *sptr++ = *cptr++);
            *sptr = '\0';
            StringUrlDecode (DestPath);
         }
      }
   }
   else
   if (rqptr->rqPathSet.OdsName == MAPURL_ODS_UTF8)
   {
      if (ConvertUtf8ToEscape (DestPath, DestPath, sizeof(DestPath)) > 0)
         if (WATCHING (rqptr, WATCH_REQUEST))
            WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                       "UTF-8 to escaped 8-bit !AZ", DestPath);
   }

   memset (&PathSet, 0, sizeof(PathSet));

   cptr = MapUrl_Map (DestPath, 0, DestSpec, sizeof(DestSpec),
                      NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, &PathSet);
   if (!cptr[0])
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "DESTINATION mapping !AZ", cptr+1);
      return (SS$_BADPARAM);
   }

   OdsParse (&tkptr->DestOds, DestSpec, strlen(DestSpec), NULL, 0,
             NAM$M_SYNCHK, NULL, rqptr);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ",
                 tkptr->DestOds.Fab.fab$l_sts, tkptr->DestOds.ExpFileName);

   if (VMSnok (status = tkptr->DestOds.Fab.fab$l_sts))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }
   if (VMSnok (status = OdsParseTerminate (&tkptr->DestOds)))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }

   if (!(PathSet.WebDavProfile ||
         PathSet.WebDavRead || 
         PathSet.WebDavServer ||
         PathSet.WebDavWrite))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "DESTINATION is not mapped for WebDAV access");
      return (SS$_NOPRIV);
   }

   /* if the source is a file specification */
   if (rqptr->ParseOds.NamNameLength || rqptr->ParseOds.NamTypeLength)
      if (DavMetaDir (rqptr, &rqptr->ParseOds))
         return (RMS$_DNF);
      else
         return (SS$_NORMAL);

   /* if destination is already a directory specification */
   if (!tkptr->DestOds.NamNameLength && !tkptr->DestOds.NamTypeLength)
      if (DavMetaDir (rqptr, &tkptr->DestOds))
         return (RMS$_DNF);
      else
         return (SS$_NORMAL);


   /*************************/
   /* make into a directory */
   /*************************/

   if (tkptr->DestOds.NamTypeLength)
   {
      /* file specification has a type */
      zptr = (sptr = DirName) + sizeof(DirName)-1;
      for (cptr = tkptr->DestOds.ExpFileName;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = '\0';
      if (sptr < zptr) *sptr++ = ']';
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         return (SS$_RESULTOVF);
      }
      *sptr = '\0';
      DirNameLength = sptr - DirName;
      sptr -= 2;
      for (cptr = sptr-1;
           cptr > DirName && *cptr != '.' && !SAME2(cptr-1,'^.');
           *sptr-- = *cptr--);
      *cptr++ = '^';
      *cptr-- = '.';
      while (cptr > DirName &&
             (*cptr != ']' || SAME2(cptr-1,'^]'))) cptr--;
      if (*cptr == ']') *cptr = '.';
   }
   else
   {
      /* file specification has no type */
      zptr = (sptr = DirName) + sizeof(DirName)-1;
      for (cptr = tkptr->DestOds.ExpFileName;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = ']';
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         return (SS$_RESULTOVF);
      }
      *sptr = '\0';
      DirNameLength = sptr - DirName;
      sptr -= 2;
      while (sptr > DirName &&
             (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--;
      if (*sptr == ']') *sptr = '.';
   }

   OdsParse (&tkptr->DestOds, DirName, DirNameLength, NULL, 0,
             NAM$M_SYNCHK, NULL, rqptr);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ",
                 tkptr->DestOds.Fab.fab$l_sts, tkptr->DestOds.ExpFileName);

   if (VMSnok (status = tkptr->DestOds.Fab.fab$l_sts))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }
   if (VMSnok (status = OdsParseTerminate (&tkptr->DestOds)))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MUNGED !AZ",
                 tkptr->DestOds.ExpFileName);

   if (DavMetaDir (rqptr, &tkptr->DestOds))
      return (RMS$_DNF);
   else
      return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Check the request user agent field for indications of a Microsoft WebDAV agent
and return true if detected.  Return false if not detected.
*/

BOOL DavWebMicrosoftDetect (REQUEST_STRUCT *rqptr)

{
   char  *cptr;
   WEBDAV_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
       WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMicrosoftDetect()");

   if (!(cptr = rqptr->rqHeader.UserAgentPtr)) return (false);

   tkptr = rqptr->WebDavTaskPtr;

   if (tkptr) tkptr->MicrosoftAgent = 0;

   if (strstr (cptr, "Microsoft"))
   {
      if (strstr (cptr, "WebDAV-MiniRedir/"))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "MICROSOFT WebDAV MiniRedir");
         if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_MINIREDIR;
         return (true);
      }
      if (strstr (cptr, "Data Access Internet Publishing Provider"))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "MICROSOFT Internet Publishing Provider");
         if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_MDAIPP;
         return (true);
      }
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "MICROSOFT unknown agent");
      if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_UNKNOWN;
      return (true);
   }
   if (strstr (cptr, "WebDrive"))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "South River Technologies WebDrive (MICROSOFT)");
      if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_WEBDRIVE;
      return (true);
   }
   if (strstr (cptr, "BitKinex"))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "BARAD-DUR LLC, BitKinex (MICROSOFT)");
      if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_BITKINEX;
      return (true);
   }
   return (false);
}

/*****************************************************************************/
/*
Some agents specify collections (directories) without a trailing slash which
are interpreted by WASD as a file specification, with or without a file type.  
This function changes such a file specification into a directory equivalent.

   WEB:[This]is^_an^_example.;    into   WEB:[This.is^_an^_example]
   WEB:[This]is^_an.example;        "    WEB:[This.is^_an^.example]
   WEB:[This]is^.another.example;   "    WEB:[This.is^.another^.example]

Depending on the 'IfExists' boolean, it will only do this if the resulting
directory specification actually exists in the file-system, or unconditionally.
The function returns SS$_NORMAL if no change has been made, SS$_SUPERCEDE if
the resulting specification is reparsed into the request result, or it can
return a VMS error status.
*/ 
 
int DavWebSlashlessMunge
(
REQUEST_STRUCT *rqptr,
BOOL IfExists
)
{
   int  status,
        DirNameLength;
   char  *cptr, *sptr, *zptr;
   char  DirName [ODS_MAX_FILE_NAME_LENGTH+1];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebSlashlessMunge() !&B !AZ",
                 IfExists, rqptr->ParseOds.ExpFileName);

   if (rqptr->ParseOds.NamTypeLength)
   {
      /* file specification has a type */
      zptr = (sptr = DirName) + sizeof(DirName)-1;
      for (cptr = rqptr->ParseOds.ExpFileName;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++)
      {
         /* swallow any MFD component */
         if (*cptr == '0' && MATCH7 (cptr, "000000]")) cptr += 7;
      }
      if (sptr < zptr) *sptr++ = '\0';
      if (sptr < zptr) *sptr++ = ']';
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         return (SS$_RESULTOVF);
      }
      *sptr = '\0';
      DirNameLength = sptr - DirName;
      sptr -= 2;
      for (cptr = sptr-1;
           cptr > DirName && *cptr != '.';
          *sptr-- = *cptr--);
      *cptr++ = '^';
      *cptr-- = '.';
      while (cptr > DirName &&
             (*cptr != ']' || SAME2(cptr-1,'^]'))) cptr--;
      if (*cptr == ']' && !SAME2(cptr-1,'^]')) *cptr = '.';
   }
   else
   if (rqptr->ParseOds.NamNameLength)
   {
      /* file specification has no type */
      zptr = (sptr = DirName) + sizeof(DirName)-1;
      for (cptr = rqptr->ParseOds.ExpFileName;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++)
      {
         /* swallow any MFD component */
         if (*cptr == '0' && MATCH7 (cptr, "000000]")) cptr += 7;
      }
      if (sptr < zptr) *sptr++ = ']';
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         return (SS$_RESULTOVF);
      }
      *sptr = '\0';
      DirNameLength = sptr - DirName;
      sptr -= 2;
      while (sptr > DirName &&
             (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--;
      if (*sptr == ']' && !SAME2(sptr-1,'^]')) *sptr = '.';
   }
   else
   {
      /* no type and no name - must be a directory specification already! */
      return (SS$_NOTMODIFIED);
   }

   if (IfExists)
   {
      /* test to see if the directory actually exists */
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsFileExists (DirName, "*.*");
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (status == RMS$_FNF || status == RMS$_NMF) status = SS$_NORMAL;
      /* no it does not (or error) */
      if (VMSnok (status)) return (SS$_NORMAL);
   }

   OdsParse (&rqptr->ParseOds, DirName, DirNameLength, NULL, 0,
             NAM$M_SYNCHK, NULL, rqptr);

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ",
                 rqptr->ParseOds.Fab.fab$l_sts, rqptr->ParseOds.ExpFileName);

   if (VMSnok (status = rqptr->ParseOds.Fab.fab$l_sts))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }
   if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds)))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MUNGED !AZ",
                 rqptr->ParseOds.ExpFileName);

   return (SS$_SUPERSEDE);
}

/*****************************************************************************/
/*
Microsoft agent(s) has an observed behaviour where it seems to bungle the
concatenation of resource names resulting in requests containing resources with
empty directories such as '/web//test/test.txt'.  These are flagged as illegal
file-system syntax during initial request processing.  If this happens and it's
a WebDAV request this function is called to investigate and internally redirect
with a 'corrected' URI if appropriate.
*/ 
 
DavWebMicrosoftMunge1 (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr, *zptr;
   char  Location [1024];

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebMicrosoftMunge1() !&Z", rqptr->rqHeader.RequestUriPtr);

   if (!rqptr->WebDavTaskPtr || !rqptr->WebDavTaskPtr->MicrosoftAgent) return;

   for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr; cptr++)
      if (SAME2(cptr,'//')) break;

   if (!*cptr) return;

   zptr = (sptr = Location) + sizeof(Location)-1;
   for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && sptr < zptr; cptr++)
      if (!SAME2(cptr,'//')) *sptr++ = *cptr;
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return;
   }
   *sptr = '\0';

   cptr = ResponseLocation (rqptr, Location, sptr - Location);

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT munge !AZ", cptr);
}

/*****************************************************************************/
/*
According to

  http://www.webdavsystem.com/server/documentation/troubleshooting

Web Folders client on Windows Vista sometimes provides incorrect Lock-Token
header. Instead of Lock-Token:
<opaquelocktoken:66db0722-1053-42c4-9940-3d6693457e32> header Lock-Token:
opaquelocktoken:66db0722-1053-42c4-9940-3d6693457e32 header is sent.

If it doesn't look quite correct then assume the worst and munge the header!
*/ 
 
DavWebMicrosoftMunge2 (REQUEST_STRUCT *rqptr)

{
   char  *cptr, *sptr, *zptr;
   char  LockToken [256];
   DICT_ENTRY_STRUCT  *denptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavWebMicrosoftMunge2() !&Z",
                 rqptr->rqHeader.WebDavLockTokenPtr);

   if (*rqptr->rqHeader.WebDavLockTokenPtr == '<') return;

   zptr = (sptr = LockToken) + sizeof(LockToken)-2;
   cptr = rqptr->rqHeader.WebDavLockTokenPtr;
   *sptr++ = '<';
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr++ = '>';
   *sptr = '\0';

   denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST,
                        "lock-token", 10, LockToken, sptr - LockToken);
   DICT_GET_VALUE (denptr);

   if (WATCHING (rqptr, WATCH_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT munge !AZ",
                 rqptr->rqHeader.WebDavLockTokenPtr);
}

/*****************************************************************************/
/*
Generate a null-terminated string containing the supplied binary time in the
IETF RFC 3339 format '<yyyy-mm-dd>T<hh:mm:ss>Z'.  Assumes the supplied string
storage is large enough to store 21 characters.
*/ 
 
int DavWebDateTimeTo3339
(
char *TimeString,
int64 *Time64Ptr
)
{
   static $DESCRIPTOR (Rfc3339FaoDsc, "!4ZW-!2ZW-!2ZWT!2ZW:!2ZW:!2ZWZ\0");
   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   int64  GmTime64;
   ushort  Time7 [7];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                 "DavWebDateTimeTo3339() !%D", Time64Ptr);

   if (Time64Ptr)
      GmTime64 = *Time64Ptr;
   else
      sys$gettim (&GmTime64);

   TimeAdjustToGMT (&GmTime64);
   sys$numtim (&Time7, &GmTime64);

   TimeStringDsc.dsc$w_length = 21;
   TimeStringDsc.dsc$a_pointer = TimeString;

   sys$fao (&Rfc3339FaoDsc, 0, &TimeStringDsc,
            Time7[0], Time7[1], Time7[2],
            Time7[3], Time7[4], Time7[5]);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
From the supplied IETF RFC 3339 format '<yyyy-mm-dd>T<hh:mm:ss>Z' generate a
VMS binary time.  String can have leading white-space and trailing anything.
*/ 
 
int DavWebDateTimeFrom3339
(
char *TimeString,
int64 *Time64Ptr
)
{
   int  status;
   char  *cptr;
   ushort  Time7 [7];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                 "DavWebDateTimeFrom3339() !&Z", TimeString);

   memset (&Time7, 0, sizeof(Time7));
   *Time64Ptr = 0;

   for (cptr = TimeString; *cptr && isspace(*cptr); cptr++);
   Time7[0] = atoi(cptr);
   while (*cptr && *cptr != '-') cptr++;
   if (!*cptr++) return (SS$_IVTIME);
   Time7[1] = atoi(cptr);
   while (*cptr && *cptr != '-') cptr++;
   if (!*cptr++) return (SS$_IVTIME);
   Time7[2] = atoi(cptr);
   while (*cptr && *cptr != 'T') cptr++;
   if (!*cptr++) return (SS$_IVTIME);
   Time7[3] = atoi(cptr);
   while (*cptr && *cptr != ':') cptr++;
   if (!*cptr++) return (SS$_IVTIME);
   Time7[4] = atoi(cptr);
   while (*cptr && *cptr != ':') cptr++;
   if (!*cptr++) return (SS$_IVTIME);
   Time7[5] = atoi(cptr);
   while (*cptr && *cptr != 'Z') cptr++;
   if (!*cptr++) return (SS$_IVTIME);

   status = lib$cvt_vectim (&Time7, Time64Ptr);
   if (VMSnok (status)) return (status);

   TimeAdjustFromGMT (Time64Ptr);

   if (WATCH_MODULE(WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "!%D", Time64Ptr);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Report on WebDAV activity.
*/ 

DavWebReport (REQUEST_STRUCT *rqptr)

{
   static char  ReportPageFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
\
<tr><th class=\"sbttl\">Configuration</th><tr>\n\
<tr><th>Expat Version:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>WebDAV:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Locking:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Lock Timeout Default:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Lock Timeout Max:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Lock&nbsp;Collection&nbsp;Depth:</th><td colspan=\"2\">!UL</td></tr>\n\
<tr><th>Quota:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Meta File Directory:</th><td colspan=\"2\">!AZ</td></tr>\n\
\
<tr><td>&nbsp;</td></tr>\n\
\
<tr><th class=\"sbttl\">Statistics</th><tr>\n\
<tr><th>Module:</th><td>!&L</td><td style=\"width:100%\"></td></tr>\n\
<tr><th>Identified-as:</th><td>!&L</td><td colspan=\"2\">(!&L)</td></tr>\n\
<tr><th>\
<a href=\"" ADMIN_REPORT_WEBDAV_XML "\" \
style=\"color:#000000;text-decoration:none;\">\
XML&nbsp;Parse</a>:</th><td>!&L</td></tr>\n\
<tr><th>Meta&nbsp;&nbsp;/Read:</th>\
<td>!&L</td><td colspan=\"2\">(!&L)</td></tr>\n\
<tr><th>/Write:</th><td>!&L</td><td colspan=\"2\">(!&L)</td></tr>\n\
\
</table>\n\
\
</td><td>\n\
<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\">\n\
\
<tr><th class=\"sbttl\">Method</th></tr>\n\
<tr><th>COPY:</th><td>!&L</td></tr>\n\
<tr><th>**DELETE:</th><td>!&L</td></tr>\n\
<tr><th>**GET:</th><td>!&L</td></tr>\n\
<tr><th>LOCK:</th><td>!&L</td></tr>\n\
<tr><th>MKCOL:</th><td>!&L</td></tr>\n\
<tr><th>MOVE:</th><td>!&L</td></tr>\n\
<tr><th>**OPTIONS:</th><td>!&L</td></tr>\n\
<tr><th>PROPFIND:</th><td>!&L</td></tr>\n\
<tr><th>PROPPATCH:</th><td>!&L</td></tr>\n\
<tr><th>**PUT:</th><td>!&L</td></tr>\n\
<tr><th>UNLOCK:</th><td>!&L</td></tr>\n\
<tr><th>Total:</th><td>!&L</td><td>(!UL%*)</td></tr>\n\
\
<tr><td>&nbsp;</td></tr>\n\
\
<tr><th class=\"sbttl\">Traffic</th></tr>\n\
<tr><th>Rx:</th>\
<td colspan=\"2\">!&,@UQ</td></tr>\n\
<tr><th>Tx:</th>\
<td colspan=\"2\">!&,@UQ</td></tr>\n\
<tr><th>Total:</th>\
<td colspan=\"2\">!&,@UQ&nbsp;&nbsp;(!UL%*)</td></tr>\n\
\
</table>\n\
</td></tr>\n\
<tr><td colspan=\"2\" nowrap><font size=\"-1\">\
<sup>*</sup><i>of server total</i>\n\
<br><sup>**</sup><i>standard HTTP method in conjunction with a \
request characteristic indicating WebDAV</i></font></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
\
<p><table class=\"lftlft\">\n\
<tr><td>\n\
<tr><td></td><th colspan=\"2\"></th>\
<th colspan=\"2\"><u>VMS DLM Locks</u></th></tr>\n\
<tr>\
<td></td>\
<th class=\"sbttl\">Service&nbsp;/&nbsp;Client</th>\
<th class=\"sbttl\">Request&nbsp;/&nbsp;Time&nbsp;/&nbsp;Meta-data</th>\
<th class=\"sbttl\">Source&nbsp;/&nbsp;Destin&nbsp;/\
Meta-src&nbsp;/&nbsp;Meta-dst</th>\
<th class=\"sbttl\">Mode</th>\
</tr>\n";

   static char  ReportRequestFao [] =
"<tr><th rowspan=\"4\">!3ZL</th>\
<td>!AZ//!AZ&nbsp;&nbsp;</td>\
<td><tt>!AZ&nbsp;!AZ</tt></td>\
<td>!&@</td><td>!AZ</td></tr>\n\
<tr><td>!AZ</td>\
<td>!20%D&nbsp;&nbsp;(!AZ)</td>\
<td>!&@</td><td>!AZ</td></tr>\n\
<tr><td></td><td>!&@</td><td>!&@</td><td>!AZ</td></tr>\n\
<tr><td></td><td>!&@</td><td>!&@</td><td>!AZ</td></tr>\n";

   static char  NoneFao [] =
"<tr class=\"hlght\"><th>000</th><td colspan=\"4\"><i>none</i></td></tr>\n";

   static char  ReportPageEndFao [] =
"<tr><td colspan=\"5\" style=\"font-size:80%\">\
<sup>*</sup><i>possible meta-data only</i></td></tr>\n\
</table>\n\
!AZ\
</div>\n\
</body>\n\
</html>\n";

   int  status,
        AgentCount,
        DisplayCount = 0,
        MethodCount;
   int64  Time64,
          BytesRxTx64,
          BytesTotal64,
          Duration64;
   unsigned long  FaoVector [48];
   unsigned long  *vecptr;
   char  *cptr;
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebReport()");

   AdminPageTitle (rqptr, "WebDAV Report");

   vecptr = FaoVector;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   *vecptr++ = XML_ExpatVersion() + 6;
   *vecptr++ = WebDavEnabled ? "[enabled]" : "[disabled]";
   *vecptr++ = WebDavLockingEnabled ? "[enabled]" : "[disabled]";
   *vecptr++ = MetaConShowSeconds (rqptr, WebDavLockTimeoutDefaultSeconds);
   *vecptr++ = MetaConShowSeconds (rqptr, WebDavLockTimeoutMaxSeconds);
   *vecptr++ = WebDavLockCollectionDepth;
   *vecptr++ = WebDavQuotaEnabled ? "[enabled]" : "[disabled]";
   *vecptr++ = Config.cfWebDav.MetaFileDirectory[0] ?
                  Config.cfWebDav.MetaFileDirectory : "<i>(none)</i>";

   *vecptr++ = AccountingPtr->DoWebDavCount;
   *vecptr++ = AccountingPtr->WebDavRequestCount;
   *vecptr++ = AccountingPtr->WebDavAromaCount;
   *vecptr++ = AccountingPtr->WebDavXmlParseCount;
   *vecptr++ = AccountingPtr->WebDavMetaReadCount;
   *vecptr++ = AccountingPtr->WebDavMetaReadAttemptCount;
   *vecptr++ = AccountingPtr->WebDavMetaWriteCount;
   *vecptr++ = AccountingPtr->WebDavMetaWriteAttemptCount;

   BytesRxTx64 = AccountingPtr->WebDavBytesRawRx64[HTTP12] +
                 AccountingPtr->WebDavBytesRawTx64[HTTP12];
   BytesTotal64 = AccountingPtr->BytesRawRx64[HTTP12] +
                  AccountingPtr->BytesRawTx64[HTTP12];

   *vecptr++ = AccountingPtr->MethodWebDavCopyCount,
   *vecptr++ = AccountingPtr->MethodWebDav_DeleteCount,
   *vecptr++ = AccountingPtr->MethodWebDav_GetCount,
   *vecptr++ = AccountingPtr->MethodWebDavLockCount,
   *vecptr++ = AccountingPtr->MethodWebDavMkColCount,
   *vecptr++ = AccountingPtr->MethodWebDavMoveCount,
   *vecptr++ = AccountingPtr->MethodWebDav_OptionsCount,
   *vecptr++ = AccountingPtr->MethodWebDavPropFindCount,
   *vecptr++ = AccountingPtr->MethodWebDavPropPatchCount,
   *vecptr++ = AccountingPtr->MethodWebDav_PutCount,
   *vecptr++ = AccountingPtr->MethodWebDavUnLockCount,

   MethodCount = AccountingPtr->MethodWebDavCopyCount +
                 AccountingPtr->MethodWebDav_DeleteCount +
                 AccountingPtr->MethodWebDav_GetCount +
                 AccountingPtr->MethodWebDavLockCount +
                 AccountingPtr->MethodWebDavMkColCount +
                 AccountingPtr->MethodWebDavMoveCount +
                 AccountingPtr->MethodWebDav_OptionsCount +
                 AccountingPtr->MethodWebDavPropFindCount +
                 AccountingPtr->MethodWebDavPropPatchCount +
                 AccountingPtr->MethodWebDav_PutCount +
                 AccountingPtr->MethodWebDavUnLockCount;
   *vecptr++ = MethodCount,
   *vecptr++ = PercentOf32 (MethodCount,
                          AccountingPtr->ProcessingTotalCount[HTTP12]);

   *vecptr++ = &AccountingPtr->WebDavBytesRawRx64;
   *vecptr++ = &AccountingPtr->WebDavBytesRawTx64;
   *vecptr++ = &BytesRxTx64;
   *vecptr++ = PercentOf64 (&BytesRxTx64, &BytesTotal64);

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", NULL);

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, ReportPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   /*******************/
   /* WebDAV requests */
   /*******************/

   DisplayCount = 0;
   sys$gettim (&Time64);

   /* process the request list from least to most recent */
   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;

      if (!rqeptr->WebDavTaskPtr) continue;

      if (!rqeptr->WebDavTaskPtr->MetaData.DlmData.LockSb.lksb$l_lkid &&
          !rqeptr->WebDavTaskPtr->DlmSource.LockSb.lksb$l_lkid &&
          !rqeptr->WebDavTaskPtr->DlmDestin.LockSb.lksb$l_lkid)
         continue;

      vecptr = FaoVector;

      *vecptr++ = ++DisplayCount;
      *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = rqeptr->ServicePtr->ServerHostPort;

      *vecptr++ = rqeptr->rqHeader.MethodName;
      *vecptr++ = rqeptr->rqHeader.RequestUriPtr;

      if (rqeptr->WebDavTaskPtr->DlmSource.LockSb.lksb$l_lkid)
      {
         *vecptr++ = "<tt>!AZ</tt>";
         *vecptr++ = rqeptr->WebDavTaskPtr->DlmSource.ResourceName;
         *vecptr++ = rqeptr->WebDavTaskPtr->DlmSource.ExMode ? "EX" : "CR";
      }
      else
      {
         *vecptr++ = "<i>none</i>";
         *vecptr++ = "";
      }

      *vecptr++ = UserAtClient (rqeptr);

      *vecptr++ = &rqeptr->rqTime.BeginTime64;

      Duration64 = rqeptr->rqTime.BeginTime64 - Time64;
      *vecptr++ = DurationString (rqptr, &Duration64);

      if (rqeptr->WebDavTaskPtr->DlmDestin.LockSb.lksb$l_lkid)
      {
         *vecptr++ = "<tt>!AZ</tt>";
         *vecptr++ = rqeptr->WebDavTaskPtr->DlmDestin.ResourceName;
         *vecptr++ = rqeptr->WebDavTaskPtr->DlmDestin.ExMode ? "EX" : "CR";
      }
      else
      {
         *vecptr++ = "<i>none</i>";
         *vecptr++ = "";
      }

      if (rqeptr->WebDavTaskPtr->MetaData.DlmData.LockSb.lksb$l_lkid)
      {
         *vecptr++ = "<tt>!AZ</tt>&nbsp;&nbsp;";
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.ReadMetaName;
         *vecptr++ = "<tt>!AZ</tt>";
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmData.ResourceName;
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmData.ExMode ?
             "EX" : "CR";
      }
      else
      {
         *vecptr++ = "";
         *vecptr++ = "<i>none</i>";
         *vecptr++ = "";
      }

      if (rqeptr->WebDavTaskPtr->MetaData.DlmDest.LockSb.lksb$l_lkid)
      {
         *vecptr++ = "<tt>!AZ</tt>&nbsp;&nbsp;";
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.WriteMetaName;
         *vecptr++ = "<tt>!AZ</tt>";
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmDest.ResourceName;
         *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmDest.ExMode ?
             "EX" : "CR";
      }
      else
      {
         *vecptr++ = "";
         *vecptr++ = "<i>none</i>";
         *vecptr++ = "";
      }

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, ReportRequestFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);
   }

   if (!DisplayCount) FaolToNet (rqptr, NoneFao, NULL);

   vecptr = FaoVector;
   *vecptr++ = AdminRefresh (rqptr);

   status = FaolToNet (rqptr, ReportPageEndFao, FaoVector);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   AdminEnd (rqptr);
}

/*****************************************************************************/

