/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                MapUrl.c

Functions supporting mapping of URLs to VMS file specifications and VMS 
specifications to URLs, uses the HTTPd rule mapping file.  The number of rules 
it can store is not fixed, but is limited by available memory and the 
practical considerations of processing large, linear rule databases. 

Comment lines may be provided by beginning a line with a '#' character.

Lines may be continued by ensuring the last character on the line is a '\'.

Virtual server syntax is the same as for authentication/authorization rules,
[[virtual-host]] (all ports of host) or [[virtual-host:virtual-port]] (
matching specified port of host) for a specific host and [[*]] to resume rules
for all virtual hosts.


Wildcards
---------
A single '*' wildcard matches intervening text up until the first character
encountered that matches the character immediately following the wildcard.

The rule

  map /one/*/three/* /first/*/third/*

would map the following request path

  /one/two/three/four

into

  /first/two/third/four

but would not match the request path

  /one/two/and-a-half/three/four

Two consecutive ('**') wildcards match intervening text up until the next
string that matches the string following the wildcard.

The rule

  map /one/**/three/* /first/*/third/*

would map the following request paths

  /one/two/three/four
  /one/two/and-a-half/three/four

respectively into

  /first/two/third/four
  /first/two/and-a-half/third/four

Historically (pre v8.1) the template wildcard-matched portions of path strings
could only be substituted in the positions of corresponding wildcards in the
result string.  There can be ocassions where there is not a one-to-one
relationship between these wildcard sections in the source (template) and
target (result) strings.  It is sometimes useful to be able to drop one or more
strings from the template and/or change the order of substitution.

The use of 'positional wildcards' (for want of an even marginally better term)
will allow this.  When a wildcard is used in the result string it may
optionally be followed by a single apostrophe character (') and a single digit
character '1'..'9'.  The digit is used to indicate which of the template
wildcards is intended (first to last).

The following request path

  /one/two/three/four/five.six

would be processed by the rule

  pass /one/*/three/*/* /*'2/one/*'1/*'3

into the mapped path

  /four/one/two/five.six

Using this notation all template wildcards must be specified 'positionally' :^)
This rule is not legal.

  pass /one/*/three/* /two/*'2/four/*

Positional wildcards ONLY APPLY to PASS, MAP, REDIRECT and USER rules.
They cannot be used with EXEC, SCRIPT and UXEC rules.


Extended File Specification
---------------------------
Extended file specifications (required for ODS-5) are supported.  A rule is
used to flag such paths for ODS-5 tailored processing.  Things that are not
done to ODS-5 paths; case-changes and character substitution.  Things that are
done to ODS-5 paths; escaping to and from the extended file specification
canonical escape syntax (i.e. '^').

Paths located on ODS-5 volumes need to have the ODS-5 rule SET against them.

  set /extended/* ODS-5

It is possible to explicitly mark a path as ODS-2 also, although as this is the
default it is usually unnecessary.  One situation where this might be useful is
where all paths are ODS-5 except a small minority, in which case a rule such as
the following might be used.

  set /* ODS-5
  set /old-path/* ODS-2

Paths derived using the USER rule are specially processed.  The SYSUAF user
home directory's structure is determined using a sys$getdvi() to determined
it's ACPTYPE.  Hence no explicit rule is required for these.


Mapping Rules
-------------
[[virtual-host:optional-virtual-port]]
EXEC       template  result           [setting(s)] [conditional(s)]
EXEC       (runtime)template  result  [setting(s)] [conditional(s)]
EXEC+      template  result           [setting(s)] [conditional(s)]
FAIL       template                                [conditional(s)]
MAP        template  result           [setting(s)] [conditional(s)]
PASS       template                                [conditional(s)]
PASS       template  result           [setting(s)] [conditional(s)]
PROTECT    template  access-control   [setting(s)] [conditional(s)]
REDIRECT   template  result           [setting(s)] [conditional(s)]
SCRIPT     template  result           [setting(s)] [conditional(s)]
SCRIPT     (runtime)template  result  [setting(s)] [conditional(s)]
SCRIPT+    template  result           [setting(s)] [conditional(s)]
SET        template  setting(s)                    [conditional(s)]
USER       template  result           [setting(s)] [conditional(s)]
UXEC       template  result           [setting(s)] [conditional(s)]
UXEC       (runtime)template  result  [setting(s)] [conditional(s)]
UXEC+      template  result           [setting(s)] [conditional(s)]

(Somewhat awkwardly the square brackets both denote optional fields and to
literally delimit a conditional.  Please do not confuse the two!)

Note that as of V8.0 path settings (anything that can be set with a SET rule)
can also follow the result string.  If the rule applies and any conditional is
matched then the settings are applied to the path BEFORE the mapping is done.

PASS rules can map to a result comprising an HTTP status code (e.g. 403) with a
following textual message (including white-space).  Just delimit the result
string with double or single quotes, or within curly braces.  For example:

PASS /private/stuff/* "403 Can't go in there!" [!ho:my.host.name]
PASS /private/stuff/* '403 "/private/stuff/" is off-limits!' [!ho:my.host.name]

EXEC rules can be used to map both directories and file types.  The former only
requires a single, trailing wildcard (example 1).  The latter requires two
wildcards (examples 2 and 3).  The  irst to locate the script, the second to
map any path.  This format can be used to globally map a file type, or confine
that mapping to specified part of a tree.

EXEC /cgi-bin/*
EXEC *.cgi* *.cgi*
EXEC /specified/path/*.cgi* /specified/path/*.cgi*

PROTECT rules can be used require authorization for for the matching path.  It
complements the facility available via the HTTPD$AUTH configuration file,
providing a simpler (and to certain extent less capble) authorization schema. 
See the relevant section in the description to AUTH.C module.


General Run-Time Mapping
------------------------
This is distinct from "Run-Time (RTE) Environment" scripting discussed below. 
It also uses a similar rule construct and historically came much after RTE
mappings.  Hence the '!' kludge mentioned shortly.  The historical way to map
script file types (other than .EXE and .COM which are handled implicitly) has
been to add [DclScriptRunTime] entries to HTTPD$CONFIG.  The (very small)
disadvantage to using this is the need to completely restart the server to
apply changes.  Design-wise the better place for these sorts of mappings (file
types to run-time engine) is here in HTTPD$MAP (it also allows /DO=MAP to
change these).  From v8.2 we can use an RTE-like construct to specify what
non-RTE engine should be activated to process the specified script.  It can be
an executable, a DCL procedure or even verb.  Here's how we might activate Perl
using these rules:

  exec /cgi-bin/*.pl* (!perl)/cgi-bin/*.pl*
  exec /cgi-bin/*.pl* (!$perl_root:[000000]perl.exe)/cgi-bin/*.pl*
  exec /cgi-bin/*.pl* (!@cgi-bin:[000000]perl.com)/cgi-bin/*.pl*
  script /this_one* (!$perl_root:[000000]perl.exe)/cgi-bin/this_one.pl*

The first uses a CLI verb, the second activates a specified executable and the
last executes a DCL procedure (which in this presumably sets up some
environment before activating Perl).

Note the leading '!'.  This is a bit of a kludge but not too onerous.


Run-Time Environment (RTE) Mapping
----------------------------------
Each persistant run-time environment must have it's own mapping environment. 
This may, or may not, correspond to physically distinct directory areas.  It is
the mapping rules that identify run-time environments.  A parenthesized
interprester specification leads the result component of the rule.  This is
first extracted and then the EXEC rule behaves as normal.  Hence the following 
example shows two such environments each with it's own interpreter.

  exec /plbin/* (cgi-bin:[000000]perlrte.exe)/ht_root/perl_local/*
  exec /pybin/* (cgi-bin:[000000]pyrte.exe)/ht_root/python_local/*
  script /this_one* (cgi-bin:[000000]oneeng.exe)/cgi-bin/this.one*

The "/plbin/*" identifies request paths beginning with this string as a
hypothetical, persistant Perl run-time interpreter, with the Perl source
scripts available to it from HT_ROOT:[PERL_LOCAL].  Similarly with a
hypothetical Java Environment.

If a request with the following URI is given to the server

  http://the.host.name/plbin/plsearch/web/doc/

the following components will be derived

  run-time interpreter ... CGI-BIN:[000000]PERLRT.EXE
  Perl source script ..... HT_ROOT:[PERL_LOCAL]PLSEARCH.PL
  request path-info ...... WEB:[DOC]


Mapping User Directories
------------------------
The USER rule effectively PASSes the user's SYSUAF default device and directory
as the first wildcard substitution in the rule.  Accounts that are disusered,
captive or have expired passwords are never mapped, they are always "denied
access by default".  In the same way privileged accounts (those possessing
SYSPRV) cannot be mapped this way (see below for a workaround).

An example; if the user name DANIEL has the default device USER$DISK: and
default directory [DANIEL] the following request path

  /~daniel/

would be mapped to the result and VMS file specification

  /user$disk/daniel/www/
  USER$DISK:[DANIEL.WWW]

using the following rule.

  USER  /~*/*  /*/www/

Note the "/www" subdirectory component.  It is recommended that user
directories always be mapped to a subdirectory of the physcial area.  This
effectively "sandboxes" Web access to that subdirectory hierarchy, allowing the
user privacy elsewhere in the home area.

To accomodate request user paths that do not incorporate a trailing delimiter
after the username the following redirect may be used to cause the browser to
re-request with a more appropriate path.

  REDIRECT  /~*  ///*/www/

WASD also "reverse maps" VMS specifications into paths and so requires
additional rules to provides these mappings.  (Reverse mapping is required
during directory listings and error reporting.)  In the above case the
following rules would actually be required (and in the stated order).

  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$disk/*/www/*

Where user home directories are spread over multiple devices (physical or
concealed logical) a reverse-mapping rule would be required for each. Consider
the following situation, where user directories are distributed across these
devices (concealed logicals)

  USER$GROUP1:
  USER$GROUP2:
  USER$GROUP2:
  USER$OTHER:

This would require the following mapping rules (in the stated order).

  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$group1/*/www/*
  PASS  /~*/*  /user$group2/*/www/*
  PASS  /~*/*  /user$group3/*/www/*
  PASS  /~*/*  /user$other/*/www/*

Of course vanilla mapping rules may be used to provide for special cases.  For
instance, if there is requirement for a particular, privileged account to have
a user mapping that could be provided as in the following (rather exagerated)
example.

  PASS  /~system/*  /sys$common/sysmgr/www/*
  USER  /~*/*  /*/www/
  PASS  /~*/*  /user$disk/*/www/*


Path SETings
------------
The SET rule allows characteristics to be set against a particular path.  They
are then transfered to any request matching the path and applied to the
request.  This applies to FILES, not necessarily to scripts that supply a full
HTTP response.  These need to check and act on information provided by relevant
CGI variables.  Note that as of V8.0 path SETings can be applied with any rule
that has a result string (i.e. the setting must follow both a template and a
result and preceded any conditional).

[NO]ACCEPT=LANG=([DEFAULT=<language>][,CHAR=<char>][,VARIANT=NAME|TYPE])
                path will attempt to resolve a language-specific document,
                with default language (optional), 'char' is a character
                (optional) separating language from file type (default '_'),
                if the path template has a file type wildcard included the
                file content-type does not need to be "text/.."

[NO]ALERT         indicate that a request with this path has been detected,
                  HTTPDMON and OPCOM alert, and the process log gets a record
ALERT=MAP         alert provided immediately after mapping,
ALERT=AUTH        or immediately after authorization,
ALERT=END         or provided at request rundown (the default)
ALERT=<integer>   the integer can be a specific HTTP status value (e.g. 501)
                  or the category (range of values, e.g. 599) alert if match

[NO]AUTH=ALL      all access must be subject to authorization (or fail)
[NO]AUTH=ONCE     authorize only the script portion of the request,
                  do not repeat authorization on any path component
[NO]AUTH=MAPPED   authorize against the mapped path

AUTH=REVALIDATE=<hh:mm:ss>  revalidate authentication for this path
                            at the specified interval

AUTH=SYSUAF=PWDEXPURL=<string>  redirection URL if s SYSUAF-authenticated
                                user's primary password has expired

[NO]CACHE              cache files (not any other output)
CACHE=NONE             do not cache
CACHE=[NO]CGI          cache CGI-compliant script output
CACHE=EXPIRES=0        cancels path cache entry expiry
CACHE=EXPIRES=DAY      entry expires on change of day
CACHE=EXPIRES=HOUR     entry expires on change of hour
CACHE=EXPIRES=MINUTE   entry expires on change of minute
CACHE=EXPIRES=<hh:mm:ss>  specify the exact period before expiry
CACHE=[NO]FILE         cache files
CACHE=GUARD=<hh:mm:ss> prevent (Pragma:) reloads during these seconds
CACHE=MAX=<integer>    up to kBytes in size (overrides [CacheFileKBytesMax])
CACHE=[NO]NET          cache response network output of any kind
CACHE=[NO]NPH          cache NPH-compliant script output
CACHE=[NO]PERMANENT    cache this, do not allow it to be flushed
CACHE=[NO]QUERY        cache including requests with query strings
CACHE=[NO]SCRIPT       synonym for (both) 'cache=cgi cache=nph'
CACHE=[NO]SSI          cache Server-Side Includes output

CGIPLUSIN=CC=NONE   set explicit carriage-control to be added to each
CGIPLUSIN=CC=LF     CGIPLUSIN record (esoteric? a bit; ask Alex Ivanov :^)
CGIPLUSIN=CC=CR
CGIPLUSIN=CC=CRLF

CGIPLUSIN=EOF       terminate each request's CGIPLUSIN stream with an EOF
                    (same comment as immediately above)

CGI=PREFIX=<string>  instead of the default "WWW_" specify what is needed,
                     to have no prefix at all specify CGI=PREFIX=

CHARSET=<string>  any "text/..." content-type files use the specified charset

CONTENT=        all files have the "Content-Type:" forced to that specified
                (obvious use is to provide plain-text or binary versions of
                files using perhaps "set /plain-text/* content=text/plain",
                then mapping that generally back with "map /plain-text/* /*")
                another: "set /bogus/* "content=text/html; charset=BLAH"

DIR=ACCESS            directory listing enabled  (same as [DirAccess])
DIR=ACCESS=SELECTIVE  allowed if directory contains .WWW_BROWSABLE
DIR=NOACCESS          directory listing disabled

DIR=CHARSET=<string>  directory listing page is given this charset
                      (this could be called the Alex Ivanov rule ;^)

DIR=LAYOUT=     same as index=<string>

DIR=IMPLIEDWILDCARD    implied wildcard enabled
DIR=NOIMPLIEDWILDCARD  implied wildcard disabled

DIR=STYLE[=DEFAULT]    current (post-8.2) style
DIR=STYLE=ORIGINAL     pre-v8.2 WASD style
DIR=STYLE=ANCHOR       post-v8.2 WASD style (current, default)
DIR=STYLE=HTDIR        Alex Ivanov's listing style

DIR=WILDCARD    wildcard-forced listing enabled   (same as [DirWildcard])
DIR=NOWILDCARD  wildcard-forced listing disabled

[NO]EXPIRED     files in the path are sent "pre-expired"

NOHTML            cancel any/all previously set html=
HTML=BODYTAG=     HTML <BODY..> tag
HTML=HEADER=      HTML page header text (can include markup)
HTML=HEADERTAG=   HTML header tag <TD..> (usually colour)
HTML=FOOTER=      HTML page footer text (can include markup)
HTML=FOOTERTAG=   HTML footer tag <TD..> (usually colour)

HTTP=ACCEPT-CHARSET=<string>   explicitly set the request's accepted charset
HTTP=ACCEPT-LANGUAGE=<string>  explicitly set the request's accepted language

INDEX=<string>  "Index of" format string (same as "?httpd=index&...")

[NO]LOG         requests to this path are not to be access-logged

MAP=[NO]AFTER      (re)map after authorization
MAP=[NO]ELLIPSIS   allows mapping of the VMS '...' ellipsis wildcard that
                   provides for tree traversal
MAP=[NO]EMPTY      traditionally a path must have contained at least one char
                   to be mapped, this allows "empty" paths to be mapped
MAP=[NO]ONCE       normally, when a script has been derived from a path, WASD
                   goes on to map the 'path' portion in a separate pass - this
                   SETing provides the derived path as-is
MAP=RESTART        begin again at rule number one (limit on this action)
MAP=ROOT=<string>  prefix all result paths with this string (path)
MAP=SET=[NO]REQUEST  path SETings affect the request (or only the mapping)
MAP=SET=[NO]IGNORE   all path SETings are ignored (except 'map=set=noignore')

NOTEPAD=[+]<string>  text (perhaps keywords) added to a request during mapping
                     that can be detected during subsequent metacon, mapping
                     and authorization rule processing (not externally visible)
                     NOTEPAD PERSISTS ACROSS INTERNALLY REDIRECTED REQUESTS!
                     
ODS=2           path marked as ODS-2 (path default)
ODS=5           path marked as ODS-5, with EFS encoded file names
ODS=ADS         path marked as having filenames Advanced Server (6) encoded
ODS=PWK         path marked as having filenames PATHWORKS (4 & 5) encoded
ODS=SMB         same as ODS=ADS (only for the comfort of Samba users)
ODS=SRI         path marked as having filenames SRI (MultiNet NFS) encoded
NOODS           reset the path's ODS file naming

[NO]PROFILE     VMS SYSUAF-authenticated security profile applies/does not
                apply to this path

PROXY=NOFORWARDED              disable the "Forwarded: by" field
PROXY=FORWARDED[=BY]           enable the field with "by" data
PROXY=FORWARDED=FOR            enable the field with "by" and "for" data
PROXY=FORWARDED=ADDRESS        enable the field with "by" and "for" address

PROXY=NOXFORWARDEDFOR          disable "X-Forwarded-For:" field
PROXY=XFORWARDEDFOR[=ENABLED]  add client host, propagate any existing
PROXY=XFORWARDEDFOR=ADDRESS    add client address, propagate any existing
PROXY=XFORWARDEDFOR=UNKNOWN    hide this host, propagate any existing

PROXY=BIND=<string>    bind the outgoing proxy socket to this IP address
PROXY=CHAIN=<string>   chain to this proxy server

PROXY=REVERSE=LOCATION  rewrite a reverse proxy 302 "Location:" response to
                        contain the proxy server (can ONLY be used following
                        a 'redirect /foo/* /http://foo.bar/*' rule)
PROXY=REVERSE=[NO]VERIFY   for a locally authorized, reverse proxied request
                           create a verifyable authorization header containing
                           the username and a unique identifier for 'callback'

QUERY-STRING=<string>  set the request's query-string (URL-encode if necessary)

REPORT=BASIC     supply basic error information for requests on this path
REPORT=DETAILED  supply detailed error information for requests on this path
REPORT=400=<integer>   bad request reported as (e.g. "report=400=403")
REPORT=403=<integer>   forbidden reported as (e.g. "report=403=404")
REPORT=404=<integer>   not found reported as (e.g. "report=404=403")

RESPONSE=HEADER=ADD=<string>  append this header field to the response header
RESPONSE=HEADER=NOADD         cancel (potentially) additional header fields
RESPONSE=HEADER=FULL   revert to normal response header transmission
RESPONSE=HEADER=NONE   suppress response header transmission (i.e. the file 
                       or content contains *full* HTTP response - NPH-like!)
RESPONSE=HEADER=BEGIN  suppress the end-of-header blank line so that the file
                       or other content can provide additional header lines

RMSCHAR=<char>  provides the RMS-invalid substitution character, which unless
                otherwise specified is the dollar symbol. Any general
                RMS-valid character may be specified (e.g. alpha-numeric,
                '$', '-' or '_', although the latter three are probably the
                only REAL choices).  When an RMS-invalid character (e.g. '+')
                or syntax (e.g. multiple periods) is encountered this character
                is substituted.  NOTE: is not applied to ODS-5 volumes.

SCRIPT=AS=<user>  the script process will be using this username/account
                  (only applies if detached script processes are enabled)
SCRIPT=BIT-BUCKET=<hh:mm:ss>  per-path equivalent to config file setting
SCRIPT=COMMAND=<string>   provide the script activation command-line with
                          qualifiers, parameters, etc. (first character must
                          be an asterisk or it overrides the full verb)
SCRIPT=CPU=<hh:mm:ss>  CPU limit for each *script's* processing
                       (not a per-process limit like CPULM would be)
SCRIPT=DEFAULT=<string>   default directory script process should be set to
                          before activation ('#" disables this, can be a U**x
                          style spec beginning '/' in which case it's ignored)
SCRIPT=FIND     (default) search for the script file in any 'script' or 'exec'
SCRIPT=NOFIND   do not search for the script file (for use with RTEs)
SCRIPT=PARAMS=[+](name=value)  a list of additional (non-CGI) variables
                               assigned and passed to a script
[NO]SCRIPT=PATH=FIND      check that the PATH_TRANSLATED exists and before
                          invoking the script report 404 if it doesn't
[NO]SCRIPT=QUERY=NONE     do not parse query string into form or key elements
[NO]SCRIPT=QUERY=RELAXED  don't error report form-url-encoding issues,
                          let the script deal with it if necessary

[NO]SEARCH=NONE           do not activate (any) default search script

SSI=PRIV        mark a path as allowed to contain SSI documents that can
SSI=NOPRIV      use privileged SSI statements (e.g. "#dcl")
SSI=<string>    where string is a list of "#dcl" parameters allowed
                (this is a finer-grained control that SSI=[NO]PRIV)

SSLCGI=NONE     sets the style of SSL-related CGI variables 
SSLCGI=APACHE_MOD_SSL      
SSLCGI=PURVEYOR      
[NO]SSLCGI

[NO]STMLF       files in this path can be converted from variable to stream-LF

THROTTLE=FROM=             n1, requests after which queuing occurs
THROTTLE=TO=               n2, requests after which the queue is FIFOed
THROTTLE=RESUME=           n3, requests after which FIFOing stops
THROTTLE=BUSY=             n4, requests after which an immediate 503 "busy"
THROTTLE=TIMEOUT=QUEUE=    t/o1, hh:mm:ss before a queued request processes
THROTTLE=TIMEOUT=BUSY=     t/o2, hh:mm:ss before a queued request terminates
THROTTLE=n1[,n2,n3,n4,t/o1,t/o2]  n1 = 'from',
                                  n2 = 'to',
                                  n3 = 'resume',
                                  n4 = 'busy',
                                  t/o1 = 'timeout-queue',
                                  t/o2 = 'timeout-busy'

TIMEOUT=KEEPALIVE=   hh:mm:ss  (can be made "none" to disable)
TIMEOUT=NOPROGRESS=  hh:mm:ss
TIMEOUT=OUTPUT=      hh:mm:ss
TIMEOUT=n,n,n        hh:mm:ss; keepalive, noprogress, output


[CONFIGFILE]
------------
The non-global [IncludeFile] equivalent [ConfigFile] designed for local,
per-service configuration management can include a subset of the full set of
mapping rules and settings.  The following are ALLOWED:

  FAIL
  MAP
  PASS
  PROTECT
  REDIRECT

  SET [NO]ACCEPT= 
  SET CHARSET= 
  SET [NO]EXPIRED
  SET [NO]HTML=
  SET INDEX=
  SET [NO]MAP=ONCE
  SET QUERY-STRING=
  SET RESPONSE=HEADER=

Those that cannot be used in [ConfigFile] are reported as errors during
configuration load.


MAP=ROOT Mapping
----------------
This path SETing allows for the more straight-forward mapping of virtual
service paths into a virtual-service-specific directory structure.  It ensures
that for all applicable rules the specified string is prepended to the mapped
path before it is converted into a file-system specification.  This remove the
need to do it with each virtual-service-specific rule.  It needs to be redone
or undone (e.g. set to empty) for each virtual service or when reverting to
general rules.  For example

  [[a.virtual.host]]
  set * map=root=/dka0/a/
  # 'a' has no access to any scripts at all
  pass /* /*
  fail *
  [[b.virtual.host]]
  set * map=root=/dka0/b/
  # 'b' are allowed to execute their own scripts
  exec /cgi-bin/* /cgi-bin/*
  pass /* /*
  fail *
  [[c.virtual.host]]
  # 'c' is allowed to execute the general server scripts
  exec /cgi-bin/* /cgi-bin/*
  set * map=root=/dka0/c/
  pass /* /*
  fail *
  [[*]]
  set * map=root=
  exec /cgi-bin/* /cgi-bin/*
  pass /* /web/*
  fail *


PERSONA-based User Scripting
----------------------------
For use with VMS V6.2 and greater (or 6.0 and 6.1 if compiled with the
PERSONA_MACRO build option), with the /PERSONA qualifier active.
The following rule set will allow user account based scripting.

  SET   /~*/www/cgi-bin/*  script=as=~
  UXEC  /~*/cgi-bin/*  /*/www/cgi-bin/*
  USER  /~*/*  /*/www/*
  REDIRECT  /~*  /~*/
  PASS  /~*/*  /dka0/users/*/*


DECnet-based User Scripting
---------------------------
If DECnet/OSU scripting is enabled then the following rule will activate a
script in the specified user's directory (all other things being equal).
Always map to some unique and specific subdirectory of the home directory.

exec /~*/cgi-bin/* /0""::/where/ever/*/cgi-bin/*

It's a little like the user-mapping rules:

pass /~* /where/ever/*


Mapping Conditionals
--------------------

 ***************
 * OBSOLESCENT *
 ***************

 ALTHOUGH MAPPING RULE CONDITIONALS DESCRIBED BELOW WILL CONTINUE
 TO BE SUPPORTED IN THE FORESEEABLE FUTURE THE PREFERED POST-7.2
 MECHANISM FOR CONDITIONAL PROCESSING OF RULES IS PROVIDED BY THE
 META-CONFIG CONDITIONAL STRUCTURES AND DIRECTIVES.

Conditionals are new to v4.4 and allow mapping rules to be applied
conditionally! That is, the conditionals contain strings that are matched to
specific elements of the request and only if matched are the corresponding
rules applied. The conditionals may contain the '*' and '%' wildcards, and
optionally be negated by an '!' at the start of the conditional string.

Conditionals must follow the rule and are delimited by '[' and ']'. Multiple,
space-separated conditions may be included within one '[...]'. This behaves as
a logical OR (i.e. the condition is true if only one is matched). Multiple
'[...]' conditionals may be included against a rule. These act as a logical
AND (i.e. all must have at least one condition matched). The result of an
entire conditional may be optionally negated by prefixing the '[' with a '!'.

If a conditional is not met the line is completely ignored.  Conditional rules
are not used for 'backward' translation (i.e. from VMS to URL path) so another
rule must exist somewhere to do these mappings.  Conditional rules are only
used by the HTTPd server, they are completely ignored by any scripts using the
MapUrl.c module.

[!]ac:accept               'Accept:'
[!]al:accept-language      'Accept-Language:'
[!]as:accept-charset       'Accept-Charset:'
[!]ca:                     boolean, callout in progress?
[!]ck:cookie               'Cookie:'
[!]dr:string               document root (set map=root=)
[!]ex:                     boolean, extended file specification path
[!]fo:host-name/address    'Forwarded:', proxies/gateways
[!]ho:host-name/address    client's of course!
[!]hm:host network mask    client mask, see immediately below
[!]me:http-method          GET, POST, etc.
[!]mp:string               derived mapped path (after script or map rule)
[!]no:string               notepad keywords/tokens
[!]pa:digit                1 if first pass, 2 if second pass through rules
[!]pi:path-info            request path information
[!]qs:query-string         request query string
[!]rc:[digit]              internally redirected count, 0..4, or boolean
[!]rf:refering-URL         'Referer:'
[!]rq:string               request field (e.g. "Keep-Alive: 300")
[!]ru:string               request URI - non-decoded path string
[!]sc:scheme               request scheme 'http:', 'http', 'https:' or 'https'
[!]sn:server-name          server host name
[!]sp:server-port          server port
[!]st:script-name          if during second pass compare to script name
[!]ua:user-agent           'User-Agent:'
[!]vs:host-name/address    virtual server, "Host:", destination
[!]xf:client               "X-Forwarded-For:" proxied cleint details

The host-mask ('HM') directive is a dotted-decimal network address, a slash,
then a dotted-decimal mask.  For example "[HM:131.185.250.0/255.255.255.192]". 
This has a 6 bit subnet.  It operates by bitwise-ANDing the client host address
with the mask, bitwise-ANDing the network address supplied with the mask, then
comparing the two results for equality.  Using the above example the host
131.185.250.250 would be accepted, but 131.185.250.50 would be rejected.


VERSION HISTORY
---------------
24-SEP-2004  MGD  bugfix; auth=revalidate= is minutes not seconds
20-APR-2004  MGD  actual on-disk structure for each PASS result (ODS-2 or
                  ODS-5) is applied to a path unless otherwise SET with ODS=,
                  don't bother translating a PASS unless it looks file-system
                  (consumes a few less cycles for things like proxy)
08-MAR-2004  MGD  MapUrl_Map() increase wildcard buffer space
26-FEB-2004  MGD  SET script=default=<directory>
13-JAN-2004  MGD  review string SETings for empty string behaviour
10-JAN-2004  MGD  SET ssi=exec=<string>
08-JAN-2004  MGD  SET response=header=[no]add[="<string>"]
16-DEC-2003  MGD  mapping now URL-encodes a redirect wildcard path portions
20-NOV-2003  MGD  SET proxy=reverse=location, proxy=reverse=verify
28-SEP-2003  MGD  when do a callout path mapping use a 'throw-away' path
                  SETing structure to prevent modification of request itself,
                  SET path=set=[no]ignore, 
                      path=set=[no]request 
                  prevent ODS-2 directory paths from containing single periods
01-SEP-2003  MGD  SET map=root=<string> (groan %^p)
                  'DR' conditional (parallels metacon "document-root:")
07-JUL-2003  MGD  SET script=command=<string>,
                  SET cache=[no]cgi, cache=expires=.., cache=[no]file,
                      cache=[no]net, cache=maxkbytes=.., cache=[no]nph,
                      cache=[no]script, cache=[no]ssi,
                      response=header=[append|full|none]
                  bugfix; do not allow SET mapping during a callout
27-JUN-2003  MGD  bugfix; request Html.. memory allocation (jpp@esme.fr)
24-MAY-2003  MGD  SET cache=[no]permanent, cache=max=<integer>
09-MAY-2003  MGD  regular expression support,
                  SET notepad=[+]<string> (associated 'NO' conditional),
                  SET map=restart,
                  SET proxy=unknown
                  'RQ' conditional (parallels metacon "request:")
22-APR-2003  MGD  improve SET rule error reporting (if no rule or no template)
02-APR-2003  MGD  SET proxy=xforwardedfor=[disabled|enabled|address|unknown]
                  SET proxy=forwarded=[disabled|by|for|address]
                  added 'XF' conditional, removed 'UD' (meaningless)
26-MAR-2003  MGD  SET alert=nnn (were 'nnn' can be 500, 599, 403, etc.)
08-MAR-2003  MGD  SET html=[bodytag|header|headertag|footer|footertag]=[..],
                  dir=style[=default|original|anchor|htdir]
15-FEB-2002  MGD  SET [no]search=none,
                  script=params=+(name=value) concatenates to any existing
17-JAN-2003  MGD  general (non-RTE) run-time allowed with (!..) syntax,
                  both run-time specifications allowed with SCRIPT rule
                  SET cgiplusin=[none|cr|lf|crlf],
                  SET cgiplusin=eof,
                  SET script=query=none,
                  SET script=path=find
10-JAN-2003  MGD  SET script=query=relaxed
16-NOV-2002  MGD  SET auth=all (all access must be authorized or fail)
06-NOV-2002  MGD  added 'MP', 'RC' and 'ST' conditionals
12-OCT-2002  MGD  refine metacon reporting,
                  refine mapping rule processing to ensure that paths with
                  forbidden syntax (e.g. multiple '/') generate RMS bad syntax
05-OCT-2002  MGD  'positional' wildcards,
                  SET query-string=,
                  provide 'rqptr->MetaConPass', 
                  confine script=as= to first pass (script resolution)
                  added 'PA' conditional
24-SEP-2002  MGD  added 'PI' and 'RU' conditionals
16-SEP-2002  MGD  MapUrl_ElementsToVms() excise parent directory syntax,
                  SET path [no]map=ellipsis (off by default),
                  SET report=4nn=nnn for mapping HTTP status
14-SEP-2002  MGD  only use MapUrl_VmsUserName() path ODS if not already set,
                  SET dir=charset= directory listing charset mapping rule
10-SEP-2002  MGD  add en/decoding for Advanced Server (Samba) file names
15-AUG-2002  MGD  SET alert=map, alert=auth, alert=end variants for ALERT,
                  SET ods=2, ods=5, now ods=pwk and ods=sri for below,
                  rework ..UrlToVms() and ..VmsToUrl() to allow for mapping
                  to and from SRI encoding (NFS) and PATHWORKS encoding
                  (and VMS to URL functions no longer explicitly URL-encode)
07-AUG-2002  MGD  bugfix; 'script' and 'exec' MetaConParseReset() state
04-AUG-2002  MGD  allow for 'pass /* 400' (i.e. no trailing message),
                  bugfix; template/result wildcard checking for scripting rules
31-MAY-2001  MGD  SET dir=access, dir=noaccess, dir=access=selective,
                  dir=impliedwildcard, dir=noimpliedwildcard,
                  dir=wildcard, dir=nowildcard
13-MAY-2002  MGD  SET auth=SYSUAF=pwdexpURL=
07-APR-2002  MGD  exchange DEVICE:[DIRECTORY]FILE.TYPE for the hopefully
                  more informative NO:[REVERSE.MAPPING.FOR.THIS]FILE.PATH
14-MAR-2002  NGD  bugfix; throttle report (jf.pieronne@laposte.net)
14-JAN-2002  MGD  added '**' and '*!' wildcard match and discard to template,
                  eliminate device:[.directory] (multiple consecutive '/'),
                  bugfix; arrghhh - wildcard substitution in MapUrl__Map()
18-NOV-2001  MGD  path SETings may now trail any rule with a result string,
                  and are applied to the matched path before mapping occurs,
                  SET http=accept-charset=, accept=lang
28-OCT-2001  MGD  bugfix; DECnet user script mapping
13-OCT-2001  MGD  ensure a conditional is not mistaken for a missing template,
                  script=params=(name=value[,name="value1 value2"]),
                  proxy=bind=IP-address, proxy=chain=host:port, alert,
                  auth=revalidate=hh:mm:ss,
                  bugfix; wildcard substitution in MapUrl__Map()
25-AUG-2001  MGD  meta-config (even workin' on my birthday, sigh)
04-AUG-2001  MGD  support module WATCHing
15-MAY-2001  MGD  bugfix; remove 'RU' conditional (mapping before auth!)
10-MAY-2001  MGD  modify throttle parameter meanings,
                  set rule SCRIPT=CPU= for limiting script process CPU
13-APR-2001  MGD  add queue length to THROTTLE=n[,n,n,hh:mm:ss,hh:mm:ss]
                  add SCRIPT=BIT-BUCKET=hh:mm:ss,
                  bugfix; conditions for NetThisVirtualService() call
05-APR-2001  MGD  bugfix; 'HM' network mask processing
28-MAR-2001  MGD  EXEC of file type
13-MAR-2001  MGD  set rules THROTTLE=n[,n,n,n] & TIMEOUT=n,n,n
                  "hh:mm:ss" for providing a more versatile period
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile(), [IncludeFile]
22-DEC-2000  MGD  use of backslash to escape non-wildcard reserved characters
                  in rule and conditional strings (i.e. \] for ])
19-NOV-2000  MGD  bugfix; mapping '/'
10-NOV-2000  MGD  bugfix; MapUrl_UrlToVms() final ';'
01-OCT-2000  MGD  set rule SCRIPT=AS=,
                  rework mapping code somewhat,
                  MapUrl_VmsUserNameCache() on reset free entries
31-AUG-2000  MGD  bugfix; MapUrl_UrlToVms() escaped version semi-colon
24-JUN-2000  MGD  add network-mask to conditionals,
                  persistant run-time environment in EXEC rule (...),
                  change 'HH' to 'VS' (virtual service) backward compatible
06-APR-2000  MGD  set rules SSLCGI= and MAP=
04-MAR-2000  MGD  use NetWriteFaol(), et.al.
02-JAN-2000  MGD  config file opened via ODS module,
                  detection of a path's on-disk-structure (ODS-2 or ODS-5),
                  modify URL->VMS and VMS->URL mapping for extended syntax,
                  avoid confusion; redirect indicators changed from '^' and
                  '@' for local and remote, to '<' and '>' respectively,
                  bugfix; trailing wildcard in set rule comparison
24-OCT-1999  MGD  groan; ensure storage does not overflow!!
                  (plus copious quantities of self-flagellation),
                  SYSUAF-based user directory mapping via a "USER" rule,
                  CGIprefix, [no]profile and [no]authonce SET rules,
                  happy seventh wedding anniversary
12-SEP-1999  MGD  virtual services modifications
10-MAY-1999  MGD  conditional mapping on HTTP cookie and referer,
                  increase the size of scratch storage,
                  mapping endless-loop detection
16-JAN-1999  MGD  allow for proxy service mappings by removing requirement
                  for template and result paths to be absolute ("/...") paths
                  (every time I have to deal with this module I groan)
10-JAN-1999  MGD  bugfix; pass rules not returning mapped path
17-OCT-1998  MGD  SET mapping rule,
                  virtual services via "[[virtual-host:virtual-port]]",
                  "qs:" query string conditional,
                  invalid RMS file name/character substitution configurable
12-AUG-1998  MGD  MapUrl_VmsToUrl() now only optionally absorbs MFD
19-JUL-1998  MGD  'E'xec DECnet-based user scripts
10-MAR-1998  MGD  allow for local redirects (using '^' detect character)
07-FEB-1998  MGD  added "sc:" scheme/protocol conditional for SSL support
20-DEC-1997  MGD  added DECnet support in _UrlToVms(),
                  result paths can now begin with an '*'
25-OCT-1997  MGD  added "as:", "fo:", and "hh:" conditionals,
                  bugfix; MsgFor() required request pointer to find langauge
15-SEP-1997  MGD  MsgFor() was not returning a leading-null message (does now),
                  HTTP status code rules (e.g. "pass /no/* 403 forbidden"),
                  removed two consecutive slashes inhibiting rule mapping
07-SEP-1997  MGD  added "sn:" and "sp:" conditionals
09-AUG-1997  MGD  v4.3, message database, mapping "conditionals",
                  more cart-wheels, hand-stands and contortions (see 01-OCT-96)
05-JUN-1997  MGD  v4.2, CGIplus "exec+" and "script+" rules
01-FEB-1997  MGD  HTTPd version 4, major changes for form-based config
01-OCT-1996  MGD  not proud of how I shoe-horned the reporting features in,
                  good example of slop-down or object-disoriented programming,
                  :^( will rework the whole thing one day, sigh!
                  bugfix; requiring use of 'TheFirstRuleWillBeTheNextRule'
06-APR-1996  MGD  modified conversion of non-VMS characters in MapUrl_UrlToVms()
15-FEB-1995  MGD  bugfix; redirect rule
01-DEC-1995  MGD  v3.0
24-MAR-1995  MGD  bugfix; end-of-string sometimes not detected when matching
20-DEC-1994  MGD  revised for multi-threaded HTTP daemon
20-JUN-1994  MGD  single-threaded daemon
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#undef __VMS_VER
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <devdef.h>
#include <dvidef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>

#include "wasd.h"

#define WASD_MODULE "MAPURL"

/**********/
/* macros */
/**********/

