/*****************************************************************************/
/*
                                  MapOds.c

Functions supporting mapping of URL-style specifications to VMS file system
specifications and back.

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


VERSION HISTORY
---------------
26-SEP-2020  MGD  bugfix; MapOdsAdsVmsToUrl() "if (!SAME2(cptr,':['))"
14-OCT-2018  MGD  bugfix; MapOdsUrlToOds5Vms() semicolon only after period
22-JUN-2016  MGD  bugfix; MapOdsUrlToOds5Vms() URLs will not contain
                    '^'-escaped sequences so just '^'-escape them
18-JUL-2013  MGD  bugfix; MapOds5VmsToUrl() et.al. allow for ".]["
05-SEP-2013  MGD  bugfix; identify MFD using "000000]" and "000000."
28-DEC-2010  MGD  MapOdsUrlTo..() consecutive '/' into a single a la Unix
29-APR-2010  MGD  bugfix; MapOdsUrlToOds5Vms() MapOdsElementsToVms()
                    include '|' and '%' as ODS-5 escaped characters
20-JAN-2009  MGD  bugfix; MapOdsUrlToOds5Vms and MapOds5VmsToUrl()
                  encode the quotation character
30-APR-2005  MGD  bugfix; DECnet access string may contain a space
17-MAR-2005  MGD  MapOdsElementsToVms() avoid accidentally creating a
                  DECnet ("::") file specification when generating device
10-DEC-2004  MGD  bugfix; MapOdsVmsToUnix() empty if empty
07-NOV-2004  MGD  MapOdsVmsToUnix() to support script=SYNTAX=unix
07-OCT-2004  MGD  unbundled from MAPURL.C
*/
/*****************************************************************************/

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

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

/* application header file */
#include "wasd.h"

#define WASD_MODULE "MAPODS"

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

#define MAPURL_RMS_SUBSTITUTION_DEFAULT '$'

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

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

extern int  EfnWait;

