/*---------------------------------------------------------------------------

  mapname.c

  This routine changes DEC-20, VAX/VMS, and DOS-style filenames into normal
  Unix names (and vice versa, in some cases); it also creates any necessary 
  directories, if the -d switch was specified.

  ---------------------------------------------------------------------------

  Notes:

     - This routine REALLY needs to be rewritten (different routines for
       each output OS, with different rules for different parts of the path
       name).  If each zip program stores local-format names (like the VMS
       one did at one time), it would probably be best to convert to an in-
       termediate format first (assuming we're not extracting under the same
       OS as that under which the zipfile was created), then from that to
       the current operating system's format.
     - The strcpy and strcat operations on both cdp and filename may over-
       write memory, since they don't check lengths.  With a kilobyte in
       which to work, this is probably not that big a deal, but it could
       cause problems eventually.

  ---------------------------------------------------------------------------*/


#include "unzip.h"


/*******************/
/* Mapname Defines */
/*******************/

#ifdef VMS
#  define PERMS   0
#else
#  define PERMS   0777
#endif

#ifndef NO_MKDIR
#  if (defined(DOS_OS2) && !defined(__GO32__))
#    if (_MSC_VER >= 600)       /* have special MSC mkdir prototype */
#      include <direct.h>
#    else                       /* own prototype because dir.h conflicts? */
       int mkdir(const char *path);
#    endif /* ?(MSC 6.0 or later) */
#    define MKDIR(path,mode)   mkdir(path)
#  else /* !DOS_OS2 || __GO32__ */
#    ifdef MACOS
#      define MKDIR(path,mode)   macmkdir(path,gnVRefNum,glDirID)
#    else /* !MACOS */
#      define MKDIR(path,mode)   mkdir(path,mode)
#    endif /* ?MACOS */
#  endif /* ?(DOS_OS2 && !__GO32__)  */
#endif /* !NO_MKDIR */




/************************/
/*  Function mapname()  */
/************************/