#define MAPURL_RMS_SUBSTITUTION_DEFAULT '$'

#define WATCH_MODULE_DETAIL (WATCH_MODULE(WATCH_MOD_MAPURL) && \
                             WATCH_MODULE(WATCH_MOD__DETAIL))

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

BOOL  MapUrlPathOds5;

MAPPING_META  MappingMeta;
MAPPING_META  *MappingMetaPtr;

#define MAPURL_DEFAULT_USER_CACHE_SIZE  32

LIST_HEAD  MapUrlUserNameCacheList;
int  MapUrlUserNameCacheCount,
     MapUrlUserNameCacheEntries;

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

extern BOOL  AuthProtectRule,
             AuthPolicySysUafRelaxed,
             CliOdsExtendedDisabled,
             CliOdsExtendedEnabled,
             OdsExtended;

extern int  EfnWait,
            ServerPort,
            ServerHostNameLength;

extern unsigned long  SysPrvMask[];

extern char  *FaoUrlEncodeTable[];

extern char  ErrorWatchSysFao[],
             ServerHostName[],
             ServerHostPort[],
             SoftwareID[],
             Utility[];

extern CONFIG_STRUCT  Config;
extern IPADDRESS  TcpIpEmptyAddress;
extern META_CONFIG  *MetaGlobalMappingPtr;
extern MSG_STRUCT  Msgs;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This overly-long function (sorry) is not AST reentrant, but when executed 
within the context of the HTTPd, is executed either without the possibility of 
AST interruption, or during AST processing and so provides atomic 
functionality.  Pointers to any strings returned must be used immediately 
(before leaving AST context), or copied into storage maintained by the calling 
routine. 

Maps from URL format to VMS file specification, and REVERSE-MAPS from VMS file 
specification to URL format.  Maps scripts.

Always returns a pointer to char.  An error message is detected by being  
returned as a pointer to a string beginning with a null-character!  The 
message begins from character one. 

Call with 'PathPtr' pointing at a string containing the URL path and 'VmsPtr' 
pointing to the storage to contain the mapped VMS equivalent.

Call with 'VmsPtr' pointing to a VMS file specification (dev:[dir]file.ext) 
and 'PathPtr' pointing to an empty string, used as storage to contain the 
REVERSE-MAPPED file specification.

Call with 'PathPtr' pointing at a string containing the URL path, 'VmsPtr' 
pointing to storage to contain the mapped VMS equivalent, 'ScriptPtr' pointing 
to storage for the URL format script path and 'ScriptVmsPtr' pointing to 
storage to contain the VMS specification of the script procedure/image.

The MapUrl_Map() just wraps MapUrl__Map() so that it's function can be WATCHed.

See alter-egos defined in MapUrl.h
*/ 

char* MapUrl_Map
(
char *PathPtr,
int SizeOfPathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char *ScriptPtr,
int SizeOfScriptPtr,
char *ScriptVmsPtr,
int SizeOfScriptVmsPtr,
char *RunTimePtr,
int SizeOfRunTimePtr,
int *PathOdsPtr,
REQUEST_STRUCT *rqptr
)
{
   BOOL  VmsToPath;
   char  *cptr, *sptr;
   char  OdsNote [32];

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

   if (!rqptr || !WATCHING(rqptr))
   {
      cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                          VmsPtr, SizeOfVmsPtr,
                          ScriptPtr, SizeOfScriptPtr,
                          ScriptVmsPtr, SizeOfScriptVmsPtr,
                          RunTimePtr, SizeOfRunTimePtr,
                          PathOdsPtr, rqptr);
      if (rqptr)
      {
         rqptr->MetaConPass = 0;
         rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
      }
      return (cptr);
   }

   if (!WATCH_CATEGORY(WATCH_MAPPING))
   {
      cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                          VmsPtr, SizeOfVmsPtr,
                          ScriptPtr, SizeOfScriptPtr,
                          ScriptVmsPtr, SizeOfScriptVmsPtr,
                          RunTimePtr, SizeOfRunTimePtr,
                          PathOdsPtr, rqptr);
      if (rqptr)
      {
         rqptr->MetaConPass = 0;
         rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
      }
      return (cptr);
   }

#if WATCH_CAT

   VmsToPath = false;
   if (PathPtr && PathPtr[0])
      WatchThis (rqptr, FI_LI, WATCH_MAPPING, "PATH !AZ", PathPtr); 
   else
   if (VmsPtr && VmsPtr[0])
   {
      VmsToPath = true;
      WatchThis (rqptr, FI_LI, WATCH_MAPPING, "VMS !AZ", VmsPtr); 
   }

   cptr = MapUrl__Map (PathPtr, SizeOfPathPtr,
                       VmsPtr, SizeOfVmsPtr, 
                       ScriptPtr, SizeOfScriptPtr,
                       ScriptVmsPtr, SizeOfScriptVmsPtr,
                       RunTimePtr, SizeOfRunTimePtr,
                       PathOdsPtr, rqptr);

   if (VmsToPath)
      WatchThis (rqptr, FI_LI, WATCH_MAPPING, "RESULT !AZ", cptr);
   else
   {
      if (!PathPtr) PathPtr = "";
      if (!VmsPtr) VmsPtr = "";
      if (!ScriptPtr) ScriptPtr = "";
      if (!ScriptVmsPtr) ScriptVmsPtr = "";
      if (!RunTimePtr) RunTimePtr = "";

      WatchThis (rqptr, FI_LI, WATCH_MAPPING, "RESULT");
      if (!cptr[0] && cptr[1])
         WatchDataFormatted ("Error: !AZ\n", cptr+1);
      else
      if (cptr[0] == '\1')
         WatchDataFormatted ("Redirect: !AZ\n", cptr+1);
      else
      {
         switch (PathOdsPtr ? *PathOdsPtr : rqptr->PathOds)
         {
            case MAPURL_PATH_ODS_2 : sptr = " (ODS-2)"; break;
            case MAPURL_PATH_ODS_5 : sptr = " (ODS-5)"; break;
            case MAPURL_PATH_ODS_ADS : sptr = " (ADS)"; break;
            case MAPURL_PATH_ODS_PWK : sptr = " (PWK)"; break;
            case MAPURL_PATH_ODS_SMB : sptr = " (SMB)"; break;
            case MAPURL_PATH_ODS_SRI : sptr = " (SRI)"; break;
            default : sptr = " (ods-2)";
         }
         WatchDataFormatted ("\
     Mapped: !AZ\n\
 Translated: !AZ!AZ\n\
     Script: !AZ\n\
Script-File: !AZ\n\
   Run-Time: !AZ\n",
           cptr, VmsPtr, VmsPtr[0] ? sptr : "",
           ScriptPtr, ScriptVmsPtr, RunTimePtr);
      }
   }

   if (rqptr)
   {
      rqptr->MetaConPass = 0;
      rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
   }
   return (cptr);

#endif /* WATCH_CAT */
}