extern unsigned long  SysPrvMask[];
extern char  *FaoUrlEncodeTable[];

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
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 MapOdsUrlToVms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
char RmsSubChar,
BOOL MapEllipsis,
int PathOds
)
{
   int  len;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToVms() !UL !UL !&B !UL !&Z",
                 PathOds, RmsSubChar, MapEllipsis, SizeOfVmsPtr, PathPtr);

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

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

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

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

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

      default :
         memcpy (VmsPtr, "SANITY:[CHECK]MapOdsUrlToVms", 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 MapOdsUrlToOds2Vms
(
char *PathPtr,
char *VmsPtr,
int SizeOfVmsPtr,
BOOL MapEllipsis,
char RmsSubChar
)
{
   BOOL  DECnet;
   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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToOds2Vms() !&B !UL !&Z !UL",
                 MapEllipsis, RmsSubChar, PathPtr, SizeOfVmsPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || SAME2(pptr,'/\0'))
   {
      SET2(VmsPtr,'\0\0');
      return (0);
   }

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

   /* each '/' component becomes a null-terminated string */
   while (*pptr && sptr < zptr)
   {
      while (*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++ = to_upper(*pptr++);
            continue;
         }
         if (SAME3(pptr,'...'))
         {
            /* 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) */
               DECnet = true;
               if (SAME2(pptr,'\"~') ||
                   SAME2(pptr,'\"$'))
                  *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
            if (SAME2(pptr,'::'))
            {
               /* DECnet (with or without access string) */
               DECnet = true;
               *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
            if (DECnet && *pptr == ' ')
            {
               *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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
      SET2(VmsPtr,'\0\0');
      return (0);
   }
   *sptr = '\0';

   if (FinalPeriodPtr) *FinalPeriodPtr = '.';

   /* convert these null-terminated elements into a VMS file specification */
   len = MapOdsElementsToVms (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.

There is an ambiguous specification that cannot be resolved algorithmically. 
The file specifications TESTING.TXT and TESTING^.TXT will both be presented to
the client as TESTING.TXT but stored in the file system as TESTING.TXT; and
TESTING^.TXT.;  This function always assumes that TESTING^.TXT.; do not exist.
*/ 
 
#ifdef ODS_EXTENDED

int MapOdsUrlToOds5Vms
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToOds5Vms() !&B !&Z !UL",
                 MapEllipsis, PathPtr, SizeOfVmsPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || SAME2(pptr,'/\0'))
   {
      SET2(VmsPtr,'\0\0');
      return (0);
   }

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

   /* each '/' component becomes a null-terminated string */
   while (*pptr && sptr < zptr)
   {
      while (*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 '=' :
            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 (SAME3(pptr,'...'))
               {
                  /* ellipsis wildcard */
                  *sptr++ = *pptr++;
                  if (sptr < zptr) *sptr++ = *pptr++;
                  if (sptr < zptr) *sptr++ = *pptr++;
               }
               else
               {
                  FinalPeriodPtr = sptr;
                  *sptr++ = '^';
                  if (sptr < zptr) *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 (!ecnt)
         {
            /* DECnet only valid in first component */
            if (*pptr == '\"' || *pptr == '=')
            {
               /* DECnet access string (allow otherwise forbidden chars) */
               DECnet = true;
               if (SAME2(pptr,'\"~') ||
                   SAME2(pptr,'\"$'))
                  *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
            if (SAME2(pptr,'::'))
            {
               /* DECnet (with or without access string) */
               DECnet = true;
               *sptr++ = *pptr++;
               if (sptr < zptr) *sptr++ = *pptr++;
               continue;
            }
            if (DECnet && *pptr == ' ')
            {
               *sptr++ = *pptr++;
               continue;
            }
         }
         /* any other character substitute a question mark */
      }
      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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
      SET2(VmsPtr,'\0\0');
      return (0);
   }
   *sptr = '\0';

   if (FinalSemiColonPtr &&
       (FinalSemiColonPtr > FinalSlashPtr || !FinalSlashPtr) &&
       (FinalSemiColonPtr > FinalPeriodPtr || !FinalPeriodPtr) &&
       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 = MapOdsElementsToVms (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 MapOdsUrlToAdsVms
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToAdsVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || SAME2(pptr,'/\0'))
   {
      SET2(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)
   {
      while (*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++ = to_upper(*pptr++);
               continue;
            }
            if (!loCase && islower(*pptr))
            {
               loCase = true;
               *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '_';
               if (sptr < zptr) *sptr++ = '$';
               if (sptr < zptr) *sptr++ = to_upper(*pptr++);
               continue;
            }
            *sptr++ = to_upper(*pptr++);
            continue;
         }
         if (*pptr == '_' && !SAME2(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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
      SET2(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 = MapOdsElementsToVms (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 MapOdsUrlToPwkVms
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToPwkVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || SAME2(pptr,'/\0'))
   {
      SET2(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)
   {
      while (*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
      SET2(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 = MapOdsElementsToVms (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 MapOdsUrlToSriVms
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsUrlToSriVms() !&B !UL !&Z",
                 MapEllipsis, SizeOfVmsPtr, PathPtr);

   pptr = PathPtr;
   if (!pptr || !*pptr || SAME2(pptr,'/\0'))
   {
      SET2(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)
   {
      while (*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++ = to_upper(*pptr++);
               continue;
            }
            if (!loCase && islower(*pptr))
            {
               loCase = true;
               *sptr++ = '$';
               if (sptr < zptr) *sptr++ = to_upper(*pptr++);
               continue;
            }
            *sptr++ = to_upper(*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
      SET2(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 (!SAME2(sptr,'5N')) continue;
      sptr[-1] = '.';
      for (cptr = sptr + 2; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
      break;
   }

   len = MapOdsElementsToVms (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 MapOdsElementsToVms
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsElementsToVms() !UL !&B",
                 ElementsCount, MapEllipsis);

   /* check the first element for DECnet syntax */
   DECnet = false;
   for (cptr = ElementsPtr; *cptr; cptr++)
   {
      if (!SAME2(cptr,'::')) continue;
      DECnet = true;
      if (WATCH_MODULE(WATCH_MOD_MAPURL))
         WatchThis (WATCHALL, 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++;
      /* avoid accidentally creating a DECnet ("::") specification */
      if (*(sptr-1) != ':' && 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 (!SAME2(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 */
      SET2(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 MapOdsVmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd,
int PathOds
)
{
   int  len;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsVmsToUrl() !UL !&B !&Z !UL",
                 PathOds, AbsorbMfd, VmsPtr, SizeOfPathPtr);

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

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

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

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

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

      default :
         memcpy (PathPtr, "/sanity/check/MapOdsUrlToVms", 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 MapOds2VmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOds2VmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && !SAME2(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 (SAME2(cptr,'::'))
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (SAME2(cptr,':['))
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && (MATCH7(cptr,"000000]") || MATCH7(cptr,"000000.")))
               cptr += 6;
            else
               *sptr++ = '/';
            continue;
         }

         if (*cptr == '.')
         {
            if (SAME3(cptr,'...'))
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               *sptr++ = '/';
               if (SAME3(cptr,'.]['))
                  cptr += 3;
               else
                  cptr++;
            }
            continue;
         }

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

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

   /* copy the file component */
   while (*cptr && sptr < zptr) *sptr++ = to_lower(*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
   SET2(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 MapOds5VmsToUrl
(
char *PathPtr,
char *VmsPtr,
int SizeOfPathPtr,
BOOL AbsorbMfd
)
{
   unsigned char  ch;
   char  *cptr, *eptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOds5VmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && !SAME2(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 (SAME2(cptr,'::'))
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (SAME2(cptr,':['))
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && (MATCH7(cptr,"000000]") || MATCH7(cptr,"000000.")))
               cptr += 6;
            else
               *sptr++ = '/';
            continue;
         }

         if (*cptr == '.')
         {
            if (SAME3(cptr,'...'))
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               *sptr++ = '/';
               if (SAME3(cptr,'.]['))
                  cptr += 3;
               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 ' ' :
               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 (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
                     ch = (to_lower(*cptr) - (int)'a' + 10) << 4;
                  if (*cptr) cptr++;
                  if (*cptr >= '0' && *cptr <= '9')
                     ch += (*cptr - (int)'0');
                  else
                  if (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
                     ch += (to_lower(*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 (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
               ch = (to_lower(*cptr) - (int)'a' + 10) << 4;
            if (*cptr) cptr++;
            if (*cptr >= '0' && *cptr <= '9')
               ch += (*cptr - (int)'0');
            else
            if (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
               ch += (to_lower(*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
   SET2(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 MapOdsAdsVmsToUrl
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsAdsVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && !SAME2(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 (SAME2(cptr,'::'))
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            loCase = true;
            continue;
         }

         if (SAME2(cptr,':['))
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && (MATCH7(cptr,"000000]") || MATCH7(cptr,"000000.")))
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            loCase = true;
            continue;
         }

         if (*cptr == '.')
         {
            if (SAME3(cptr,'...'))
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               *sptr++ = '/';
               if (SAME3(cptr,'.]['))
                  cptr += 3;
               else
                  cptr++;
            }
            loCase = true;
            continue;
         }

         if (*cptr == ']')
         {
            cptr++;
            *sptr++ = '/';
            isDevice = false;
            loCase = true;
            break;
         }

         /* don't need to be concerned with periods in directories */
         if (!SAME2(cptr,'__') || isDevice)
         {
            if (loCase)
               *sptr++ = to_lower(*cptr++);
            else
               *sptr++ = to_upper(*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 (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
            ch = (to_lower(*cptr) - (int)'a' + 10) << 4;
         else
            ch = '?';
         if (*cptr) cptr++;   
         if (*cptr >= '0' && *cptr <= '9')
            ch += (*cptr - (int)'0');
         else
         if (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
            ch += (to_lower(*cptr) - (int)'a' + 10);
         else
            ch = '?';
         if (*cptr) cptr++;
         if (loCase)
            *sptr++ = to_lower(ch);
         else
            *sptr++ = to_upper(ch);
      }
   }

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

      /* need to cater for possible '__xx', '_._xx', '__.xx' */
      if (!SAME2(cptr,'__') && !SAME2(cptr,'_.'))
      {
         /* an isolated underscore */
         if (sptr < zptr) *sptr++ = *cptr++;
         continue;
      }
      cptr++;
      if (SAME2(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 (SAME2(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 (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
         ch = (to_lower(*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 (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
         ch += (to_lower(*cptr) - (int)'a' + 10);
      else
         ch = '?';
      if (*cptr) cptr++;
      if (loCase)
         *sptr++ = to_lower(ch);
      else
         *sptr++ = to_upper(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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
   SET2(PathPtr,'\0\0');
   return (0);
}

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

{
   static char  AdsFileType [96];

   int  Length;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsAdsFileType() !&Z", FileType);

   Length = MapOdsAdsVmsToUrl (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 MapOdsPwkVmsToUrl
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsPwkVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && !SAME2(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 (SAME2(cptr,'::'))
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            continue;
         }

         if (SAME2(cptr,':['))
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && (MATCH7(cptr,"000000]") || MATCH7(cptr,"000000.")))
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (SAME3(cptr,'...'))
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               *sptr++ = '/';
               if (SAME3(cptr,'.]['))
                  cptr += 3;
               else
                  cptr++;
            }
            continue;
         }

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

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

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

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

      /* convert from PATHWORKS encoding */
      cptr++;
      ch = 0;
      if (*cptr >= '0' && *cptr <= '9')
         ch = (*cptr - (int)'0') << 4;
      else
      if (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
         ch = (to_lower(*cptr) - (int)'a' + 10) << 4;
      else
         ch = '?';
      if (*cptr) cptr++;   
      if (*cptr >= '0' && *cptr <= '9')
         ch += (*cptr - (int)'0');
      else
      if (to_lower(*cptr) >= 'a' && to_lower(*cptr) <= 'f')
         ch += (to_lower(*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
   SET2(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 MapOdsSriVmsToUrl
(
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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsSriVmsToUrl() !&B !&Z !UL",
                 AbsorbMfd, VmsPtr, SizeOfPathPtr);

   zptr = (sptr = PathPtr) + SizeOfPathPtr;

   /* look for indications it's a full file specification */
   cptr = VmsPtr;
   while (*cptr && !SAME2(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 (SAME2(cptr,'::'))
         {
            /* DECnet */
            *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = *cptr++;
            if (sptr < zptr) *sptr++ = '/';
            loCase = true;
            continue;
         }

         if (SAME2(cptr,':['))
         {
            cptr++;
            cptr++;
            /* remove any reference to a Master File Directory */
            if (AbsorbMfd && (MATCH7(cptr,"000000]") || MATCH7(cptr,"000000.")))
               cptr += 6;
            else
               *sptr++ = '/';
            isDevice = false;
            loCase = true;
            continue;
         }

         if (*cptr == '.')
         {
            *sptr++ = '/';
            if (SAME3(cptr,'...'))
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            {
               *sptr++ = '/';
               if (SAME3(cptr,'.]['))
                  cptr += 3;
               else
                  cptr++;
            }
            loCase = true;
            continue;
         }

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

         if (isDevice)
            *sptr++ = to_lower(*cptr++);
         else
         if (*cptr == '$')
            *sptr++ = MapOdsSriCharVmsToUrl (&cptr, &loCase);
         else
         if (loCase)
            *sptr++ = to_lower(*cptr++);
         else
            *sptr++ = to_upper(*cptr++);
      }
   }

   /* copy the file component */
   loCase = true;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '.') loCase = true;
      if (*cptr == '$')
         *sptr++ = MapOdsSriCharVmsToUrl (&cptr, &loCase);
      else
      if (loCase)
         *sptr++ = to_lower(*cptr++);
      else
         *sptr++ = to_upper(*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 (WATCHALL, WATCH_MOD_MAPURL, "OVERFLOW");
   SET2(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 MapOdsSriCharVmsToUrl
(
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, 0x28, 
     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 (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsSriCharVmsToUrl() !&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 = to_lower(*cptr++);
         else
            ch = to_upper(*cptr++);
      }
      else
      if (*cptr == '$')
         ch = *cptr++;
      else
      {
         ch = '?';
         cptr++;
      }
   }
   else
   if (*loCasePtr)
      ch = to_lower(*cptr++);
   else
      ch = to_upper(*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* MapOdsSriFileType (char *FileType)

{
   static char  SriFileType [96];

   int  Length;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsSriFileType() !&Z", FileType);

   Length = MapOdsSriVmsToUrl (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);
}

/*****************************************************************************/
/*
Map something like ODS-2:[dir1.dir2]name.TYPE to /ODS-2/dir1/dir2/name.TYPE 
and ODS-5:[dir1.dir2]name^.dot.TYPE to /ODS-5/dir1/dir2/name.dot.TYPE
suitable for use by the C-RTL.  Returns a VMS status.
*/ 
 
int MapOdsVmsToUnix
(
char *VmsFileSpec,
char *UnixFileSpec,
int SizeOfUnixFileSpec
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_MAPURL))
      WatchThis (WATCHALL, WATCH_MOD_MAPURL,
                 "MapOdsVmsToUnix() !&Z !UL", VmsFileSpec, SizeOfUnixFileSpec);

   zptr = (sptr = UnixFileSpec) + SizeOfUnixFileSpec;

   if (!VmsFileSpec[0])
   {
      *sptr = '\0';
      return (SS$_NORMAL);
   }

   if (sptr < zptr) *sptr++ = '/';
   cptr = VmsFileSpec;
   while (*cptr && *cptr != ':' && sptr < zptr)
   {
      if (*cptr == '^') cptr++;
      if (*cptr) *sptr++ = *cptr++;
   }
   if (*cptr == ':') cptr++;
   if (*cptr == '[') cptr++;
   if (sptr < zptr) *sptr++ = '/';
   while (*cptr && *cptr != ']' && sptr < zptr)
   {
      while (*cptr && *cptr != '.' && *cptr != ']' && sptr < zptr)
      {
         if (*cptr == '^') cptr++;
         if (*cptr) *sptr++ = *cptr++;
      }
      if (*cptr == '.')
      {
         if (sptr < zptr) *sptr++ = '/';
         cptr++;
      }
   }
   if (*cptr == ']') cptr++;
   if (sptr < zptr) *sptr++ = '/';
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '^') cptr++;
      if (*cptr) *sptr++ = *cptr++;
   }
   if (sptr >= zptr) return (SS$_RESULTOVF);
   *sptr = '\0';
   return (SS$_NORMAL);
}

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