int mapname(create_dirs)   /* return 0 if no error, 1 if caution (filename */
    int create_dirs;       /*  truncated), 2 if warning (skip file because */
{                          /*  dir doesn't exist), 3 if error (skip file) */
#ifdef NO_MKDIR
    char command[FILNAMSIZ+40]; /* buffer for system() call */
#endif
#ifdef VMS
    int stat_val;               /* temp. holder for stat() return value */
    char *dp, *xp;              /* pointers to directory name */
    char *np;                   /* pointer into filename */
#endif /* VMS */
#ifdef DOS_VMS
    char *last_dot=NULL;        /* last dot not converted to underscore */
#endif /* DOS_VMS */
#ifdef OS2
    char *last;
    extern char longfilename[]; /*  AFTER file created and closed */
    extern int longname;        /* used also in file_io.c:  set EAs */
    int longdir;
#endif /* OS2 */
    char name[FILNAMSIZ];       /* file name buffer */
    char *pp, *cp, *cdp;        /* character pointers */
    char delim = '\0';          /* directory delimiter */
    int quote = FALSE;          /* flags */
    int indir = FALSE;
    int done = FALSE;
    int created = FALSE;
    register unsigned workch;   /* hold the character being tested */


/*---------------------------------------------------------------------------
    Initialize various pointers and counters and stuff.
  ---------------------------------------------------------------------------*/

#ifdef MAP_DEBUG
    fprintf(stderr, "%s ", filename);   /* echo name of this file */
#endif
    cdp = (char *)NULL;
    pp = name;                  /* point to translation buffer */
    *name = '\0';               /* initialize buffer */
    if (!jflag) {               /* -j => junk pathnames */
        cdp = (char *)malloc(strlen(filename) + 3);   /* place for holding */
        if (cdp == (char *)NULL) {                    /*  directory name */
            fprintf(stderr, "mapname:  out of memory [%s]\n", filename);
            return 3;
        }
#ifdef VMS
        *cdp++ = '[';
        xp = cdp;               /* always points to last non-NULL char */
        *cdp++ = '.';
#endif /* VMS */
#ifdef MACOS
        *cdp = ':';             /* the Mac uses ':' as a directory separator */
        cdp[1] = '\0';
#else /* !MACOS */
        *cdp = '\0';
#endif /* ?MACOS */
    }

/*---------------------------------------------------------------------------
    Begin main loop through characters in filename.
  ---------------------------------------------------------------------------*/

    for (cp = filename; (workch = (unsigned char) *cp++) != 0  &&  !done;) {

        if (quote) {                 /* if char quoted, */
            *pp++ = (char) workch;   /*  include it literally */
            quote = FALSE;
        } else if (indir) {          /* if in directory name, */
            if (workch == (unsigned)delim)  /*  look for end delimiter */
                indir = FALSE;
        } else
            switch (workch) {
            case '<':                /* discard DEC-20 directory name */
                indir = TRUE;
                delim = '>';
                break;
            case '[':                /* discard VMS directory name */
                indir = TRUE;
                delim = ']';
                break;
            case '/':                /* discard Unix path name  */
            case '\\':               /*  or MS-DOS path name... */
                                     /*  iff -j flag was given  */
                /*
                 * Special processing case:  if -j flag was not specified on
                 * command line and create_dirs is TRUE, create any necessary
                 * directories included in the pathname.  Creation of dirs is
                 * straightforward on BSD and MS-DOS machines but requires use
                 * of the system() command on SysV systems (or any others which
                 * don't have mkdir()).  The stat() check is necessary with
                 * MSC because it doesn't have an EEXIST errno, and it saves
                 * the overhead of multiple system() calls on SysV machines.
                 */

                if (!jflag) {
                    *pp = '\0';
#ifdef VMS
                    dp = name;
                    while (*++xp = *dp++);  /* copy name to cdp */
                    last_dot = NULL;        /* dir name:  no dots allowed */
                    strcpy(xp, ".dir");     /* add extension for stat check */
                    stat_val = stat(cdp, &statbuf);
                    *xp = '\0';             /* remove extension for all else */
                    if (stat_val) {         /* doesn't exist, so create */
#else /* !VMS */
#ifdef MSDOS
                    if (last_dot != NULL) {  /* one dot in dir name is legal */
                        *last_dot = '.';
                        last_dot = NULL;
                    }
#endif /* MSDOS */
                    strcat(cdp, name);
#ifdef OS2
                    if ((longdir = !IsFileNameValid(cdp)) != 0) {
                        last = strrchr(cdp, '/');
                        strcpy(longfilename, last ? last + 1 : cdp);
                        fprintf(stderr, "renaming directory \"%s\"", cdp);
                        ChangeNameForFAT(cdp);
                        fprintf(stderr, " to \"%s\"\n", cdp);
                    }
#endif /* OS2 */
                    if (stat(cdp, &statbuf)) {  /* doesn't exist, so create */
#endif /* ?VMS */
                        if (!create_dirs) { /* told not to create (freshening) */
							free(cdp);
                            return 2;
                        }
#ifdef NO_MKDIR
                        sprintf(command,
                          "IFS=\" \t\n\" /bin/mkdir %s 2>/dev/null", cdp);
                        if (system(command)) {
#else /* !NO_MKDIR */
                        if (MKDIR(cdp, PERMS) == -1) {
#endif /* ?NO_MKDIR */
                            perror(cdp);
                            free(cdp);
                            fprintf(stderr, "mapame:  unable to process [%s]\n",
                              filename);
                            return 3;
                        }
                        created = TRUE;
#ifdef OS2
                        if (longdir)
                            SetLongNameEA(cdp, longfilename);
#endif /* OS2 */
                    } else if (!(statbuf.st_mode & S_IFDIR)) {
                        fprintf(stderr,
                          "mapname:  %s exists but is not a directory\n", cdp);
                        free(cdp);
                        fprintf(stderr, "mapame:  unable to process [%s]\n",
                          filename);
                        return 3;
                    }
#ifdef VMS
                    *xp = '/';  /* for now... (mkdir()) */
#else /* !VMS */
#ifdef MACOS
                    strcat(cdp, ":");
#else /* !MACOS */
                    strcat(cdp, "/");
#endif /* ?MACOS */
#endif /* ?VMS */
                }
                pp = name;
                break;
            case ':':
#ifdef UNIX                       /* colon is a valid character in Unix */
                *pp++ = workch;   /*  filenames, so keep it; anywhere else, */
#else /* !UNIX */                 /*  change it to an underscore (should  */
                *pp++ = '_';      /*  NOT have stored drive/node names!!) */
#endif /* ?UNIX */
             /* pp = name;  (OLD) discard DEC dev: or node:: name */
                break;
            case '.':                   /* DEC-20 generation number or */
#ifdef DOS_VMS                          /*  MS-DOS or VMS separator */
                last_dot = pp;          /* point at last dot so far... */
                *pp++ = '_';            /* convert dot to underscore */
#else /* !DOS_VMS */
                *pp++ = workch;
#endif /* ?DOS_VMS */
                break;
            case ';':                   /* VMS generation or DEC-20 attrib */
#ifdef MACOS
                if (V_flag || macflag)
#else /* !MACOS */
                if (V_flag)                 /* if requested, save VMS ";##" */
#endif /* ?MACOS */                         /*  version info or Macintosh */
                    *pp++ = (char) workch;  /*  (?) info; otherwise discard */
                else                        /*  everything starting with */
                    done = TRUE;            /*  semicolon.  (Worry about */
                break;                      /*  DEC-20 later.) */
            case '\026':                /* control-V quote for special chars */
                quote = TRUE;           /* set flag for next character */
                break;
            case ' ':
#if (defined(VMS) || defined(MTS))
                *pp++ = '_';            /* change spaces to underscore */
#else /* !(VMS || MTS) */               /*  under VMS and MTS, and under DOS */
#ifdef DOS_OS2                          /*  and OS/2 if -s not specified. */
                if (!sflag)
                    *pp++ = '_';
                else
#endif /* DOS_OS2 */
                *pp++ = (char) workch;  /* otherwise, leave as spaces */
#endif /* ?(VMS || MTS) */
                break;
            default:
#ifdef MACOS
                if ((macflag && ((unsigned)workch > 0x1F)) || isprint(workch))
#else /* !MACOS */
#if (defined(DOS_OS2) || (defined(UNIX) && !defined(VMS)))  /* allow non-US */
                if (isprint(workch) || (128 <= workch && workch <= 254))
#else /* !(DOS_OS2 || UNIX) */
                if (isprint(workch))    /* other printable, just keep */
#endif /* ?(DOS_OS2 || UNIX) */
#endif /* ?MACOS */
                    *pp++ = (char) workch;
            } /* end switch */
    } /* end for loop */
    *pp = '\0';                         /* done with name:  terminate it */
#ifdef DOS_VMS                          /*  and put a dot back in if VMS */
    if (last_dot != NULL)               /*  or MS-DOS */
        *last_dot = '.';
#endif /* DOS_VMS */

/*---------------------------------------------------------------------------
    We COULD check for existing names right now, create a "unique" name, etc.
    At present, we do this in extract_or_test_files() (immediately after we
    return from here).  If conversion went bad, the name'll either be nulled
    out (in which case we'll return non-0), or following procedures won't be
    able to create the extracted file and other error msgs will result.
  ---------------------------------------------------------------------------*/

    if (filename[strlen(filename) - 1] == '/') {
        /* A directory was extracted. It had a trailing /, 
         * don't report the error below. */
        if (created) {
            printf("   Creating: %s", filename);
#ifdef OS2
            SetPathInfo(filename, lrec.last_mod_file_date,
                                  lrec.last_mod_file_time, -1);
            if (extra_field)
                SetEAs(filename, extra_field);
#endif
            printf("\n");
        }
		if (!jflag) 
			free(cdp);
        return 2; /* but skip file */
    }

    if (*name == '\0') {
        fprintf(stderr, "mapname:  conversion of [%s] failed\n", filename);
		if (!jflag) 
			free(cdp);
        return 3;
    }

#ifdef OS2
    if (!longname && ((longname = !IsFileNameValid(name)) != 0)) {
        /* in case of second call after user renamed the file, skip this */
        last = strrchr(name, '/');      /* THIS time, save for file_io */
        last = last ? last + 1 : name;  /* only last component */
        strcpy(longfilename, last);
        fprintf(stderr, "renaming \"%s\"", name);
        ChangeNameForFAT(last);
        fprintf(stderr, " to \"%s\"\n", name);
    }
#endif /* OS2 */

#ifdef VMS
    /* convert filename to legal VMS one, substituting underscores for
     * all invalid characters */
    for (np = name;  *np;  ++np)
        if (!(isdigit(*np) || isalpha(*np) || (*np == '$') ||
            (*np == '-') || (*np == '_') || (*np == '.') || (*np == ';')))
            *np = '_';
#endif /* VMS */

    if (!jflag) {
#ifdef VMS
        *xp++ = ']';                 /* proper end-of-dir-name delimiter */
        if (xp == cdp) {             /* no path-name stuff, so... */
            strcpy(filename, name);  /* copy file name into global */
            cdp -= 2;                /*   prepare to free malloc'd space */
        } else {                     /* we've added path-name stuff... */
            *xp = '\0';              /*   so terminate and convert to */
            dp = cdp;                /*   VMS subdir separators (skip */
            while (*++dp)            /*   first char:  better not be */
                if (*dp == '/')      /*   "/"):  change all slashes */
                    *dp = '.';       /*   to dots */
            cdp -= 2;                /*   include leading bracket and dot */
            strcpy(filename, cdp);   /* copy VMS-style path name into global */
            strcat(filename, name);  /* concatenate file name to global */
        }
#else /* !VMS */
        strcpy(filename, cdp);       /* either "" or slash-terminated path */
        strcat(filename, name);      /* append file name to path name */
#endif /* ?VMS */
        free(cdp);
    } else
        strcpy(filename, name);      /* copy converted name into global */

#if PATH_MAX < (FILNAMSIZ - 1)
    /* check the length of the file name and truncate if necessary */
    if (PATH_MAX < strlen(filename)) {
        fprintf(stderr, "caution:  truncating filename\n");
        filename[PATH_MAX] = '\0';
        fprintf(stderr, "[ %s ]\n", filename);
        return 1;             /* 1:  warning error */
    }
#endif

    return 0;
}