char* MapUrl__Map
(
char *PathPtr,
int SizeOfPathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char *ScriptPtr,
int SizeOfScriptPtr,
char *ScriptVmsPtr,
int SizeOfScriptVmsPtr,
char *RunTimePtr,
int SizeOfRunTimePtr,
int *PathOdsPtr,
REQUEST_STRUCT *rqptr
)
{
   static char  DerivedPathBuffer [1024+128],
                PathBuffer [1024+128],
                VmsBuffer [1024+128];

   BOOL  EvanescentZeroed,
         ExecFileType,
         LoadingRules,
         MapEllipsis,
         MapPathToVms,
         StatusCodeMapping,
         SubstituteForUserName;
   int  idx, widx, status,
        MapRootLength,
        MetaConPass,
        MetaConRestartCount,
        PathOds,
        SetPathIgnore,
        TotalLength,
        WatchThisMatch,
        WatchThisOne,
        WildStringCount;
   unsigned short  Length;
   char  ch;
   char  *cptr, *eptr, *pptr, *rptr, *sptr, *tptr, *uptr, *zptr,
         *MapRootPtr;
   char  *WildString [REGEX_PMATCH_MAX];
   char  RmsSubChar;
   char  Scratch [2048+128],
         UserDefault [256],
         VmsUserMappedBuffer [256],
         WildBuffer [2048+128];
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;
   REQUEST_PATHSET  EvanescentPathSet;
   REQUEST_PATHSET  *rqpsptr;
   regex_t  *pregptr;
   regmatch_t  pmatch [REGEX_PMATCH_MAX];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_Map() !UL !&Z !UL !&Z !UL !&Z !UL",
                 SizeOfPathPtr, PathPtr, SizeOfVmsPtr, VmsPtr,
                 SizeOfScriptPtr, ScriptPtr, SizeOfScriptVmsPtr);

   /* if just loading the rules then free any previous set */
   if (LoadingRules = (!PathPtr && !VmsPtr && !ScriptPtr))
      MetaConUnload (&MetaGlobalMappingPtr, &MapUrl_ConfigUnloadLineData);

   if (!MetaGlobalMappingPtr)
   {
      /*********************/
      /* read mapping file */
      /*********************/

      MapUrl_ConfigLoad (&MetaGlobalMappingPtr);
   }

   /* if just (re)loading rules then return now */
   if (LoadingRules) return ("\0\0");

   /*****************************************/
   /* map from the URL or VMS to VMS or URL */
   /*****************************************/

   if (!PathPtr)
   {
      *(unsigned short*)PathBuffer = '\0\0';
      PathPtr = PathBuffer + 1;
      SizeOfPathPtr = sizeof(PathBuffer);
   }
   if (!VmsPtr)
   {
      *(VmsPtr = VmsBuffer) = '\0';
      SizeOfVmsPtr = sizeof(VmsBuffer);
   }
   /* making 'ScriptPtr' non-empty means "exec" and "script" are ignored */
   if (!ScriptPtr) ScriptPtr = "?";

   if (PathPtr[0])
   {
      /* if the URL is not an empty string then force the conversion to VMS */
      MapPathToVms = true;
      *VmsPtr = '\0';
   }
   else
   {
      /* URL was an empty string, convert from VMS to URL */
      MapPathToVms = false;
      /* generate a URL-style version of the VMS specification */
      MapUrl_VmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, true,
                       rqptr->rqPathSet.PathOds);
   }

   /***********************/
   /* loop thru the rules */
   /***********************/

   if (!MetaGlobalMappingPtr)
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_NO_RULES)-1);

   WatchThisOne = WatchThisMatch = 0;
   if (WATCHING(rqptr))
   {
      if (WATCH_CATEGORY(WATCH_MAPPING)) WatchThisOne = WATCH_MAPPING;
      if (WATCH_CATEGORY(WATCH_MATCH)) WatchThisMatch = WATCH_MATCH;
   }

   MapRootLength = PathOds = 0;
   RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
   EvanescentZeroed = MapEllipsis = SetPathIgnore = false;
   MapRootPtr = NULL;

   MetaConRestartCount = 0;
   MetaConPass = 1;
   if (rqptr)
   {
      rqptr->MetaConPass = MetaConPass;
      rqptr->MetaConRestartCount = MetaConRestartCount;
      rqptr->MetaConMappedPtr = rqptr->MetaConScriptPtr = NULL;
      /* if mapping from callout then prevent actual request SETing changes */
      if (rqptr->rqCgi.CalloutInProgress)
      {
         rqpsptr = &EvanescentPathSet;
         memset (&EvanescentPathSet, 0, sizeof(EvanescentPathSet));
         EvanescentZeroed = true;
      }
      else
         rqpsptr = &rqptr->rqPathSet;
   }
   else
      rqpsptr = NULL;

   MetaConParseReset (MetaGlobalMappingPtr, true);
   for (;;)
   {
      mclptr = MetaGlobalMappingPtr->ParsePtr =
         MetaGlobalMappingPtr->ParseNextPtr;

      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z\n",
            mclptr, mclptr->Size, mclptr->Token, mclptr->Number,
            mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr);

      /* if terminating empty "line" */
      if (!mclptr->Size) break;
      if (mclptr->Token == METACON_TOKEN_TEXT)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = mclptr->TextPtr;
         mrptr = mclptr->LineDataPtr;
         MetaGlobalMappingPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
      }
      else
      if (mclptr->Token != METACON_TOKEN_DIRECTORY)
      {
         /* not a simple text line, have meta-config parse this one for us */
         cptr = MetaConParse (rqptr, MetaGlobalMappingPtr, &mclptr,
                              WatchThisOne);
         /* if the end of rules */
         if (!cptr) break;

         /* if error string */
         if (!*cptr && *(cptr+1)) return (cptr);
         /* if inline rule and expression was false */
         if (*(unsigned long*)cptr == '\0\0\0\1') continue;
         mrptr = mclptr->LineDataPtr;
      }

      if (mclptr->Token == METACON_TOKEN_DIRECTORY)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = mclptr->TextPtr + sizeof("[ConfigDirectory]");
         while (*cptr && ISLWS(*cptr)) cptr++;
         zptr = (sptr = rqptr->ConfigDirectory) +
                sizeof(rqptr->ConfigDirectory)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         rqptr->ConfigDirectoryLength = sptr - rqptr->ConfigDirectory;
         MetaGlobalMappingPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
         continue;
      }

      /* there was a problem in initializing the rule */
      if (!mrptr)
      {
         if (WatchThisOne) WatchDataFormatted ("?!AZ\n", cptr);
         continue;
      }

      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!&X !UL !&Z !&Z !&Z\n", 
            mrptr, mrptr->RuleType, mrptr->TemplatePtr,
            mrptr->ResultPtr, mrptr->ConditionalPtr);

      /* hmmm, endless loop!! */
      if (MetaConPass > 2)
         return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);

      /* if 'ScriptPtr' is not to be used then ignore "exec" and "script" */
      if (ScriptPtr[0] &&
          (mrptr->RuleType == MAPURL_RULE_EXEC ||
           mrptr->RuleType == MAPURL_RULE_SCRIPT ||
           mrptr->RuleType == MAPURL_RULE_UXEC))
         continue;

      /*****************************************************************/
      /* compare the URL with the template/result in the mapping entry */
      /*****************************************************************/

      if (mrptr->RuleType == MAPURL_RULE_SET ||
          mrptr->RuleType == MAPURL_RULE_PROTECT)
      {
         /*********************/
         /* SET/PROTECT rules */
         /*********************/

         /* cheap check obvious non-/match before using expensive functions */
         tptr = mrptr->TemplatePtr;
         pregptr = &mrptr->RegexPregTemplate;
         pptr = PathPtr;
         if (Config.cfMisc.RegexEnabled && *tptr == REGEX_CHAR) tptr++;
         /* while matching vanilla characters */
         while ((isalnum(*tptr) || *tptr == '/' ||
                 *tptr == '-' || *tptr == '_') &&
                tolower(*pptr) == tolower(*tptr))
         {
            pptr++;
            tptr++;
         }
         /* if non-matching vanilla character */
         if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
         {
            if (WatchThisMatch)
               WatchThis (rqptr, FI_LI, WATCH_MATCH, "NO !&Z", tptr);
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
            continue;
         }

         /* if a trailing wildcard then it's a match already! */
         if (*(unsigned short*)tptr == '*\0')
         {
            if (WatchThisMatch)
               WatchThis (rqptr, FI_LI, WATCH_MATCH, "YES");
         }
         else
         {
            /* expensive comparison of path and template */
            if (!StringMatchAndRegex (rqptr, PathPtr, mrptr->TemplatePtr,
                                      SMATCH_STRING_REGEX, pregptr, NULL))
            {
               /* not matched then straight to next rule */
               if (!WatchThisOne) continue;
               MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
               continue;
            }
         }

         if (WATCH_MODULE(WATCH_MOD_MAPURL))
            WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "MATCHED");
      }
      else
      {
         /***************/
         /* other rules */
         /***************/

         /* when mapping VMS to URL then ignore all BUT "pass" & "set" rules */
         if (!MapPathToVms && mrptr->RuleType != MAPURL_RULE_PASS) continue;

         /* if reverse-mapping VMS to URL use the result if available */
         if (!MapPathToVms && mrptr->ResultPtr[0])
         {
            if (MapRootPtr)
            {
               /* if a mapping root is set then eliminate this first */
               cptr = MapRootPtr;
               pptr = PathPtr;
               while (*cptr && *pptr && tolower(*cptr) == tolower(*pptr))
               {
                  cptr++;
                  pptr++;
               }
               if (*cptr || !*pptr)
               {
                  /* the path and root do not match */
                  if (!WatchThisMatch) continue;
                  WatchThis (rqptr, FI_LI, WATCH_MATCH, "NO (root) !&Z", cptr);
                  continue;
                }
            }
            cptr = tptr = mrptr->ResultPtr;
            pregptr = NULL;
         }
         else
         {
            cptr = tptr = mrptr->TemplatePtr;
            pregptr = &mrptr->RegexPregTemplate;
         }

         /* cheap check obvious non-/match before using expensive functions */
         if (Config.cfMisc.RegexEnabled && *tptr == REGEX_CHAR) tptr++;
         pptr = PathPtr;
         /* while matching vanilla characters */
         while ((isalnum(*tptr) || *tptr == '/' ||
                 *tptr == '-' || *tptr == '_') &&
                tolower(*pptr) == tolower(*tptr))
         {
            pptr++;
            tptr++;
         }
         /* if non-matching vanilla character */
         if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
         {
            if (WatchThisMatch)
               WatchThis (rqptr, FI_LI, WATCH_MATCH, "NO !&Z", tptr);
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
            continue;
         }

         /* if a trailing wildcard then it's a match already! */
         if (*(unsigned short*)tptr == '*\0')
         {
            if (WatchThisMatch)
               WatchThis (rqptr, FI_LI, WATCH_MATCH, "YES");
            /* simulate the setting of the 'pmatch' data */
            pmatch[0].rm_so = 0;
            pmatch[1].rm_so = pptr - PathPtr;
            while (*pptr) pptr++;
            pmatch[0].rm_eo = pmatch[1].rm_eo = pptr - PathPtr;
            for (idx = 2; idx < REGEX_PMATCH_MAX; idx++)
                pmatch[idx].rm_so = pmatch[idx].rm_eo = -1;
         }
         else
         {
            /* expensive comparison of path and template */
            if (!StringMatchAndRegex (rqptr, PathPtr, cptr,
                                      SMATCH_STRING_REGEX, pregptr, &pmatch))
            {
               /* not matched then straight to next rule */
               if (!WatchThisOne) continue;
               MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_NOT, SetPathIgnore);
               continue;
            }
         }

         /************/
         /* matched! */
         /************/

         /* copy using 'regmatch_t' data, strings into 'WildBuffer' */
         zptr = (sptr = WildBuffer) + sizeof(WildBuffer);
         for (idx = WildStringCount = 0; idx < REGEX_PMATCH_MAX; idx++)
         {
            WildString[idx] = "";
            if (pmatch[idx].rm_so == -1 || pmatch[idx].rm_eo == -1) continue;
            WildString[WildStringCount++] = sptr;
            pptr = PathPtr + pmatch[idx].rm_so;
            cptr = PathPtr + pmatch[idx].rm_eo;
            while (pptr < cptr && sptr < zptr) *sptr++ = *pptr++;
            if (sptr >= zptr) break;
            *sptr++ = '\0';
         }
         if (sptr >= zptr) 
         {
            ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
         }
         /* the actual number of wildcard/group matched strings */
         if (WildStringCount) WildStringCount--;

         if (WATCH_MODULE(WATCH_MOD_MAPURL))
         {
            WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "MATCHED");
            StringWatchPmatch (PathPtr, &pmatch);
            WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "WILDSTRING");
            for (idx = 1; idx <= WildStringCount; idx++)
               WatchDataFormatted ("!UL. !&Z\n", idx, WildString[idx]);
         }
      }

      if (mrptr->ConditionalPtr[0])
      {
         /* when not mapping using a request then ignore conditionals */
         if (rqptr)
         {
            /* if condition not met then continue */
            if (!MapUrl_Conditional ((REQUEST_STRUCT*)rqptr, mrptr, rqpsptr))
            {
               if (WatchThisOne)
                  MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                    MAPURL_REPORT_MATCH_RULE, SetPathIgnore);
               continue;
            }

            /* report rule and conditional matched */
            if (WatchThisOne)
               MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                                 MAPURL_REPORT_MATCH_RULECOND, SetPathIgnore);
         }

         if (WATCH_MODULE_DETAIL)
            WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "CONDITIONAL MATCHED");
      }
      else
      {
         /* report rule, there was no conditional */
         if (WatchThisOne)
            MapUrl_WatchRule (rqptr, mrptr, PathPtr, PathOds,
                              MAPURL_REPORT_MATCH_RULENOCOND, SetPathIgnore);
      }

      if (mrptr->PathSet)
      {
         /****************/
         /* path SETings */
         /****************/

         mrpsptr = &mrptr->mpPathSet;

         if (mrpsptr->MapSetIgnore)
         {
            /* takes effect immediately */
            SetPathIgnore = true;
            continue;
         }
         else
         if (mrpsptr->MapSetNoIgnore)
            SetPathIgnore = false;
 
         /* ignoring path SETings (except for the above of course ;^) */
         if (SetPathIgnore) continue;

         /***********************/
         /* being ignored? - no */
         /***********************/

         if (mrpsptr->MapRestart)
         {
            MetaConRestartCount++;
            if (WatchThisOne)
               WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                          "RESTART !UL", MetaConRestartCount); 
            if (MetaConRestartCount > MAPURL_RESTART_MAX)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            if (rqptr) rqptr->MetaConRestartCount = MetaConRestartCount;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;
         }

         if (mrpsptr->NoAcceptLang)
         {
            rqpsptr->AcceptLangChar = '\0';
            rqpsptr->AcceptLangWildcard =
               rqpsptr->AcceptLangTypeVariant = false;
            rqpsptr->AcceptLangPtr = NULL;
         }
         else
         if (mrpsptr->AcceptLangChar)
         {
            rqpsptr->AcceptLangChar = mrpsptr->AcceptLangChar;
            rqpsptr->AcceptLangWildcard = mrpsptr->AcceptLangWildcard;
            rqpsptr->AcceptLangTypeVariant = mrpsptr->AcceptLangTypeVariant;
            if (mrpsptr->AcceptLangLength)
            {
               rqpsptr->AcceptLangPtr =
                  VmGetHeap (rqptr, mrpsptr->AcceptLangLength+1);
               memcpy (rqpsptr->AcceptLangPtr,
                       mrpsptr->AcceptLangPtr,
                       mrpsptr->AcceptLangLength+1);
            }
         }

         if (mrpsptr->NoAlert)
            rqpsptr->Alert = 0;
         else
         if (mrpsptr->Alert)
            rqpsptr->Alert = mrpsptr->Alert;

         if (mrpsptr->NoAuthAll)
            rqpsptr->AuthorizeAll = false;
         else
         if (mrpsptr->AuthAll)
            rqpsptr->AuthorizeAll = true;

         if (mrpsptr->NoAuthMapped)
            rqpsptr->AuthorizeMapped = false;
         else
         if (mrpsptr->AuthMapped)
            rqpsptr->AuthorizeMapped = true;

         if (mrpsptr->NoAuthOnce)
            rqpsptr->AuthorizeOnce = false;
         else
         if (mrpsptr->AuthOnce)
            rqpsptr->AuthorizeOnce = true;
   
         /* only if they haven't been set in any previous pass */
         if (!rqpsptr->AuthRevalidateTimeout)
            rqpsptr->AuthRevalidateTimeout = mrpsptr->AuthRevalidateTimeout;

         if (mrpsptr->AuthSysUafPwdExpUrlLength)
         {
            rqpsptr->AuthSysUafPwdExpUrlPtr =
               VmGetHeap (rqptr, mrpsptr->AuthSysUafPwdExpUrlLength+1);
            memcpy (rqpsptr->AuthSysUafPwdExpUrlPtr,
                    mrpsptr->AuthSysUafPwdExpUrlPtr,
                    mrpsptr->AuthSysUafPwdExpUrlLength+1);
         }
   
         if (mrpsptr->NoCache)
         {
            rqpsptr->NoCache = true;
            rqpsptr->CacheCGI =
               rqpsptr->CacheNoFile =
               rqpsptr->CacheNet =
               rqpsptr->CacheNPH =
               rqpsptr->CachePermanent =
               rqpsptr->CacheQuery = false;
            rqpsptr->CacheExpiresAfter =
               rqpsptr->CacheGuardSeconds =
               rqpsptr->CacheMaxKBytes = 0;
         }
         else
         if (mrpsptr->CacheSetting)
         {
            if (mrpsptr->Cache)
               rqpsptr->NoCache = rqpsptr->CacheNoFile = false;
 
            if (mrpsptr->CacheNoCGI)
               rqpsptr->CacheCGI = false;
            else
            if (mrpsptr->CacheCGI)
               rqpsptr->CacheCGI = true;

            if (mrpsptr->CacheNoFile)
               rqpsptr->CacheNoFile = true;
            else
            if (mrpsptr->CacheFile)
               rqpsptr->CacheNoFile = false;

            if (mrpsptr->CacheNoNet)
               rqpsptr->CacheNet = false;
            else
            if (mrpsptr->CacheNet)
               rqpsptr->CacheNet = true;

            if (mrpsptr->CacheNoNPH)
               rqpsptr->CacheNPH = false;
            else
            if (mrpsptr->CacheNPH)
               rqpsptr->CacheNPH = true;

            if (mrpsptr->CacheNoPermanent)
               rqpsptr->CachePermanent = false;
            else
            if (mrpsptr->CachePermanent)
               rqpsptr->CachePermanent = true;

            if (mrpsptr->CacheNoQuery)
               rqpsptr->CacheQuery = false;
            else
            if (mrpsptr->CacheQuery)
               rqpsptr->CacheQuery = true;

            if (mrpsptr->CacheNoScript)
               rqpsptr->CacheCGI = rqpsptr->CacheNPH = false;
            else
            if (mrpsptr->CacheScript)
               rqpsptr->CacheCGI = rqpsptr->CacheNPH = true;

            if (mrpsptr->CacheNoSSI)
               rqpsptr->CacheSSI = false;
            else
            if (mrpsptr->CacheSSI)
               rqpsptr->CacheSSI = true;

            if (mrpsptr->CacheExpiresAfter)
            {
               if (mrpsptr->CacheExpiresAfter == CACHE_EXPIRES_NONE)
                  rqpsptr->CacheExpiresAfter = 0;
               else
                  rqpsptr->CacheExpiresAfter = mrpsptr->CacheExpiresAfter;
            }
            if (mrpsptr->CacheGuardSeconds)
            {
               /* negative 1 indicates to revert to configuration maximum */
               if (mrpsptr->CacheGuardSeconds == -1)
                  rqpsptr->CacheGuardSeconds = 0;
                else
                  rqpsptr->CacheGuardSeconds = mrpsptr->CacheGuardSeconds;
            }
            if (mrpsptr->CacheMaxKBytes)
            {
               /* negative 1 indicates to revert to configuration maximum */
               if (mrpsptr->CacheMaxKBytes == -1)
                  rqpsptr->CacheMaxKBytes = 0;
               else
                  rqpsptr->CacheMaxKBytes = mrpsptr->CacheMaxKBytes;
            }
         }

         if (mrpsptr->CgiPlusInCC[0] || mrpsptr->CgiPlusInCC[1])
         {
            if (mrpsptr->CgiPlusInCC[0])
               memcpy (rqpsptr->CgiPlusInCC, mrpsptr->CgiPlusInCC,
                       sizeof(rqpsptr->CgiPlusInCC));
            else
               memset (rqpsptr->CgiPlusInCC, 0,
                       sizeof(rqpsptr->CgiPlusInCC));
         }

         if (mrpsptr->CgiPlusInNoWriteof)
            rqpsptr->CgiPlusInWriteof = false;
         else
         if (mrpsptr->CgiPlusInWriteof)
            rqpsptr->CgiPlusInWriteof = true;

         if (mrpsptr->NoDefaultSearch)
            rqpsptr->NoDefaultSearch = true;
         else
         if (mrpsptr->DefaultSearch)
            rqpsptr->NoDefaultSearch = false;

         if (mrpsptr->DirNoAccess)
         {
            rqpsptr->DirNoAccess = true;
            rqpsptr->DirAccess = rqpsptr->DirAccessSelective = false;
         }
         else
         if (mrpsptr->DirAccess)
         {
            rqpsptr->DirAccess = true;
            rqpsptr->DirNoAccess = rqpsptr->DirAccessSelective = false;
         }
         else
         if (mrpsptr->DirAccessSelective)
         {
            rqpsptr->DirAccessSelective = true;
            rqpsptr->DirAccess = rqpsptr->DirNoAccess = false;
         }

         if (mrpsptr->DirNoImpliedWildcard)
            rqpsptr->DirNoImpliedWildcard = true;
         else
         if (mrpsptr->DirImpliedWildcard)
            rqpsptr->DirNoImpliedWildcard = false;

         if (mrpsptr->DirStyle == MAPURL_DIR_STYLE_DEFAULT ||
             mrpsptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR)
            rqpsptr->DirStyle = 0;
         else
         if (mrpsptr->DirStyle)
            rqpsptr->DirStyle = mrpsptr->DirStyle;

         if (mrpsptr->DirNoWildcard)
         {
            rqpsptr->DirNoWildcard = true;
            rqpsptr->DirWildcard = false;
         }
         else
         if (mrpsptr->DirWildcard)
         {
            rqpsptr->DirNoWildcard = false;
            rqpsptr->DirWildcard = true;
         }

         if (mrpsptr->NoExpired)
            rqpsptr->Expired = false;
         else
         if (mrpsptr->Expired)
            rqpsptr->Expired = true;

         if (mrpsptr->NoHtmlEquals)
         {
            rqpsptr->HtmlBodyTagPtr =
               rqpsptr->HtmlFooterPtr =
               rqpsptr->HtmlFooterTagPtr =
               rqpsptr->HtmlHeaderPtr =
               rqpsptr->HtmlHeaderTagPtr = NULL;
         }
         if (mrpsptr->HtmlBodyTagPtr)
         {
            /* empty string resets */
            if (mrpsptr->HtmlBodyTagLength)
            {
               rqpsptr->HtmlBodyTagPtr =
                  VmGetHeap (rqptr, mrpsptr->HtmlBodyTagLength+1);
               memcpy (rqpsptr->HtmlBodyTagPtr,
                       mrpsptr->HtmlBodyTagPtr,
                       mrpsptr->HtmlBodyTagLength+1);
            }
            else
               rqpsptr->HtmlBodyTagPtr = NULL;
         }
         if (mrpsptr->HtmlFooterPtr)
         {
            /* empty string resets */
            if (mrpsptr->HtmlFooterLength)
            {
               rqpsptr->HtmlFooterPtr =
                  VmGetHeap (rqptr, mrpsptr->HtmlFooterLength+1);
               memcpy (rqpsptr->HtmlFooterPtr,
                       mrpsptr->HtmlFooterPtr,
                       mrpsptr->HtmlFooterLength+1);
            }
            else
               rqpsptr->HtmlFooterPtr = NULL;
         }
         if (mrpsptr->HtmlFooterTagPtr)
         {
            /* empty string resets */
            if (mrpsptr->HtmlFooterTagLength)
            {
               rqpsptr->HtmlFooterTagPtr =
                  VmGetHeap (rqptr, mrpsptr->HtmlFooterTagLength+1);
               memcpy (rqpsptr->HtmlFooterTagPtr,
                       mrpsptr->HtmlFooterTagPtr,
                       mrpsptr->HtmlFooterTagLength+1);
            }
            else
               rqpsptr->HtmlFooterTagPtr = NULL;
         }
         if (mrpsptr->HtmlHeaderPtr)
         {
            /* empty string resets */
            if (mrpsptr->HtmlHeaderLength)
            {
               rqpsptr->HtmlHeaderPtr =
                  VmGetHeap (rqptr, mrpsptr->HtmlHeaderLength+1);
               memcpy (rqpsptr->HtmlHeaderPtr,
                       mrpsptr->HtmlHeaderPtr,
                       mrpsptr->HtmlHeaderLength+1);
            }
            else
               rqpsptr->HtmlHeaderPtr = NULL;
         }
         if (mrpsptr->HtmlHeaderTagPtr)
         {
            /* empty string resets */
            if (mrpsptr->HtmlHeaderTagLength)
            {
               rqpsptr->HtmlHeaderTagPtr =
                  VmGetHeap (rqptr, mrpsptr->HtmlHeaderTagLength+1);
               memcpy (rqpsptr->HtmlHeaderTagPtr,
                       mrpsptr->HtmlHeaderTagPtr,
                       mrpsptr->HtmlHeaderTagLength+1);
            }
            else
               rqpsptr->HtmlHeaderTagPtr = NULL;
         }

         if (mrpsptr->NoLog)
            rqpsptr->NoLog = true;
         else
         if (mrpsptr->Log)
            rqpsptr->NoLog = false;

         if (mrpsptr->NoMapEllipsis)
            rqpsptr->MapEllipsis = MapEllipsis = false;
         else
         if (mrpsptr->MapEllipsis)
            rqpsptr->MapEllipsis = MapEllipsis = true;

         if (mrpsptr->MapNonEmpty)
            rqpsptr->MapEmpty = false;
         else
         if (mrpsptr->MapEmpty)
            rqpsptr->MapEmpty = true;

         if (mrpsptr->MapOnce)
            rqpsptr->MapOnce = true;
         else
         if (mrpsptr->MapOnce)
            rqpsptr->MapOnce = false;

         if (mrpsptr->MapRootPtr)
         {
            /* empty string resets */
            if (mrpsptr->MapRootLength)
            {
               MapRootPtr = mrpsptr->MapRootPtr;
               MapRootLength = mrpsptr->MapRootLength;
               rqpsptr->MapRootPtr =
                  VmGetHeap (rqptr, mrpsptr->MapRootLength+1);
               memcpy (rqpsptr->MapRootPtr,
                       mrpsptr->MapRootPtr,
                       mrpsptr->MapRootLength+1);
            }
            else
            {
               MapRootPtr = rqpsptr->MapRootPtr = NULL;
               MapRootLength = 0;
            }
         }

         if (mrpsptr->MapSetRequest)
            rqpsptr = &rqptr->rqPathSet;
         else
         if (mrpsptr->MapSetNoRequest)
         {
            rqpsptr = &EvanescentPathSet;
            if (!EvanescentZeroed)
            {
               memset (&EvanescentPathSet, 0, sizeof(EvanescentPathSet));
               EvanescentZeroed = true;
            }
         }

         if (mrpsptr->PrivSsi)
            rqpsptr->PrivSsi = true;
         else
         if (mrpsptr->NoPrivSsi)
            rqpsptr->PrivSsi = false;
   
         if (mrpsptr->NoProfile)
            rqpsptr->NoProfile = true;
         else
         if (mrpsptr->Profile)
            rqpsptr->NoProfile = false;

         if (mrpsptr->ProxyForwardedBy)
            rqpsptr->ProxyForwardedBy = mrpsptr->ProxyForwardedBy;

         if (mrpsptr->ProxyXForwardedFor)
            rqpsptr->ProxyXForwardedFor = mrpsptr->ProxyXForwardedFor;

         if (mrpsptr->NoProxyReverseVerify)
            rqpsptr->ProxyReverseVerify = false;
         else
         if (mrpsptr->ProxyReverseVerify)
            rqpsptr->ProxyReverseVerify = true;

         if (mrpsptr->NoProxyUnknownRequestFields)
            rqpsptr->ProxyUnknownRequestFields = false;
         else
         if (mrpsptr->ProxyUnknownRequestFields)
            rqpsptr->ProxyUnknownRequestFields = true;

         /* BASIC overrides DETAILED */
         if (mrpsptr->ReportBasic)
         {
            rqpsptr->ReportBasic = true;
            rqpsptr->ReportDetailed = false;
         }
         else
         if (mrpsptr->ReportDetailed)
         {
            rqpsptr->ReportBasic = false;
            rqpsptr->ReportDetailed = true;
         }

         if (mrpsptr->Report400as)
            rqpsptr->Report400as = mrpsptr->Report400as;
         if (mrpsptr->Report403as)
            rqpsptr->Report403as = mrpsptr->Report403as;
         if (mrpsptr->Report404as)
            rqpsptr->Report404as = mrpsptr->Report404as;

         if (mrpsptr->ResponseHeaderNone)
            rqpsptr->ResponseHeaderNone = true;
         else
         if (mrpsptr->ResponseHeaderBegin)
            rqpsptr->ResponseHeaderBegin = true;
         else
         if (mrpsptr->ResponseHeaderFull)
            rqpsptr->ResponseHeaderBegin =
               rqpsptr->ResponseHeaderNone = false;

         if (mrpsptr->RmsSubChar)
            RmsSubChar = mrpsptr->RmsSubChar;

         /* careful! this one's a little back-to-front */
         if (mrpsptr->ScriptFind)
            rqpsptr->ScriptNoFind = false;
         else
         if (mrpsptr->ScriptNoFind)
            rqpsptr->ScriptNoFind = true;

         if (mrpsptr->ScriptNoPathFind)
            rqpsptr->ScriptPathFind = false;
         else
         if (mrpsptr->ScriptPathFind)
            rqpsptr->ScriptPathFind = true;

         if (mrpsptr->ScriptNoQueryNone)
            rqpsptr->ScriptQueryNone = false;
         else
         if (mrpsptr->ScriptQueryNone)
            rqpsptr->ScriptQueryNone = true;

         if (mrpsptr->ScriptNoQueryRelaxed)
            rqpsptr->ScriptQueryRelaxed = false;
         else
         if (mrpsptr->ScriptQueryRelaxed)
            rqpsptr->ScriptQueryRelaxed = true;

         if (!rqpsptr->ThrottleFrom &&
             mrpsptr->ThrottleFrom)
         {
            /* only if it hasn't been set in any previous pass */
            rqpsptr->ThrottleBusy = mrpsptr->ThrottleBusy;
            rqpsptr->ThrottleFrom = mrpsptr->ThrottleFrom;
            rqpsptr->ThrottleIndex = mrpsptr->ThrottleIndex;
            rqpsptr->ThrottleResume = mrpsptr->ThrottleResume;
            rqpsptr->ThrottleTo = mrpsptr->ThrottleTo;
            rqpsptr->ThrottleTimeoutBusy = mrpsptr->ThrottleTimeoutBusy;
            rqpsptr->ThrottleTimeoutQueue = mrpsptr->ThrottleTimeoutQueue;
         }

         /* only if they haven't been set in any previous pass */
         if (!rqpsptr->ScriptBitBucketTimeout)
            rqpsptr->ScriptBitBucketTimeout = mrpsptr->ScriptBitBucketTimeout;

         if (!rqpsptr->ScriptCpuMax)
            rqpsptr->ScriptCpuMax = mrpsptr->ScriptCpuMax;

         if (!rqpsptr->TimeoutKeepAlive)
         {
            /* hmmm, bit of a kludge (but easy) */
            if (mrpsptr->TimeoutKeepAlive < 0)
               rqptr->KeepAliveRequest = false;
            else
               rqpsptr->TimeoutKeepAlive = mrpsptr->TimeoutKeepAlive;
         }

         if (!rqpsptr->TimeoutNoProgress)
            rqpsptr->TimeoutNoProgress = mrpsptr->TimeoutNoProgress;
   
         if (!rqpsptr->TimeoutOutput)
            rqpsptr->TimeoutOutput = mrpsptr->TimeoutOutput;
   
         if (mrpsptr->NoStmLF)
            rqpsptr->StmLF = false;
         else
         if (mrpsptr->StmLF)
            rqpsptr->StmLF = true;
   
         if (mrpsptr->SSLCGIvar)
         {
            if (rqpsptr->SSLCGIvar == SESOLA_CGI_VAR_NONE)
               rqpsptr->SSLCGIvar = 0;
            else
               rqpsptr->SSLCGIvar = mrpsptr->SSLCGIvar;
         }

         if (mrpsptr->PathOds) rqpsptr->PathOds = mrpsptr->PathOds;

         /* allowed to be an empty string */
         if (mrpsptr->CgiPrefixPtr)
         {
            rqpsptr->CgiPrefixPtr =
               VmGetHeap (rqptr, mrpsptr->CgiPrefixLength+1);
            memcpy (rqpsptr->CgiPrefixPtr,
                    mrpsptr->CgiPrefixPtr,
                    mrpsptr->CgiPrefixLength+1);
         }

         /* allowed to be an empty string (suppresses charset) */
         if (mrpsptr->CharsetPtr)
         {
            rqpsptr->CharsetPtr =
               VmGetHeap (rqptr, mrpsptr->CharsetLength+1);
            memcpy (rqpsptr->CharsetPtr,
                    mrpsptr->CharsetPtr,
                    mrpsptr->CharsetLength+1);
         }

         /* empty string resets (reverts type, must have a content type) */
         if (mrpsptr->ContentTypePtr)
         {
            if (mrpsptr->ContentTypeLength)
            {
               rqpsptr->ContentTypePtr =
                  VmGetHeap (rqptr, mrpsptr->ContentTypeLength+1);
               memcpy (rqpsptr->ContentTypePtr,
                       mrpsptr->ContentTypePtr,
                       mrpsptr->ContentTypeLength+1);
            }
            else
               rqpsptr->ContentTypePtr = NULL;
         }

         /* allowed to be an empty string (suppresses charset) */
         if (mrpsptr->DirCharsetPtr)
         {
            rqpsptr->DirCharsetPtr =
               VmGetHeap (rqptr, mrpsptr->DirCharsetLength+1);
            memcpy (rqpsptr->DirCharsetPtr,
                    mrpsptr->DirCharsetPtr,
                    mrpsptr->DirCharsetLength+1);
         }

         /* allowed to be an empty string */
         if (mrpsptr->HttpAcceptCharsetPtr)
         {
            if (mrpsptr->HttpAcceptCharsetPtr[0])
            {
               rqptr->rqHeader.AcceptCharsetPtr =
                  VmGetHeap (rqptr, mrpsptr->HttpAcceptCharsetLength+1);
               memcpy (rqptr->rqHeader.AcceptCharsetPtr,
                       mrpsptr->HttpAcceptCharsetPtr,
                       mrpsptr->HttpAcceptCharsetLength+1);
            }
            else
            {
               /* empty, as if it had never been part of the request header */
               rqptr->rqHeader.AcceptCharsetPtr = NULL;
               rqptr->rqHeader.AcceptCharsetLength = 0;
            }
         }
   
         /* allowed to be an empty string */
         if (mrpsptr->HttpAcceptLangPtr)
         {
            if (mrpsptr->HttpAcceptLangPtr[0])
            {
               rqptr->rqHeader.AcceptLangPtr =
                  VmGetHeap (rqptr, mrpsptr->HttpAcceptLangLength+1);
               memcpy (rqptr->rqHeader.AcceptLangPtr,
                       mrpsptr->HttpAcceptLangPtr,
                       mrpsptr->HttpAcceptLangLength+1);
            }
            else
            {
               /* empty, as if it had never been part of the request header */
               rqptr->rqHeader.AcceptLangPtr = NULL;
               rqptr->rqHeader.AcceptLangLength = 0;
            }
         }
   
         if (mrpsptr->IndexPtr)
         {
            /* empty string resets */
            if (mrpsptr->IndexLength)
            {
               rqpsptr->IndexPtr =
                  VmGetHeap (rqptr, mrpsptr->IndexLength+1);
               memcpy (rqpsptr->IndexPtr,
                       mrpsptr->IndexPtr,
                       mrpsptr->IndexLength+1);
            }
            else
               rqpsptr->IndexPtr = NULL;
         }
   
         if (mrpsptr->NotePadPtr &&
             !mrpsptr->NotePadLength)
         {
            /* empty string resets */
            rqptr->NotePadPtr = NULL;
            rqptr->NotePadLength = 0;
         }
         else
         if (mrpsptr->NotePadPtr)
         {
            cptr = mrpsptr->NotePadPtr;
            if (*cptr == '+')
            {
               /* append these parameters to (any) existing */
               cptr++;
               rqptr->NotePadPtr =
                  VmReallocHeap (rqptr, rqptr->NotePadPtr,
                                 rqptr->NotePadLength +
                                    mrpsptr->NotePadLength+1,
                                 FI_LI);
               sptr = rqptr->NotePadPtr + rqptr->NotePadLength;
               rqptr->NotePadLength += mrpsptr->NotePadLength - 1;
            }
            else
            {
               if (rqptr->NotePadLength < mrpsptr->NotePadLength)
                  rqptr->NotePadPtr = sptr =
                     VmGetHeap (rqptr, mrpsptr->NotePadLength+1);
               else
                  sptr = rqptr->NotePadPtr;
               rqptr->NotePadLength = mrpsptr->NotePadLength;
            }
            memcpy (sptr, cptr, mrpsptr->NotePadLength+1);
         }

         if (mrpsptr->ProxyReverseLocationPtr)
         {
            /* empty string resets */
            if (mrpsptr->ProxyReverseLocationLength)
            {
               rqpsptr->ProxyReverseLocationPtr =
                  VmGetHeap (rqptr, mrpsptr->ProxyReverseLocationLength+1);
               memcpy (rqpsptr->ProxyReverseLocationPtr,
                       mrpsptr->ProxyReverseLocationPtr,
                       mrpsptr->ProxyReverseLocationLength+1);
            }
            else
               rqpsptr->ProxyReverseLocationPtr = NULL;
         }

         /* query string is allowed to be set empty */
         if (mrpsptr->QueryStringPtr)
         {
            if (rqptr->rqHeader.QueryStringLength < mrpsptr->QueryStringLength)
               rqptr->rqHeader.QueryStringPtr =
                  VmGetHeap (rqptr, mrpsptr->QueryStringLength+1);
            memcpy (rqptr->rqHeader.QueryStringPtr,
                    mrpsptr->QueryStringPtr,
                    mrpsptr->QueryStringLength+1);
            rqptr->rqHeader.QueryStringLength = mrpsptr->QueryStringLength;
         }

         /* leave this in for backward compatibility (but use empty string) */
         if (mrpsptr->ResponseHeaderNoAdd)
         {
            rqpsptr->ResponseHeaderAddPtr = NULL;
            rqpsptr->ResponseHeaderAddLength = 0;
         }

         if (mrpsptr->ResponseHeaderAddPtr &&
             !mrpsptr->ResponseHeaderAddLength)
         {
            /* empty string resets */
            rqpsptr->ResponseHeaderAddPtr = NULL;
            rqpsptr->ResponseHeaderAddLength = 0;
         }
         else
         if (mrpsptr->ResponseHeaderAddLength)
         {
            cptr = mrpsptr->ResponseHeaderAddPtr;
            if (rqpsptr->ResponseHeaderAddLength)
            {
               /* append these parameters to existing */
               rqpsptr->ResponseHeaderAddPtr =
                  VmReallocHeap (rqptr, rqpsptr->ResponseHeaderAddPtr,
                                 rqpsptr->ResponseHeaderAddLength +
                                 mrpsptr->ResponseHeaderAddLength+2, FI_LI);
               sptr = rqpsptr->ResponseHeaderAddPtr +
                      rqpsptr->ResponseHeaderAddLength;
               rqpsptr->ResponseHeaderAddLength +=
                  mrpsptr->ResponseHeaderAddLength + 1;
            }
            else
            {
               rqpsptr->ResponseHeaderAddPtr = sptr =
                  VmGetHeap (rqptr, mrpsptr->ResponseHeaderAddLength+2);
               rqpsptr->ResponseHeaderAddLength =
                  mrpsptr->ResponseHeaderAddLength + 1;
            }
            memcpy (sptr, cptr, mrpsptr->ResponseHeaderAddLength);
            sptr += mrpsptr->ResponseHeaderAddLength;
            *(unsigned short*)sptr = '\n\0';
         }

         if (MetaConPass == 1 &&
             mrpsptr->ScriptAsPtr)
         {
            /* empty string resets */
            if (mrpsptr->ScriptAsLength)
            {
               rqpsptr->ScriptAsPtr =
                  VmGetHeap (rqptr, mrpsptr->ScriptAsLength+1);
               memcpy (rqpsptr->ScriptAsPtr,
                       mrpsptr->ScriptAsPtr,
                       mrpsptr->ScriptAsLength+1);
            }
            else
               rqpsptr->ScriptAsPtr = NULL;
         }

         if (mrpsptr->ScriptCommandPtr)
         {
            /* empty string resets */
            if (mrpsptr->ScriptCommandLength)
            {
               rqpsptr->ScriptCommandPtr =
                  VmGetHeap (rqptr, mrpsptr->ScriptCommandLength+1);
               memcpy (rqpsptr->ScriptCommandPtr,
                       mrpsptr->ScriptCommandPtr,
                       mrpsptr->ScriptCommandLength+1);
            }
            else
               rqpsptr->ScriptCommandPtr = NULL;
         }

         if (mrpsptr->ScriptDefaultPtr)
         {
            /* empty string resets */
            if (mrpsptr->ScriptDefaultLength)
            {
               rqpsptr->ScriptDefaultPtr =
                  VmGetHeap (rqptr, mrpsptr->ScriptDefaultLength+1);
               memcpy (rqpsptr->ScriptDefaultPtr,
                       mrpsptr->ScriptDefaultPtr,
                       mrpsptr->ScriptDefaultLength+1);
            }
            else
               rqpsptr->ScriptDefaultPtr = NULL;
         }

         if (mrpsptr->ScriptParamsPtr &&
             !mrpsptr->ScriptParamsLength)
         {
            /* empty string resets */
            mrpsptr->ScriptParamsPtr = NULL;
            mrpsptr->ScriptParamsLength = 0;
         }
         else
         if (mrpsptr->ScriptParamsPtr)
         {
            cptr = mrpsptr->ScriptParamsPtr;
            if (*cptr == '+')
            {
               /* append these parameters to (any) existing */
               cptr++;
               rqpsptr->ScriptParamsPtr =
                  VmReallocHeap (rqptr, rqpsptr->ScriptParamsPtr,
                                 rqpsptr->ScriptParamsLength +
                                 mrpsptr->ScriptParamsLength+1, FI_LI);
               sptr = rqpsptr->ScriptParamsPtr +
                      rqpsptr->ScriptParamsLength;
               rqpsptr->ScriptParamsLength +=
                  mrpsptr->ScriptParamsLength - 1;
            }
            else
            {
               if (rqpsptr->ScriptParamsLength <
                   mrpsptr->ScriptParamsLength)
                  rqpsptr->ScriptParamsPtr = sptr =
                     VmGetHeap (rqptr, mrpsptr->ScriptParamsLength+1);
               else
                  sptr = rqpsptr->ScriptParamsPtr;
               rqpsptr->ScriptParamsLength = mrpsptr->ScriptParamsLength;
            }
            memcpy (sptr, cptr, mrpsptr->ScriptParamsLength+1);
         }

         if (mrpsptr->SsiExecPtr)
         {
            /* empty string resets */
            if (mrpsptr->SsiExecLength)
            {
               rqpsptr->SsiExecPtr =
                  VmGetHeap (rqptr, mrpsptr->SsiExecLength+1);
               memcpy (rqpsptr->SsiExecPtr,
                       mrpsptr->SsiExecPtr,
                       mrpsptr->SsiExecLength+1);
            }
            else
               rqpsptr->SsiExecPtr = NULL;
         }

         if (IPADDRESS_IS_RESET(&rqpsptr->ProxyBindIpAddress) &&
             IPADDRESS_IS_SET(&mrpsptr->ProxyBindIpAddress))
            rqpsptr->ProxyBindIpAddress = mrpsptr->ProxyBindIpAddress;

         if (IPADDRESS_IS_RESET(&rqpsptr->ProxyChainIpAddress) &&
             IPADDRESS_IS_SET(&mrpsptr->ProxyChainIpAddress))
         {
            IPADDRESS_COPY (&rqpsptr->ProxyChainIpAddress,
                            &mrpsptr->ProxyChainIpAddress);
            rqpsptr->ProxyChainPort = mrpsptr->ProxyChainPort;
            if (mrpsptr->ProxyChainHostPortPtr &&
                mrpsptr->ProxyChainHostPortPtr[0])
            {
               rqpsptr->ProxyChainHostPortPtr =
                  VmGetHeap (rqptr, mrpsptr->ProxyChainHostPortLength+1);
               memcpy (rqpsptr->ProxyChainHostPortPtr,
                       mrpsptr->ProxyChainHostPortPtr,
                       mrpsptr->ProxyChainHostPortLength+1);
            }
         }

         /* when mapping VMS to URL then ignore all BUT "pass" rules */
         if (!MapPathToVms && mrptr->RuleType != MAPURL_RULE_PASS) continue;
      }

      /***************/
      /* end SETings */
      /***************/

      if (rqpsptr->PathOds)
      {
         /* path file-system has been set, perhaps to turn off ODS-5! */
         if (rqpsptr->PathOds == MAPURL_PATH_ODS_0)
            PathOds = rqptr->PathOds = rqpsptr->PathOds = 0;
         else
            PathOds = rqptr->PathOds = rqpsptr->PathOds; 
      }
      else
      {
         /* set from the $GETDVI volume file-system */
         PathOds = rqptr->PathOds = mrptr->ResultPathOds;
      }
      if (PathOdsPtr) *PathOdsPtr = PathOds;

      if (MapPathToVms)
      {
         /***************************/
         /* mapping from URL to VMS */
         /***************************/

         switch (mrptr->RuleType)
         {
         case MAPURL_RULE_EXEC :
         case MAPURL_RULE_UXEC :

            /*******************/
            /* EXEC/UXEC rule */
            /*******************/

            if (!WildStringCount)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);

            /* first get the script component from the request */
            tptr = mrptr->TemplatePtr;
            zptr = (sptr = ScriptPtr) + SizeOfScriptPtr;
            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               if (!rqpsptr->ScriptAsPtr ||
                   rqpsptr->ScriptAsPtr[0] != '~')
               {
                  /* hmmm, not enabled with a mapping rule!! */
                  return (MsgFor(rqptr,MSG_GENERAL_DISABLED)-1);
               }
               /* first insert the /~username component */
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
               if (*tptr) tptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[2];
            }
            else
            if (*(unsigned short*)tptr == '/~')
            {
               /* mapping a DECnet username based script */
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
               while (*tptr == '*') tptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[2];
            }
            else
            {
               /* mapping a vanilla exec rule */
               cptr = WildString[1];
            }

            while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
            while (*tptr && *(unsigned short*)tptr == '**') tptr++;
            if (*(unsigned short*)tptr == '*.')
            {
               /* EXECing a file type here */
               ExecFileType = true;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               /* now get the script file type (e.g. ".cgi") */
               tptr++;
               while (*tptr && *tptr != '*' && sptr < zptr) *sptr++ = *tptr++;
            }
            else
            {
               /* EXECing a directory here */
               ExecFileType = false;
               while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            }

            if (sptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            *sptr = '\0';

            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               /* "append" the username to the circumflex */
               rqpsptr->ScriptAsPtr = sptr =
                   VmGetHeap (rqptr, strlen(WildString[1])+2);
               *sptr++ = '~';
               for (cptr = WildString[1]; *cptr; *sptr++ = toupper(*cptr++));
               *sptr = '\0';
            }

            /* now get what it's mapped into (the more complex bit ;^) */
            rptr = mrptr->ResultPtr;

            if (*rptr == '(')
            {
               /* run-time, e.g. exec /plbin/@ (ht_exe:pl.exe)/plbin/@ */
               rptr++;
               zptr = (sptr = RunTimePtr) + SizeOfRunTimePtr;
               while (*rptr && *rptr != ')' && sptr < zptr) *sptr++ = *rptr++;
               if (sptr >= zptr)
               {
                  ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
                  return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
               }
               *sptr = '\0';
               if (*rptr) rptr++;
            }

            zptr = (sptr = Scratch) + sizeof(Scratch);

            if (mrptr->RuleType == MAPURL_RULE_UXEC)
            {
               /* begin with the mapped user instead of the /~username */
               if (rqpsptr->PathOds)
                  uptr = MapUrl_VmsUserName (rqptr, WildString[1], NULL);
               else
               {
                  uptr = MapUrl_VmsUserName (rqptr, WildString[1], &PathOds);
                  rqptr->PathOds = PathOds;
                  if (PathOdsPtr) *PathOdsPtr = PathOds;
               }
               if (!uptr[0]) return (uptr);
               /* copy in the now-mapped username */
               while (*uptr && sptr < zptr) *sptr++ = *uptr++;
               /* skip over the equivalent in the result (i.e. "/~*") */
               while (*rptr && *rptr != '*') rptr++;
               if (*rptr) rptr++;
               /* skip the first wildcarded string (username) */
               cptr = WildString[widx=2];
            }
            else
            if (*(unsigned short*)mrptr->TemplatePtr == '/~')
            {
               /*
                  e.g. exec /~@/cgi-bin/@ /0""::/web/user/@/cgi-bin/@
                  i.e. the user part must be prepended to the script part.
               */
               while (*rptr && *rptr != '*' && sptr < zptr)
               {
                  if (*rptr != '\"' || *(unsigned long*)rptr != '\"\"::')
                  {
                     *sptr++ = *rptr++;
                     continue;
                  }
                  /* mapping a DECnet username based script */
                  *sptr++ = *rptr++;
                  cptr = WildString[1];
                  while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               }
               while (*rptr == '*') rptr++;
               cptr = WildString[1];
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[widx=2];
            }
            else
            {
               /* vanilla script exec */
               if (MapRootPtr)
               {
                  if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
                     WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && sptr < zptr;
                       *sptr++ = *cptr++);
               }
               cptr = WildString[widx=1];
            }

            /* now the leading script-location part of the result */
            while (*rptr && *rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            while (*rptr == '*') rptr++;

            if (WATCH_MODULE_DETAIL)
               WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "!&Z", cptr);
            if (ExecFileType)
            {
               /* EXECing a file type here, script name (full buffer) */
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               cptr = WildString[++widx];
               /* now get the script file type (e.g. ".cgi") */
               while (*rptr && *rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            }
            else
            {
               /* EXECing a directory here, script name */
               while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            }

            if (sptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            *sptr = '\0';

            /* generate the VMS script name */
            MapUrl_UrlToVms (Scratch, ScriptVmsPtr, SizeOfScriptVmsPtr,
                             RmsSubChar, MapEllipsis, PathOds);

            /* indicate CGIplus script via leading '+' instead of '/' */
            if (mrptr->IsCgiPlusScript && (!RunTimePtr || !RunTimePtr[0]))
               ScriptPtr[0] = '+';

            /* get all including and after the first slash as the new path */
            zptr = (sptr = DerivedPathBuffer) + sizeof(DerivedPathBuffer);
            rqptr->MetaConMappedPtr = sptr;
            /* continue copying from the wildcard string */
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            *sptr++ = '\0';
            TotalLength = sptr - DerivedPathBuffer;

            if (WATCH_MODULE_DETAIL)
               WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "E/UXEC !&Z !&Z !&Z",
                          ScriptPtr, ScriptVmsPtr, DerivedPathBuffer);

            if (!rqpsptr->MapEmpty && !DerivedPathBuffer[0])
               return ("\0\0");

            if (rqpsptr->MapOnce)
            {
               /* convert the URL-style to a VMS-style specification */
               MapUrl_UrlToVms (DerivedPathBuffer, VmsPtr, SizeOfVmsPtr,
                                RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                             "E/UXEC !&Z !&Z", DerivedPathBuffer, VmsPtr);
               return (DerivedPathBuffer);
            }

            /* restarting at first rule map the path derived from the script */
            MetaConPass++;
            if (rqptr)
            {
               rqptr->MetaConPass = MetaConPass;
               rqptr->MetaConScriptPtr = ScriptPtr;
            }
            *VmsPtr = '\0';
            PathPtr = DerivedPathBuffer;
            RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
            MapEllipsis = rqpsptr->MapEllipsis = false;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;

         case MAPURL_RULE_FAIL :

            /*************/
            /* FAIL rule */
            /*************/

            if (WATCH_MODULE_DETAIL)
               WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "FAIL !&Z", PathPtr);
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_RULE)-1);

         case MAPURL_RULE_MAP :

            /************/
            /* MAP rule */
            /************/

            /* place this in a buffer so not to overwrite original path */
            zptr = (pptr = PathPtr = PathBuffer+1) + sizeof(PathBuffer)-1;
            rqptr->MetaConMappedPtr = pptr;
            widx = 1;
            /* scan through the result string */
            rptr = mrptr->ResultPtr;
            while (*rptr)
            {
               while (*rptr && *rptr != '*' && pptr < zptr) *pptr++ = *rptr++;
               if (!*rptr || pptr >= zptr) break;
               /* a wildcard asterisk, substitute from original path */
               while (*rptr == '*') rptr++;
               if (*rptr == '\'')
               {
                  /* specific wildcard component, e.g. "*'1" */
                  rptr++;
                  if (isdigit(*rptr))
                  {
                     idx = *rptr++ - '0';
                     cptr = WildString[idx];
                  }
                  else
                     cptr = "";
               }
               else
               if (widx < REGEX_PMATCH_MAX)
                  cptr = WildString[widx++];
               else
                  cptr = "";
               while (*cptr && pptr < zptr) *pptr++ = *cptr++;
            }
            if (pptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *pptr = '\0';

            /* continue with the substituted URL mapping */
            if (WATCH_MODULE_DETAIL)
               WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "MAP !&Z", PathPtr);
            continue;

         case MAPURL_RULE_PASS :
         case MAPURL_RULE_REDIRECT :
         case MAPURL_RULE_USER :

            /****************************/
            /* PASS/REDIRECT/USER rules */
            /****************************/

            cptr = WildString[widx=1];
            rptr = mrptr->ResultPtr; 
            zptr = (pptr = PathBuffer+1) + sizeof(PathBuffer)-1;

            /* if there is no result to map just pass back the original path */
            if (!*rptr) 
            {
               if (mrptr->RuleType == MAPURL_RULE_PASS && MapRootPtr)
               {
                  if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
                     WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && pptr < zptr;
                       *pptr++ = *cptr++);
                  for (cptr = PathPtr; *cptr && pptr < zptr; *pptr++ = *cptr++);
                  if (pptr >= zptr)
                     return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
                  *pptr = '\0';
                  pptr = PathBuffer;
               }
               else
                  pptr = PathPtr;

               if (*pptr != '/')
               {
                  /* doesn't look like file-system, don't bother translating */
                  return (PathPtr);
               }

               /* convert the URL-style to a VMS-style specification */
               MapUrl_UrlToVms (pptr, VmsPtr, SizeOfVmsPtr,
                                RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                              "PASS !&Z !&Z", PathPtr, VmsPtr);
               return (PathPtr);
            }

            /* if redirect rule leave room for the redirection indicator */
            if (mrptr->RuleType == MAPURL_RULE_REDIRECT) pptr++;

            if (mrptr->RuleType == MAPURL_RULE_USER)
            {
               /* begin with the mapped user instead of the /~username */
               if (rqpsptr->PathOds)
                  uptr = MapUrl_VmsUserName (rqptr, WildString[1], NULL);
               else
               {
                  uptr = MapUrl_VmsUserName (rqptr, WildString[1], &PathOds);
                  rqptr->PathOds = PathOds;
                  if (PathOdsPtr) *PathOdsPtr = PathOds;
               }
               if (!uptr[0]) return (uptr);
               /* copy in the-now mapped username */
               while (*uptr && pptr < zptr) *pptr++ = *uptr++;
               /* skip over the equivalent in the result (i.e. "/~*") */
               while (*rptr && *rptr != '*') rptr++;
               if (*rptr) rptr++;
               /* skip first wildcard string (username) */
               cptr = WildString[widx=2];
            }

            /* scan through the result string */
            if (mrptr->RuleType == MAPURL_RULE_PASS &&
                isdigit(rptr[0]) && isdigit(rptr[1]) &&
                isdigit(rptr[2]) && (!rptr[3] || rptr[3] == ' '))
            {
               StatusCodeMapping = true;
               while (*rptr && pptr < zptr) *pptr++ = *rptr++;
            }
            else
            { 
               StatusCodeMapping = false;
               if (mrptr->RuleType == MAPURL_RULE_PASS && MapRootPtr)
               {
                  if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
                     WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                                "ROOT !AZ", MapRootPtr);
                  for (cptr = MapRootPtr;
                       *cptr && pptr < zptr;
                       *pptr++ = *cptr++);
               }
               /* map wildcard asterisks */
               while (*rptr)
               {
                  while (*rptr && *rptr != '*' && pptr < zptr)
                     *pptr++ = *rptr++;
                  if (!*rptr) break;
                  /* wildcard asterisk, substitute from the wildcard buffer */
                  while (*rptr == '*') rptr++;
                  if (*rptr == '\'')
                  {
                     /* specific wildcard component, e.g. "*'1" */
                     rptr++;
                     if (isdigit(*rptr))
                     {
                        idx = *rptr++ - '0';
                        cptr = WildString[idx];
                     }
                     else
                        cptr = "";
                  }
                  else
                  if (widx < REGEX_PMATCH_MAX)
                     cptr = WildString[widx++];
                  else
                     cptr = "";
                  if (mrptr->RuleType == MAPURL_RULE_REDIRECT)
                  {
                     /* URL-encode wildcard portions from the path */
                     while (*cptr && pptr < zptr)
                     {
                        for (eptr = FaoUrlEncodeTable[*(unsigned char*)cptr];
                             *eptr && pptr < zptr;
                             *pptr++ = *eptr++);
                        cptr++;
                     }
                  }
                  else
                     while (*cptr && pptr < zptr) *pptr++ = *cptr++;
                  if (pptr >= zptr) break;
               }
            }
            if (pptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            *pptr = '\0';

            if (mrptr->RuleType == MAPURL_RULE_PASS ||
                mrptr->RuleType == MAPURL_RULE_USER)
            {
               /* if status code mapping return leading null char */
               if (StatusCodeMapping)
               {
                  PathBuffer[0] = '\0';
                  return (PathBuffer);
               }

               if (PathBuffer[1] != '/')
               {
                  /* doesn't look like file-system, don't bother translating */
                  return (PathBuffer+1);
               }

               /* convert the URL-style to a VMS-style specification */
               MapUrl_UrlToVms (PathBuffer+1, VmsPtr, SizeOfVmsPtr,
                                RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                             "PASS !&Z !&Z", PathPtr, VmsPtr);
               return (PathBuffer+1);
            }
            else
            {
               if (WATCH_MODULE_DETAIL)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                             "REDIRECT !&Z !&Z", PathPtr, VmsPtr);
               /* indicate it's a redirect */
               PathBuffer[1] = '\1';
               return (PathBuffer+1);
            }

         case MAPURL_RULE_SCRIPT :

            /***************/
            /* SCRIPT rule */
            /***************/

            tptr = mrptr->TemplatePtr;
            zptr = (pptr = ScriptPtr) + SizeOfScriptPtr;
            while (*tptr && *tptr != '*' && pptr < zptr) *pptr++ = *tptr++;
            if (pptr > ScriptPtr && pptr[-1] == '/') pptr--;
            if (pptr >= zptr)
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            *pptr = '\0';

            rptr = mrptr->ResultPtr;

            if (*rptr == '(')
            {
               /* run-time, e.g. exec /plbin/@ (ht_exe:pl.exe)/plbin/@ */
               rptr++;
               zptr = (sptr = RunTimePtr) + SizeOfRunTimePtr;
               while (*rptr && *rptr != ')' && sptr < zptr) *sptr++ = *rptr++;
               if (sptr >= zptr)
               {
                  ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
                  return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
               }
               *sptr = '\0';
               if (*rptr) rptr++;
            }

            zptr = (sptr = Scratch) + sizeof(Scratch);
            if (MapRootPtr)
            {
               if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
                  WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                             "ROOT !AZ", MapRootPtr);
               for (cptr = MapRootPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
            }
            while (*rptr &&
                   *rptr != '*' &&
                   *(unsigned short*)rptr != '/*' &&
                   sptr < zptr) *sptr++ = *rptr++;
            if (sptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            *sptr = '\0';

            /* convert the URL-format script to a VMS-format specification */
            MapUrl_UrlToVms (Scratch, ScriptVmsPtr, SizeOfScriptVmsPtr,
                             RmsSubChar, MapEllipsis, PathOds);

            /* indicate CGIplus script via leading '+' instead of '/' */
            if (mrptr->IsCgiPlusScript && (!RunTimePtr || !RunTimePtr[0]))
               ScriptPtr[0] = '+';

            /* get wildcard matched second section of path as new path */
            zptr = (sptr = DerivedPathBuffer) + sizeof(DerivedPathBuffer);
            rqptr->MetaConMappedPtr = sptr;
            if (rqpsptr->MapOnce && MapRootPtr)
            {
               if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
                  WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                             "ROOT !AZ", MapRootPtr);
               for (cptr = MapRootPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
            }
            while (*rptr != '*' && sptr < zptr) *sptr++ = *rptr++;
            cptr = WildString[1];
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '\0';
            if (sptr >= zptr)
            {
               ErrorNoticed (SS$_RESULTOVF, "buffer", FI_LI);
               return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
            }
            TotalLength = sptr - DerivedPathBuffer;

            if (WATCH_MODULE_DETAIL)
               WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                          "SCRIPT !&Z !&Z !&Z",
                          ScriptPtr, ScriptVmsPtr, DerivedPathBuffer);

            if (!rqpsptr->MapEmpty && !DerivedPathBuffer[0])
               return ("\0\0");

            if (rqpsptr->MapOnce)
            {
               /* convert the URL-style to a VMS-style specification */
               MapUrl_UrlToVms (DerivedPathBuffer, VmsPtr, SizeOfVmsPtr,
                                RmsSubChar, MapEllipsis, PathOds);
               if (WATCH_MODULE_DETAIL)
                  WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                             "SCRIPT !&Z !&Z", DerivedPathBuffer, VmsPtr);
               return (DerivedPathBuffer);
            }

            /* restarting at first rule map the path derived from the script */
            MetaConPass++;
            if (rqptr)
            {
               rqptr->MetaConPass = MetaConPass;
               rqptr->MetaConScriptPtr = ScriptPtr;
            }
            *VmsPtr = '\0';
            PathPtr = DerivedPathBuffer;
            RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
            MapEllipsis = rqpsptr->MapEllipsis = false;
            MetaConParseReset (MetaGlobalMappingPtr, true);
            continue;

         case MAPURL_RULE_PROTECT :

            /****************/
            /* PROTECT rule */
            /****************/

            if (MetaConPass == 1)
            {
               /* pass one protects the full path or script component */
               if (mrptr->ResultLength)
               {
                  rqptr->rqAuth.Protect1Ptr =
                     VmGetHeap (rqptr, mrptr->ResultLength+1);
                  rqptr->rqAuth.Protect1Length = mrptr->ResultLength;
                  memcpy (rqptr->rqAuth.Protect1Ptr,
                          mrptr->ResultPtr,
                          mrptr->ResultLength+1);
               }
               else
               {
                  /* empty result string cancels any previous authorization */
                  rqptr->rqAuth.Protect1Ptr = NULL;
                  rqptr->rqAuth.Protect1Length = 0;
               }
            }
            else
            {
               /* pass two protects any path following the script component */
               if (mrptr->ResultLength)
               {
                  rqptr->rqAuth.Protect2Ptr =
                     VmGetHeap (rqptr, mrptr->ResultLength+1);
                  rqptr->rqAuth.Protect2Length = mrptr->ResultLength;
                  memcpy (rqptr->rqAuth.Protect2Ptr,
                          mrptr->ResultPtr,
                          mrptr->ResultLength+1);
               }
               else
               {
                  /* empty result string cancels any previous authorization */
                  rqptr->rqAuth.Protect1Ptr = NULL;
                  rqptr->rqAuth.Protect1Length = 0;
               }
            }

            continue;

         case MAPURL_RULE_SET :

            /************/
            /* SET rule */
            /************/

            /* path SETings have already been applied */
            continue;
         }
      }
      else
      {
         /***************************/
         /* mapping from VMS to URL */
         /***************************/

         /* REVERSE maps a VMS "result" to a "template" :^) */

         cptr = WildString[1];
         if (MapRootPtr)
         {
            if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
               WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                          "ROOT !AZ", MapRootPtr);
            while (MapRootLength-- && *cptr) cptr++;
         }
         zptr = (pptr = PathPtr) + SizeOfPathPtr;
         tptr = mrptr->TemplatePtr;
         /* scan through the template string */
         while (*tptr)
         {
            while (*tptr && *tptr != '*' && pptr < zptr) *pptr++ = *tptr++;
            if (!*tptr) break;
            /* a wildcard asterisk, substitute from result path */
            while (*tptr == '*') tptr++;
            if (*cptr)
            {
               while (*cptr && pptr < zptr) *pptr++ = *cptr++;
               cptr++;
            }
         }
         if (pptr >= zptr)
            return (MsgFor(rqptr,MSG_MAPPING_DENIED_INTERNAL)-1);
         *pptr = '\0';

         if (WATCH_MODULE_DETAIL)
            WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "PASS !&Z", PathPtr);
         return (PathPtr);
      }
   }

   /***********************/
   /* a mapping not found */
   /***********************/

   if (*PathPtr != '/')
   {
      /* a "normal" request must begin with an absolute path, it's a proxy! */
      return (PathPtr);
   }

   if (MapPathToVms)
      return (MsgFor(rqptr,MSG_MAPPING_DENIED_DEFAULT)-1);
   else
      return (MAPURL_NO_REVERSE_PATH);
}

/*****************************************************************************/
/*
Load mapping rules into meta-config structure.
*/ 
 
int MapUrl_ConfigLoad (META_CONFIG **MetaConPtrPtr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ConfigLoad() !AZ", CONFIG_MAP_FILE_NAME);

   status = MetaConLoad (MetaConPtrPtr, CONFIG_MAP_FILE_NAME,
                         &MapUrl_ConfigLoadCallBack, true, true);
   if (*MetaConPtrPtr == MetaGlobalMappingPtr)
   {
      /* server startup/reload */
      MetaConStartupReport (MetaGlobalMappingPtr, "MAP");
      if (VMSnok (status)) exit (status);
   }
   return (status);
}

/*****************************************************************************/
/*
Called by MetaConUnload() to free resources allocated during mapping rule
configuration.
*/ 
 
MapUrl_ConfigUnload (META_CONFIG *mcptr)

{
   int  status;
   MAPPING_META  *mmptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "MapUrl_ConfigUnload()");

   if (mcptr->MappingMetaPtr)
   {
      if (mcptr->MappingMetaPtr == MappingMetaPtr)
      {
         memset (MappingMetaPtr, 0, sizeof(MAPPING_META));
         MappingMetaPtr = NULL;
      }
      else
         VmFree (mcptr->MappingMetaPtr, FI_LI);
      mcptr->MappingMetaPtr = NULL;
   }
}

/*****************************************************************************/
/*
Called by MetaConUnload() callback for each line's associated data, basically
to check for a regular expression structure and free it if present, then just
dispose of the line data itself.
*/ 
 
MapUrl_ConfigUnloadLineData (void *LineDataPtr)

{
   int  status;
   MAP_RULE_META  *mrptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ConfigUnloadLineData()");

   mrptr = (MAP_RULE_META*)LineDataPtr;
   if (mrptr->RegexPregTemplate.buffer) regfree (&mrptr->RegexPregTemplate);
   VmFree (mrptr, FI_LI);
}

/*****************************************************************************/
/*
For each non-meta-config directive line read by MetaConLoad() this function is
called to parse the line text's contents and to configure the private data
structure associated with each rule.
*/ 

BOOL MapUrl_ConfigLoadCallBack (META_CONFIG *mcptr)

{
   static char  Ods5Disabled [] = "ODS-5 processing CLI disabled",
                Ods5Enabled [] = "ODS-5 processing enabled",
                Ods5NotSupported [] = "ODS-5 not supported",
                ProblemAcceptLang [] =
                   "Set path \'accept=lang\' parameter problem",
                ProblemConditional [] = "Conditional problem",
                ProblemConfused [] = "Cannot understand this one at all",
                ProblemHostNameLookup [] = "Cannot resolve host name !AZ, !&m",
                ProblemNameValue [] = "Set path \'name=value\' pair problem",
                ProblemNotAllowed [] = "Rule cannot be used in [ConfigFile]",
                ProblemOverflow [] = "Storage overflow",
                ProblemProtect [] = "Protect path problem",
                ProblemRegex [] = "Regex: !AZ",
                ProblemResultPath [] = "Problem with \'result\' path",
                ProblemResultRequired [] = "Requires a \'result\'",
                ProblemResultStatus [] = "Rule \'result\' cannot be a status",
                ProblemRmsSubstitution [] = "RMS substitution character",
                ProblemSetPath [] = "Set path problem",
                ProblemSpecifiedWildcardCannot [] =
                   "Cannot use \'specified\' wildcards with rule",
                ProblemSpecifiedWildcardMix [] =
                   "Mix of \'specified\' and non-\'specified\' wildcards",
                ProblemSpecifiedWildcardRange [] =
                   "Value of \'specified\' wildcard out-of-range",
                ProblemTemplateRequired [] = "Requires a \'template\'",
                ProblemThrottleValues [] =
                   "Successive dependent throttle values must be larger",
                ProblemWildcardMapping [] = "Wildcard mapping problem",
                ProblemTooManyWildcard [] = "Too many wildcards";

   BOOL  AllowedInConfigFile,
         ConfigProblem,
         IsCgiPlusScript,
         PathSet,
         VirtualServerUnknown;
   int  status,
        CacheSetting,
        ConditionalOffset,
        ConfigFileIndex,
        ResultPathOds,
        RuleType,
        SetPathNoAcceptLang,
        SetPathAlert,
        SetPathNoAlert,
        SetPathAuthAll,
        SetPathNoAuthAll,
        SetPathAuthMapped,
        SetPathNoAuthMapped,
        SetPathAuthOnce,
        SetPathNoAuthOnce,
        SetPathAuthRevalidateTimeout,
        SetPathAcceptLangTypeVariant,
        SetPathAcceptLangOffset,
        SetPathAcceptLangWildcard,
        SetPathAuthSysUafPwdExpUrlOffset,
        SetPathCache,
        SetPathNoCache,
        SetPathCacheCGI,
        SetPathCacheNoCGI,
        SetPathCacheFile,
        SetPathCacheNoFile,
        SetPathCacheExpiresAfter,
        SetPathCacheGuardSeconds,
        SetPathCacheMaxKBytes,
        SetPathCacheNet,
        SetPathCacheNoNet,
        SetPathCacheNPH,
        SetPathCacheNoNPH,
        SetPathCachePermanent,
        SetPathCacheNoPermanent,
        SetPathCacheQuery,
        SetPathCacheNoQuery,
        SetPathCacheScript,
        SetPathCacheNoScript,
        SetPathCacheSSI,
        SetPathCacheNoSSI,
        SetPathCgiPlusInWriteof,
        SetPathCgiPlusInNoWriteof,
        SetPathDefaultSearch,
        SetPathNoDefaultSearch,
        SetPathDirAccess,
        SetPathDirNoAccess,
        SetPathDirAccessSelective,
        SetPathDirImpliedWildcard,
        SetPathDirNoImpliedWildcard,
        SetPathDirStyle,
        SetPathDirWildcard,
        SetPathDirNoWildcard,
        SetPathCgiPrefixOffset,
        SetPathCharsetOffset,
        SetPathContentTypeOffset,
        SetPathHtmlBodyTagOffset,
        SetPathHtmlFooterOffset,
        SetPathHtmlFooterTagOffset,
        SetPathHtmlHeaderOffset,
        SetPathHtmlHeaderTagOffset,
        SetPathNoHtmlEquals,
        SetPathHttpAcceptCharsetOffset,
        SetPathHttpAcceptLangOffset,
        SetPathDirCharsetOffset,
        SetPathIndexOffset,
        SetPathExpired,
        SetPathNoExpired,
        SetPathLog,
        SetPathNoLog,
        SetPathMapEllipsis,
        SetPathNoMapEllipsis,
        SetPathMapEmpty,
        SetPathMapNonEmpty,
        SetPathMapOnce,
        SetPathNoMapOnce,
        SetPathMapRestart,
        SetPathMapSetIgnore,
        SetPathMapSetNoIgnore,
        SetPathMapSetRequest,
        SetPathMapSetNoRequest,
        SetPathOds,
        SetPathPrivSsi,
        SetPathNoPrivSsi,
        SetPathProfile,
        SetPathNoProfile,
        SetPathMapRootOffset,
        SetPathNotePadOffset,
        SetPathProxyBindIpAddressOffset,
        SetPathProxyChainHostPortOffset,
        SetPathProxyChainPort,
        SetPathProxyForwardedBy,
        SetPathProxyReverseLocationOffset,
        SetPathProxyXForwardedFor,
        SetPathProxyReverseVerify,
        SetPathNoProxyReverseVerify,
        SetPathProxyUnknownRequestFields,
        SetPathNoProxyUnknownRequestFields,
        SetPathQueryStringOffset,
        SetPathReportBasic,
        SetPathReportDetailed,
        SetPathReport400as,
        SetPathReport403as,
        SetPathReport404as,
        SetPathResponseHeaderAddOffset,
        SetPathScriptAsOffset,
        SetPathScriptCommandOffset,
        SetPathScriptDefaultOffset,
        SetPathScriptFind,
        SetPathScriptNoFind,
        SetPathScriptPathFind,
        SetPathScriptNoPathFind,
        SetPathScriptQueryNone,
        SetPathScriptNoQueryNone,
        SetPathScriptQueryRelaxed,
        SetPathScriptNoQueryRelaxed,
        SetPathScriptBitBucketTimeout,
        SetPathScriptCpuMax,
        SetPathScriptParamsOffset,
        SetPathSsiExecOffset,
        SetPathThrottleBusy,
        SetPathThrottleFrom,
        SetPathThrottleResume,
        SetPathThrottleTo,
        SetPathThrottleTimeoutBusy,
        SetPathThrottleTimeoutQueue,
        SetPathTimeoutKeepAlive,
        SetPathTimeoutNoProgress,
        SetPathTimeoutOutput,
        SetPathStmLF,
        SetPathNoStmLF,
        SetPathSSLCGIvar,
        SetPathResponseHeaderBegin,
        SetPathResponseHeaderFull,
        SetPathResponseHeaderNone,
        SetPathResponseHeaderNoAdd,
        TemplateWildcardCount,
        OffsetSize,
        ResultOffset,
        ResultWildcardCount,
        ResultSpecifiedWildcardCount;
   unsigned short  Length;
   char  RmsSubChar,
         SetPathAcceptLangChar;
   char  *cptr, *sptr, *zptr,
         *ConditionalPtr,
         *EndPtr,
         *ResultPtr,
         *SetPathAcceptLangPtr,
         *SetPathAuthSysUafPwdExpUrlPtr,
         *SetPathCgiPrefixPtr,
         *SetPathCharsetPtr,
         *SetPathContentTypePtr,
         *SetPathDirCharsetPtr,
         *SetPathHtmlBodyTagPtr,
         *SetPathHtmlFooterPtr,
         *SetPathHtmlFooterTagPtr,
         *SetPathHtmlHeaderPtr,
         *SetPathHtmlHeaderTagPtr,
         *SetPathHttpAcceptCharsetPtr,
         *SetPathHttpAcceptLangPtr,
         *SetPathIndexPtr,
         *SetPathMapRootPtr,
         *SetPathNotePadPtr,
         *SetPathProxyBindIpAddressPtr,
         *SetPathProxyChainHostPortPtr,
         *SetPathProxyReverseLocationPtr,
         *SetPathQueryStringPtr,
         *SetPathResponseHeaderAddPtr,
         *SetPathScriptAsPtr,
         *SetPathScriptCommandPtr,
         *SetPathScriptDefaultPtr,
         *SetPathScriptParamsPtr,
         *SetPathSsiExecPtr,
         *SetPathPtr,
         *StringPtr,
         *TemplatePtr;
   char  Name [256],
         DiskDevice [64+1],
         RuleName [64],
         SetPathCgiPlusInCC [4],
         StringBuffer [1024],
         Value [1024];
   IPADDRESS  SetPathProxyBindIpAddress,
              SetPathProxyChainIpAddress;
   MAPPING_META  *mmptr;
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;
   regex_t  RegexPreg;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ConfigLoadCallBack() !&F !&X",
                 &MapUrl_ConfigLoadCallBack, mcptr);
      if (WATCH_MODULE(WATCH_MOD__DETAIL))
      {
         mclptr = mcptr->ParsePtr;
         WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z !&Z\n",
            mclptr, mclptr->Size, mclptr->Token, mclptr->Number,
            mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr,
            mclptr->InlineTextPtr);
      }
   }

   /* get a pointer to the current "line" */
   mclptr = mcptr->ParsePtr;

   /* if this is during server startup/reload set the global service pointer */
   if (mcptr == MetaGlobalMappingPtr)
      mmptr = mcptr->MappingMetaPtr = MappingMetaPtr = &MappingMeta;
   else
   /* if a report then conjure one up through quantum mechanics */
   if (!mcptr->MappingMetaPtr)
      mmptr = mcptr->MappingMetaPtr = VmGet (sizeof(MAPPING_META));
   else
      /* not the first time through */
      mmptr = mcptr->MappingMetaPtr;

   if (mclptr->Token == METACON_TOKEN_PRE)
   {
      /******************/
      /* pre-initialize */
      /******************/

      MapUrlPathOds5 = OdsExtended = false;

      return (true);
   }

   if (mclptr->Token == METACON_TOKEN_POST)
   {
      /****************/
      /* post-process */
      /****************/

      /* if global service pointer, during server startup or reload */
      if (mcptr == MetaGlobalMappingPtr)
      {
         /* if no ODS5 paths then EFS can be disabled */
#ifdef ODS_EXTENDED
         if (CliOdsExtendedDisabled)
         {
            OdsExtended = false;
            MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5Disabled);
         }
         else
#endif /* ODS_EXTENDED */
         if (CliOdsExtendedEnabled || MapUrlPathOds5)
         {
#ifdef ODS_EXTENDED
            if (SysInfo.VersionInteger >= 720)
            {
               OdsExtended = true;
               MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5Enabled);
            }
            else
#endif /* ODS_EXTENDED */
            {
               OdsExtended = false;
               MetaConReport (mcptr, METACON_REPORT_INFORM, Ods5NotSupported);
            }
         }
         else
            OdsExtended = false;

         /* reset the username cache */
         MapUrl_VmsUserNameCache (NULL, NULL, NULL, NULL);

         /* initialize the throttled paths */
         ThrottleInit ();
      }

      return (true);
   }

   /***********/
   /* process */
   /***********/

   /* if it's not text/inline then mapping's not interested in it */
   if (mclptr->Token != METACON_TOKEN_TEXT && !mclptr->InlineTextPtr)
      return (true);

   /* buffer the text associated with the current "line" */
   zptr = (sptr = StringBuffer) + sizeof(StringBuffer);
   if (mclptr->InlineTextPtr)
      cptr = mclptr->InlineTextPtr;
   else
      cptr = mclptr->TextPtr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemOverflow);
      return (false);
   }
   *sptr = '\0';
   cptr = StringBuffer;

   AllowedInConfigFile =
      ConfigProblem =
      CacheSetting =
      IsCgiPlusScript = 
      SetPathNoAcceptLang =
      SetPathAcceptLangTypeVariant = SetPathAcceptLangWildcard = 
      SetPathNoAlert =
      SetPathAuthAll = SetPathNoAuthAll =
      SetPathAuthMapped = SetPathNoAuthMapped =
      SetPathAuthOnce = SetPathNoAuthOnce = SetPathCacheGuardSeconds =
      SetPathCache = SetPathNoCache =
      SetPathCacheCGI = SetPathCacheNoCGI = 
      SetPathCacheFile = SetPathCacheNoFile = 
      SetPathCacheMaxKBytes =
      SetPathCacheNet = SetPathCacheNoNet = 
      SetPathCacheNPH = SetPathCacheNoNPH = 
      SetPathCachePermanent = SetPathCacheNoPermanent =
      SetPathCacheQuery = SetPathCacheNoQuery =
      SetPathCacheScript = SetPathCacheNoScript =
      SetPathCacheSSI = SetPathCacheNoSSI = 
      SetPathCgiPlusInWriteof = SetPathCgiPlusInNoWriteof =
      SetPathDefaultSearch = SetPathNoDefaultSearch =
      SetPathDirAccess = SetPathDirNoAccess = SetPathDirAccessSelective = 
      SetPathDirImpliedWildcard = SetPathDirNoImpliedWildcard =
      SetPathDirWildcard = SetPathDirNoWildcard =
      SetPathExpired = SetPathNoExpired =
      SetPathLog = SetPathNoLog = SetPathNoHtmlEquals =
      SetPathMapEllipsis = SetPathNoMapEllipsis =
      SetPathMapEmpty = SetPathMapNonEmpty =
      SetPathMapOnce = SetPathNoMapOnce = SetPathMapRestart = 
      SetPathMapSetIgnore = SetPathMapSetNoIgnore = 
      SetPathMapSetRequest = SetPathMapSetNoRequest = 
      SetPathPrivSsi = SetPathNoPrivSsi =
      SetPathProfile = SetPathNoProfile =
      SetPathReportBasic = SetPathReportDetailed =
      SetPathReport400as = SetPathReport403as = SetPathReport404as =
      SetPathResponseHeaderBegin = SetPathResponseHeaderFull =
      SetPathResponseHeaderNone = SetPathResponseHeaderNoAdd =
      SetPathScriptFind = SetPathScriptNoFind =
      SetPathScriptPathFind = SetPathScriptNoPathFind =
      SetPathScriptQueryNone = SetPathScriptNoQueryNone =
      SetPathScriptQueryRelaxed = SetPathScriptNoQueryRelaxed =
      SetPathStmLF = SetPathNoStmLF =
      PathSet = false;

   ResultOffset = SetPathAuthRevalidateTimeout =
      SetPathAlert = SetPathAuthSysUafPwdExpUrlOffset =
      SetPathAcceptLangOffset = SetPathCacheExpiresAfter =
      SetPathCgiPrefixOffset = SetPathCharsetOffset =
      SetPathContentTypeOffset = SetPathDirStyle = 
      SetPathHtmlBodyTagOffset = SetPathHtmlHeaderOffset =
      SetPathHtmlHeaderTagOffset = SetPathHtmlFooterOffset =
      SetPathHtmlFooterTagOffset = SetPathOds =
      SetPathHttpAcceptCharsetOffset = SetPathHttpAcceptLangOffset =
      SetPathDirCharsetOffset = SetPathIndexOffset =
      SetPathMapRootOffset = SetPathNotePadOffset = 
      SetPathProxyBindIpAddressOffset = SetPathProxyChainPort =
      SetPathProxyChainHostPortOffset = SetPathQueryStringOffset =
      SetPathProxyForwardedBy = SetPathProxyXForwardedFor =
      SetPathProxyReverseLocationOffset =
      SetPathProxyReverseVerify = SetPathNoProxyReverseVerify =
      SetPathProxyUnknownRequestFields = SetPathNoProxyUnknownRequestFields =
      SetPathResponseHeaderAddOffset = SetPathScriptAsOffset =
      SetPathScriptCommandOffset = SetPathScriptDefaultOffset = 
      SetPathScriptBitBucketTimeout =
      SetPathScriptCpuMax = SetPathScriptParamsOffset =
      SetPathSsiExecOffset = SetPathSSLCGIvar = SetPathThrottleBusy =
      SetPathThrottleFrom = SetPathThrottleResume =
      SetPathThrottleTo = SetPathThrottleTimeoutBusy =
      SetPathThrottleTimeoutQueue = SetPathTimeoutKeepAlive =
      SetPathTimeoutNoProgress = SetPathTimeoutOutput = 0;

   SetPathAcceptLangPtr = SetPathAuthSysUafPwdExpUrlPtr =
      SetPathCgiPrefixPtr = SetPathCharsetPtr =
      SetPathContentTypePtr = SetPathHtmlBodyTagPtr =
      SetPathHtmlFooterPtr = SetPathHtmlFooterTagPtr =
      SetPathHtmlHeaderPtr = SetPathHtmlHeaderTagPtr =
      SetPathHttpAcceptCharsetPtr = SetPathDirCharsetPtr =
      SetPathHttpAcceptLangPtr = SetPathIndexPtr =
      SetPathMapRootPtr = SetPathNotePadPtr = 
      SetPathProxyBindIpAddressPtr = SetPathProxyReverseLocationPtr =
      SetPathProxyChainHostPortPtr = SetPathQueryStringPtr =
      SetPathResponseHeaderAddPtr = SetPathScriptAsPtr =
      SetPathScriptCommandPtr = SetPathScriptDefaultPtr =
      SetPathScriptParamsPtr = SetPathSsiExecPtr = NULL;

   SetPathAcceptLangChar = RmsSubChar = '\0';
   memset (SetPathCgiPlusInCC, 0, sizeof(SetPathCgiPlusInCC));

   /********/
   /* rule */
   /********/

   StringPtr = cptr;
   if (VMSnok (StringParseValue (&StringPtr, RuleName, sizeof(RuleName))))
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
      return (true);
   }
   cptr = StringPtr;

   IsCgiPlusScript = false;
   if (strsame (RuleName, "EXEC", -1))
      RuleType = MAPURL_RULE_EXEC;
   else
   if (strsame (RuleName, "EXEC+", -1))
   {
      RuleType = MAPURL_RULE_EXEC;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RuleName, "FAIL", -1))
   {
      RuleType = MAPURL_RULE_FAIL;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RuleName, "MAP", -1))
   {
      RuleType = MAPURL_RULE_MAP;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RuleName, "PASS", -1))
   {
      RuleType = MAPURL_RULE_PASS;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RuleName, "REDIRECT", -1))
   {
      RuleType = MAPURL_RULE_REDIRECT;
      AllowedInConfigFile = true;
   }
   else
   if (strsame (RuleName, "SCRIPT", -1))
      RuleType = MAPURL_RULE_SCRIPT;
   else
   if (strsame (RuleName, "SCRIPT+", -1))
   {
      RuleType = MAPURL_RULE_SCRIPT;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RuleName, "SET", -1))
      RuleType = MAPURL_RULE_SET;
   else
   if (strsame (RuleName, "UXEC", -1) ||
       strsame (RuleName, "UEXEC", -1))
      RuleType = MAPURL_RULE_UXEC;
   else
   if (strsame (RuleName, "UXEC+", -1) ||
       strsame (RuleName, "UEXEC+", -1))
   {
      RuleType = MAPURL_RULE_UXEC;
      IsCgiPlusScript = true;
   }
   else
   if (strsame (RuleName, "USER", -1))
      RuleType = MAPURL_RULE_USER;
   else
   if (strsame (RuleName, "PROTECT", -1))
   {
      RuleType = MAPURL_RULE_PROTECT;
      AllowedInConfigFile = true;
   }
   else
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
      return (true);
   }

   /* ensure a conditional is not mistaken for a missing template */
   if (!*cptr || *cptr == '[' || *(unsigned short*)cptr == '![') 
   {
      /* there must be a "template" to map from */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTemplateRequired);
      return (true);
   }

   /* extract template (the path the rule is applied to) */
   StringPtr = cptr;
   if (VMSnok (StringSpanValue (&StringPtr, &TemplatePtr, &EndPtr)))
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTemplateRequired);
      return (true);
   }
   cptr = StringPtr;
   *EndPtr = '\0';
   /* note the position of the empty string in case neither of these! */
   ResultPtr = SetPathPtr = ConditionalPtr = EndPtr;
   ResultOffset = ConditionalOffset = EndPtr - TemplatePtr;

   if (RuleType != MAPURL_RULE_SET)
   {
      /*********************/
      /* path mapping rule */
      /*********************/

      /* if this doesn't look like a conditional */
      if (*cptr && *cptr != '[' && *(unsigned short*)cptr != '![')
      {
         StringPtr = cptr;
         if (VMSnok (StringSpanValue (&StringPtr, &ResultPtr, &EndPtr)))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
            return (true);
         }
         cptr = StringPtr;
         *EndPtr = '\0';
         ResultOffset = ResultPtr - TemplatePtr;
      }
   }

   /* if it doesn't look like a conditional then assume it's SET directives */
   if (*cptr && *cptr != '[' && *(unsigned short*)cptr != '![')
   {
      /****************/
      /* path SETings */
      /****************/

      while (*cptr)
      {
         /* break if this looks like a conditional */
         if (*cptr == '[' || *(unsigned short*)cptr == '![') break;

         StringPtr = cptr;
         if (VMSnok (StringSpanValue (&StringPtr, &SetPathPtr, &EndPtr)))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
            return (true);
         }
         cptr = StringPtr;
         *EndPtr = '\0';
         if (!*SetPathPtr)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
            return (true);
         }

         PathSet = true;
         if (strsame (SetPathPtr, "ACCEPT=LANG", -1))
            SetPathAcceptLangChar = '_';
         else
         if (strsame (SetPathPtr, "NOACCEPT=LANG", -1) ||
             strsame (SetPathPtr, "ACCEPT=NOLANG", -1))
            SetPathNoAcceptLang = true;
         else
         if (strsame (SetPathPtr, "ACCEPT=LANG=", 12))
         {
            SetPathAcceptLangChar = '_';
            sptr = SetPathPtr + 12;
            if (*sptr == '(') sptr++;
            while (*sptr)
            {
               if (strsame (sptr, "CHAR=", 5))
               {
                  SetPathAcceptLangChar = sptr[5];
                  sptr += 6;
               }
               else
               if (strsame (sptr, "DEFAULT=", 8))
               {
                  sptr += 8;
                  SetPathAcceptLangPtr = sptr;
                  SetPathAcceptLangOffset = SetPathAcceptLangPtr - TemplatePtr;
                  while (*sptr && *sptr != ',' && *sptr != ')') sptr++;
                  if (*sptr) *sptr++ = '\0';
               }
               else
               if (strsame (sptr, "VARIANT=", 8))
               {
                  sptr += 8;
                  if (strsame (sptr, "NAME", 4))
                     SetPathAcceptLangTypeVariant = false;
                  else
                  if (strsame (sptr, "TYPE", 4))
                     SetPathAcceptLangTypeVariant = true;
                  else
                  {
                     MetaConReport (mcptr, METACON_REPORT_ERROR,
                                    ProblemAcceptLang);
                     return (true);
                  }
                  while (*sptr && *sptr != ',' && *sptr != ')') sptr++;
               }
               else
               {
                  MetaConReport (mcptr, METACON_REPORT_ERROR,
                                 ProblemAcceptLang);
                  return (true);
               }
               if (*sptr == ',' || *sptr == ')') sptr++;
            }
            if (SetPathAcceptLangChar != '_' &&
                SetPathAcceptLangChar != '-' &&
                SetPathAcceptLangChar != '.' &&
                SetPathAcceptLangChar != '$')
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemAcceptLang);
               return (true);
            }
            /* check if there is a wildcard file type */
            for (sptr = TemplatePtr; *sptr; sptr++);
            while (sptr > TemplatePtr && *sptr != '.' && *sptr != '/') sptr--;
            if (*sptr == '.') SetPathAcceptLangWildcard = true;
         }
         else
         if (strsame (SetPathPtr, "ALERT", -1))
            SetPathAlert = MAPURL_PATH_ALERT_END;
         else
         if (strsame (SetPathPtr, "ALERT=END", -1))
            SetPathAlert = MAPURL_PATH_ALERT_END;
         else
         if (strsame (SetPathPtr, "ALERT=AUTH", -1))
            SetPathAlert = MAPURL_PATH_ALERT_AUTH;
         else
         if (strsame (SetPathPtr, "ALERT=MAP", -1))
            SetPathAlert = MAPURL_PATH_ALERT_MAP;
         else
         if (strsame (SetPathPtr, "ALERT=", 6) &&
             isdigit(SetPathPtr[6]))
         {
            SetPathAlert = atoi(SetPathPtr+6);
            if (!SetPathAlert) SetPathNoAlert = true;
         }
         else
         if (strsame (SetPathPtr, "NOALERT", -1))
            SetPathNoAlert = true;
         else
         if (strsame (SetPathPtr, "AUTH=ALL", -1))
            SetPathAuthAll = true;
         else
         if (strsame (SetPathPtr, "NOAUTH=ALL", -1))
            SetPathNoAuthAll = true;
         else
         if (strsame (SetPathPtr, "AUTH=MAPPED", -1))
            SetPathAuthMapped = true;
         else
         if (strsame (SetPathPtr, "NOAUTH=MAPPED", -1) ||
             strsame (SetPathPtr, "AUTH=REQUEST", -1))
            SetPathNoAuthMapped = true;
         else
         if (strsame (SetPathPtr, "AUTH=ONCE", -1) ||
             strsame (SetPathPtr, "AUTHONCE", -1))
            SetPathAuthOnce = true;
         else
         if (strsame (SetPathPtr, "AUTH=NOONCE", -1) ||
             strsame (SetPathPtr, "NOAUTH=ONCE", -1) ||
             strsame (SetPathPtr, "NOAUTHONCE", -1))
            SetPathNoAuthOnce = true;
         else
         if (strsame (SetPathPtr, "AUTH=REVALIDATE=", 16))
            SetPathAuthRevalidateTimeout =
               MetaConSetSeconds (mcptr, SetPathPtr+16, 60);
         else
         if (strsame (SetPathPtr, "AUTH=SYSUAF=PWDEXPURL=", 22))
         {
            SetPathAuthSysUafPwdExpUrlPtr = SetPathPtr + 22;
            SetPathAuthSysUafPwdExpUrlOffset =
               SetPathAuthSysUafPwdExpUrlPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "CACHE", -1))
            CacheSetting = SetPathCache = true;
         else
         if (strsame (SetPathPtr, "NOCACHE", -1) ||
             strsame (SetPathPtr, "CACHE=NONE", -1))
            CacheSetting = SetPathNoCache = true;
         else
         if (strsame (SetPathPtr, "CACHE=CGI", -1))
            CacheSetting = SetPathCacheCGI = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOCGI", -1))
            CacheSetting = SetPathCacheNoCGI = true;
         else
         if (strsame (SetPathPtr, "CACHE=FILE", -1))
            CacheSetting = SetPathCacheFile = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOFILE", -1))
            CacheSetting = SetPathCacheNoFile = true;
         else
         if (strsame (SetPathPtr, "CACHE=NET", -1))
            CacheSetting = SetPathCacheNet = true;
         else
         if (strsame (SetPathPtr, "CACHE=NONET", -1))
            CacheSetting = SetPathCacheNoNet = true;
         else
         if (strsame (SetPathPtr, "CACHE=NPH", -1))
            CacheSetting = SetPathCacheNPH = true;
         else
         if (strsame (SetPathPtr, "CACHE=NONPH", -1))
            CacheSetting = SetPathCacheNoNPH = true;
         else
         if (strsame (SetPathPtr, "CACHE=PERM", 10))
            CacheSetting = SetPathCachePermanent = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOPERM", 12))
            CacheSetting = SetPathCacheNoPermanent = true;
         else
         if (strsame (SetPathPtr, "CACHE=QUERY", -1))
            CacheSetting = SetPathCacheQuery = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOQUERY", -1))
            CacheSetting = SetPathCacheNoQuery = true;
         else
         if (strsame (SetPathPtr, "CACHE=SCRIPT", -1))
            CacheSetting = SetPathCacheScript = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOSCRIPT", -1))
            CacheSetting = SetPathCacheNoScript = true;
         else
         if (strsame (SetPathPtr, "CACHE=SSI", -1))
            CacheSetting = SetPathCacheSSI = true;
         else
         if (strsame (SetPathPtr, "CACHE=NOSSI", -1))
            CacheSetting = SetPathCacheNoSSI = true;
         else
         if (strsame (SetPathPtr, "CACHE=EXPIRES=", 14))
         {
            sptr = SetPathPtr + 14;
            if (strsame (sptr, "DAY", -1))
               SetPathCacheExpiresAfter = CACHE_EXPIRES_DAY;
            else
            if (strsame (sptr, "HOUR", -1))
               SetPathCacheExpiresAfter = CACHE_EXPIRES_HOUR;
            else
            if (strsame (sptr, "MINUTE", -1))
               SetPathCacheExpiresAfter = CACHE_EXPIRES_MINUTE;
            else
            if (strsame (sptr, "0", -1))
               SetPathCacheExpiresAfter = CACHE_EXPIRES_NONE;
            else
               SetPathCacheExpiresAfter = MetaConSetSeconds (mcptr, sptr, 1);
            CacheSetting = true;
         }
         else
         if (strsame (SetPathPtr, "CACHE=GUARD=", 12))
         {
            sptr = SetPathPtr + 12;
            SetPathCacheGuardSeconds = MetaConSetSeconds (mcptr, sptr, 1);
            /* negative 1 indicates to revert to configuration maximum */
            if (!SetPathCacheGuardSeconds) SetPathCacheGuardSeconds = -1;
            CacheSetting = true;
         }
         else
         if (strsame (SetPathPtr, "CACHE=MAX=", 10))
         {
            sptr = SetPathPtr + 10;
            SetPathCacheMaxKBytes = atoi(sptr);
            while (isdigit(*sptr)) sptr++;
            if (strsame (sptr, "MB", 2))
               SetPathCacheMaxKBytes = SetPathCacheMaxKBytes << 10;
            /* negative 1 indicates to revert to configuration maximum */
            if (!SetPathCacheMaxKBytes) SetPathCacheMaxKBytes = -1;
            CacheSetting = true;
         }
         else
         if (strsame (SetPathPtr, "CGI=PREFIX=", 11) ||
             strsame (SetPathPtr, "CGIPREFIX=", 10))
         {
            SetPathCgiPrefixPtr = SetPathPtr + 10;
            if (*SetPathCgiPrefixPtr == '=') SetPathCgiPrefixPtr++;
            SetPathCgiPrefixOffset = SetPathCgiPrefixPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=EOF", -1))
            SetPathCgiPlusInWriteof = true;
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=NOEOF", -1) ||
             strsame (SetPathPtr, "NOCGIPLUSIN=EOF", -1))
            SetPathCgiPlusInNoWriteof = true;
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=CC=NONE", -1))
            memcpy (SetPathCgiPlusInCC, "\0**", sizeof(SetPathCgiPlusInCC));
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=CC=LF", -1))
            memcpy (SetPathCgiPlusInCC, "\n\0*", sizeof(SetPathCgiPlusInCC));
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=CC=CR", -1))
            memcpy (SetPathCgiPlusInCC, "\r\0*", sizeof(SetPathCgiPlusInCC));
         else
         if (strsame (SetPathPtr, "CGIPLUSIN=CC=CRLF", -1))
            memcpy (SetPathCgiPlusInCC, "\r\n\0", sizeof(SetPathCgiPlusInCC));
         else
         if (strsame (SetPathPtr, "CHARSET=", 8))
         {
            SetPathCharsetPtr = SetPathPtr + 8;
            SetPathCharsetOffset = SetPathCharsetPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "CONTENT=", 8))
         {
            SetPathContentTypePtr = SetPathPtr + 8;
            SetPathContentTypeOffset = SetPathContentTypePtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "CONTENT-TYPE=", 13))
         {
            /* just a synonym for "content=", bet someone uses it! */
            SetPathContentTypePtr = SetPathPtr + 13;
            SetPathContentTypeOffset = SetPathContentTypePtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "DIR=ACCESS=SELECTIVE", -1))
            SetPathDirAccessSelective = true;
         else
         if (strsame (SetPathPtr, "DIR=ACCESS", -1))
            SetPathDirAccess = true;
         else
         if (strsame (SetPathPtr, "DIR=NOACCESS", -1))
            SetPathDirNoAccess = true;
         else
         if (strsame (SetPathPtr, "DIR=CHARSET=", 12))
         {
            SetPathDirCharsetPtr = SetPathPtr + 12;
            SetPathDirCharsetOffset = SetPathDirCharsetPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "DIR=LAYOUT=", 11))
         {
            SetPathIndexPtr = SetPathPtr + 11;
            SetPathIndexOffset = SetPathIndexPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "DIR=IMPLIEDWILDCARD", -1))
            SetPathDirImpliedWildcard = true;
         else
         if (strsame (SetPathPtr, "DIR=NOIMPLIEDWILDCARD", -1))
            SetPathDirNoImpliedWildcard = true;
         else
         if (strsame (SetPathPtr, "DIR=STYLE", -1) ||
             strsame (SetPathPtr, "DIR=STYLE=DEFAULT", -1))
            SetPathDirStyle = MAPURL_DIR_STYLE_DEFAULT;
         else
         if (strsame (SetPathPtr, "DIR=STYLE=ORIGINAL", -1))
            SetPathDirStyle = MAPURL_DIR_STYLE_ORIGINAL;
         else
         if (strsame (SetPathPtr, "DIR=STYLE=ANCHOR", -1))
            SetPathDirStyle = MAPURL_DIR_STYLE_ANCHOR;
         else
         if (strsame (SetPathPtr, "DIR=STYLE=HTDIR", -1))
            SetPathDirStyle = MAPURL_DIR_STYLE_HTDIR;
         else
         if (strsame (SetPathPtr, "DIR=WILDCARD", -1))
            SetPathDirWildcard = true;
         else
         if (strsame (SetPathPtr, "DIR=NOWILDCARD", -1))
            SetPathDirNoWildcard = true;
         else
         if (strsame (SetPathPtr, "NOHTML", 6))
         {
            SetPathNoHtmlEquals = true;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTML=BODYTAG=", 13))
         {
            SetPathHtmlBodyTagPtr = SetPathPtr + 13;
            SetPathHtmlBodyTagOffset = SetPathHtmlBodyTagPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTML=FOOTER=", 12))
         {
            SetPathHtmlFooterPtr = SetPathPtr + 12;
            SetPathHtmlFooterOffset = SetPathHtmlFooterPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTML=FOOTERTAG=", 15))
         {
            SetPathHtmlFooterTagPtr = SetPathPtr + 15;
            SetPathHtmlFooterTagOffset = SetPathHtmlFooterTagPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTML=HEADER=", 12))
         {
            SetPathHtmlHeaderPtr = SetPathPtr + 12;
            SetPathHtmlHeaderOffset = SetPathHtmlHeaderPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTML=HEADERTAG=", 15))
         {
            SetPathHtmlHeaderTagPtr = SetPathPtr + 15;
            SetPathHtmlHeaderTagOffset = SetPathHtmlHeaderTagPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTTP=ACCEPT-CHARSET=", 20))
         {
            SetPathHttpAcceptCharsetPtr = SetPathPtr + 20;
            SetPathHttpAcceptCharsetOffset =
               SetPathHttpAcceptCharsetPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "HTTP=ACCEPT-LANGUAGE=", 21))
         {
            SetPathHttpAcceptLangPtr = SetPathPtr + 21;
            SetPathHttpAcceptLangOffset =
               SetPathHttpAcceptLangPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "INDEX=", 6))
         {
            SetPathIndexPtr = SetPathPtr + 6;
            SetPathIndexOffset = SetPathIndexPtr - TemplatePtr;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "EXPIRED", -1))
         {
            SetPathExpired = true;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "NOEXPIRED", -1))
         {
            SetPathNoExpired = true;
            AllowedInConfigFile = true;
         }
         else
         if (strsame (SetPathPtr, "LOG", -1))
            SetPathLog = true;
         else
         if (strsame (SetPathPtr, "NOLOG", -1))
            SetPathNoLog = true;
         else
         if (strsame (SetPathPtr, "MAP=ELLIPSIS", -1))
            SetPathMapEllipsis = true;
         else
         if (strsame (SetPathPtr, "NOMAP=ELLIPSIS", -1) ||
             strsame (SetPathPtr, "MAP=NOELLIPSIS", -1))
            SetPathNoMapEllipsis = false;
         else
         if (strsame (SetPathPtr, "MAPEMPTY", -1) ||
             strsame (SetPathPtr, "MAP=EMPTY", -1))
            SetPathMapEmpty = true;
         else
         if (strsame (SetPathPtr, "NOMAPEMPTY", -1) ||
             strsame (SetPathPtr, "NOMAP=EMPTY", -1) ||
             strsame (SetPathPtr, "MAP=NONEMPTY", -1))
            SetPathMapNonEmpty = true;
         else
         if (strsame (SetPathPtr, "MAP=ONCE", -1) ||
             strsame (SetPathPtr, "MAPONCE", -1))
            SetPathMapOnce = true;
         else
         if (strsame (SetPathPtr, "MAP=NOONCE", -1) ||
             strsame (SetPathPtr, "NOMAP=ONCE", -1) ||
             strsame (SetPathPtr, "NOMAPONCE", -1))
            SetPathNoMapOnce = true;
         else
         if (strsame (SetPathPtr, "MAP=RESTART", -1))
            SetPathMapRestart = true;
         else
         if (strsame (SetPathPtr, "MAP=ROOT=", 9))
         {
            SetPathMapRootPtr = SetPathPtr + 9;
            SetPathMapRootOffset = SetPathMapRootPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "MAP=SET=REQUEST", -1))
            SetPathMapSetRequest = true;
         else
         if (strsame (SetPathPtr, "NOMAP=SET=REQUEST", -1) ||
             strsame (SetPathPtr, "MAP=SET=NOREQUEST", -1))
            SetPathMapSetNoRequest = true;
         else
         if (strsame (SetPathPtr, "MAP=SET=IGNORE", -1))
            SetPathMapSetIgnore = true;
         else
         if (strsame (SetPathPtr, "NOMAP=SET=IGNORE", -1) ||
             strsame (SetPathPtr, "MAP=SET=NOIGNORE", -1))
            SetPathMapSetNoIgnore = true;
         else
         if (strsame (SetPathPtr, "NOTEPAD=", 8))
         {
            SetPathNotePadPtr = SetPathPtr + 8;
            SetPathNotePadOffset = SetPathNotePadPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "ODS=0", -1) ||
             strsame (SetPathPtr, "NOODS", -1))
            SetPathOds = MAPURL_PATH_ODS_0;
         else
         if (strsame (SetPathPtr, "ODS=2", -1) ||
             strsame (SetPathPtr, "ODS-2", -1))
            SetPathOds = MAPURL_PATH_ODS_2;
         else
         if (strsame (SetPathPtr, "ODS=5", -1) ||
             strsame (SetPathPtr, "ODS-5", -1))
         {
            SetPathOds = MAPURL_PATH_ODS_5;
#ifdef ODS_EXTENDED
            /* indicate that at least one mapping is set ODS-5 */
            MapUrlPathOds5 = true;
#endif /* ODS_EXTENDED */
         }
         else
         if (strsame (SetPathPtr, "ODS=ADS", -1))
            SetPathOds = MAPURL_PATH_ODS_ADS;
         else
         if (strsame (SetPathPtr, "ODS=PWK", -1))
            SetPathOds = MAPURL_PATH_ODS_PWK;
         else
         if (strsame (SetPathPtr, "ODS=SMB", -1))
            SetPathOds = MAPURL_PATH_ODS_SMB;
         else
         if (strsame (SetPathPtr, "ODS=SRI", -1))
            SetPathOds = MAPURL_PATH_ODS_SRI;
         else
         if (strsame (SetPathPtr, "PROFILE", -1))
            SetPathProfile = true;
         else
         if (strsame (SetPathPtr, "NOPROFILE", -1))
            SetPathNoProfile = true;
         else
         if (strsame (SetPathPtr, "PROXY=BIND=", 11))
         {
            SetPathProxyBindIpAddressPtr = SetPathPtr + 11;
            SetPathProxyBindIpAddressOffset =
               SetPathProxyBindIpAddressPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "PROXY=CHAIN=", 12))
         {
            SetPathProxyChainHostPortPtr = SetPathPtr + 12;
            SetPathProxyChainHostPortOffset =
               SetPathProxyChainHostPortPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "PROXY=NOFORWARDED", -1))
            SetPathProxyForwardedBy = PROXY_FORWARDED_NONE;
         else
         if (strsame (SetPathPtr, "PROXY=FORWARDED", -1) ||
             strsame (SetPathPtr, "PROXY=FORWARDED=BY", -1))
            SetPathProxyForwardedBy = PROXY_FORWARDED_BY;
         else
         if (strsame (SetPathPtr, "PROXY=FORWARDED=FOR", -1))
            SetPathProxyForwardedBy = PROXY_FORWARDED_FOR;
         else
         if (strsame (SetPathPtr, "PROXY=FORWARDED=ADDRESS", -1))
            SetPathProxyForwardedBy = PROXY_FORWARDED_ADDRESS;
         else
         if (strsame (SetPathPtr, "PROXY=NOXFORWARDEDFOR", -1))
            SetPathProxyXForwardedFor = PROXY_XFORWARDEDFOR_NONE;
         else
         if (strsame (SetPathPtr, "PROXY=XFORWARDEDFOR", -1) ||
             strsame (SetPathPtr, "PROXY=XFORWARDEDFOR=ENABLED", -1))
            SetPathProxyXForwardedFor = PROXY_XFORWARDEDFOR_ENABLED;
         else
         if (strsame (SetPathPtr, "PROXY=XFORWARDEDFOR=ADDRESS", -1))
            SetPathProxyXForwardedFor = PROXY_XFORWARDEDFOR_ADDRESS;
         else
         if (strsame (SetPathPtr, "PROXY=XFORWARDEDFOR=UNKNOWN", -1))
            SetPathProxyXForwardedFor = PROXY_XFORWARDEDFOR_UNKNOWN;
         else
         if (strsame (SetPathPtr, "PROXY=REVERSE=LOCATION=", 23))
         {
            SetPathProxyReverseLocationPtr = SetPathPtr + 23;
            SetPathProxyReverseLocationOffset = SetPathProxyReverseLocationPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "PROXY=REVERSE=VERIFY", -1))
            SetPathProxyReverseVerify = true;
         else
         if (strsame (SetPathPtr, "PROXY=REVERSE=NOVERIFY", -1) ||
             strsame (SetPathPtr, "NOPROXY=REVERSE=VERIFY", -1))
            SetPathNoProxyReverseVerify = true;
         else
         if (strsame (SetPathPtr, "PROXY=UNKNOWN", -1))
            SetPathProxyUnknownRequestFields = true;
         else
         if (strsame (SetPathPtr, "PROXY=NOUNKNOWN", -1) ||
             strsame (SetPathPtr, "NOPROXY=UNKNOWN", -1))
            SetPathNoProxyUnknownRequestFields = true;
         else
         if (strsame (SetPathPtr, "QUERY-STRING=", 13))
         {
            SetPathQueryStringPtr = SetPathPtr + 13;
            SetPathQueryStringOffset = SetPathQueryStringPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "REPORT=BAS", 10))
            SetPathReportBasic = true;
         else
         if (strsame (SetPathPtr, "REPORT=DET", 10))
            SetPathReportDetailed = true;
         else
         if (strsame (SetPathPtr, "REPORT=400=", 11))
            SetPathReport400as = atoi(SetPathPtr+11);
         else
         if (strsame (SetPathPtr, "REPORT=403=", 11))
            SetPathReport403as = atoi(SetPathPtr+11);
         else
         if (strsame (SetPathPtr, "REPORT=404=", 11))
            SetPathReport404as = atoi(SetPathPtr+11);
         else
         if (strsame (SetPathPtr, "RMSCHAR=", 8))
            RmsSubChar = SetPathPtr[8];
         else
         if (strsame (SetPathPtr, "RESPONSE=HEADER=BEGIN", -1))
            SetPathResponseHeaderBegin = true;
         else
         if (strsame (SetPathPtr, "RESPONSE=HEADER=FULL", -1))
            SetPathResponseHeaderFull = true;
         else
         if (strsame (SetPathPtr, "RESPONSE=HEADER=NONE", -1))
            SetPathResponseHeaderNone = true;
         else
         if (strsame (SetPathPtr, "NORESPONSE=HEADER=ADD", -1) ||
             strsame (SetPathPtr, "RESPONSE=HEADER=NOADD", -1))
            SetPathResponseHeaderNoAdd = true;
         else
         if (strsame (SetPathPtr, "RESPONSE=HEADER=ADD=", 20))
         {
            SetPathResponseHeaderAddPtr = SetPathPtr + 20;
            SetPathResponseHeaderAddOffset =
               SetPathResponseHeaderAddPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=AS=", 10))
         {
            /* while setting the required pointers force to upper-case */
            for (sptr = SetPathScriptAsPtr = SetPathPtr+10; *sptr; sptr++)
               *sptr = toupper(*sptr);
            SetPathScriptAsOffset = SetPathScriptAsPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=BIT-BUCKET=", 18))
            SetPathScriptBitBucketTimeout =
               MetaConSetSeconds (mcptr, SetPathPtr+18, 1);
         else
         if (strsame (SetPathPtr, "SCRIPT=CPU=", 11))
            SetPathScriptCpuMax =
               MetaConSetSeconds (mcptr, SetPathPtr+11, 1);
         else
         if (strsame (SetPathPtr, "SCRIPT=COMMAND=", 15))
         {
            SetPathScriptCommandPtr = SetPathPtr + 15;
            SetPathScriptCommandOffset = SetPathScriptCommandPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=DEFAULT=", 15))
         {
            SetPathScriptDefaultPtr = SetPathPtr + 15;
            SetPathScriptDefaultOffset = SetPathScriptDefaultPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=FIND", -1))
            SetPathScriptFind = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=NOFIND", -1) ||
             strsame (SetPathPtr, "NOSCRIPT=FIND", -1))
            SetPathScriptNoFind = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=PARAM=", 13))
         {
            SetPathScriptParamsPtr = SetPathPtr + 13;
            SetPathScriptParamsOffset = SetPathScriptParamsPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=PARAMS=", 14))
         {
            SetPathScriptParamsPtr = SetPathPtr + 14;
            SetPathScriptParamsOffset = SetPathScriptParamsPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SCRIPT=PATH=FIND", -1))
            SetPathScriptPathFind = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=NOPATH=FIND", -1) ||
             strsame (SetPathPtr, "NOSCRIPT=PATH=FIND", -1))
            SetPathScriptNoPathFind = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=QUERY=NONE", -1))
            SetPathScriptQueryNone = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=NOQUERY=NONE", -1) ||
             strsame (SetPathPtr, "NOSCRIPT=QUERY=NONE", -1))
            SetPathScriptNoQueryNone = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=QUERY=RELAXED", -1))
            SetPathScriptQueryRelaxed = true;
         else
         if (strsame (SetPathPtr, "SCRIPT=NOQUERY=RELAXED", -1) ||
             strsame (SetPathPtr, "NOSCRIPT=QUERY=RELAXED", -1))
            SetPathScriptNoQueryRelaxed = true;
         else
         if (strsame (SetPathPtr, "SEARCH=NONE", -1))
            SetPathNoDefaultSearch = true;
         else
         if (strsame (SetPathPtr, "NOSEARCH=NONE", -1) ||
             strsame (SetPathPtr, "SEARCH=NONONE", -1))
            SetPathDefaultSearch = true;
         else
         if (strsame (SetPathPtr, "SSI=PRIV", 8))
            SetPathPrivSsi = true;
         else
         if (strsame (SetPathPtr, "SSI=NOPRIV", -1) ||
             strsame (SetPathPtr, "NOSSI=PRIV", -1))
            SetPathNoPrivSsi = true;
         else
         if (strsame (SetPathPtr, "SSI=EXEC=", 9))
         {
            SetPathSsiExecPtr = SetPathPtr + 9;
            SetPathSsiExecOffset = SetPathSsiExecPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "SSI=DCL=", 8))
         {
            /* synonym for 'ssi=exec=' */
            SetPathSsiExecPtr = SetPathPtr + 8;
            SetPathSsiExecOffset = SetPathSsiExecPtr - TemplatePtr;
         }
         else
         if (strsame (SetPathPtr, "NOSSLCGI", -1))
            SetPathSSLCGIvar = SESOLA_CGI_VAR_NONE;
         else
         if (strsame (SetPathPtr, "SSLCGI=NONE", -1))
            SetPathSSLCGIvar = SESOLA_CGI_VAR_NONE;
         else
         if (strsame (SetPathPtr, "SSLCGI=APACHE_MOD_SSL", -1))
            SetPathSSLCGIvar = SESOLA_CGI_VAR_APACHE_MOD_SSL;
         else
         if (strsame (SetPathPtr, "SSLCGI=PURVEYOR", -1))
            SetPathSSLCGIvar = SESOLA_CGI_VAR_PURVEYOR;
         else
         if (strsame (SetPathPtr, "STMLF", -1))
            SetPathStmLF = true;
         else
         if (strsame (SetPathPtr, "NOSTMLF", -1))
            SetPathNoStmLF = true;
         else
         if (strsame (SetPathPtr, "THROTTLE=BUSY=", 14))
            SetPathThrottleBusy = atoi(SetPathPtr+14);
         else
         if (strsame (SetPathPtr, "THROTTLE=FROM=", 14))
            SetPathThrottleFrom = atoi(SetPathPtr+14);
         else
         if (strsame (SetPathPtr, "THROTTLE=RESUME=", 16))
            SetPathThrottleResume = atoi(SetPathPtr+16);
         else
         if (strsame (SetPathPtr, "THROTTLE=TO=", 12))
            SetPathThrottleTo = atoi(SetPathPtr+12);
         else
         if (strsame (SetPathPtr, "THROTTLE=TIMEOUT=BUSY=", 22))
            SetPathThrottleTimeoutBusy =
               MetaConSetSeconds (mcptr, SetPathPtr+22, 1);
         else
         if (strsame (SetPathPtr, "THROTTLE=TIMEOUT=QUEUE=", 23))
            SetPathThrottleTimeoutQueue =
               MetaConSetSeconds (mcptr, SetPathPtr+23, 1);
         else
         /* get the order correct chucklehead!! */
         if (strsame (SetPathPtr, "THROTTLE=", 9))
         {
            /* throttle=from,to,resume,busy,t/o-queue,t/o-busy */
            sptr = SetPathPtr+9;
            if (*sptr == '(') sptr++;
            if (isdigit(*sptr)) SetPathThrottleFrom = atoi(sptr);
            while (*sptr && isdigit(*sptr)) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr)) SetPathThrottleTo = atoi(sptr);
            while (*sptr && isdigit(*sptr)) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr)) SetPathThrottleResume = atoi(sptr);
            while (*sptr && isdigit(*sptr)) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr)) SetPathThrottleBusy = atoi(sptr);
            while (*sptr && isdigit(*sptr)) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr))
               SetPathThrottleTimeoutQueue =
                  MetaConSetSeconds (mcptr, sptr, 1);
            while (*sptr && (isdigit(*sptr) || *sptr == ':')) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr))
               SetPathThrottleTimeoutBusy =
                  MetaConSetSeconds (mcptr, sptr, 1);
            while (*sptr && (isdigit(*sptr) || *sptr == ':')) *sptr++;
         }
         else
         if (strsame (SetPathPtr, "TIMEOUT=KEEPALIVE=", 18))
            SetPathTimeoutKeepAlive =
               MetaConSetSeconds (mcptr, SetPathPtr+18, 1);
         else
         if (strsame (SetPathPtr, "TIMEOUT=NOPROGRESS=", 19))
            SetPathTimeoutNoProgress =
               MetaConSetSeconds (mcptr, SetPathPtr+19, 1);
         else
         if (strsame (SetPathPtr, "TIMEOUT=OUTPUT=", 15))
            SetPathTimeoutOutput =
               MetaConSetSeconds (mcptr, SetPathPtr+15, 1);
         else
         if (strsame (SetPathPtr, "TIMEOUT=", 8))
         {
            sptr = SetPathPtr+8;
            if (isdigit(*sptr))
               SetPathTimeoutKeepAlive = MetaConSetSeconds (mcptr, sptr, 1);
            while (*sptr && (isdigit(*sptr) || *sptr == ':')) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr))
               SetPathTimeoutNoProgress = MetaConSetSeconds (mcptr, sptr, 1);
            while (*sptr && (isdigit(*sptr) || *sptr == ':')) *sptr++;
            if (*sptr == ',') *sptr++;
            if (isdigit(*sptr))
               SetPathTimeoutOutput = MetaConSetSeconds (mcptr, sptr, 1);
            while (*sptr && (isdigit(*sptr) || *sptr == ':')) *sptr++;
         }
         else
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
            return (true);
         }
      }
   }

   if (RuleType == MAPURL_RULE_SET && !PathSet)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemSetPath);
      return (true);
   }

   if (RuleType == MAPURL_RULE_PROTECT)
   {
      /****************/
      /* protect rule */
      /****************/

      /*
         PROTECT rules are always added to the mapping database, whether
         correct or not, so that even when incorrect the path is still
         access controlled (and in an incorrect case always denied!)
      */
      sptr = AuthConfigParseProtectRule (NULL, ResultPtr, strlen(ResultPtr));
      if (sptr)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, sptr);
         ConfigProblem = true;
      }
      AuthProtectRule = true;
   }

   /* check for optional mapping conditional(s) */
   if (*cptr == '[' || *(unsigned short*)cptr == '![')
   {
      /***************/
      /* conditional */
      /***************/

      ConditionalPtr = cptr;
      ConditionalOffset = cptr - TemplatePtr;
      while (*cptr == '[' || *(unsigned short*)cptr == '![')
      {
         while (*cptr == '[' || *cptr == '!') cptr++;
         while (*cptr && *cptr != ']')
         {
            while (ISLWS(*cptr)) cptr++;
            sptr = cptr;
            while (*cptr && *cptr != ':' && !ISLWS(*cptr) && *cptr != ']')
            {
               if (*cptr == '\\') cptr++;
               *cptr = toupper(*cptr);
               if (*cptr) cptr++;
            }
            /* basic check of conditional rule */
            if (*sptr == '!') sptr++;
            if ((*(unsigned short*)sptr != 'AC' &&
                 *(unsigned short*)sptr != 'AL' &&
                 *(unsigned short*)sptr != 'AS' &&
                 *(unsigned short*)sptr != 'CA' &&
                 *(unsigned short*)sptr != 'CK' &&
                 *(unsigned short*)sptr != 'DR' &&
                 *(unsigned short*)sptr != 'EX' &&
                 *(unsigned short*)sptr != 'FO' &&
                 *(unsigned short*)sptr != 'HH' &&
                 *(unsigned short*)sptr != 'HM' &&
                 *(unsigned short*)sptr != 'HO' &&
                 *(unsigned short*)sptr != 'ME' &&
                 *(unsigned short*)sptr != 'MP' &&
                 *(unsigned short*)sptr != 'NO' &&
                 *(unsigned short*)sptr != 'PA' &&
                 *(unsigned short*)sptr != 'PI' &&
                 *(unsigned short*)sptr != 'QS' &&
                 *(unsigned short*)sptr != 'RC' &&
                 *(unsigned short*)sptr != 'RF' &&
                 *(unsigned short*)sptr != 'RQ' &&
                 *(unsigned short*)sptr != 'RU' &&
                 *(unsigned short*)sptr != 'SC' &&
                 *(unsigned short*)sptr != 'SN' &&
                 *(unsigned short*)sptr != 'SP' &&
                 *(unsigned short*)sptr != 'ST' &&
                 *(unsigned short*)sptr != 'UA' &&
                 *(unsigned short*)sptr != 'VS' &&
                 *(unsigned short*)sptr != 'XF') ||
                sptr[2] != ':')
            {
               /***********************/
               /* conditional problem */
               /***********************/

               MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConditional);
               return (true);
            }

            while (*cptr && !ISLWS(*cptr) && *cptr != ']')
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) cptr++;
            }
            while (ISLWS(*cptr)) cptr++;
         }
         if (*cptr == ']') cptr++;
         while (ISLWS(*cptr)) cptr++;
      }
      /* terminate in the white-space following the conditional(s) */
      if (*cptr) *cptr = '\0';
   }

   OffsetSize = cptr - TemplatePtr + 1;

   /**********************/
   /* allowed rule check */
   /**********************/

   if (mclptr->MetaFileType == METACON_TYPE_CONFIG)
   {
      if (!AllowedInConfigFile)
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemNotAllowed);
         return (true);
      }
   }

   /***************************/
   /* rule consistency checks */
   /***************************/

   if (RmsSubChar &&
       !isalnum(RmsSubChar) && RmsSubChar != '$' &&
       RmsSubChar != '-' && RmsSubChar != '_')
   {
      MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemRmsSubstitution);
      return (true);
   }

   TemplateWildcardCount = 0;
   cptr = TemplatePtr;
   while (*cptr)
   {
      if (*cptr++ != '*') continue;
      TemplateWildcardCount++;
      /* contiguous count as one! */
      while (*cptr == '*') cptr++;
   }

   if (TemplateWildcardCount > REGEX_PMATCH_MAX)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemTooManyWildcard);
      return (true);
   }

   memset (&RegexPreg, 0, sizeof(RegexPreg));
   if (Config.cfMisc.RegexEnabled && *TemplatePtr == REGEX_CHAR)
   {
      cptr = StringRegexCompile (TemplatePtr+1, &RegexPreg);
      if (cptr)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemRegex, cptr);
         return (true);
      }
   }

   ResultWildcardCount = ResultSpecifiedWildcardCount = 0;
   cptr = ResultPtr;
   while (*cptr)
   {
      if (*cptr++ != '*') continue;
      ResultWildcardCount++;
      /* contiguous count as one! */
      while (*cptr == '*') cptr++;
      if (*cptr != '\'') continue;
      ResultSpecifiedWildcardCount++;
      cptr++;
      if (*cptr < '1' || *cptr > '9')
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        ProblemSpecifiedWildcardRange);
         return (true);
      }
   }

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "!&Z !&Z !&Z !UL !UL !UL",
                 TemplatePtr, ResultPtr, ConditionalPtr,
                 TemplateWildcardCount, ResultWildcardCount,
                 ResultSpecifiedWildcardCount);

   ResultPathOds = 0;

   switch (RuleType)
   {
      case MAPURL_RULE_SCRIPT :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            return (true);
         }
         if (ResultSpecifiedWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemSpecifiedWildcardCannot);
            return (true);
         }
         if (ResultWildcardCount != TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            return (true);
         }
         break;

      case MAPURL_RULE_EXEC :
      case MAPURL_RULE_UXEC :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            return (true);
         }
         if (ResultSpecifiedWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemSpecifiedWildcardCannot);
            return (true);
         }
         if (!ResultWildcardCount ||
             !TemplateWildcardCount ||
             ResultWildcardCount != TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            return (true);
         }
         break;

      case MAPURL_RULE_FAIL :
         break;

      case MAPURL_RULE_PASS :

         if (ResultSpecifiedWildcardCount &&
             ResultSpecifiedWildcardCount != ResultWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_WARNING,
                           ProblemSpecifiedWildcardMix);
            return (true);
         }
         if (!isdigit(ResultPtr[0]) &&
             ResultWildcardCount > TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           ProblemWildcardMapping);
            return (true);
         }

#ifdef ODS_EXTENDED

         /* determine the on-disk structure */
         zptr = (sptr = DiskDevice) + sizeof(DiskDevice)-1;
         if (ResultPtr[0])
            cptr = ResultPtr;
         else
            cptr = TemplatePtr;
         if (*cptr == '/') cptr++;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr < zptr) *sptr++ = ':';
         *sptr = '\0';
         ResultPathOds = MapUrl_VolumeOds (DiskDevice);

#endif /* ODS_EXTENDED */

         break;

      case MAPURL_RULE_MAP :
      case MAPURL_RULE_REDIRECT :
      case MAPURL_RULE_USER :

         if (!ResultPtr[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultRequired);
            return (true);
         }
         if (isdigit(ResultPtr[0]))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemResultStatus);
            return (true);
         }
         if (ResultSpecifiedWildcardCount &&
             ResultSpecifiedWildcardCount != ResultWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_WARNING,
                           ProblemSpecifiedWildcardMix);
            return (true);
         }
         if (ResultWildcardCount > TemplateWildcardCount)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemWildcardMapping);
            return (true);
         }
         break;

      case MAPURL_RULE_PROTECT :
      case MAPURL_RULE_SET :

         break;

      default :
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemConfused);
         return (true);
   }

   /*****************************/
   /* throttle integrity checks */
   /*****************************/

   if (SetPathThrottleFrom)
   {
      /* throttle=from,to,resume,busy,t/o-resume,t/o-busy */
      if ((SetPathThrottleTo &&
           SetPathThrottleTo <= SetPathThrottleFrom) ||
          (SetPathThrottleResume && !SetPathThrottleTo) ||
          (SetPathThrottleResume &&
           SetPathThrottleResume <= SetPathThrottleTo) ||
          (SetPathThrottleBusy && SetPathThrottleTo &&
           SetPathThrottleBusy <= SetPathThrottleTo) ||
          (SetPathThrottleBusy && SetPathThrottleResume &&
           SetPathThrottleBusy <= SetPathThrottleResume))
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemThrottleValues);
         return (true);
      }
   }

   /************************************/
   /* script=param=(name=value) checks */
   /************************************/

   if (SetPathScriptParamsPtr)
   {
      /* these will generally end up as DCL symbols for a script */
      StringPtr = SetPathScriptParamsPtr;
      for (;;)
      {
         status = StringParseNameValue (&StringPtr, true,
                                        Name, sizeof(Name),
                                        Value, sizeof(Value));
         if (VMSnok (status)) break;
      }
      if (status != SS$_ENDOFFILE)
      {
         MetaConReport (mcptr, METACON_REPORT_WARNING, ProblemNameValue);
         return (true);
      }
   }

   /*********************************/
   /* add the rule as the line data */
   /*********************************/

   /* allocate memory for the structure plus the null terminated strings */
   mrptr = (MAP_RULE_META*)VmGet (sizeof(MAP_RULE_META) + OffsetSize);

   /* set the meta-config data pointer to this structure */
   mclptr->LineDataPtr = mrptr;

   mclptr->ConfigProblem = ConfigProblem;

   mrptr->RuleType = RuleType;
   mrptr->MetaConNumber = mclptr->Number;
   mrptr->PathSet = PathSet;
   mrptr->IsCgiPlusScript = IsCgiPlusScript;

#ifdef ODS_EXTENDED

   if (ResultPathOds)
   {
      /* indicate that this path is located on an ODS-5 volume */
      mrptr->ResultPathOds = ResultPathOds;
      /* indicate that at least one mapped volume is ODS-5 */
      if (ResultPathOds == MAPURL_PATH_ODS_5) MapUrlPathOds5 = true;
   }

#endif /* ODS_EXTENDED */

   mrpsptr = &mrptr->mpPathSet;
   mrpsptr->AcceptLangChar = SetPathAcceptLangChar;
   mrpsptr->AcceptLangTypeVariant = SetPathAcceptLangTypeVariant;
   mrpsptr->AcceptLangWildcard = SetPathAcceptLangWildcard;
   mrpsptr->NoAcceptLang = SetPathNoAcceptLang;
   mrpsptr->Alert = SetPathAlert;
   mrpsptr->NoAlert = SetPathNoAlert;
   mrpsptr->AuthAll = SetPathAuthAll;
   mrpsptr->NoAuthAll = SetPathNoAuthAll;
   mrpsptr->AuthMapped = SetPathAuthMapped;
   mrpsptr->NoAuthMapped = SetPathNoAuthMapped;
   mrpsptr->AuthOnce = SetPathAuthOnce;
   mrpsptr->NoAuthOnce = SetPathNoAuthOnce;
   mrpsptr->AuthRevalidateTimeout = SetPathAuthRevalidateTimeout;
   mrpsptr->CacheSetting = CacheSetting;
   mrpsptr->Cache = SetPathCache;
   mrpsptr->NoCache = SetPathNoCache;
   mrpsptr->CacheCGI = SetPathCacheCGI;
   mrpsptr->CacheNoCGI = SetPathCacheNoCGI;
   mrpsptr->CacheExpiresAfter = SetPathCacheExpiresAfter;
   mrpsptr->CacheGuardSeconds = SetPathCacheGuardSeconds;
   mrpsptr->CacheFile = SetPathCacheFile;
   mrpsptr->CacheMaxKBytes = SetPathCacheMaxKBytes;
   mrpsptr->CacheNoFile = SetPathCacheNoFile;
   mrpsptr->CacheNet = SetPathCacheNet;
   mrpsptr->CacheNoNet = SetPathCacheNoNet;
   mrpsptr->CacheNPH = SetPathCacheNPH;
   mrpsptr->CacheNoNPH = SetPathCacheNoNPH;
   mrpsptr->CachePermanent = SetPathCachePermanent;
   mrpsptr->CacheNoPermanent = SetPathCacheNoPermanent;
   mrpsptr->CacheQuery = SetPathCacheQuery;
   mrpsptr->CacheNoQuery = SetPathCacheNoQuery;
   mrpsptr->CacheScript = SetPathCacheScript;
   mrpsptr->CacheNoScript = SetPathCacheNoScript;
   mrpsptr->CacheSSI = SetPathCacheSSI;
   mrpsptr->CacheNoSSI = SetPathCacheNoSSI;
   memcpy (mrpsptr->CgiPlusInCC, SetPathCgiPlusInCC, 4);
   mrpsptr->CgiPlusInWriteof = SetPathCgiPlusInWriteof;
   mrpsptr->CgiPlusInNoWriteof = SetPathCgiPlusInNoWriteof;
   mrpsptr->DefaultSearch = SetPathDefaultSearch;
   mrpsptr->NoDefaultSearch = SetPathNoDefaultSearch;
   mrpsptr->DirAccess = SetPathDirAccess;
   mrpsptr->DirNoAccess = SetPathDirNoAccess;
   mrpsptr->DirAccessSelective = SetPathDirAccessSelective;
   mrpsptr->DirImpliedWildcard = SetPathDirImpliedWildcard;
   mrpsptr->DirNoImpliedWildcard = SetPathDirNoImpliedWildcard;
   mrpsptr->DirStyle = SetPathDirStyle;
   mrpsptr->DirWildcard = SetPathDirWildcard;
   mrpsptr->DirNoWildcard = SetPathDirNoWildcard;
   mrpsptr->Expired = SetPathExpired;
   mrpsptr->NoExpired = SetPathNoExpired;
   mrpsptr->Log = SetPathLog;
   mrpsptr->NoLog = SetPathNoLog;
   mrpsptr->MapEllipsis = SetPathMapEllipsis;
   mrpsptr->NoMapEllipsis = SetPathNoMapEllipsis;
   mrpsptr->MapEmpty = SetPathMapEmpty;
   mrpsptr->MapNonEmpty = SetPathMapNonEmpty;
   mrpsptr->MapOnce = SetPathMapOnce;
   mrpsptr->NoMapOnce = SetPathNoMapOnce;
   mrpsptr->MapRestart = SetPathMapRestart;
   mrpsptr->MapSetIgnore = SetPathMapSetIgnore;
   mrpsptr->MapSetNoIgnore = SetPathMapSetNoIgnore;
   mrpsptr->MapSetRequest = SetPathMapSetRequest;
   mrpsptr->MapSetNoRequest = SetPathMapSetNoRequest;
   mrpsptr->PathOds = SetPathOds;
   mrpsptr->RmsSubChar = RmsSubChar;
   mrpsptr->ScriptFind = SetPathScriptFind;
   mrpsptr->ScriptNoFind = SetPathScriptNoFind;
   mrpsptr->ScriptPathFind = SetPathScriptPathFind;
   mrpsptr->ScriptNoPathFind = SetPathScriptNoPathFind;
   mrpsptr->ScriptQueryNone = SetPathScriptQueryNone;
   mrpsptr->ScriptNoQueryNone = SetPathScriptNoQueryNone;
   mrpsptr->ScriptQueryRelaxed = SetPathScriptQueryRelaxed;
   mrpsptr->ScriptNoQueryRelaxed = SetPathScriptNoQueryRelaxed;
   mrpsptr->PrivSsi = SetPathPrivSsi;
   mrpsptr->NoPrivSsi = SetPathNoPrivSsi;
   mrpsptr->Profile = SetPathProfile;
   mrpsptr->NoProfile = SetPathNoProfile;
   mrpsptr->ProxyForwardedBy = SetPathProxyForwardedBy;
   mrpsptr->ProxyXForwardedFor = SetPathProxyXForwardedFor;
   mrpsptr->ProxyReverseVerify = SetPathProxyReverseVerify;
   mrpsptr->NoProxyReverseVerify = SetPathNoProxyReverseVerify;
   mrpsptr->ProxyUnknownRequestFields = SetPathProxyUnknownRequestFields;
   mrpsptr->NoProxyUnknownRequestFields = SetPathNoProxyUnknownRequestFields;
   mrpsptr->ReportBasic = SetPathReportBasic;
   mrpsptr->ReportDetailed = SetPathReportDetailed;
   mrpsptr->Report400as = SetPathReport400as;
   mrpsptr->Report403as = SetPathReport403as;
   mrpsptr->Report404as = SetPathReport404as;
   mrpsptr->ResponseHeaderBegin = SetPathResponseHeaderBegin;
   mrpsptr->ResponseHeaderFull = SetPathResponseHeaderFull;
   mrpsptr->ResponseHeaderNone = SetPathResponseHeaderNone;
   mrpsptr->ResponseHeaderNoAdd = SetPathResponseHeaderNoAdd;
   mrpsptr->StmLF = SetPathStmLF;
   mrpsptr->NoStmLF = SetPathNoStmLF;
   mrpsptr->SSLCGIvar = SetPathSSLCGIvar;
   mrpsptr->ScriptBitBucketTimeout = SetPathScriptBitBucketTimeout;
   mrpsptr->ScriptCpuMax = SetPathScriptCpuMax;
   mrpsptr->TimeoutKeepAlive = SetPathTimeoutKeepAlive;
   mrpsptr->TimeoutNoProgress = SetPathTimeoutNoProgress;
   mrpsptr->TimeoutOutput = SetPathTimeoutOutput;

   if (mrpsptr->ThrottleFrom = SetPathThrottleFrom)
   {
      mrpsptr->ThrottleIndex = mmptr->ThrottleIndex++;
      if (mmptr->ThrottleIndex > mmptr->ThrottleTotal)
         mmptr->ThrottleTotal = mmptr->ThrottleIndex;
      mrpsptr->ThrottleBusy = SetPathThrottleBusy;
      mrpsptr->ThrottleResume = SetPathThrottleResume;
      mrpsptr->ThrottleTo = SetPathThrottleTo;
      mrpsptr->ThrottleTimeoutBusy = SetPathThrottleTimeoutBusy;
      mrpsptr->ThrottleTimeoutQueue = SetPathThrottleTimeoutQueue;
   }

   /* copy all the now null terminated strings as one block */
   cptr = mrptr->TemplatePtr = &mrptr->Storage;
   memcpy (cptr, TemplatePtr, OffsetSize);

   if (RegexPreg.buffer)
   {
      /* the template was a regular expression, create a compiled version */
      StringRegexCompile (TemplatePtr+1, &mrptr->RegexPregTemplate);
      /* free the temporary compiled version */
      regfree (&RegexPreg);
   }

   /* now adjust the rule pointers to this block of null-terminated strings */
   if (ResultOffset)
   {
      mrptr->ResultPtr = cptr + ResultOffset;
      mrptr->ResultLength = strlen(mrptr->ResultPtr);
   }
   if (ConditionalOffset) mrptr->ConditionalPtr = cptr + ConditionalOffset;
   if (SetPathAcceptLangOffset)
   {
      mrpsptr->AcceptLangPtr = sptr = cptr + SetPathAcceptLangOffset;
      mrpsptr->AcceptLangLength = StringStripValue (sptr);
   }
   if (SetPathAuthSysUafPwdExpUrlOffset)
   {
      mrpsptr->AuthSysUafPwdExpUrlPtr = sptr =
         cptr + SetPathAuthSysUafPwdExpUrlOffset;
      mrpsptr->AuthSysUafPwdExpUrlLength = StringStripValue (sptr);
   }
   if (SetPathCgiPrefixOffset)
   {
      mrpsptr->CgiPrefixPtr = cptr + SetPathCgiPrefixOffset;
      mrpsptr->CgiPrefixLength = strlen(mrpsptr->CgiPrefixPtr);
   }
   if (SetPathCharsetOffset)
   {
      mrpsptr->CharsetPtr = sptr = cptr + SetPathCharsetOffset;
      mrpsptr->CharsetLength = StringStripValue (sptr);
   }
   if (SetPathContentTypeOffset)
   {
      mrpsptr->ContentTypePtr = sptr = cptr + SetPathContentTypeOffset;
      mrpsptr->ContentTypeLength = StringStripValue (sptr);
   }
   if (SetPathDirCharsetOffset)
   {
      mrpsptr->DirCharsetPtr = sptr = cptr + SetPathDirCharsetOffset;
      mrpsptr->DirCharsetLength = StringStripValue (sptr);
   }
   if (SetPathNoHtmlEquals) mrpsptr->NoHtmlEquals = true;
   if (SetPathHtmlBodyTagOffset)
   {
      mrpsptr->HtmlBodyTagPtr = sptr = cptr + SetPathHtmlBodyTagOffset;
      mrpsptr->HtmlBodyTagLength = StringStripValue (sptr);
   }
   if (SetPathHtmlFooterOffset)
   {
      mrpsptr->HtmlFooterPtr = sptr = cptr + SetPathHtmlFooterOffset;
      mrpsptr->HtmlFooterLength = StringStripValue (sptr);
   }
   if (SetPathHtmlFooterTagOffset)
   {
      mrpsptr->HtmlFooterTagPtr = sptr = cptr + SetPathHtmlFooterTagOffset;
      mrpsptr->HtmlFooterTagLength = StringStripValue (sptr);
   }
   if (SetPathHtmlHeaderOffset)
   {
      mrpsptr->HtmlHeaderPtr = sptr = cptr + SetPathHtmlHeaderOffset;
      mrpsptr->HtmlHeaderLength = StringStripValue (sptr);
   }
   if (SetPathHtmlHeaderTagOffset)
   {
      mrpsptr->HtmlHeaderTagPtr = sptr = cptr + SetPathHtmlHeaderTagOffset;
      mrpsptr->HtmlHeaderTagLength = StringStripValue (sptr);
   }
   if (SetPathHttpAcceptCharsetOffset)
   {
      mrpsptr->HttpAcceptCharsetPtr = sptr =
         cptr + SetPathHttpAcceptCharsetOffset;
      mrpsptr->HttpAcceptCharsetLength = StringStripValue (sptr);
   }
   if (SetPathHttpAcceptLangOffset)
   {
      mrpsptr->HttpAcceptLangPtr = sptr = cptr + SetPathHttpAcceptLangOffset;
      mrpsptr->HttpAcceptLangLength = StringStripValue (sptr);
   }
   if (SetPathIndexOffset)
   {
      mrpsptr->IndexPtr = cptr + SetPathIndexOffset;
      mrpsptr->IndexLength = strlen(mrpsptr->IndexPtr);
   }
   if (SetPathMapRootOffset)
   {
      mrpsptr->MapRootPtr = cptr + SetPathMapRootOffset;
      mrpsptr->MapRootLength = strlen(mrpsptr->MapRootPtr);
   }
   if (SetPathNotePadOffset)
   {
      mrpsptr->NotePadPtr = cptr + SetPathNotePadOffset;
      mrpsptr->NotePadLength = strlen(mrpsptr->NotePadPtr);
   }
   if (SetPathProxyBindIpAddressOffset)
   {
      mrpsptr->ProxyBindIpAddressPtr = cptr + SetPathProxyBindIpAddressOffset;
      mrpsptr->ProxyBindIpAddressLength =
         strlen(mrpsptr->ProxyBindIpAddressPtr);
      status = TcpIpStringToAddress (mrpsptr->ProxyBindIpAddressPtr,
                                     &mrpsptr->ProxyBindIpAddress);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Could not get bind address for !AZ",
                        mrpsptr->ProxyBindIpAddressPtr);
   }
   if (SetPathProxyChainHostPortOffset)
   {
      mrpsptr->ProxyChainHostPortPtr = cptr + SetPathProxyChainHostPortOffset;
      mrpsptr->ProxyChainHostPortLength = strlen(mrpsptr->ProxyChainHostPortPtr);

      IPADDRESS_ZERO4 (&mrpsptr->ProxyChainIpAddress) 
      status = NetHostNameLookup (mcptr,
                                  mrpsptr->ProxyChainHostPortPtr,
                                  0, NULL, NULL, NULL,
                                  &mrpsptr->ProxyChainIpAddress,
                                  &mrpsptr->ProxyChainPort);
      if (!mrpsptr->ProxyChainPort)
         mrpsptr->ProxyChainPort = DEFAULT_HTTP_PROXY_PORT;
      if (VMSnok (status))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemHostNameLookup, 
                        mrpsptr->ProxyChainHostPortPtr, status);
         /* make sure the connection can't succeed */
         IPADDRESS_SET_UNUSABLE (&mrpsptr->ProxyChainIpAddress)
         mrpsptr->ProxyChainPort = 0;
      }
   }
   if (SetPathProxyReverseLocationOffset)
   {
      mrpsptr->ProxyReverseLocationPtr = cptr + SetPathProxyReverseLocationOffset;
      mrpsptr->ProxyReverseLocationLength = strlen(mrpsptr->ProxyReverseLocationPtr);
   }
   if (SetPathQueryStringOffset)
   {
      /* the query string is allowed to be empty! */
      mrpsptr->QueryStringPtr = sptr = cptr + SetPathQueryStringOffset;
      mrpsptr->QueryStringLength = StringStripValue (sptr);
   }
   if (SetPathResponseHeaderAddOffset)
   {
      mrpsptr->ResponseHeaderAddPtr = sptr =
          cptr + SetPathResponseHeaderAddOffset;
      mrpsptr->ResponseHeaderAddLength = StringStripValue (sptr);
   }
   if (SetPathScriptAsOffset)
   {
      mrpsptr->ScriptAsPtr = cptr + SetPathScriptAsOffset;
      mrpsptr->ScriptAsLength = strlen(mrpsptr->ScriptAsPtr);
   }
   if (SetPathScriptCommandOffset)
   {
      mrpsptr->ScriptCommandPtr = sptr = cptr + SetPathScriptCommandOffset;
      mrpsptr->ScriptCommandLength = StringStripValue (sptr);
   }
   if (SetPathScriptDefaultOffset)
   {
      mrpsptr->ScriptDefaultPtr = sptr = cptr + SetPathScriptDefaultOffset;
      mrpsptr->ScriptDefaultLength = StringStripValue (sptr);
   }
   if (SetPathScriptParamsOffset)
   {
      mrpsptr->ScriptParamsPtr = cptr + SetPathScriptParamsOffset;
      mrpsptr->ScriptParamsLength = strlen(mrpsptr->ScriptParamsPtr);
   }
   if (SetPathSsiExecOffset)
   {
      mrpsptr->SsiExecPtr = sptr = cptr + SetPathSsiExecOffset;
      mrpsptr->SsiExecLength = StringStripValue (sptr);
   }

   if (WATCH_MODULE_DETAIL)
      WatchDataFormatted ("!&X !UL !&Z !&Z (!UL) !&B !&Z\n",
         mrptr, mrptr->RuleType, mrptr->TemplatePtr,
         mrptr->ResultPtr, mrptr->ResultPathOds, mrptr->PathSet,
         mrptr->ConditionalPtr);

   return (true);
}

/*****************************************************************************/
/*
Convert a URL-style specification into a RMS-style specification, example:
"/disk/dir1/dir2/file.txt" into "disk:[dir1.dir2]file.txt".

Can process ODS-2, ODS-5 (EFS), SRI (MultiNet NFS), PATHWORKS (v4/5) and
Advanced Server (PATHWORKS V6) / Samba encodings.

Returns the length of the VMS specification.
*/ 
 
int MapUrl_UrlToVms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char RmsSubChar,
BOOL MapEllipsis,
int PathOds
)
{
   int  len;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToVms() !UL !UL !&B !UL !&Z",
                 PathOds, RmsSubChar, MapEllipsis, SizeOfVmsPtr, PathPtr);

   switch (PathOds)
   {
      case 0 :
      case MAPURL_PATH_ODS_2 :
         len = MapUrl_UrlToOds2Vms (PathPtr, VmsPtr, SizeOfVmsPtr,
                                    MapEllipsis, RmsSubChar);
         break;

#ifdef ODS_EXTENDED
      case MAPURL_PATH_ODS_5 :
         len = MapUrl_UrlToOds5Vms (PathPtr, VmsPtr, SizeOfVmsPtr, MapEllipsis);
         break;
#endif /* ODS_EXTENDED */

      case MAPURL_PATH_ODS_ADS :
      case MAPURL_PATH_ODS_SMB :
         len = MapUrl_UrlToAdsVms (PathPtr, VmsPtr, SizeOfVmsPtr, MapEllipsis);
         break;

      case MAPURL_PATH_ODS_PWK :
         len = MapUrl_UrlToPwkVms (PathPtr, VmsPtr, SizeOfVmsPtr, MapEllipsis);
         break;

      case MAPURL_PATH_ODS_SRI :
         len = MapUrl_UrlToSriVms (PathPtr, VmsPtr, SizeOfVmsPtr, MapEllipsis);
         break;

      default :
         memcpy (VmsPtr, "SANITY:[CHECK]MAPURL_URLTOVMS", len=30);
   }

   /* ensure it's not mistaken for an error message! */
   if (!VmsPtr[0]) VmsPtr[1] = '\0';

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

/*****************************************************************************/
/*
Convert a URL-style specification into an ODS-2 RMS-style specification,
example: "/disk/dir1/dir2/file.txt" into "disk:[dir1.dir2]file.txt".

Forbidden characters (non-alphanumberic and non-"$_-") are converted to the
'RmsSubChar' (by default a dollar symbol).  Where a specification has multiple
".", all but the final one of the file component is also converted to a
'RmsSubChar'.

Returns the length of the VMS specification.
*/ 
 
int MapUrl_UrlToOds2Vms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis,
char RmsSubChar
)
{
   int  ecnt, len;
   char  *cptr, *eptr, *pptr, *sptr, *zptr,
         *FinalPeriodPtr;
   char  PathComponents [ODS_MAX_FILE_NAME_LENGTH];

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToOds2Vms() !&B !UL !&Z !UL",
                 MapEllipsis, RmsSubChar, PathPtr, SizeOfVmsPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || *(unsigned short*)pptr == '/\0')
   {
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   ecnt = 0;
   FinalPeriodPtr = NULL;
   zptr = (sptr = PathComponents) + sizeof(PathComponents);

   /* each '/' component becomes a null-terminated string */
   while (*pptr && sptr < zptr)
   {
      if (*pptr == '/') pptr++;
      eptr = sptr;
      while (*pptr && *pptr != '/' && sptr < zptr)
      {
         if (isalnum (*pptr) ||
             *pptr == '$' || *pptr == '_' || *pptr == '-' ||
             *pptr == '*' || *pptr == '%' || *pptr == ';')
         {
            /* ODS-2 legal character (or wildcard) */
            *sptr++ = toupper(*pptr++);
            continue;
         }
         if (*pptr == '.' && *(unsigned short*)(pptr+1) == '..')
         {
            /* ellipsis wildcard */
            *sptr++ = *pptr++;
            if (sptr < zptr) *sptr++ = *pptr++;
            if (sptr < zptr) *sptr++ = *pptr++;
            continue;
         }
         if (!ecnt)
         {
            /* DECnet only valid in first component */
            if (*pptr == '\"' || *pptr == '=')
            {
               /* DECnet access string (allow otherwise forbidden chars) */
               if (*(unsigned short*)pptr == '\"~' ||
                   *(unsigned short*)pptr == '\"$')
                  *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
            if (*(unsigned short*)pptr == '::')
            {
               /* DECnet (with or without access string) */
               *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
         }
         /* any other character substitute a question mark */
         if (*pptr == '.') FinalPeriodPtr = sptr;
         if (sptr < zptr) *sptr++ = '?';
         pptr++;
      }
      if (sptr < zptr) *sptr++ = '\0';
      ecnt++;
      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!UL !&Z\n", ecnt, eptr);
   }

   if (sptr >= zptr)
   {
      /* storage overflowed */
      if (WATCH_MODULE_DETAIL)
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }
   *sptr = '\0';

   if (FinalPeriodPtr) *FinalPeriodPtr = '.';

   /* convert these null-terminated elements into a VMS file specification */
   len = MapUrl_ElementsToVms (PathComponents, ecnt, VmsPtr, SizeOfVmsPtr,
                               MapEllipsis);

   /* convert the non-RMS characters into the RMS substitution character */
   if (!RmsSubChar) RmsSubChar = MAPURL_RMS_SUBSTITUTION_DEFAULT;
   for (cptr = VmsPtr; *cptr; cptr++)
   {
      if (*cptr != '?') continue;
      *cptr = RmsSubChar;
   }

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

/*****************************************************************************/
/*
Convert a URL-style specification into an ODS-5 RMS-style specification,
example: "/disk/dir1/dir2/file.tar.gz" into "disk:[dir1.dir2]file^.tar.gz".

For ODS-5 paths forbidden characters are ^ (character or hexadecimal) escaped
and the case not altered.  Refer to the EFS sections of the RMS documentaion.

Returns the length of the VMS specification.
*/ 
 
#ifdef ODS_EXTENDED

int MapUrl_UrlToOds5Vms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   BOOL  DECnet;
   int  ecnt, len;
   char  *cptr, *eptr, *pptr, *sptr, *zptr,
         *FinalPeriodPtr,
         *FinalSemiColonPtr,
         *FinalSlashPtr;
   char  PathComponents [ODS_MAX_FILE_NAME_LENGTH];

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToOds5Vms() !&B !&Z !UL",
                 MapEllipsis, PathPtr, SizeOfVmsPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || *(unsigned short*)pptr == '/\0')
   {
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   ecnt = 0;
   FinalPeriodPtr = FinalSemiColonPtr = FinalSlashPtr = NULL;
   zptr = (sptr = PathComponents) + sizeof(PathComponents);

   /* each '/' component becomes a null-terminated string */
   while (*pptr && sptr < zptr)
   {
      if (*pptr == '/') pptr++;
      eptr = sptr;
      while (*pptr && *pptr != '/' && sptr < zptr)
      {
         switch (*pptr)
         {
            case '!' : case '#' : case '&' : case '\'' :
            case '`' : case '(' : case ')' : case '+' :
            case '@' : case '{' : case '}' : case ',' :
            case ';' : case '[' : case ']' : case '=' :
               if (*pptr == ';') FinalSemiColonPtr = sptr;
               *sptr++ = '^';
               if (sptr < zptr) *sptr++ = *pptr++;
               break;

            case ' ' :
               *sptr++ = '^';
               if (sptr < zptr) *sptr++ = '_';
               pptr++;
               break;

            case '.' :
               if (*pptr == '.' && *(unsigned short*)(pptr+1) == '..')
               {
                  /* ellipsis wildcard */
                  *sptr++ = *pptr++;
                  if (sptr < zptr) *sptr++ = *pptr++;
                  if (sptr < zptr) *sptr++ = *pptr++;
               }
               else
               {
                  FinalPeriodPtr = sptr;
                  *sptr++ = '^';
                  if (sptr < zptr) *sptr++ = *pptr++;
               }
               break;

            case '^' :
               *sptr++ = *pptr++;
               if (isxdigit(*pptr))
               {
                  if (sptr < zptr) *sptr++ = *pptr++;
                  if (sptr < zptr && *pptr) *sptr++ = *pptr++;
               }
               else
               if (sptr < zptr && *pptr)
                  *sptr++ = *pptr++;
               break;

            default :
               if (*pptr == 0x07f ||
                   *pptr == 0x0a0 ||
                   *pptr == 0x0ff ||
                   (*pptr >= 0x80 && *pptr <= 0x9f))
               {
                  if (sptr+3 < zptr)
                  {
                     *sptr++ = '^';
                     *sptr++ = HexDigits[(*pptr & 0xf0) >> 4];
                     *sptr++ = HexDigits[*pptr++ & 0x0f];
                  }
                  else
                     sptr = zptr;
               }
               else
                  *sptr++ = *pptr++;
         }
      }
      if (*pptr == '/') FinalSlashPtr = sptr;
      if (sptr < zptr) *sptr++ = '\0';
      ecnt++;
      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!UL !&Z\n", ecnt, eptr);
   }

   if (sptr >= zptr)
   {
      /* storage overflowed */
      if (WATCH_MODULE_DETAIL)
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }
   *sptr = '\0';

   if (FinalSemiColonPtr &&
       (FinalSemiColonPtr > FinalSlashPtr || !FinalSlashPtr) &&
       sptr < zptr)
   {
      /* a final semicolon delimitting the version must not be escaped */
      cptr = (sptr = FinalSemiColonPtr) + 1;
      while (*cptr) *sptr++ = *cptr++;
      *sptr = '\0';
   }

   if (FinalPeriodPtr &&
       (FinalPeriodPtr > FinalSlashPtr || !FinalSlashPtr) &&
       sptr < zptr)
   {
      /* a final period delimitting the type must not be escaped */
      cptr = (sptr = FinalPeriodPtr) + 1;
      while (*cptr) *sptr++ = *cptr++;
      *sptr = '\0';
   }

   len = MapUrl_ElementsToVms (PathComponents, ecnt, VmsPtr, SizeOfVmsPtr,
                               MapEllipsis);

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

#endif /* ODS_EXTENDED */

/*****************************************************************************/
/*
Convert a URL-style specification into a RMS-style specification, example:
"/disk/dir1/dir2/file.tar.gz" into "disk:[dir1.dir2]file__27tar.gz" - with
Advanced Server / Samba character conversion. If a character is not RMS legal
(excluding the dollar which is the escape) then encode it as a
double-underscore-escaped hexadecimal number.

Something like this ...
1) If an ODS-2 forbidden character replace with '__xx' (i.e. two underscores
plus the hex representation of the character).
2) Successive underscores have the first encoded as '__xx'.
3) If the final period occurs within the first 39 characters then it's not
encoded, it becomes the ODS-2 period.  If it occurs after character 39 then
it's left encoded and a (required ODS-2 but name-extraneous) period is inserted
at character 40. 
4) Case change is indicated with a leading '__$' (begins as lower).

Seems cumbersome, less versatile and less space-conservative than SRI!!
For the life of me, why didn't they use SRI encoding?

Returns the length of the VMS specification.
*/ 
 
int MapUrl_UrlToAdsVms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   BOOL  inDevice,
         loCase;
   int  ecnt, len;
   char  *cptr, *eptr, *pptr, *sptr, *zptr,
         *FinalPeriodPtr,
         *FinalSlashPtr;
   char  PathComponents [ODS_MAX_FILE_NAME_LENGTH];

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToAdsVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || *(unsigned short*)pptr == '/\0')
   {
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   /* any 'device' will not have any encoded characters in it's name */
   for (cptr = pptr; *cptr == '/'; cptr++);
   while (*cptr && *cptr != '/') cptr++;
   if (*cptr) inDevice = true; else inDevice = false;

   FinalPeriodPtr = FinalSlashPtr = NULL;
   ecnt = 0;
   /* leave an extra char elbow room for inserting a period - see below */
   zptr = (sptr = PathComponents) + sizeof(PathComponents)-1;
   eptr = sptr;

   /* each component becomes a null-terminated string in a character array */
   while (*pptr && sptr < zptr)
   {
      if (*pptr == '/') pptr++;
      FinalPeriodPtr = NULL;
      loCase = true;
      eptr = sptr;
      while (*pptr && *pptr != '/' && sptr < zptr)
      {
         if (isalnum (*pptr) || *pptr == '-' || *pptr == '$' ||
             *pptr == '*' || *pptr == '%' || inDevice)
         {
            if (loCase && isupper(*pptr))
            {
               loCase = false;
               *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '$';
               if (sptr < zptr) *sptr++ = toupper(*pptr++);
               continue;
            }
            if (!loCase && islower(*pptr))
            {
               loCase = true;
               *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '$';
               if (sptr < zptr) *sptr++ = toupper(*pptr++);
               continue;
            }
            *sptr++ = toupper(*pptr++);
            continue;
         }
         if (*pptr == '_' && *(unsigned short*)pptr != '__')
         {
            /* single underscore */
            *sptr++ = *pptr++;
            continue;
         }
         if (*pptr == '.') FinalPeriodPtr = sptr;
         *sptr++ = '_';
         if (sptr < zptr) *sptr++ = '_';
         if (sptr < zptr) *sptr++ = HexDigits[(*pptr & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[*pptr & 0x0f];
         pptr++;
      }
      if (sptr < zptr) *sptr++ = '\0';
      ecnt++;
      inDevice = false;
      if (*pptr == '/') FinalSlashPtr = sptr;
      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!UL !&Z\n", ecnt, eptr);
   }

   if (sptr >= zptr)
   {
      /* storage overflowed */
      if (WATCH_MODULE_DETAIL)
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }
   *sptr = '\0';

   if (FinalPeriodPtr && FinalPeriodPtr <= eptr + 39)
   {
      /* less than required ODS-2 period, replace with 'real' period */
      sptr = FinalPeriodPtr;
      *sptr++ = '.';
      cptr = sptr + 3;
      while (*cptr) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   if (sptr > eptr + 39)
   {
      /* greater than required ODS-2 period, insert an ODS-2 period */
      cptr = sptr++;
      while (cptr > eptr+39) *sptr-- = *cptr--;
      *sptr = *cptr;
      *cptr = '.';
      while (*sptr) sptr++;
   }

   len = MapUrl_ElementsToVms (PathComponents, ecnt, VmsPtr, SizeOfVmsPtr,
                               MapEllipsis);

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

/*****************************************************************************/
/*
Convert a URL-style specification into a RMS-style specification, example:
"/disk/dir1/dir2/file.tar.gz" into "disk:[dir1.dir2]file$27tar.gz" - with
PATHWORKS v4/5 character conversion.  This is much simpler than SRI.  If a
character  is not RMS legal (excluding the dollar which is the escape) then
encode it as a dollar-escaped hexadecimal number.

Returns the length of the VMS specification.
*/ 
 
int MapUrl_UrlToPwkVms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   BOOL  inDevice;
   int  ecnt, len;
   char  *cptr, *eptr, *pptr, *sptr, *zptr,
         *FinalPeriodPtr,
         *FinalSlashPtr;
   char  PathComponents [ODS_MAX_FILE_NAME_LENGTH];

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToPwkVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || *(unsigned short*)pptr == '/\0')
   {
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   /* any 'device' will not have any encoded characters in it's name */
   for (cptr = pptr; *cptr == '/'; cptr++);
   while (*cptr && *cptr != '/') cptr++;
   if (*cptr) inDevice = true; else inDevice = false;

   FinalPeriodPtr = FinalSlashPtr = NULL;
   ecnt = 0;
   zptr = (sptr = PathComponents) + sizeof(PathComponents);

   /* each component becomes a null-terminated string in a character array */
   while (*pptr && sptr < zptr)
   {
      if (*pptr == '/') pptr++;
      eptr = sptr;
      while (*pptr && *pptr != '/' && sptr < zptr)
      {
         if (isalnum (*pptr) || *pptr == '-' || *pptr == '_' ||
                                *pptr == '*' || *pptr == '%' || inDevice)
         {
            *sptr++ = *pptr++;
            continue;
         }
         if (*pptr == '.') FinalPeriodPtr = sptr;
         *sptr++ = '$';
         if (sptr < zptr) *sptr++ = HexDigits[(*pptr & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[*pptr & 0x0f];
         pptr++;
      }
      if (sptr < zptr) *sptr++ = '\0';
      ecnt++;
      inDevice = false;
      if (*pptr == '/') FinalSlashPtr = sptr;
      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!UL !&Z\n", ecnt, eptr);
   }

   if (sptr >= zptr)
   {
      /* storage overflowed */
      if (WATCH_MODULE_DETAIL)
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }
   *sptr = '\0';

   /* turn the final period back into one */
   if (FinalPeriodPtr)
   {
      sptr = FinalPeriodPtr;
      *sptr++ = '.';
      for (cptr = sptr + 2; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }

   len = MapUrl_ElementsToVms (PathComponents, ecnt, VmsPtr, SizeOfVmsPtr,
                               MapEllipsis);

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

/*****************************************************************************/
/*
Convert a URL-style specification into a RMS-style specification, example:
"/disk/dir1/dir2/file.tar.gz" into "disk:[dir1.dir2]file.tar$27gz" - with SRI
character conversion.  The SRI scheme is quite complex.  Refer to online
documentation associated with Process Software's MultiNet NFS server. 
Alphabetic '$' case flags reset on a per-path-component basis.

Returns the length of the VMS specification.
*/ 
 
int MapUrl_UrlToSriVms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis
)
{
   /* directory wildcard concessions; '%' should be "$5E" and '*' be "$5J" */
   static char  *SriConvTable [] = {
"$6A", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", "$4G",  /* 0x07 */
"$4H", "$4I", "$4J", "$4K", "$4L", "$4M", "$4N", "$4O",  /* 0x0f */
"$4P", "$4Q", "$4R", "$4S", "$4T", "$4U", "$4V", "$4W",  /* 0x17 */
"$4X", "$4Y", "$4Z", "$6B", "$6C", "$6D", "$6E", "$6F",  /* 0x1f */
"$7A", "$5A", "$5B", "$5C", "$$",  NULL,  "$5F", "$5G",  /* 0x27 */
"$5H", "$5I", NULL,  "$5K", "$5L", NULL,  "$5N", "$5O",  /* 0x2f */
NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x37 */
NULL,  NULL,  "$5Z", "$7B", "$7C", "$7D", "$7E", "$7F",  /* 0x3f */
"$8A", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x47 */
NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x4f */
NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x57 */
NULL,  NULL,  NULL,  "$8B", "$8C", "$8D", "$8E", NULL,   /* 0x5f */
"$9A", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x67 */
NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x6f */
NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,   /* 0x77 */
NULL,  NULL,  NULL,  "$9B", "$9C", "$9D", "$9E", "$9F",  /* 0x7f */
"$200", "$201", "$202", "$203", "$204", "$205", "$206", "$207", /* 0x87 */
"$210", "$211", "$212", "$213", "$214", "$215", "$216", "$217", /* 0x8f */
"$220", "$221", "$222", "$223", "$224", "$225", "$226", "$227", /* 0x97 */
"$230", "$231", "$232", "$233", "$234", "$235", "$236", "$237", /* 0x9f */
"$240", "$241", "$242", "$243", "$244", "$245", "$246", "$247", /* 0xa7 */
"$250", "$251", "$252", "$253", "$254", "$255", "$256", "$257", /* 0xaf */
"$260", "$261", "$262", "$263", "$264", "$265", "$266", "$267", /* 0xb7 */
"$270", "$271", "$272", "$273", "$274", "$275", "$276", "$277", /* 0xbf */
"$300", "$301", "$302", "$303", "$304", "$305", "$306", "$307", /* 0xc7 */
"$310", "$311", "$312", "$313", "$314", "$315", "$316", "$317", /* 0xcf */
"$320", "$321", "$322", "$323", "$324", "$325", "$326", "$327", /* 0xd7 */
"$330", "$331", "$332", "$333", "$334", "$335", "$336", "$337", /* 0xdf */
"$340", "$341", "$342", "$343", "$344", "$345", "$346", "$347", /* 0xe7 */
"$350", "$351", "$352", "$353", "$354", "$355", "$356", "$357", /* 0xef */
"$360", "$361", "$362", "$363", "$364", "$365", "$366", "$367", /* 0xf7 */
"$370", "$371", "$372", "$373", "$374", "$375", "$376", "$377", /* 0xff */
};

   BOOL  inDevice,
         loCase,
         typeHit;
   int  ecnt, len, pcnt;
   char  *cptr, *eptr, *pptr, *sptr, *zptr,
         *FinalSlashPtr;
   char  PathComponents [ODS_MAX_FILE_NAME_LENGTH];

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_UrlToSriVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || *(unsigned short*)pptr == '/\0')
   {
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   /* any 'device' will not have any encoded characters in it's name */
   for (cptr = pptr; *cptr == '/'; cptr++);
   while (*cptr && *cptr != '/') cptr++;
   if (*cptr) inDevice = true; else inDevice = false;

   ecnt = pcnt = 0;
   FinalSlashPtr = NULL;
   typeHit = false;
   zptr = (sptr = PathComponents) + sizeof(PathComponents);

   /* each component becomes a null-terminated string in a character array */
   while (*pptr && sptr < zptr)
   {
      if (*pptr == '/') pptr++;
      loCase = true;
      eptr = sptr;
      while (*pptr && *pptr != '/' && sptr < zptr)
      {
         if (*pptr == '.' && !typeHit)
         {
            /* is it the final period in the specification? */
            for (cptr = pptr; *cptr && *cptr != '/'; cptr++);
            if (!*cptr)
            {
               /* yes! */
               for (cptr = pptr; *cptr && *cptr != '.'; cptr++);
               if (*cptr)
               {
                  typeHit = true;
                  loCase = true;
               }
            }
         }
         if (!SriConvTable[*(unsigned char*)pptr] || inDevice)
         {
            /* unencoded alpha-numeric (or other legal) character */
            if (loCase && isupper(*pptr))
            {
               loCase = false;
               *sptr++ = '$';
               if (sptr < zptr) *sptr++ = toupper(*pptr++);
               continue;
            }
            if (!loCase && islower(*pptr))
            {
               loCase = true;
               *sptr++ = '$';
               if (sptr < zptr) *sptr++ = toupper(*pptr++);
               continue;
            }
            *sptr++ = toupper(*pptr++);
            continue;
         }
         /* dollar-encoded character */
         cptr = SriConvTable[*(unsigned char*)pptr];
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         pptr++;
      }
      if (sptr < zptr) *sptr++ = '\0';
      ecnt++;
      inDevice = false;
      if (*pptr == '/') FinalSlashPtr = sptr;
      if (WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!UL !&Z\n", ecnt, eptr);
   }

   if (sptr >= zptr)
   {
      /* storage overflowed */
      if (WATCH_MODULE_DETAIL)
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }
   *sptr = '\0';

   /* turn the first (if any) '$5N' of the file name back into a period */
   if (!(sptr = FinalSlashPtr)) sptr = PathComponents;
   while (*sptr)
   {
      if (*sptr++ != '$') continue;
      if (*(unsigned short*)sptr != '5N') continue;
      sptr[-1] = '.';
      for (cptr = sptr + 2; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
      break;
   }

   len = MapUrl_ElementsToVms (PathComponents, ecnt, VmsPtr, SizeOfVmsPtr,
                               MapEllipsis);

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, VmsPtr);

   return (len);
}

/*****************************************************************************/
/*
'ElementsPtr' contains a series of null-delimited strings (the '/' path
components) terminated by a empty string.  'ElementsCount' the number of
'/'-delimited parts of the string, including the "file" element - even if
empty.  Turn this into a VMS file specification in storage pointed to by
'VmsPtr'.  For empty elements (consecutive '/') substitute a forbidden
character that will render the specification useless.  For parent directory
syntax do the same thing.

Returns the length of the VMS specification.
*/ 
 
int MapUrl_ElementsToVms
(
char *ElementsPtr,
int ElementsCount,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis
)
{
/* character inserted into a file specification to render it useless */
#define BSCHAR '|'

   BOOL  DECnet;
   int  ecnt;
   char  *cptr, *sptr, *tptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ElementsToVms() !UL !&B", ElementsCount, MapEllipsis);

   /* check the first element for DECnet syntax */
   DECnet = false;
   for (cptr = ElementsPtr; *cptr; cptr++)
   {
      if (*(unsigned short*)cptr != '::') continue;
      DECnet = true;
      if (WATCH_MODULE(WATCH_MOD_MAPURL))
         WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "DECNET");
      break;
   }

   cptr = ElementsPtr;
   zptr = (sptr = VmsPtr) + SizeOfVmsPtr;

   if (DECnet) ElementsCount--;
   if (ElementsCount == 1)
   {
      /********************/
      /* single component */
      /********************/

      if (*cptr)
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      else
         if (sptr < zptr) *sptr++ = BSCHAR;
   }
   else
   if (ElementsCount == 2)
   {
      /***********************/
      /* top-level directory */
      /***********************/

      if (DECnet)
      {
         /* copy first component as-is */
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         cptr++;
      }
      /* start with the first component, the "device" */
      if (*cptr)
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      else
         if (sptr < zptr) *sptr++ = BSCHAR;
      cptr++;
      /* append a Master File Directory component */
      for (tptr = ":[000000]"; *tptr && sptr < zptr; *sptr++ = *tptr++);
      /* add the second component, the "file" (which can be empty) */
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   else
   if (ElementsCount >= 3)
   {
      /****************/
      /* subdirectory */
      /****************/

      if (DECnet)
      {
         /* copy first component as-is */
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         cptr++;
      }
      /* start with the first component, the "device" */
      if (*cptr)
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      else
         if (sptr < zptr) *sptr++ = BSCHAR;
      cptr++;
      if (sptr < zptr) *sptr++ = ':';
      if (sptr < zptr) *sptr++ = '[';
      if (cptr[0] == '.' && cptr[1] == '.' && cptr[2] == '.' && MapEllipsis)
         for (tptr = "000000"; *tptr && sptr < zptr; *sptr++ = *tptr++);
      for (ecnt = 2; ecnt < ElementsCount; ecnt++)
      {
         /* add the next component, a "directory" */
         if (!*cptr && ecnt < ElementsCount)
         {
            /* empty element (on all but any file component) */
            if (sptr < zptr) *sptr++ = BSCHAR;
         }
         else
         if (cptr[0] == '-')
         {
            /* check for parent directory syntax, squash if it is! */
            for (tptr = cptr; *tptr == '-' || *tptr == '.'; tptr++);
            if (*tptr)
            {
               /* NOT parent directory syntax */
               if (*(sptr-1) != '[' && sptr < zptr) *sptr++ = '.';
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               /* parent directory syntax */
               while (*cptr && sptr < zptr)
               {
                  *sptr++ = BSCHAR;
                  cptr++;
               }
            } 
         }
         else
         {
            if (*(sptr-1) != '[' && *cptr != '.' && sptr < zptr) *sptr++ = '.';
            while (*cptr && sptr < zptr)
            {
               if (*cptr != '.')
               {
                  *sptr++ = *cptr++;
                  continue;
               }
               if (*(unsigned short*)(cptr+1) != '..' || MapEllipsis)
               {
                  *sptr++ = *cptr++;
                  continue;
               }
               /* excise the ellipsis wildcard */
               cptr += 3;
               if (sptr < zptr) *sptr++ = BSCHAR;
               if (sptr < zptr) *sptr++ = BSCHAR;
               if (sptr < zptr) *sptr++ = BSCHAR;
            }
         }
         cptr++;
      }
      if (*(sptr-1) == '[')
      {
         /* the entire directory structure has been excised! */
         for (tptr = "000000"; *tptr && sptr < zptr; *sptr++ = *tptr++);
      }
      if (sptr < zptr) *sptr++ = ']';
      /* add the last component, the "file" (which can be empty) */
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   if (sptr < zptr) *sptr = '\0';

   if (sptr >= zptr)
   {
      /* storage overflowed */
      *(unsigned short*)VmsPtr = '\0\0';
      return (0);
   }

   if (WATCH_MODULE_DETAIL)
      WatchDataFormatted ("!&B !UL !&Z\n", DECnet, sptr-VmsPtr, VmsPtr);

   return (sptr - VmsPtr);

#undef BSCHAR
}

/*****************************************************************************/
/*
Convert a VMS full file specification into a URL-style specification, example:
"DEVICE:[DIR1.DIR2]FILE.TXT" into "/device/dir1/dir2/file.txt", or just a file
name and type portion (ensuring it's correctly converted and escaped).  The URL
has URL forbidden characters URL-encoded.

Can process ODS-2, ODS-5 (EFS), SRI (MultiNet NFS), PATHWORKS (v4/5) and
Advanced Server (PATHWORKS V6) / Samba encodings.

Returns the length of the generated URL-style path.
*/ 
 
int MapUrl_VmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd,
int PathOds
)
{
   int  len;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_VmsToUrl() !UL !&B !&Z !UL",
                 PathOds, AbsorbMfd, VmsPtr, SizeOfPathPtr);

   switch (PathOds)
   {
      case 0 :
      case MAPURL_PATH_ODS_2 :
         len = MapUrl_Ods2VmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, AbsorbMfd);
         break;

#ifdef ODS_EXTENDED
      case MAPURL_PATH_ODS_5 :
         len = MapUrl_Ods5VmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, AbsorbMfd);
         break;
#endif /* ODS_EXTENDED */

      case MAPURL_PATH_ODS_ADS :
      case MAPURL_PATH_ODS_SMB :
         len = MapUrl_AdsVmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, AbsorbMfd);
         break;

      case MAPURL_PATH_ODS_PWK :
         len = MapUrl_PwkVmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, AbsorbMfd);
         break;

      case MAPURL_PATH_ODS_SRI :
         len = MapUrl_SriVmsToUrl (PathPtr, VmsPtr, SizeOfPathPtr, AbsorbMfd);
         break;

      default :
         memcpy (PathPtr, "/sanity/check/mapurl_urltovms", len=30);
   }

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!UL !&Z\n", len, PathPtr);

   return (len);
}

/*****************************************************************************/
/*
Convert a VMS full file specification into a URL-style specification, example:
"DEVICE:[DIR1.DIR2]FILE.TXT" into "/device/dir1/dir2/file.txt", or just a file
name and type portion (ensuring it's correctly converted and escaped).  Expects
file specification to be ODS-2 compliant and forces the URL to all lower-case.

Returns the length of the generated URL-style path.
*/ 
 
int MapUrl_Ods2VmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_Ods2VmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && *(unsigned short*)cptr != ':[' &&
          *cptr != ']' && *cptr != '/') cptr++;

   if (!*cptr || *cptr == '/')
   {
      /* no VMS device/directory components detected */
      cptr = VmsPtr;
   }
   else
   {
      /* copy device/directory components */
      cptr = VmsPtr;
      if (sptr < zptr) *sptr++ = '/';
      while (*cptr && sptr < zptr)
      {
         if (*(unsigned short*)cptr == '::')
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (*(unsigned short*)cptr == ':[')
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && strncmp (cptr, "000000", 6) == 0)
               cptr += 6;
            else
               *sptr++ = '/';
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (*cptr == '.' && *(unsigned short*)(cptr+1) == '..')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
               cptr++;
            continue;
         }

         if (*cptr == ']')
         {
            *sptr++ = '/';
            cptr++;
            break;
         }

         *sptr++ = tolower(*cptr++);
      }
   }

   /* copy the file component */
   while (*cptr && sptr < zptr) *sptr++ = tolower(*cptr++);
   if (sptr < zptr) *sptr++ = '\0';
   if (sptr < zptr) *sptr++ = '\0';

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!&Z\n", PathPtr);

   if (sptr < zptr) return ((sptr - PathPtr) - 2);

   /* storage overflowed */
   if (WATCH_MODULE_DETAIL)
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
   *(unsigned short*)PathPtr = '\0\0';
   return (0);
}

/*****************************************************************************/
/*
Convert a VMS full file ODS-5 compliant specification into a URL-style
specification, example: "DEVICE:[dir1.DIR2]a^.file.txt" into
"/device/dir1/DIR2/a file.txt", or a URL-style string that contains ODS-5
escaped   characters, example "/device/device/dir1/DIR2/a^.file.txt" into
"/device/dir1/DIR2/a file.txt" (ensuring it's correctly converted and escaped). 
Will decode literal ^-escaped and hexadecimal ^-escaped characters and leaves
the case as-is.

Returns the length of the generated URL-style path.
*/ 
 
#ifdef ODS_EXTENDED

int MapUrl_Ods5VmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   char  ch;
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_Ods5VmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && *(unsigned short*)cptr != ':[' &&
          *cptr != ']' && *cptr != '/')
   {
      if (cptr[0] == '^' && isxdigit(cptr[1]) && isxdigit(cptr[2]))
         cptr += 3;
      else
      if (cptr[0] == '^' && cptr[1] == 'U' &&
          isxdigit(cptr[2]) && isxdigit(cptr[3]) &&
          isxdigit(cptr[4]) && isxdigit(cptr[5]))
         cptr += 6;
      else
      if (cptr[0] == '^')
         cptr += 2;
      else
         cptr++;
   }

   if (!*cptr || *cptr == '/')
   {
      /* no VMS device/directory components detected */
      cptr = VmsPtr;
   }
   else
   {
      /* copy device/directory components */
      cptr = VmsPtr;
      if (sptr < zptr) *sptr++ = '/';
      while (*cptr && sptr < zptr)
      {
         if (*(unsigned short*)cptr == '::')
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (*(unsigned short*)cptr == ':[')
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && strncmp (cptr, "000000", 6) == 0)
               cptr += 6;
            else
               *sptr++ = '/';
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (*cptr == '.' && *(unsigned short*)(cptr+1) == '..')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
               cptr++;
            continue;
         }

         if (*cptr == ']')
         {
            *sptr++ = '/';
            cptr++;
            break;
         }

         if (*cptr == '^')
         {
            /* ODS-5 extended specification escape character */
            cptr++;
            switch (*cptr)
            {
               case '!' : case '#' : case '&' : case '\'' : case '`' :
               case '(' : case ')' : case '+' : case '@'  : case '{' :
               case '}' : case '.' : case ',' : case ';'  : case '[' :
               case ']' : case '%' : case '^' : case '='  : case ' ' :
                  *sptr++ = *cptr++;
                  break;
               case '_' :
                  *sptr++ = ' ';
                  cptr++;
                  break;
               default :
                  if (!isxdigit (*cptr))
                  {
                     *sptr++ = *cptr++;
                     break;
                  }
                  ch = 0;
                  if (*cptr >= '0' && *cptr <= '9')
                     ch = (*cptr - (int)'0') << 4;
                  else
                  if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
                     ch = (tolower(*cptr) - (int)'a' + 10) << 4;
                  if (*cptr) cptr++;
                  if (*cptr >= '0' && *cptr <= '9')
                     ch += (*cptr - (int)'0');
                  else
                  if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
                     ch += (tolower(*cptr) - (int)'a' + 10);
                  if (*cptr) cptr++;
                  *sptr++ = ch;
            }
   
            continue;
         }

         if (sptr < zptr) *sptr++ = *cptr++;
      }
   }

   /* copy the file component */
   while (*cptr && sptr < zptr)
   {
      if (*cptr != '^')
      {
         *sptr++ = *cptr++;
         continue;
      }

      /* ODS-5 extended specification escape character */
      cptr++;
      switch (*cptr)
      {
         case '!' : case '#' : case '&' : case '\'' : case '`' :
         case '(' : case ')' : case '+' : case '@'  : case '{' :
         case '}' : case '.' : case ',' : case ';'  : case '[' :
         case ']' : case '%' : case '^' : case '='  : case ' ' :
            *sptr++ = *cptr++;
            break;
         case '_' :
            *sptr++ = ' ';
            cptr++;
            break;
         default :
            if (!isxdigit (*cptr))
            {
               *sptr++ = *cptr++;
               break;
            }
            ch = 0;
            if (*cptr >= '0' && *cptr <= '9')
               ch = (*cptr - (int)'0') << 4;
            else
            if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
               ch = (tolower(*cptr) - (int)'a' + 10) << 4;
            if (*cptr) cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               ch += (*cptr - (int)'0');
            else
            if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
               ch += (tolower(*cptr) - (int)'a' + 10);
            if (*cptr) cptr++;
            *sptr++ = ch;
      }
   }
   if (sptr < zptr) *sptr++ = '\0';
   if (sptr < zptr) *sptr++ = '\0';

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!&Z\n", PathPtr);

   if (sptr < zptr) return ((sptr - PathPtr) - 2);

   /* storage overflowed */
   if (WATCH_MODULE_DETAIL)
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
   *(unsigned short*)PathPtr = '\0\0';
   return (0);
}

#endif /* ODS_EXTENDED */

/*****************************************************************************/
/*
Convert a VMS full file specification into a URL-style specification, example:
"DEVICE:[DIR1.DIR2]__$A__2A__$FILE.TXT" into "/device/dir1/dir2/A file.txt", or
a  URL-style specification, example "/device/dir1/dir2/A__20file.txt" into
"/device/dir1/dir2/A file.txt" (ensuring it's correctly converted and escaped). 
Expects file specification to be Advanced Server compliant.  Alphabetic '$'
case flags reset on a per-path-component basis.

Returns the length of the generated URL-style path.
*/ 
 
int MapUrl_AdsVmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   BOOL  isDevice,
         loCase;
   char  ch;
   char  *cptr, *eptr, *sptr, *zptr,
         *NamePtr,
         *PeriodPtr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_AdsVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && *(unsigned short*)cptr != ':[' &&
          *cptr != ']' && *cptr != '/') cptr++;

   if (!*cptr || *cptr == '/')
   {
      /* no VMS device/directory components detected */
      cptr = VmsPtr;
   }
   else
   {
      /* copy device/directory components */
      isDevice = loCase = true;
      cptr = VmsPtr;
      if (sptr < zptr) *sptr++ = '/';
      while (*cptr && sptr < zptr)
      {
         if (*(unsigned short*)cptr == '::')
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            loCase = true;
            continue;
         }

         if (*(unsigned short*)cptr == ':[')
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && strncmp (cptr, "000000", 6) == 0)
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            loCase = true;
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (*cptr == '.' && *(unsigned short*)(cptr+1) == '..')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
               cptr++;
            loCase = true;
            continue;
         }

         if (*cptr == ']')
         {
            cptr++;
            *sptr++ = '/';
            break;
         }

         /* don't need to be concerned with periods in directories */
         if (*(unsigned short*)cptr != '__' || isDevice)
         {
            if (loCase)
               *sptr++ = tolower(*cptr++);
            else
               *sptr++ = toupper(*cptr++);
            continue;
         }

         /* convert from Advanced Server encoding */
         cptr++;
         cptr++;
         if (*cptr == '$')
         {
            cptr++;
            loCase = !loCase;
            continue;
         }
         ch = 0;
         if (*cptr >= '0' && *cptr <= '9')
            ch = (*cptr - (int)'0') << 4;
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            ch = (tolower(*cptr) - (int)'a' + 10) << 4;
         else
            ch = '?';
         if (*cptr) cptr++;   
         if (*cptr >= '0' && *cptr <= '9')
            ch += (*cptr - (int)'0');
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            ch += (tolower(*cptr) - (int)'a' + 10);
         else
            ch = '?';
         if (*cptr) cptr++;
         if (loCase)
            *sptr++ = tolower(ch);
         else
            *sptr++ = toupper(ch);
      }
   }

   /* copy the file component */
   PeriodPtr = NULL;
   NamePtr = cptr;
   loCase = true;
   while (*cptr && sptr < zptr)
   {
      if (*cptr != '_')
      {
         if (*cptr != '.')
         {
            if (loCase)
               *sptr++ = tolower(*cptr++);
            else
               *sptr++ = toupper(*cptr++);
            continue;
         }
         PeriodPtr = sptr;
         *sptr++ = *cptr++;
         continue;
      }

      /* need to cater for possible '__xx', '_._xx', '__.xx' */
      if (*(unsigned short*)cptr != '__' && *(unsigned short*)cptr != '_.')
      {
         /* an isolated underscore */
         if (sptr < zptr) *sptr++ = *cptr++;
         continue;
      }
      cptr++;
      if (*(unsigned short*)cptr == '._')
      {
         if (cptr != NamePtr + 39)
         {
            /* not an ODS-2 required period */
            if (sptr < zptr) *sptr++ = *cptr++;
            continue;
         }
         /* step over the ODS-2 required period */
         cptr++;
      }
      else
      if (*(unsigned short*)cptr == '_.')
      {
         /* after two underscores can only be an ODS-2 required period */
         cptr++;
      }
      cptr++;

      /* convert from Advanced Server encoding */
      if (*cptr == '$')
      {
         cptr++;
         loCase = !loCase;
         continue;
      }
      ch = 0;
      if (*cptr >= '0' && *cptr <= '9')
         ch = (*cptr - (int)'0') << 4;
      else
      if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
         ch = (tolower(*cptr) - (int)'a' + 10) << 4;
      else
         ch = '?';
      if (*cptr) cptr++;   
      /* if it's an ODS-2 required period just ignore it (i.e. '__x.x') */
      if (*cptr == '.') cptr++;
      if (*cptr >= '0' && *cptr <= '9')
         ch += (*cptr - (int)'0');
      else
      if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
         ch += (tolower(*cptr) - (int)'a' + 10);
      else
         ch = '?';
      if (*cptr) cptr++;
      if (loCase)
         *sptr++ = tolower(ch);
      else
         *sptr++ = toupper(ch);
   }

   if (sptr < zptr) *sptr++ = '\0';
   if (sptr < zptr) *sptr++ = '\0';

   if (PeriodPtr)
   {
      /* remove the ODS-2 required period if there is a trailing period */
      for (cptr = PeriodPtr+1; *cptr && *cptr != '.'; cptr++);
      if (*cptr)
      {
         for (cptr = (sptr = PeriodPtr) + 1; *cptr; *sptr++ = *cptr++);
         if (sptr < zptr) *sptr++ = '\0';
         if (sptr < zptr) *sptr++ = '\0';
      }
   }

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!&Z\n", PathPtr);

   if (sptr < zptr) return ((sptr - PathPtr) - 2);

   /* storage overflowed */
   if (WATCH_MODULE_DETAIL)
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
   *(unsigned short*)PathPtr = '\0\0';
   return (0);
}

/*****************************************************************************/
/*
The Advanced Server (PATHWORKS 6) / Samba encoding maps the period as described
in the description of MapUrl_UrlToAdsVms().  This function decodes the file
anem plus type then find the last period in it and uses that following as the
file type.
*/ 
 
char* MapUrl_AdsFileType (char *FileType)

{
   static char  AdsFileType [96];

   int  Length;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_AdsFileType() !&Z", FileType);

   Length = MapUrl_AdsVmsToUrl (AdsFileType, FileType,
                                sizeof(AdsFileType), true);
   cptr = AdsFileType + Length;
   while (cptr > AdsFileType && *cptr != '.') cptr--;
   if (WATCH_MODULE(WATCH_MOD_MAPURL)) WatchDataFormatted ("!&Z\n", cptr);

   return (cptr);
}

/*****************************************************************************/
/*
Convert a VMS full file specification into a URL-style specification, example:
"DEVICE:[DIR1.DIR2]A$2AFILE.TXT" into "/device/dir1/dir2/a file.txt", or a
URL-style specification, example "/device/dir1/dir2/a$20file.txt" into
"/device/dir1/dir2/a file.txt" (ensuring it's correctly converted and escaped). 
Expects file specification to be PATHWORKS v4/5 compliant and forces the URL to
all  lower-case.

Returns the length of the generated URL-style path.
*/ 
 
int MapUrl_PwkVmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   BOOL  isDevice;
   char  ch;
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_PwkVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && *(unsigned short*)cptr != ':[' &&
          *cptr != ']' && *cptr != '/') cptr++;

   if (!*cptr || *cptr == '/')
   {
      /* no VMS device/directory components detected */
      cptr = VmsPtr;
   }
   else
   {
      /* copy device/directory components */
      isDevice = true;
      cptr = VmsPtr;
      if (sptr < zptr) *sptr++ = '/';
      while (*cptr && sptr < zptr)
      {
         if (*(unsigned short*)cptr == '::')
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (*(unsigned short*)cptr == ':[')
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && strncmp (cptr, "000000", 6) == 0)
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (*cptr == '.' && *(unsigned short*)(cptr+1) == '..')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
               cptr++;
            continue;
         }

         if (*cptr == ']')
         {
            cptr++;
            *sptr++ = '/';
            break;
         }

         if (*cptr != '$' || isDevice)
         {
            *sptr++ = tolower(*cptr++);
            continue;
         }

         /* convert from PATHWORKS encoding */
         cptr++;
         ch = 0;
         if (*cptr >= '0' && *cptr <= '9')
            ch = (*cptr - (int)'0') << 4;
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            ch = (tolower(*cptr) - (int)'a' + 10) << 4;
         else
            ch = '?';
         if (*cptr) cptr++;   
         if (*cptr >= '0' && *cptr <= '9')
            ch += (*cptr - (int)'0');
         else
         if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
            ch += (tolower(*cptr) - (int)'a' + 10);
         else
            ch = '?';
         if (*cptr) cptr++;
         *sptr++ = ch;
      }
   }

   /* copy the file component */
   while (*cptr && sptr < zptr)
   {
      if (*cptr != '$')
      {
         *sptr++ = tolower(*cptr++);
         continue;
      }

      /* convert from PATHWORKS encoding */
      cptr++;
      ch = 0;
      if (*cptr >= '0' && *cptr <= '9')
         ch = (*cptr - (int)'0') << 4;
      else
      if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
         ch = (tolower(*cptr) - (int)'a' + 10) << 4;
      else
         ch = '?';
      if (*cptr) cptr++;   
      if (*cptr >= '0' && *cptr <= '9')
         ch += (*cptr - (int)'0');
      else
      if (tolower(*cptr) >= 'a' && tolower(*cptr) <= 'f')
         ch += (tolower(*cptr) - (int)'a' + 10);
      else
         ch = '?';
      if (*cptr) cptr++;
      *sptr++ = ch;
   }
   if (sptr < zptr) *sptr++ = '\0';
   if (sptr < zptr) *sptr++ = '\0';

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!&Z\n", PathPtr);

   if (sptr < zptr) return ((sptr - PathPtr) - 2);

   /* storage overflowed */
   if (WATCH_MODULE_DETAIL)
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
   *(unsigned short*)PathPtr = '\0\0';
   return (0);
}

/*****************************************************************************/
/*
Convert a VMS full file SRI (e.g. MultiNet NFS) compliant specification into a
URL-style specification, example:  "DEVICE:[DIR1.DIR2]A$5NFILE.TXT" into
"/device/dir1/dir2/a file.txt", or a URL-style specification, example
"/device/dir1/dir2/a$5nfile.txt" into "/device/dir1/dir2/a file.txt" (ensuring
it's correctly converted and escaped). Expects file specification to be SRI
compliant.  The SRI scheme is quite complex.  Refer to online documentation
associated with Process Software's MultiNet NFS server.  Alphabetic '$' case
flags reset on a per-path-component basis.

Returns the length of the generated URL-style path.
*/ 
 
int MapUrl_SriVmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   BOOL  isDevice,
         loCase;
   char  ch;
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_SriVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && *(unsigned short*)cptr != ':[' &&
          *cptr != ']' && *cptr != '/') cptr++;

   if (!*cptr || *cptr == '/')
   {
      /* no VMS device/directory components detected */
      cptr = VmsPtr;
   }
   else
   {
      /* copy device/directory components */
      isDevice = loCase = true;
      cptr = VmsPtr;
      if (sptr < zptr) *sptr++ = '/';
      while (*cptr && sptr < zptr)
      {
         if (*(unsigned short*)cptr == '::')
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            loCase = true;
            continue;
         }

         if (*(unsigned short*)cptr == ':[')
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && strncmp (cptr, "000000", 6) == 0)
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            loCase = true;
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (*cptr == '.' && *(unsigned short*)(cptr+1) == '..')
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
               cptr++;
            loCase = true;
            continue;
         }

         if (*cptr == ']')
         {
            *sptr++ = '/';
            cptr++;
            break;
         }

         if (isDevice)
            *sptr++ = tolower(*cptr++);
         else
         if (*cptr == '$')
            *sptr++ = MapUrl_SriDecode (&cptr, &loCase);
         else
         if (loCase)
            *sptr++ = tolower(*cptr++);
         else
            *sptr++ = toupper(*cptr++);
      }
   }

   /* copy the file component */
   loCase = true;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '.') loCase = true;
      if (*cptr == '$')
         *sptr++ = MapUrl_SriDecode (&cptr, &loCase);
      else
      if (loCase)
         *sptr++ = tolower(*cptr++);
      else
         *sptr++ = toupper(*cptr++);
   }
   if (sptr < zptr) *sptr++ = '\0';
   if (sptr < zptr) *sptr++ = '\0';

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchDataFormatted ("!&Z\n", PathPtr);

   if (sptr < zptr) return ((sptr - PathPtr) - 2);

   /* storage overflowed */
   if (WATCH_MODULE_DETAIL)
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "OVERFLOW");
   *(unsigned short*)PathPtr = '\0\0';
   return (0);
}

/*****************************************************************************/
/*
Return a single character that has been decoded from the string pointed to by
the storage supplied by 'stringPtrPtr'.  If the decode detects an error in the
encoding then it returns a question mark (crude, but hey it should be obvious
to the end-user!)  The SRI scheme is quite complex.   Refer to online 
documentation associated with Process Software's MultiNet NFS server.
*/ 
 
char MapUrl_SriDecode
(
char **stringPtrPtr,
BOOL *loCasePtr
)
{
   static char  SriDecodeTable [6][26] =
{
     /* table $4A-Z */
   { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,  /* A-H */
     0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,  /* I-P */
     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,  /* Q-X */
     0x19, 0x1a },                                    /* Y-Z */

     /* table $5A-Z */
   { 0x21, 0x22, 0x23, '?',  0x25, 0x26, 0x27, '?', 
     0x29, 0x2a, 0x2b, 0x2c, '?',  0x2e, 0x2f, '?', 
     '?',  '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  0x3a },

     /* table $6A-Z */
   { 0x00, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, '?',  '?', 
     0x29, '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?' },

     /* table $7A-Z */
   { 0x20, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, '?',  '?', 
     0x29, '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?' },

     /* table $8A-Z */
   { 0x40, 0x5b, 0x5c, 0x5d, 0x5e, '?',  '?',  '?', 
     0x29, '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?' },

     /* table $9A-Z */
   { 0x60, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, '?',  '?', 
     0x29, '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?',  '?',  '?',  '?',  '?',  '?',  '?', 
     '?',  '?' },
};

   char  ch, idx;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_SriDecode() !&B !4AZ", *loCasePtr, *stringPtrPtr);

   cptr = *stringPtrPtr;
   if (*cptr == '$')
   {
      cptr++;
      if (*cptr == '2' || *cptr == '3')
      {
         ch = (*cptr++ - '0') << 6;
         if (*cptr >= '0' && *cptr <= '7')
         {
            ch |= (*cptr++ - '0') << 3;
            if (*cptr >= '0' && *cptr <= '7')
               ch |= (*cptr++ -'0');
            else
            {
               ch = '?';
               cptr++;
            }
         }
         else
         {
            ch = '?';
            cptr++;
         }
      }
      else
      if (*cptr >= '4' && *cptr <= '9')
      {
         idx = *cptr++ - '4';
         if (*cptr >= 'A' && *cptr <= 'Z')
            ch = SriDecodeTable[idx][*(unsigned char*)cptr-'A'];
         else
            ch = '?';
         cptr++;
      }
      else
      if (*cptr >= 'A' && *cptr <= 'Z')
      {
         *loCasePtr = !*loCasePtr;
         if (*loCasePtr)
            ch = tolower(*cptr++);
         else
            ch = toupper(*cptr++);
      }
      else
      if (*cptr == '$')
         ch = *cptr++;
      else
      {
         ch = '?';
         cptr++;
      }
   }
   else
   if (*loCasePtr)
      ch = tolower(*cptr++);
   else
      ch = toupper(*cptr++);

   if (WATCH_MODULE_DETAIL)
      WatchDataFormatted ("{!UL}!-!#AZ 0x!2XL !-!&C\n",
                          cptr - *stringPtrPtr, *stringPtrPtr, ch);

   *stringPtrPtr = cptr;

   return (ch);
}

/*****************************************************************************/
/*
The SRI encoding maps the first period in any multi-period file type as a
period and any thereafter as '$5N' sequences.  Find any final '$5N' sequence
and map this into a "real" file type returning a pointer to this static
storage.  If there is no '$5N' in the string then just return a pointer to the
original string.
*/ 
 
char* MapUrl_SriFileType (char *FileType)

{
   static char  SriFileType [96];

   int  Length;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_SriFileType() !&Z", FileType);

   Length = MapUrl_SriVmsToUrl (SriFileType, FileType,
                                sizeof(SriFileType), true);
   cptr = SriFileType + Length;
   while (cptr > SriFileType && *cptr != '.') cptr--;
   if (WATCH_MODULE(WATCH_MOD_MAPURL)) WatchDataFormatted ("!&Z\n", cptr);

   return (cptr);
}

/*****************************************************************************/
/*
Given the current document's URL path and the specified document's URL path, 
generate a resultant URL path based on whether the specified document's path 
is absolute or relative the the current document's path.  Insufficient space to
contain the result path will not crash the function and the result will be set
to an empty string.
*/ 

int MapUrl_VirtualPath
(
char *CurrentPath,
char *DocumentPath,
char *ResultPath,
int SizeOfResultPath
)
{
   char  *cptr, *dptr, *sptr, *tptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_VirtualPath() !&Z !&Z !UL",
                 CurrentPath, DocumentPath, SizeOfResultPath);

   if (*(dptr = DocumentPath) == '/' || strstr (DocumentPath, "//")) 
   {
      /*****************/
      /* absolute path */
      /*****************/

      strcpy (ResultPath, dptr);
      zptr = (sptr = ResultPath) + SizeOfResultPath;
      while (*dptr && sptr < zptr) *sptr++ = *dptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
      return (sptr - ResultPath);
   }

   /* step over any "relative to this directory" syntax ("./") */
   if (dptr[0] == '.' && dptr[1] == '/') dptr += 2;
   if (*dptr != '.')
   {
      /*****************/
      /* inferior path */
      /*****************/

      zptr = (sptr = tptr = ResultPath) + SizeOfResultPath;
      cptr = CurrentPath;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '/') tptr = sptr+1;
         *sptr++ = *cptr++;
      }
      sptr = tptr;
      while (*dptr && sptr < zptr) *sptr++ = *dptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
      return (sptr - ResultPath);
   }

   /*****************/
   /* relative path */
   /*****************/

   zptr = (sptr = tptr = ResultPath) + SizeOfResultPath;
   cptr = CurrentPath;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '/') tptr = sptr;
      *sptr++ = *cptr++;
   }
   sptr = tptr;
   /* loop, stepping back one level for each "../" in the document path */
   while (dptr[0] == '.' && dptr[1] == '.' && dptr[2] == '/')
   {
      dptr += 3;
      if (sptr > ResultPath) sptr--;
      while (sptr > ResultPath && *sptr != '/') sptr--;
   }
   if (sptr > ResultPath && sptr < zptr)
      sptr++;
   else
   if (sptr < zptr)
      *sptr++ = '/';
   while (*dptr && sptr < zptr) *sptr++ = *dptr++;
   if (sptr >= zptr) return (-1);
   *sptr = '\0';
   return (sptr - ResultPath);
}

/*****************************************************************************/
/*
A server administration report on the server's mapping rules. This function
just wraps the reporting function, loading a temporary database if necessary
for reporting from the rule file.
*/

MapUrl_Report
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
BOOL UseServerDatabase,
char *VirtualService
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_Report() !&X !UL !&Z",
                 NextTaskFunction, UseServerDatabase, VirtualService);

   if (UseServerDatabase)
   {
      /* use mapping rules already loaded into the server */
      MapUrl_ReportNow (rqptr, true, MetaGlobalMappingPtr, VirtualService);
   }
   else
   {
      /* load temporary set of rules from mapping file */
      status = MapUrl_ConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         MapUrl_ReportNow (rqptr, false, mcptr, VirtualService);
      MetaConUnload (&mcptr, &MapUrl_ConfigUnloadLineData);
   }

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Return a report on the HTTPd server's mapping rules ... now!
*/

MapUrl_ReportNow
(
REQUEST_STRUCT *rqptr,
BOOL UseServerDatabase,
META_CONFIG *mcptr,
char *VirtualService
)
{
   static char  BeginRules [] =
"<P><H3><U>Mapping Rules</U></H3>\n";

   static char  BeginTable [] =
"<P><TABLE CELLPADDING=2 CELLSPACING=0 BORDER=0>\n\
<TR><TD><PRE>";

   static char  RuleFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>!AZ</B>  !&;AZ!&@!&@!&@!&?</STRIKE>\r\r\n";

   static char  NonRuleFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>!#AZ</B>  !&;AZ!&?</STRIKE>\r\r\n";
   static char  ProblemFao [] = "<B>!3ZL</B>  !#* <STRIKE>!&;AZ</STRIKE>\n";
   static char  RuleVirtualFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>[[!&;AZ]]</B>!&?</STRIKE>\r\r\n";
   static char  RuleImplicitVirtualFao [] =
"<B>000</B>  !#* <B>[[*:*]]</B>\n";

   static char  UserMappingFao [] =
"!AZ\
</PRE>\n\
</TD></TR>\n\
</TABLE>\n\
<P><H3><U>User Mapping Cache</U></H3>\n\
<P><TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR>\
<TH></TH>\
<TH ALIGN=left><U>Name</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Path</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>ODS</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Hits</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Last</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Reuse</U></TH>\
</TR>\n\
<TR HEIGHT=5></TR>\n";

   static char  EntryFao [] =
"<TR>\
<TH ALIGN=right><B>!3ZL</B>&nbsp;&nbsp;</TH>\
<TD ALIGN=left!&? BGCOLOR=\"#eeeeee\"\r\r>!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=left!&? BGCOLOR=\"#eeeeee\"\r\r>!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=right!&? BGCOLOR=\"#eeeeee\"\r\r>!AZ&nbsp;&nbsp;</TD>\
<TD ALIGN=right!&? BGCOLOR=\"#eeeeee\"\r\r>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=left!&? BGCOLOR=\"#eeeeee\"\r\r><NOBR>!20%D&nbsp;&nbsp;</NOBR></TD>\
<TD ALIGN=right!&? BGCOLOR=\"#eeeeee\"\r\r>!UL</TD>\
</TR>\n";

   static char EmptyUserMappingFao [] =
"<TR><TH><B>000</B>&nbsp;&nbsp;</TH>\
<TD COLSPAN=6 BGCOLOR=\"#eeeeee\"><I>empty</I></TD><TR>\n";

   static char  EndPageFao [] =
"</TABLE>\n\
<HR SIZE=1 NOSHADE WIDTH=60% ALIGN=left>\n\
</BODY>\n\
</HTML>\n";

   BOOL  ThisVirtualService;
   int  status,
        EntryCount;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   unsigned short  Length;
   char  *cptr;
   char  Buffer [2048],
         RmsSubChar [2],
         TimeString [32];
   LIST_ENTRY  *leptr;
   MAP_RULE_META  *mrptr;
   MAP_URL_USER_ENTRY  *ucptr;
   METACON_LINE  *mclptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL, "MapUrl_ReportNow() !&B !&Z",
                 UseServerDatabase, VirtualService);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_MAPPING_RULES;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Path Mapping");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalMappingPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalMappingPtr, mcptr->IncludeFile);

   /*****************/
   /* mapping rules */
   /*****************/

   status = NetWriteFaol (rqptr, BeginRules, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   AdminVirtualServiceForm (rqptr, ADMIN_REPORT_MAPPING,
                            VirtualService, UseServerDatabase);

   status = NetWriteFaol (rqptr, BeginTable, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   status = SS$_NORMAL;
   EntryCount = 0;
   ThisVirtualService = true;

   MetaConParseReset (mcptr, true);
   while (mclptr = MetaConParseRaw (mcptr))
   {
      if (mclptr->Token == METACON_TOKEN_SERVICE)
      {
         /* if filtering on a virtual service */
         if (VirtualService[0] &&
             *(unsigned long*)mclptr->TextPtr != '*:*\0' &&
             !StringMatch (rqptr, mclptr->TextPtr, VirtualService))
         {
            ThisVirtualService = false;
            continue;
         }
         ThisVirtualService = true;
         vecptr = FaoVector;
         *vecptr++ = mclptr->Number;
         *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3;
         *vecptr++ = mclptr->ConfigProblem;
         *vecptr++ = mclptr->TextPtr;
         *vecptr++ = mclptr->ConfigProblem;
         status = NetWriteFaol (rqptr, RuleVirtualFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         continue;
      }
      else
      if (mclptr->Number == 1)
      {
         vecptr = FaoVector;
         *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3;
         status = NetWriteFaol (rqptr, RuleImplicitVirtualFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }

      if (!ThisVirtualService) continue;
      EntryCount++;

      if (mclptr->Token != METACON_TOKEN_TEXT)
      {
         for (cptr = mclptr->TextPtr; *cptr && !ISLWS(*cptr); cptr++);
         vecptr = FaoVector;
         *vecptr++ = mclptr->Number;
         switch (mclptr->Token)
         {
            case METACON_TOKEN_ELIF :
            case METACON_TOKEN_ELSE :
            case METACON_TOKEN_UNIF :
            case METACON_TOKEN_IFIF :
            if (mclptr->FlowControlLevel)
               *vecptr++ = (mclptr->MetaFileLevel+
                            mclptr->FlowControlLevel) * 3;
            else
               *vecptr++ = (mclptr->MetaFileLevel+
                            mclptr->FlowControlLevel+1) * 3;
            break;
         default:
            *vecptr++ = (mclptr->MetaFileLevel+
                         mclptr->FlowControlLevel+1) * 3;
         }
         *vecptr++ = mclptr->ConfigProblem ||
                     (mclptr->InlineTextPtr && !mclptr->LineDataPtr);
         *vecptr++ = cptr - mclptr->TextPtr;
         *vecptr++ = mclptr->TextPtr;
         while (*cptr && ISLWS(*cptr)) cptr++;
         *vecptr++ = cptr;
         *vecptr++ = mclptr->ConfigProblem ||
                     (mclptr->InlineTextPtr && !mclptr->LineDataPtr);
         status = NetWriteFaol (rqptr, NonRuleFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         continue;
      }

      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;
      *vecptr++ = (mclptr->MetaFileLevel+
                   mclptr->FlowControlLevel) * 3 + 3;

      if (!(mrptr = mclptr->LineDataPtr))
      {
         *vecptr++ = mclptr->TextPtr;
         status = NetWriteFaol (rqptr, ProblemFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
         continue;
      }

      if (WATCHING(rqptr) && WATCH_MODULE_DETAIL)
         WatchDataFormatted ("!&X !UL !&Z !&Z !&Z\n",
            mrptr, mrptr->RuleType, mrptr->TemplatePtr,
            mrptr->ResultPtr, mrptr->ConditionalPtr);

      *vecptr++ = mclptr->ConfigProblem;

      if (mrptr->RuleType == MAPURL_RULE_EXEC)
      {
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "exec+";
         else
            *vecptr++ = "exec";
      }
      else
      if (mrptr->RuleType == MAPURL_RULE_FAIL)
         *vecptr++ = "fail";
      else
      if (mrptr->RuleType == MAPURL_RULE_MAP)
         *vecptr++ = "map";
      else
      if (mrptr->RuleType == MAPURL_RULE_PASS)
         *vecptr++ = "pass";
      else
      if (mrptr->RuleType == MAPURL_RULE_PROTECT)
         *vecptr++ = "protect";
      else
      if (mrptr->RuleType == MAPURL_RULE_REDIRECT)
         *vecptr++ = "redirect";
      else
      if (mrptr->RuleType == MAPURL_RULE_SCRIPT)
      {
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "script+";
         else
            *vecptr++ = "script";
      }
      else
      if (mrptr->RuleType == MAPURL_RULE_SET)
         *vecptr++ = "set";
      else
      if (mrptr->RuleType == MAPURL_RULE_UXEC)
         *vecptr++ = "uxec";
      else
      if (mrptr->RuleType == MAPURL_RULE_USER)
         *vecptr++ = "user";
      else
         *vecptr++ = "*ERROR*";

      *vecptr++ = mrptr->TemplatePtr;
      if (isdigit(*mrptr->ResultPtr))
      {
         *vecptr++ = "  &quot;!&;AZ&quot;";
         *vecptr++ = mrptr->ResultPtr;
      }
      else
      if (mrptr->ResultPtr[0])
      {
         *vecptr++ = "  !&;AZ";
         *vecptr++ = mrptr->ResultPtr;
      }
      else
         *vecptr++ = "";

      if (mrptr->PathSet)
      {
         /* just one space, ExplainPathSet() adds it's own leading space */
         *vecptr++ = " !&;AZ";
         *vecptr++ = MapUrl_ExplainPathSet (mrptr);
      }
      else
         *vecptr++ = "";

      if (mrptr->ConditionalPtr[0])
      {
         *vecptr++ = "  !&;AZ";
         *vecptr++ = mrptr->ConditionalPtr;
      }
      else
         *vecptr++ = "";

      *vecptr++ = mclptr->ConfigProblem;

      status = NetWriteFaol (rqptr, RuleFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

      /* stop reporting a specific virtual service if terminating rule */
      if (!VirtualService[0]) continue;
      if (mrptr->RuleType == MAPURL_RULE_FAIL &&
          *(unsigned short*)mrptr->TemplatePtr == '*\0') break;
      if (mrptr->RuleType == MAPURL_RULE_PASS &&
          *(unsigned short*)mrptr->TemplatePtr == '*\0') break;
   }

   vecptr = FaoVector;
   if (EntryCount)
      *vecptr++ = "";
   else
      *vecptr++ = "<I>(none)</I>\n";
   *vecptr++ = MapUrlUserNameCacheEntries;

   status = NetWriteFaol (rqptr, UserMappingFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   /*****************/
   /* user mappings */
   /*****************/

   EntryCount = 0;

   /* process the cache entry list from most to least recent */
   for (leptr = MapUrlUserNameCacheList.HeadPtr;
        leptr;
        leptr = leptr->NextPtr)
   {
      ucptr = (MAP_URL_USER_ENTRY*)leptr;

      /* if empty name, must have been reset, no point in going any further */
      if (!ucptr->UserName[0]) break;

      vecptr = FaoVector;
      *vecptr++ = ++EntryCount;
      *vecptr++ = !(EntryCount % 2);
      *vecptr++ = ucptr->UserName;
      *vecptr++ = !(EntryCount % 2);
      *vecptr++ = ucptr->UserPath;

      *vecptr++ = !(EntryCount % 2);
      if (ucptr->PathOds == MAPURL_PATH_ODS_2)
         *vecptr++ = "ODS-2";
      else
      if (ucptr->PathOds == MAPURL_PATH_ODS_5)
         *vecptr++ = "ODS-5";
      else
         *vecptr++ = "ods-2";

      *vecptr++ = !(EntryCount % 2);
      *vecptr++ = ucptr->HitCount;
      *vecptr++ = !(EntryCount % 2);
      *vecptr++ = &ucptr->LastBinaryTime;
      *vecptr++ = !(EntryCount % 2);
      *vecptr++ = ucptr->ReuseCount;

      status = NetWriteFaol (rqptr, EntryFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }
   if (!MapUrlUserNameCacheList.HeadPtr)
   {
      status = NetWriteFaol (rqptr, EmptyUserMappingFao, NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   status = NetWriteFaol (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
}

/*****************************************************************************/
/*
Report an individual rule during processing of a mapping rule check.
*/

MapUrl_WatchRule
(
REQUEST_STRUCT *rqptr,
MAP_RULE_META  *mrptr,
char *PathPtr,
int PathOds,
int Match,
BOOL SetPathIgnore
)
{
   static char  RuleFao [] = "!3ZL !AZ!AZ  !AZ  !8AZ  !AZ  !&@!&@!&@\n";

   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  Buffer [2048],
         RmsSubChar [2];

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_WatchRule() !&Z !UL !UL", PathPtr, PathOds, Match);

   vecptr = FaoVector;
   *vecptr++ = mrptr->MetaConNumber;
   *vecptr++ = PathPtr;

   if (PathOds == MAPURL_PATH_ODS_0)
      *vecptr++ = "  NOODS";
   else
   if (PathOds == MAPURL_PATH_ODS_2)
      *vecptr++ = "  ODS=2";
   else
   if (PathOds == MAPURL_PATH_ODS_5)
      *vecptr++ = "  ODS=5";
   else
   if (PathOds == MAPURL_PATH_ODS_PWK)
      *vecptr++ = "  ODS=PWK";
   else
   if (PathOds == MAPURL_PATH_ODS_SMB)
      *vecptr++ = "  ODS=SMB";
   else
   if (PathOds == MAPURL_PATH_ODS_SRI)
      *vecptr++ = "  ODS=SRI";
   else
      *vecptr++ = "";
 
   if (Match == MAPURL_REPORT_MATCH_NOT)
      *vecptr++ = "..";
   else
   if (Match == MAPURL_REPORT_MATCH_RULE)
      *vecptr++ = "YN";
   else
   if (Match == MAPURL_REPORT_MATCH_RULECOND)
      *vecptr++ = "YY";
   else
   if (Match ==  MAPURL_REPORT_MATCH_RULENOCOND)
      *vecptr++ = "Y-";
   else
      *vecptr++ = "??";

   switch (mrptr->RuleType)
   {
      case MAPURL_RULE_EXEC :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "EXEC+";
         else
            *vecptr++ = "EXEC";
         break;
      case MAPURL_RULE_FAIL :
         *vecptr++ = "FAIL";
         break;
      case MAPURL_RULE_MAP :
         *vecptr++ = "MAP";
         break;
      case MAPURL_RULE_PASS :     
         *vecptr++ = "PASS";
         break;
      case MAPURL_RULE_PROTECT :
         *vecptr++ = "PROTECT";
         break;
      case MAPURL_RULE_REDIRECT :
         *vecptr++ = "REDIRECT";
         break;
      case MAPURL_RULE_SCRIPT :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "SCRIPT+";
         else
            *vecptr++ = "SCRIPT";
         break;
      case MAPURL_RULE_SET :
         if (SetPathIgnore)
            *vecptr++ = "(SET)";
         else
            *vecptr++ = "SET";
         break;
      case MAPURL_RULE_USER :
         *vecptr++ = "USER";
         break;
      case MAPURL_RULE_UXEC :
         if (mrptr->IsCgiPlusScript)
            *vecptr++ = "UXEC+";
         else
            *vecptr++ = "UXEC";
         break;
      default :
         *vecptr++ = "*ERROR*";
   }

   *vecptr++ = mrptr->TemplatePtr;

   if (isdigit(*mrptr->ResultPtr))
   {
      *vecptr++ = "\"!AZ\"";
      *vecptr++ = mrptr->ResultPtr;
   }
   else
   {
      *vecptr++ = "!AZ";
      *vecptr++ = mrptr->ResultPtr;
   }

   if (mrptr->PathSet)
   {
      *vecptr++ = " !AZ";
      *vecptr++ = MapUrl_ExplainPathSet (mrptr);
   }
   else
      *vecptr++ = "";

   if (mrptr->ConditionalPtr[0])
   {
      *vecptr++ = " !AZ";
      *vecptr++ = mrptr->ConditionalPtr;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (Buffer, sizeof(Buffer), &Length, RuleFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (status, "WriteFaol()", FI_LI);

   if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_MAPPING)))
      WatchData (Buffer, Length); 
}

/*****************************************************************************/
/*
Display mapping rules used to resolve the path in the query string.  Called
from the Request.c modules.  For HTTP report, uses blocking network write.
There are a number of intermediate WriteFaol()s.  This was done only to make
the format strings a bit easier to manage.
*/

char* MapUrl_ExplainPathSet (MAP_RULE_META *mrptr)

{
   static char  Buffer [2048];

   int  blen, status;
   unsigned short  Length;
   unsigned long  FaoVector [64];
   unsigned long  *vecptr;
   char  *bptr;
   MAP_SET_META  *mrpsptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL, "MapUrl_ExplainPathSet()");

   bptr = Buffer;
   blen = sizeof(Buffer);
   mrpsptr = &mrptr->mpPathSet;

   vecptr = FaoVector;

   if (mrpsptr->NoAcceptLang)
      *vecptr++ = " NOaccept=LANG";
   else
   if (mrpsptr->AcceptLangChar)
   {
      *vecptr++ = " accept=LANG=(default=!AZ,char=!&C,!&?type\rname\r)";
      if (mrpsptr->AcceptLangPtr)
         *vecptr++ = mrpsptr->AcceptLangPtr;
      else
         *vecptr++ = "";
      *vecptr++ = mrpsptr->AcceptLangChar;
      *vecptr++ = mrpsptr->AcceptLangTypeVariant;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->NoAlert)
      *vecptr++ = " NOALERT";
   else
   if (mrpsptr->Alert == MAPURL_PATH_ALERT_END)
      *vecptr++ = " ALERT=end";
   else
   if (mrpsptr->Alert == MAPURL_PATH_ALERT_AUTH)
      *vecptr++ = " ALERT=auth";
   else
   if (mrpsptr->Alert == MAPURL_PATH_ALERT_MAP)
      *vecptr++ = " ALERT=map";
   else
   if (mrpsptr->Alert)
   {
      *vecptr++ = " ALERT=!UL";
      *vecptr++ = mrpsptr->Alert;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->NoAuthAll)
      *vecptr++ = " NOauth=ALL";
   else
   if (mrpsptr->AuthAll)
      *vecptr++ = " auth=ALL";
   else
      *vecptr++ = "";

   if (mrpsptr->NoAuthMapped)
      *vecptr++ = " NOauth=MAPPED";
   else
   if (mrpsptr->AuthMapped)
      *vecptr++ = " auth=MAPPED";
   else
      *vecptr++ = "";

   if (mrpsptr->NoAuthOnce)
      *vecptr++ = " NOauth=ONCE";
   else
   if (mrpsptr->AuthOnce)
      *vecptr++ = " auth=ONCE";
   else
      *vecptr++ = "";

   if (mrpsptr->AuthSysUafPwdExpUrlPtr)
   {
      *vecptr++ = " auth=SYSUAF=pwdexpURL=!&\"AZ";
      *vecptr++ = mrpsptr->AuthSysUafPwdExpUrlPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->AuthRevalidateTimeout)
   {
      *vecptr++ = " auth=REVALIDATE=!2ZL:!2ZL:!2ZL";
      *vecptr++ = mrpsptr->AuthRevalidateTimeout / 3600;
      *vecptr++ = (mrpsptr->AuthRevalidateTimeout % 3600) / 60;
      *vecptr++ = (mrpsptr->AuthRevalidateTimeout % 3600) % 60;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
                       "!&@!&@!AZ!AZ!AZ!&@!&@", &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   bptr += Length;
   blen -= Length;
   vecptr = FaoVector;

   if (mrpsptr->NoCache)
      *vecptr++ = " NOcache";
   else
   if (mrpsptr->Cache)
      *vecptr++ = " cache";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoCGI)
      *vecptr++ = " cache=noCGI";
   else
   if (mrpsptr->CacheCGI)
      *vecptr++ = " cache=CGI";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoFile)
      *vecptr++ = " cache=NOfile";
   else
   if (mrpsptr->CacheFile)
      *vecptr++ = " cache=file";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoNet)
      *vecptr++ = " cache=NOnet";
   else
   if (mrpsptr->CacheNet)
      *vecptr++ = " cache=net";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoNPH)
      *vecptr++ = " cache=noNPH";
   else
   if (mrpsptr->CacheNPH)
      *vecptr++ = " cache=NPH";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoPermanent)
      *vecptr++ = " cache=NOpermanent";
   else
   if (mrpsptr->CachePermanent)
      *vecptr++ = " cache=permanent";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoQuery)
      *vecptr++ = " cache=NOquery";
   else
   if (mrpsptr->CacheQuery)
      *vecptr++ = " cache=query";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoScript)
      *vecptr++ = " cache=NOscript";
   else
   if (mrpsptr->CacheScript)
      *vecptr++ = " cache=script";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheNoSSI)
      *vecptr++ = " cache=noSSI";
   else
   if (mrpsptr->CacheSSI)
      *vecptr++ = " cache=SSI";
   else
      *vecptr++ = "";

   if (mrpsptr->CacheExpiresAfter == CACHE_EXPIRES_NONE)
      *vecptr++ = " cache=expires=none";
   else
   if (mrpsptr->CacheExpiresAfter == CACHE_EXPIRES_DAY)
      *vecptr++ = " cache=expires=day";
   else
   if (mrpsptr->CacheExpiresAfter == CACHE_EXPIRES_HOUR)
      *vecptr++ = " cache=expires=hour";
   else
   if (mrpsptr->CacheExpiresAfter == CACHE_EXPIRES_MINUTE)
      *vecptr++ = " cache=expires=minute";
   else
   if (mrpsptr->CacheExpiresAfter)
   {
      *vecptr++ = " cache=expires=!2ZL:!2ZL:!2ZL";
      *vecptr++ = mrpsptr->CacheExpiresAfter / 3600;
      *vecptr++ = (mrpsptr->CacheExpiresAfter % 3600) / 60;
      *vecptr++ = (mrpsptr->CacheExpiresAfter % 3600) % 60;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->CacheGuardSeconds)
   {
      *vecptr++ = " cache=guard=!UL";
      if (mrpsptr->CacheGuardSeconds == -1)
         *vecptr++ = 0;
      else
         *vecptr++ = mrpsptr->CacheGuardSeconds;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->CacheMaxKBytes)
   {
      *vecptr++ = " cache=max=!UL";
      if (mrpsptr->CacheMaxKBytes == -1)
         *vecptr++ = 0;
      else
         *vecptr++ = mrpsptr->CacheMaxKBytes;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->CgiPlusInNoWriteof)
      *vecptr++ = " cgiplusin=NOeof";
   else
   if (mrpsptr->CgiPlusInWriteof)
      *vecptr++ = " cgiplusin=EOF";
   else
      *vecptr++ = "";

   if (mrpsptr->CgiPlusInCC[0] || mrpsptr->CgiPlusInCC[1])
   {
      if (mrpsptr->CgiPlusInCC[0] == '\r' &&
          mrpsptr->CgiPlusInCC[1] == '\n')
         *vecptr++ = " cgiplusin=cc=CRLF";
      else
      if (mrpsptr->CgiPlusInCC[0] == '\n')
         *vecptr++ = " cgiplusin=cc=LF";
      else
      if (mrpsptr->CgiPlusInCC[0] == '\r')
         *vecptr++ = " cgiplusin=cc=CR";
      else
         *vecptr++ = " cgiplusin=cc=NONE";
   }
   else
      *vecptr++ = "";

   if (mrpsptr->CgiPrefixPtr)
   {
      *vecptr++ = " CGI=prefix=!AZ";
      *vecptr++ = mrpsptr->CgiPrefixPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->CharsetPtr)
   {
      *vecptr++ = " charset=!&\"AZ";
      *vecptr++ = mrpsptr->CharsetPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ContentTypePtr)
   {
      *vecptr++ = " content=!&\"AZ";
      *vecptr++ = mrpsptr->ContentTypePtr;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
                       "!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ!&@!&@!&@!AZ!AZ!&@!&@!&@",
                       &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   bptr += Length;
   blen -= Length;
   vecptr = FaoVector;

   if (mrpsptr->DirCharsetPtr)
   {
      *vecptr++ = " dir=charset=!&\"AZ";
      *vecptr++ = mrpsptr->DirCharsetPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->DirNoAccess)
      *vecptr++ = " dir=NOaccess";
   else
   if (mrpsptr->DirAccess)
      *vecptr++ = " dir=access";
   else
   if (mrpsptr->DirAccessSelective)
      *vecptr++ = " dir=access=selective";
   else
      *vecptr++ = "";

   if (mrpsptr->DirNoImpliedWildcard)
      *vecptr++ = " dir=NOimpliedwildcard";
   else
   if (mrpsptr->DirImpliedWildcard)
      *vecptr++ = " dir=impliedwildcard";
   else
      *vecptr++ = "";

   if (mrpsptr->DirStyle == MAPURL_DIR_STYLE_DEFAULT)
      *vecptr++ = " dir=STYLE=default";
   else
   if (mrpsptr->DirStyle == MAPURL_DIR_STYLE_ORIGINAL)
      *vecptr++ = " dir=STYLE=original";
   else
   if (mrpsptr->DirStyle == MAPURL_DIR_STYLE_ANCHOR)
      *vecptr++ = " dir=STYLE=anchor";
   else
   if (mrpsptr->DirStyle == MAPURL_DIR_STYLE_HTDIR)
      *vecptr++ = " dir=STYLE=htdir";
   else
      *vecptr++ = "";

   if (mrpsptr->DirNoWildcard)
      *vecptr++ = " dir=NOwildcard";
   else
   if (mrpsptr->DirWildcard)
      *vecptr++ = " dir=wildcard";
   else
      *vecptr++ = "";

   if (mrpsptr->NoExpired)
      *vecptr++ = " NOexpired";
   else
   if (mrpsptr->Expired)
      *vecptr++ = " expired";
   else
      *vecptr++ = "";

   if (mrpsptr->HttpAcceptCharsetPtr)
   {
      *vecptr++ = " http=Accept-Charset=!&\"AZ";
      *vecptr++ = mrpsptr->HttpAcceptCharsetPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HtmlBodyTagPtr)
   {
      *vecptr++ = " html=bodytag=!&\"AZ";
      *vecptr++ = mrpsptr->HtmlBodyTagPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HtmlHeaderTagPtr)
   {
      *vecptr++ = " html=headertag=!&\"AZ";
      *vecptr++ = mrpsptr->HtmlHeaderTagPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HtmlHeaderPtr)
   {
      *vecptr++ = " html=header=!&\"AZ";
      *vecptr++ = mrpsptr->HtmlHeaderPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HtmlFooterTagPtr)
   {
      *vecptr++ = " html=footertag=!&\"AZ";
      *vecptr++ = mrpsptr->HtmlFooterTagPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HtmlFooterPtr)
   {
      *vecptr++ = " html=footer=!&\"AZ";
      *vecptr++ = mrpsptr->HtmlFooterPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->HttpAcceptLangPtr)
   {
      *vecptr++ = " http=Accept-Language=!&\"AZ";
      *vecptr++ = mrpsptr->HttpAcceptLangPtr;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
                       "!&@!AZ!AZ!AZ!AZ!&@!&@!&@!&@!&@!&@!&@",
                       &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   bptr += Length;
   blen -= Length;
   vecptr = FaoVector;

   if (mrpsptr->IndexPtr)
   {
      *vecptr++ = " index=!AZ";
      *vecptr++ = mrpsptr->IndexPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->NoLog)
      *vecptr++ = " NOlog";
   else
   if (mrpsptr->Log)
      *vecptr++ = " log";
   else
      *vecptr++ = "";

   if (mrpsptr->NoMapEllipsis)
      *vecptr++ = " map=NOELLIPSIS";
   else
   if (mrpsptr->MapEllipsis)
      *vecptr++ = " map=ELLIPSIS";
   else
      *vecptr++ = "";

   if (mrpsptr->MapNonEmpty)
      *vecptr++ = " map=NOEMPTY";
   else
   if (mrpsptr->MapEmpty)
      *vecptr++ = " map=EMPTY";
   else
      *vecptr++ = "";

   if (mrpsptr->NoMapOnce)
      *vecptr++ = " map=NOONCE";
   else
   if (mrpsptr->MapOnce)
      *vecptr++ = " map=ONCE";
   else
      *vecptr++ = "";

   if (mrpsptr->MapRestart)
      *vecptr++ = " map=RESTART";
   else
      *vecptr++ = "";

   if (mrpsptr->MapSetNoIgnore)
      *vecptr++ = " map=SET=noignore";
   else
   if (mrpsptr->MapSetIgnore)
      *vecptr++ = " map=SET=ignore";
   else
      *vecptr++ = "";

   if (mrpsptr->MapSetNoRequest)
      *vecptr++ = " map=SET=norequest";
   else
   if (mrpsptr->MapSetRequest)
      *vecptr++ = " map=SET=request";
   else
      *vecptr++ = "";

   if (mrpsptr->PathOds == MAPURL_PATH_ODS_0)
      *vecptr++ = " NOods";
   else
   if (mrpsptr->PathOds == MAPURL_PATH_ODS_2)
      *vecptr++ = " ods=2";
   else
   if (mrpsptr->PathOds == MAPURL_PATH_ODS_5)
      *vecptr++ = " ods=5";
   else
   if (mrpsptr->PathOds == MAPURL_PATH_ODS_PWK)
      *vecptr++ = " ods=PWK";
   else
   if (mrpsptr->PathOds == MAPURL_PATH_ODS_SMB)
      *vecptr++ = " ods=SMB";
   else
   if (mrpsptr->PathOds == MAPURL_PATH_ODS_SRI)
      *vecptr++ = " ods=SRI";
   else
      *vecptr++ = "";

   if (mrpsptr->NoProfile)
      *vecptr++ = " NOprofile";
   else
   if (mrpsptr->Profile)
      *vecptr++ = " profile";
   else
      *vecptr++ = "";

   if (mrpsptr->ProxyBindIpAddressPtr)
   {
      *vecptr++ = " proxy=BIND=!AZ";
      *vecptr++ = mrpsptr->ProxyBindIpAddressPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ProxyChainHostPortPtr)
   {
      *vecptr++ = " proxy=CHAIN=!AZ";
      *vecptr++ = mrpsptr->ProxyChainHostPortPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ProxyForwardedBy == PROXY_FORWARDED_NONE)
      *vecptr++ = " proxy=NOforwarded";
   else
   if (mrpsptr->ProxyForwardedBy == PROXY_FORWARDED_BY)
      *vecptr++ = " proxy=FORWARDED=by";
   else
   if (mrpsptr->ProxyForwardedBy == PROXY_FORWARDED_FOR)
      *vecptr++ = " proxy=FORWARDED=for";
   else
   if (mrpsptr->ProxyForwardedBy == PROXY_FORWARDED_ADDRESS)
      *vecptr++ = " proxy=FORWARDED=address";
   else
      *vecptr++ = "";

   if (mrpsptr->ProxyReverseLocationLength)
   {
      *vecptr++ = " proxy=REVERSE=location=!AZ";
      *vecptr++ = mrpsptr->ProxyReverseLocationPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ProxyXForwardedFor == PROXY_XFORWARDEDFOR_NONE)
      *vecptr++ = " proxy=NOxforwardedfor";
   else
   if (mrpsptr->ProxyXForwardedFor == PROXY_XFORWARDEDFOR_ENABLED)
      *vecptr++ = " proxy=XFORWARDEDFOR=enabled";
   else
   if (mrpsptr->ProxyXForwardedFor == PROXY_XFORWARDEDFOR_ADDRESS)
      *vecptr++ = " proxy=XFORWARDEDFOR=address";
   else
   if (mrpsptr->ProxyXForwardedFor == PROXY_XFORWARDEDFOR_UNKNOWN)
      *vecptr++ = " proxy=XFORWARDEDFOR=unknown";
   else
      *vecptr++ = "";

   if (mrpsptr->NoProxyReverseVerify)
      *vecptr++ = " proxy=REVERSE=NOverify";
   else
   if (mrpsptr->ProxyReverseVerify)
      *vecptr++ = " proxy=REVERSE=verify";
   else
      *vecptr++ = "";

   if (mrpsptr->NoProxyUnknownRequestFields)
      *vecptr++ = " proxy=NOUNKNOWN";
   else
   if (mrpsptr->ProxyUnknownRequestFields)
      *vecptr++ = " proxy=UNKNOWN";
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
                       "!&@!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ!AZ!&@!&@!AZ!&@!AZ!AZ!AZ",
                       &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   bptr += Length;
   blen -= Length;
   vecptr = FaoVector;

   if (mrpsptr->MapRootPtr)
   {
      *vecptr++ = " map=root=!AZ";
      *vecptr++ = mrpsptr->MapRootPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->NotePadPtr)
   {
      *vecptr++ = " notepad=!AZ";
      *vecptr++ = mrpsptr->NotePadPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ReportBasic)
      *vecptr++ = " report=BASIC";
   else
   if (mrpsptr->ReportDetailed)
      *vecptr++ = " report=DETAILED";
   else
      *vecptr++ = "";

   if (mrpsptr->Report400as)
   {
      *vecptr++ = " report=400=!UL";
      *vecptr++ = mrpsptr->Report400as;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->Report403as)
   {
      *vecptr++ = " report=403=!UL";
      *vecptr++ = mrpsptr->Report403as;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->Report404as)
   {
      *vecptr++ = " report=404=!UL";
      *vecptr++ = mrpsptr->Report404as;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ResponseHeaderNone)
      *vecptr++ = " response=header=none";
   else
   if (mrpsptr->ResponseHeaderBegin)
      *vecptr++ = " response=header=begin";
   else
   if (mrpsptr->ResponseHeaderFull)
      *vecptr++ = " response=header=full";
   else
      *vecptr++ = "";

   if (mrpsptr->ResponseHeaderNoAdd)
      *vecptr++ = " response=header=NOadd";
   else
      *vecptr++ = "";

   if (mrpsptr->ResponseHeaderAddLength)
   {
      *vecptr++ = " response=header=add=!#&\"AZ";
      *vecptr++ = mrpsptr->ResponseHeaderAddLength;
      *vecptr++ = mrpsptr->ResponseHeaderAddPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptNoFind)
      *vecptr++ = " NOscript=FIND";
   else
   if (mrpsptr->ScriptFind)
      *vecptr++ = " script=FIND";
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptNoPathFind)
      *vecptr++ = " NOscript=PATH=find";
   else
   if (mrpsptr->ScriptPathFind)
      *vecptr++ = " script=PATH=find";
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptNoQueryNone)
      *vecptr++ = " NOscript=QUERY=none";
   else
   if (mrpsptr->ScriptQueryNone)
      *vecptr++ = " script=QUERY=none";
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptNoQueryRelaxed)
      *vecptr++ = " NOscript=QUERY=relaxed";
   else
   if (mrpsptr->ScriptQueryRelaxed)
      *vecptr++ = " script=QUERY=relaxed";
   else
      *vecptr++ = "";

   if (mrpsptr->NoDefaultSearch)
      *vecptr++ = " search=none";
   else
   if (mrpsptr->DefaultSearch)
      *vecptr++ = " NOsearch=none";
   else
      *vecptr++ = "";

   if (mrpsptr->NoPrivSsi)
      *vecptr++ = " NOssi=PRIV";
   else
   if (mrpsptr->PrivSsi)
      *vecptr++ = " ssi=PRIV";
   else
      *vecptr++ = "";

   if (mrpsptr->SsiExecPtr)
   {
      *vecptr++ = " ssi=EXEC=!AZ";
      *vecptr++ = mrpsptr->SsiExecPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->SSLCGIvar == SESOLA_CGI_VAR_APACHE_MOD_SSL)
      *vecptr++ = " SSLCGI=Apache_mod_SSL";
   else
   if (mrpsptr->SSLCGIvar == SESOLA_CGI_VAR_PURVEYOR)
      *vecptr++ = " SSLCGI=Purveyor";
   else
      *vecptr++ = "";

   if (mrpsptr->NoStmLF)
      *vecptr++ = " NOstmLF";
   else
   if (mrpsptr->StmLF)
      *vecptr++ = " stmLF";
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
"!&@!&@!AZ!&@!&@!&@!AZ!AZ!&@!AZ!AZ!AZ!AZ!AZ!AZ!&@!AZ!AZ",
                       &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   bptr += Length;
   blen -= Length;
   vecptr = FaoVector;

   if (mrpsptr->QueryStringPtr)
   {
      *vecptr++ = " query-string=!&\"AZ";
      *vecptr++ = mrpsptr->QueryStringPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->RmsSubChar)
   {
      *vecptr++ = " RMSchar=!&C";
      *vecptr++ = mrpsptr->RmsSubChar;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptAsPtr)
   {
      *vecptr++ = " script=AS=!AZ";
      *vecptr++ = mrpsptr->ScriptAsPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptCommandPtr)
   {
      *vecptr++ = " script=COMMAND=!AZ";
      *vecptr++ = mrpsptr->ScriptCommandPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptDefaultPtr)
   {
      *vecptr++ = " script=DEFAULT=!AZ";
      *vecptr++ = mrpsptr->ScriptDefaultPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptBitBucketTimeout)
   {
      *vecptr++ = " script=BIT-BUCKET=!2ZL:!2ZL:!2ZL";
      *vecptr++ = mrpsptr->ScriptBitBucketTimeout / 3600;
      *vecptr++ = (mrpsptr->ScriptBitBucketTimeout % 3600) / 60;
      *vecptr++ = (mrpsptr->ScriptBitBucketTimeout % 3600) % 60;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptCpuMax)
   {
      *vecptr++ = " script=CPU=!UL";
      *vecptr++ = mrpsptr->ScriptCpuMax;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ScriptParamsPtr)
   {
      *vecptr++ = " script=PARAMS=!AZ";
      *vecptr++ = mrpsptr->ScriptParamsPtr;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->TimeoutKeepAlive ||
       mrpsptr->TimeoutNoProgress ||
       mrpsptr->TimeoutOutput)
   {
      *vecptr++ = " timeout=!&@(k),!2ZL:!2ZL:!2ZL(np),!2ZL:!2ZL:!2ZL(o)";
      if (mrpsptr->TimeoutKeepAlive < 0)
         *vecptr++ = "none";
      else
      {
         *vecptr++ = "!2ZL:!2ZL:!2ZL";
         *vecptr++ = mrpsptr->TimeoutKeepAlive / 3600;
         *vecptr++ = (mrpsptr->TimeoutKeepAlive % 3600) / 60;
         *vecptr++ = (mrpsptr->TimeoutKeepAlive % 3600) % 60;
      }
      *vecptr++ = mrpsptr->TimeoutNoProgress / 3600;
      *vecptr++ = (mrpsptr->TimeoutNoProgress % 3600) / 60;
      *vecptr++ = (mrpsptr->TimeoutNoProgress % 3600) % 60;
      *vecptr++ = mrpsptr->TimeoutOutput / 3600;
      *vecptr++ = (mrpsptr->TimeoutOutput % 3600) / 60;
      *vecptr++ = (mrpsptr->TimeoutOutput % 3600) % 60;
   }
   else
      *vecptr++ = "";

   if (mrpsptr->ThrottleFrom)
   {
      *vecptr++ = " throttle=!UL,!UL,!UL,!UL,!2ZL:!2ZL:!2ZL,!2ZL:!2ZL:!2ZL";
      *vecptr++ = mrpsptr->ThrottleFrom;
      *vecptr++ = mrpsptr->ThrottleTo;
      *vecptr++ = mrpsptr->ThrottleResume;
      *vecptr++ = mrpsptr->ThrottleBusy;
      *vecptr++ = mrpsptr->ThrottleTimeoutQueue / 3600;
      *vecptr++ = (mrpsptr->ThrottleTimeoutQueue % 3600) / 60;
      *vecptr++ = (mrpsptr->ThrottleTimeoutQueue % 3600) % 60;
      *vecptr++ = mrpsptr->ThrottleTimeoutBusy / 3600;
      *vecptr++ = (mrpsptr->ThrottleTimeoutBusy % 3600) / 60;
      *vecptr++ = (mrpsptr->ThrottleTimeoutBusy % 3600) % 60;
   }
   else
      *vecptr++ = "";

   status = WriteFaol (bptr, blen, &Length,
                       "!&@!&@!&@!&@!&@!&@!&@!&@!&@!&@", &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("*ERROR*");
   }

   return (Buffer);
}

/*****************************************************************************/
/*
Supplied with an integer in 'ThrottleIndex' representing the element number of
the 'ThrottleArray' path, scan through all the paths in the rule list
noting all SET THROTTLE= paths until the count equals that of the integer
supplied.  This is the rule representing that index.  Return a pointer to the
rule, or NULL.
*/ 
 
MAP_RULE_META* MapUrl_ThrottleRule (int ThrottleIndex)

{
   int  RuleCount;
   MAP_RULE_META  *mrptr;
   MAP_SET_META  *mrpsptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ThrottlePath() !UL", ThrottleIndex);

   RuleCount = 0;
   MetaConParseReset (MetaGlobalMappingPtr, true);
   while (mclptr = MetaConParseRaw (MetaGlobalMappingPtr))
   {
      if (!(mrptr = mclptr->LineDataPtr)) continue;
      mrpsptr = &mrptr->mpPathSet;
      if (!mrpsptr->ThrottleFrom) continue;
      if (ThrottleIndex != RuleCount++) continue;
      return (mrptr);
   }
   return (NULL);
}

/*****************************************************************************/
/*
Reload the mapping rules.
*/

MapUrl_ControlReload
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   int  status;
   char  *cptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ControlReload() !&X", NextTaskFunction);

   MetaConUnload (&MetaGlobalMappingPtr, &MapUrl_ConfigUnloadLineData);

   MapUrl_ConfigLoad (&MetaGlobalMappingPtr);

   /* purge DCL module script task list and script name cache */
   DclLoadedMappingRules();

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;

   if (MetaGlobalMappingPtr->LoadReport.ItemCount)
   {
      ReportSuccess (rqptr,
"$Server !AZ mapping rules reloaded.\n\
<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH><FONT COLOR=\"#ff0000\">!UL Report!%s During Load</FONT></TH></TR>\n\
<TR><TD><PRE>!&;AZ</PRE></TD></TR>\n\
</TABLE>\n",
         ServerHostPort, MetaGlobalMappingPtr->LoadReport.ItemCount,
         MetaGlobalMappingPtr->LoadReport.TextPtr);
   }
   else
      ReportSuccess (rqptr, "$Server !AZ mapping rules reloaded.",
                     ServerHostPort);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Scan the conditional string evaluating the conditions!  Return true or false.
Anything it cannot understand it ignores!
*/

BOOL MapUrl_Conditional
(
REQUEST_STRUCT *rqptr,
MAP_RULE_META *mrptr,
REQUEST_PATHSET *rqpsptr
)
{
   BOOL  NegateThisCondition,
         NegateEntireConditional,
         Result,
         SoFarSoGood;
   int  idx, status,
        ConditionalCount,
        WatchThisOne;
   char  *cptr, *sptr, *tptr, *zptr,
         *CurrentPtr;
   char  Scratch [2048];

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_Conditional() !&Z", mrptr->ConditionalPtr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_MAPPING))
      WatchThisOne = WATCH_MAPPING;
   else
      WatchThisOne = 0;

   CurrentPtr = NULL;
   ConditionalCount = 0;
   NegateEntireConditional = NegateThisCondition = SoFarSoGood = false;
   cptr = mrptr->ConditionalPtr;
   if (WATCH_CAT && WatchThisOne)
      WatchDataFormatted ("conditional !AZ\n", cptr);
   while (*cptr)
   {
      while (ISLWS(*cptr)) cptr++;
      if (!*cptr) break;

      if (*cptr == '[' || *(unsigned short*)cptr == '![')
      {
         if (*cptr == '!')
         {
            NegateEntireConditional = true;
            cptr++;
         }
         else
            NegateEntireConditional = false;
         cptr++;
         ConditionalCount = 0;
         SoFarSoGood = false;
         continue;
      }
      if (*cptr == ']')
      {
         cptr++;
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         if (ConditionalCount && !SoFarSoGood)
         {
            cptr = "";
            break;
         }
         continue;
      }
      if (SoFarSoGood)
      {
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         /* at least one OK, skip to the end of the conditional */
         while (*cptr && *cptr != ']') cptr++;
         /* remember, there may be more than one per line */
         if (*cptr) continue;
         break;
      }

      CurrentPtr = cptr;
      NegateThisCondition = Result = false;
      zptr = (sptr = Scratch) + sizeof(Scratch)-1;

      if (*cptr == '!')
      {
         cptr++;
         NegateThisCondition = true;
      }

      switch (*(unsigned short*)cptr)
      {
         case 'AC' :

            /*************/
            /* "Accept:" */
            /*************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->rqHeader.AcceptPtr)
               Result = MapUrl_ConditionalList (rqptr, Scratch,
                                                rqptr->rqHeader.AcceptPtr);
            break;
             
         case 'AL' :

            /**********************/
            /* "Accept-Language:" */
            /**********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = MapUrl_ConditionalList (rqptr, Scratch,
                                             rqptr->rqHeader.AcceptLangPtr);
            break;

         case 'AS' :

            /*********************/
            /* "Accept-Charset:" */
            /*********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = MapUrl_ConditionalList (rqptr, Scratch,
                                             rqptr->rqHeader.AcceptCharsetPtr);
            break;

         case 'CA' :

            /************/
            /* callout? */
            /************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            Result = rqptr->rqCgi.CalloutInProgress;
            break;

         case 'CK' :

            /**********/
            /* cookie */
            /**********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.CookiePtr,
                                             Scratch);
            break;

         case 'DR' :

            /*****************/
            /* document root */
            /*****************/

            /* map=root=<string> */
            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqpsptr->MapRootPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqpsptr->MapRootPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'EX' :

            /**********************/
            /* extended file path */
            /**********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            Result = rqptr->PathOdsExtended;
            break;

         case 'HM' :

            /****************************/
            /* client host network mask */
            /****************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            tptr = Scratch;  /* preserve string address using ptr */
            status = TcpIpNetMask (rqptr, WATCH_MAPPING, &tptr,
                                   &rqptr->rqClient.IpAddress);
            /* if there's a problem with the mask then just fail it */
            if (VMSnok(status) && status != SS$_UNREACHABLE) return (false);
            Result = (status == SS$_NORMAL);
            break;

         case 'HO' :

            /****************************/
            /* client host name/address */
            /****************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            for (sptr = Scratch; *sptr && !isalnum(*sptr); sptr++);
            if (isdigit(*sptr))
               sptr = rqptr->rqClient.IpAddressString;
            else
               sptr = rqptr->rqClient.Lookup.HostName;
            Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
            break;

         case 'FO' :

            /*******************/
            /* proxy forwarded */
            /*******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            /* it's unlikely we'll have any of this header field */
            if (rqptr->rqHeader.ForwardedPtr)
               Result = MapUrl_ConditionalList (rqptr, Scratch,
                                                rqptr->rqHeader.ForwardedPtr);
            break;

         case 'ME' :

            /***************/
            /* HTTP method */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.MethodName,
                                             Scratch);
            break;

         case 'MP' :

            /*********************/
            /* being mapped path */
            /*********************/

            /* path remaining after script/exec rule, or after a 'map' rule */
            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->MetaConMappedPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqptr->MetaConMappedPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'NO' :

            /***********/
            /* notepad */
            /***********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (rqptr->NotePadPtr)
               Result = StringMatchGreedyRegex (rqptr,
                                                rqptr->NotePadPtr,
                                                Scratch);
            else
               Result = false;
            break;

         case 'PA' :

            /********/
            /* pass */
            /********/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = (Scratch[0] - '0' == rqptr->MetaConPass);
            break;

         case 'PI' :

            /*************/
            /* path-info */
            /*************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.PathInfoPtr,
                                             Scratch);
            break;

         case 'QS' :

            /****************/
            /* query string */
            /****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.QueryStringPtr,
                                             Scratch);
            break;

         case 'RC' :

            /********************/
            /* redirected count */
            /********************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (Scratch[0])
               Result = (Scratch[0] - '0' == rqptr->RedirectCount);
            else
               Result = rqptr->RedirectCount;
            break;

         case 'RF' :

            /****************/
            /* refering URL */
            /****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.RefererPtr,
                                             Scratch);
            break;

         case 'RQ' :

            /****************4*/
            /* request field */
            /*****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            for (idx = 0; idx < rqptr->rqHeader.RequestFieldsCount; idx++)
            {
               sptr = rqptr->rqHeader.RequestFieldsPtr[idx];
               Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
               if (Result) break;
            }

            break;

         case 'RU' :

            /***************/
            /* request URI */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.RequestUriPtr,
                                             Scratch);
            break;

         case 'SC' :

            /****************************************/
            /* request scheme ("http:" or "https:") */
            /****************************************/

            ConditionalCount++;
            cptr += 3;
            /* also ignore any trailing colon on the scheme string */
            while (*cptr && !ISLWS(*cptr) && *cptr != ':' && *cptr != ']' &&
                   sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (*cptr == ':') cptr++;
            /* any trailing colon has been stripped */
            if (strsame (Scratch, "https", -1) &&
                rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
               Result = true;
            else
            if (strsame (Scratch, "http", -1) &&
                rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
               Result = true;
            else
               Result = false;

            break;

         case 'SN' :

            /***************/
            /* server name */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->ServicePtr->ServerHostName,
                                             Scratch);
            break;

         case 'SP' :

            /***************/
            /* server port */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                           rqptr->ServicePtr->ServerPortString,
                                             Scratch);
            break;

         case 'ST' :

            /***************/
            /* script name */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (sptr = rqptr->MetaConScriptPtr)
            {
               if (*sptr == '+')
               {
                  /* CGIplus indicator */
                  *sptr = '/';
                  Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
                  *sptr = '+';
               }
               else
                  Result = StringMatchGreedyRegex (rqptr, sptr, Scratch);
            }
            else
               Result = false;
            break;

         case 'UA' :

            /*****************/
            /* "User-Agent:" */
            /*****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.UserAgentPtr,
                                             Scratch);
            break;

         case 'VS' :
         case 'HH' :  /* backward compatibility */

            /******************************************/
            /* virtual service ("Host:") name/address */
            /******************************************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.HostPtr,
                                             Scratch);
            break;

         case 'XF' :

            /*******************/
            /* x-forwarded-for */
            /*******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            Result = StringMatchGreedyRegex (rqptr,
                                             rqptr->rqHeader.XForwardedForPtr,
                                             Scratch);
            break;

         default :

            /*******************/
            /* unknown, ignore */
            /*******************/

            /* should never occur due to basic check in MapUrl_ConfigLoad()! */
            if (WATCH_CAT && WatchThisOne)
               WatchDataFormatted ("IGNORE !AZ\n", CurrentPtr);
            while (*cptr && !ISLWS(*cptr) && *cptr != ']') cptr++;
            continue;
      }

      if (NegateThisCondition)
         SoFarSoGood = SoFarSoGood || !Result;
      else
         SoFarSoGood = SoFarSoGood || Result;

      if (WATCH_CAT && WatchThisOne)
         WatchDataFormatted ("!AZ !AZ\n",
                             SoFarSoGood ? "MATCH" : "NOMATCH", CurrentPtr);
   }

   if (!ConditionalCount)
      SoFarSoGood = true;
   else
   if (NegateEntireConditional)
      SoFarSoGood = !SoFarSoGood;

   if (WATCH_CAT && WatchThisOne)
      WatchDataFormatted ("!AZ conditional\n",
                          SoFarSoGood ? "PASSED" : "FAILED");

   return (SoFarSoGood);
}

/*****************************************************************************/
/*
Compare single "??:" conditional string to each of a comma-separated list in
the form "item" or "item1, item2, item3", etc. String may contain '*' and '%'
wildcards.  We can munge the list string like this because we're operating at
AST-delivery level and cannot be preempted!
*/

BOOL MapUrl_ConditionalList
(
REQUEST_STRUCT *rqptr,
char *String,
char *List
)
{
   char  ch;
   char  *cptr, *lptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_ConditionalList() !&Z !&Z", String, List);

   if (!(lptr = List)) return (false);
   if (!String) return (false);
   while (*lptr)
   {
      /* spaces and commas are element separators, step over */
      while (ISLWS(*lptr) || *lptr == ',') lptr++;
      /* if end of list (or empty) then finished */
      if (!*lptr) return (false);
      /* find end of this element, save next character, terminate element */
      for (cptr = lptr; *lptr && !ISLWS(*lptr) && *lptr != ','; lptr++);
      ch = *lptr;
      *lptr = '\0';
      /* look for what we want */
      if (StringMatchGreedyRegex (rqptr, cptr, String))
      {
         /* restore list */
         *lptr = ch;
         return (true);
      }
      /* restore list */
      *lptr = ch;
   }
   /* ran out of list elements, so it can't be a match! */
   return (false);
}

/*****************************************************************************/
/*
Get the path representing the username default device/directory.  This can be
retrieved from a cache, or retrieved from directly from the SYSUAF and the
cache subsequently updated.  The 'MAPURL_USER_RULE_FORBIDDEN_MSG' used to
return a user-access-denied to the calling routine, is designed to mask the
actual reason for denial.  This is done to help prevent the leakage of user
account information by being able to differentiate between accounts that exist
and those that don't, by looking at the message.  RequestExecute() checks for
this message and converts it into a "directory not found" message, the same as
that generated if a user does not have a [.WWW] subdirectory in the home area.
*/ 

char* MapUrl_VmsUserName
(
REQUEST_STRUCT* rqptr,
char *UserNamePtr,
int *PathOdsPtr
)
{
   /* UAI flags that disallow SYSUAF-controlled account access */
   static unsigned long  DisallowVmsFlags =
          UAI$M_DISACNT | UAI$M_PWD_EXPIRED | UAI$M_PWD2_EXPIRED |
          UAI$M_CAPTIVE | UAI$M_RESTRICTED;

   static unsigned long  Context = -1;

   static unsigned long  UaiFlags;
   static unsigned long  UaiPriv [2];
   static char  UaiDefDev [MAPURL_USER_DEFDEV_SIZE+1],
                UaiDefDir [MAPURL_USER_DEFDIR_SIZE+1],
                UserName [MAPURL_USER_NAME_SIZE+1],
                UserPath [MAPURL_USER_PATH_SIZE+1];
   static $DESCRIPTOR (UserNameDsc, UserName);

   static struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } UaiItems [] = 
   {
      { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 },
      { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 },
      { sizeof(UaiDefDev), UAI$_DEFDEV, &UaiDefDev, 0 },
      { sizeof(UaiDefDir), UAI$_DEFDIR, &UaiDefDir, 0 },
      { 0,0,0,0 }
   };

   int  status,
        PathOds;
   char  *cptr, *sptr, *zptr;
   char  UserDefault [MAPURL_USER_DEFDEV_SIZE+MAPURL_USER_DEFDIR_SIZE+1];

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_MAPURL)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_VmsUserName() !&Z", UserNamePtr);

   zptr = (sptr = UserName) + sizeof(UserName);
   for (cptr = UserNamePtr; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   if (sptr >= zptr) return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   *sptr = '\0';
   UserNameDsc.dsc$w_length = sptr - UserName;

   /* look for it, and if found, return it from the cache */
   if ((cptr = MapUrl_VmsUserNameCache (rqptr, UserName, NULL, PathOdsPtr)))
      return (cptr);

   /***************************************/
   /* get the information from the SYSUAF */
   /***************************************/

   /* turn on SYSPRV to allow access to SYSUAF records */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0);

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

   if (VMSnok (status)) 
   {
      if (status == RMS$_RNF)
      {
         if (WATCHING(rqptr) &&
             WATCH_CATEGORY(WATCH_MAPPING))
            WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                       "USER fail !AZ unknown", UserName);
         return (MAPURL_USER_RULE_FORBIDDEN_MSG);
      }
      if (WATCHING(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                    "USER sys$getuai() !&S %!&M", status, status);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   /* automatically disallow if any of these flags are set! */
   if (UaiFlags & DisallowVmsFlags)
   {
      if (WATCHING(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                    "USER !AZ fail SYSUAF flags", UserName);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   if (!AuthPolicySysUafRelaxed && (UaiPriv[0] & PRV$M_SYSPRV))
   {
      /* not allowing all accounts, exclude those with extended privileges */
      if (WATCHING(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                    "USER !AZ fail SYSUAF privileges", UserName);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   zptr = (sptr = UserDefault) + sizeof(UserDefault);
   for (cptr = UaiDefDev+1; UaiDefDev[0] && sptr < zptr; UaiDefDev[0]--)
      *sptr++ = *cptr++;
   for (cptr = UaiDefDir+1; UaiDefDir[0] && sptr < zptr; UaiDefDir[0]--)
      *sptr++ = *cptr++;
   if (sptr >= zptr)
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   *sptr = '\0';

#ifdef ODS_EXTENDED

   if (OdsExtended)
   {
      /* on-disk-structure of user area */
      PathOds = MapUrl_VolumeOds (UserDefault);
      if (PathOds && (PathOds != MAPURL_PATH_ODS_2 &&
                      PathOds != MAPURL_PATH_ODS_5))
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_MAPPING))
            WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                       "USER !AZ !AZ ODS-? %X8XL %!&M",
                       UserName, UserDefault, PathOds, PathOds);
         return (MAPURL_USER_RULE_FORBIDDEN_MSG);
      }
   }
   else

#endif /* ODS_EXTENDED */

      PathOds = 0;

   /* generate a URL-style version of the VMS specification */
   MapUrl_VmsToUrl (UserPath, UserDefault, sizeof(UserPath), true, PathOds);

   if (WATCHING(rqptr) &&
       WATCH_CATEGORY(WATCH_MAPPING))
      WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                 "USER !AZ !AZ !AZ ODS-!UL",
                 UserName, UserDefault, UserPath, PathOds);

   /* trim trailing slash */
   for (cptr = UserPath; *cptr && *(unsigned short*)cptr != '/\0'; cptr++);
   *cptr = '\0';

   /* update it in the cache */
   MapUrl_VmsUserNameCache (rqptr, UserName, UserPath, &PathOds);

   /* if required return the path's on-disk-structure */
   if (PathOdsPtr) *PathOdsPtr = PathOds;

   return (UserPath);
}

/*****************************************************************************/
/*
Keep a linked-list of cache entries.  If 'UserName' and 'UserPath' are NULL
then the list is reset (this happen on mapping rule reload).  If 'UserName' is
non-NULL and 'UserPath' is NULL the list is searched for a matching username
and the associated path string returned.  If 'UserName' and 'UserPath' are
non-NULL add/update a cache entry.  If the list has reached maximum size reuse
the last entry, otherwise create a new entry.  Move/add the entry to the head
of the list.  It's therefore a first-in/first-out queue.  Cache contents remain
current until demands on space (due to new entries) cycles through the maximum
available entries.  To explicitly flush the contents reload the rules.
*/ 

char* MapUrl_VmsUserNameCache
(
REQUEST_STRUCT* rqptr,
char *UserName,
char *UserPath,
int *PathOdsPtr
)
{
   LIST_ENTRY  *leptr;
   MAP_URL_USER_ENTRY  *ucptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_VmsUserNameCache() !&Z !&Z !&X",
                 UserName, UserPath, PathOdsPtr);

   if (!UserName && !UserPath)
   {
      /***************/
      /* reset cache */
      /***************/

      MapUrlUserNameCacheEntries = Config.cfMisc.MapUserNameCacheEntries;
      if (!MapUrlUserNameCacheEntries)
         MapUrlUserNameCacheEntries = MAPURL_DEFAULT_USER_CACHE_SIZE;
      MapUrlUserNameCacheCount = 0;

      /* empty the list */
      leptr = MapUrlUserNameCacheList.HeadPtr;
      MapUrlUserNameCacheList.HeadPtr = MapUrlUserNameCacheList.TailPtr = NULL;
      MapUrlUserNameCacheList.EntryCount = 0;
      while (leptr) 
      {
         ucptr = (MAP_URL_USER_ENTRY*)leptr;
         leptr = leptr->NextPtr;
         VmFree (ucptr, FI_LI);
      }
      return (NULL);
   }

   if (!UserPath)
   {
      /****************/
      /* search cache */
      /****************/

      /* process the cache entry list from most to least recent */
      for (leptr = MapUrlUserNameCacheList.HeadPtr;
           leptr;
           leptr = leptr->NextPtr)
      {
         ucptr = (MAP_URL_USER_ENTRY*)leptr;

         /* if this one has been reset there won't be any more down the list */
         if (!ucptr->UserName[0]) break;

         /* compare the first two characters (at least one and a null) */
         if (*(unsigned short*)ucptr->UserName != *(unsigned short*)UserName)
            continue;
         /* full string comparison */
         if (strcmp (ucptr->UserName, UserName)) continue;

         /*************/
         /* cache hit */
         /*************/

         if ((void*)MapUrlUserNameCacheList.HeadPtr != (void*)ucptr)
         {
            /* move it to the head of the list */
            ListRemove (&MapUrlUserNameCacheList, ucptr);
            ListAddHead (&MapUrlUserNameCacheList, ucptr);
         }

         ucptr->HitCount++;
         memcpy (&ucptr->LastBinaryTime, &rqptr->rqTime.Vms64bit, 8);

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_MAPPING))
            WatchThis (rqptr, FI_LI, WATCH_MAPPING,
                       "USER !AZ cache !AZ/ ODS-!UL !UL hits",
                       ucptr->UserName, ucptr->UserPath,
                       ucptr->PathOds, ucptr->HitCount);

         if (PathOdsPtr) *PathOdsPtr = ucptr->PathOds;
         return (ucptr->UserPath);
      }

      /* not found */
      return (NULL);
   }

   /****************/
   /* update cache */
   /****************/

   if (MapUrlUserNameCacheCount < MapUrlUserNameCacheEntries)
   {
      /* allocate memory for a new entry */
      ucptr = VmGet (sizeof (MAP_URL_USER_ENTRY));
      MapUrlUserNameCacheCount++;
   }
   else
   {
      /* reuse the tail entry (least recent) */
      ucptr = MapUrlUserNameCacheList.TailPtr;
      ucptr->ReuseCount++;
      ListRemove (&MapUrlUserNameCacheList, ucptr);
   }

   /* add entry to the head of the user cache list (most recent) */
   ListAddHead (&MapUrlUserNameCacheList, ucptr);

   strncpy (ucptr->UserName, UserName, sizeof(ucptr->UserName));
   strncpy (ucptr->UserPath, UserPath, sizeof(ucptr->UserPath));
   ucptr->UserName[sizeof(ucptr->UserName)-1] =
      ucptr->UserPath[sizeof(ucptr->UserPath)-1] = '\0';
   ucptr->HitCount = 1;
   memcpy (&ucptr->LastBinaryTime, &rqptr->rqTime.Vms64bit, 8);
#ifdef ODS_EXTENDED
   if (PathOdsPtr) ucptr->PathOds = *PathOdsPtr;
#else /* ODS_EXTENDED */
   if (PathOdsPtr) ucptr->PathOds = 0;
#endif /* ODS_EXTENDED */

   return (UserPath);
}

/****************************************************************************/
/*
Get the volume's underlying file system (ODS-2 or ODS-5), by sys$getdvi() the
ACPTYPE for the device.  Return the appropriate WASD value for detected file
system or zero to indicate some other condition.
*/ 

#ifdef ODS_EXTENDED

/* if pre-7.2 Alpha then define this */
#ifndef DVI$C_ACP_F11V5
#define DVI$C_ACP_F11V5 11
#endif

int MapUrl_VolumeOds (char *DeviceName)

{
   static int  DevOds;
   static unsigned long  DevAcpType,
                         DevChar;
   static $DESCRIPTOR (DevNameDsc, "");
   static struct {
      short  buf_len;
      short  item;
      char   *buf_addr;
      short  *ret_len;
   }
   AcpTypeItemList [] = 
   {
      { sizeof(DevAcpType), DVI$_ACPTYPE, &DevAcpType, 0 },
      { sizeof(DevChar), DVI$_DEVCHAR, &DevChar, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   char  *cptr;
   struct {
      unsigned short  Status;
      unsigned short  Count;
      unsigned long  Unused;
   } IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (NULL, FI_LI, WATCH_MOD_MAPURL,
                 "MapUrl_VolumeOds() !&Z", DeviceName);

   for (cptr = DeviceName; *cptr && *cptr != ':'; cptr++);
   /* if we can't find a device in it then assume it's ODS-2 */
   if (!*cptr) return (0);

   DevNameDsc.dsc$a_pointer = DeviceName;
   DevNameDsc.dsc$w_length = cptr - DeviceName;

   DevChar = DevAcpType = 0;

   status = sys$getdviw (EfnWait, 0, &DevNameDsc, &AcpTypeItemList,
                         &IOsb, 0, 0, 0);
   if (WATCH_MODULE(WATCH_MOD_MAPURL))
       WatchDataFormatted ("sys$getdviw() !#AZ !&S !&S !UL\n",
                           DevNameDsc.dsc$w_length, DevNameDsc.dsc$a_pointer,
                           status, IOsb.Status, DevAcpType);

   if (VMSok (status)) status = IOsb.Status;
   /* if error */
   if (VMSnok(status)) return (0); 
   /* if volume not mounted */
   if (!(DevChar & DEV$M_MNT)) return (0);
   if (DevAcpType == DVI$C_ACP_F11V2) return (DevOds = MAPURL_PATH_ODS_2);
   if (DevAcpType == DVI$C_ACP_F11V5) return (DevOds = MAPURL_PATH_ODS_5);
   return (0);
}

#endif /* ODS_EXTENDED */

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

