/***************************************************************************** * * $Id$ * Purpose ...............: Mangle a unix name to DOS 8.3 filename * ***************************************************************************** * Copyright (C) 1997-2002 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***************************************************************************** * Ideas taken from Samba, Copyright (C) Andrew Tridgell 1992-1998 *****************************************************************************/ #include "../config.h" #include "libs.h" #include "structs.h" #include "clcomm.h" #include "common.h" /* * Prototype functions */ int strhaslower(const char *); char *safe_strcpy(char *, const char *, size_t); static void init_chartest(void); static int is_reserved_msdos(char *); int is_8_3(char *); #define PTR_DIFF(p1,p2) ((int)(((const char *)(p1)) - (const char *)(p2))) /* -------------------------------------------------------------------------- ** * Other stuff... * * magic_char - This is the magic char used for mangling. It's global. * * MANGLE_BASE - This is the number of characters we use for name mangling. * * basechars - The set characters used for name mangling. This * is static (scope is this file only). * * mangle() - Macro used to select a character from basechars (i.e., * mangle(n) will return the nth digit, modulo MANGLE_BASE). * * chartest - array 0..255. The index range is the set of all possible * values of a byte. For each byte value, the content is a * two nibble pair. See BASECHAR_MASK and ILLEGAL_MASK, * below. * * ct_initialized - False until the chartest array has been initialized via * a call to init_chartest(). * * BASECHAR_MASK - Masks the upper nibble of a one-byte value. * * ILLEGAL_MASK - Masks the lower nibble of a one-byte value. * * isillegal() - Given a character, check the chartest array to see * if that character is in the illegal characters set. * This is faster than using strchr(). * */ char magic_char = '~'; static char basechars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%"; #define MANGLE_BASE (sizeof(basechars)/sizeof(char)-1) static unsigned char chartest[256] = { 0 }; static int ct_initialized = FALSE; #define mangle(V) ((char)(basechars[(V) % MANGLE_BASE])) #define BASECHAR_MASK 0xf0 #define ILLEGAL_MASK 0x0f #define isillegal(C) ( (chartest[ ((C) & 0xff) ]) & ILLEGAL_MASK ) /**************************************************************************** does a string have any lowercase chars in it? ****************************************************************************/ int strhaslower(const char *s) { while (*s) { if (islower(*s)) return TRUE; s++; } return FALSE; } /******************************************************************* safe string copy into a known length string. maxlength does not include the terminating zero. ********************************************************************/ char *safe_strcpy(char *dest,const char *src, size_t maxlength) { size_t len; if (!dest) { Syslog('f', "ERROR: NULL dest in safe_strcpy"); return NULL; } if (!src) { *dest = 0; return dest; } len = strlen(src); if (len > maxlength) { WriteError("ERROR: string overflow by %d in safe_strcpy [%.50s]", (int)(len-maxlength), src); len = maxlength; } memcpy(dest, src, len); dest[len] = 0; return dest; } /* ************************************************************************** ** * Initialize the static character test array. * * Input: none * * Output: none * * Notes: This function changes (loads) the contents of the * array. The scope of is this file. * * ************************************************************************** ** */ static void init_chartest( void ) { char *illegalchars = (char *)"*\\/?<>|\":"; unsigned char *s; memset( (char *)chartest, '\0', 256 ); for( s = (unsigned char *)illegalchars; *s; s++ ) chartest[*s] = ILLEGAL_MASK; for( s = (unsigned char *)basechars; *s; s++ ) chartest[*s] |= BASECHAR_MASK; ct_initialized = TRUE; } /* init_chartest */ /* ************************************************************************** ** * Return True if a name is a special msdos reserved name. * * Input: fname - String containing the name to be tested. * * Output: True, if the name matches one of the list of reserved names. * * Notes: This is a static function called by is_8_3(), below. * * ************************************************************************** ** */ static int is_reserved_msdos( char *fname ) { char upperFname[13]; char *p; strncpy (upperFname, fname, 12); /* lpt1.txt and con.txt etc are also illegal */ p = strchr(upperFname,'.'); if (p) *p = '\0'; tu(upperFname); p = upperFname + 1; switch (upperFname[0]) { case 'A': if( 0 == strcmp( p, "UX" ) ) return TRUE; break; case 'C': if ((0 == strcmp( p, "LOCK$" )) || (0 == strcmp( p, "ON" )) || (0 == strcmp( p, "OM1" )) || (0 == strcmp( p, "OM2" )) || (0 == strcmp( p, "OM3" )) || (0 == strcmp( p, "OM4" ))) return TRUE; break; case 'L': if( (0 == strcmp( p, "PT1" )) || (0 == strcmp( p, "PT2" )) || (0 == strcmp( p, "PT3" ))) return TRUE; break; case 'N': if( 0 == strcmp( p, "UL" ) ) return TRUE; break; case 'P': if( 0 == strcmp( p, "RN" ) ) return TRUE; break; } return FALSE; } /* is_reserved_msdos */ /* ************************************************************************** ** * Return True if the name is a valid DOS name in 8.3 DOS format. * * Input: fname - File name to be checked. * check_case - If True, then the * name will be checked to see if all characters * are the correct case. * * Output: True if the name is a valid DOS name, else FALSE. * * ************************************************************************** ** */ int is_8_3( char *fname) { int len; int l, i; char *p; char *dot_pos; char *slash_pos = strrchr( fname, '/' ); /* If there is a directory path, skip it. */ if (slash_pos) fname = slash_pos + 1; len = strlen(fname); /* Can't be 0 chars or longer than 12 chars */ if ((len == 0) || (len > 12)) return FALSE; /* Mustn't be an MS-DOS Special file such as lpt1 or even lpt1.txt */ if (is_reserved_msdos(fname)) return FALSE; init_chartest(); for (i = 0; i < strlen(fname); i++) { if (isillegal(fname[i])) { Syslog('f', "Illegal character in filename"); return FALSE; } } /* Can't contain invalid dos chars */ p = fname; dot_pos = NULL; while (*p) { if (*p == '.' && !dot_pos) dot_pos = (char *)p; p++; } /* no dot and less than 9 means OK */ if (!dot_pos) return (len <= 8); l = PTR_DIFF(dot_pos, fname); /* base must be at least 1 char except special cases . and .. */ if (l == 0) return(0 == strcmp( fname, "." ) || 0 == strcmp( fname, ".." )); /* base can't be greater than 8 */ if (l > 8) return FALSE; if (len - l == 1 && !strchr( dot_pos + 1, '.' )) { *dot_pos = 0; return TRUE; } /* extension must be between 1 and 3 */ if ((len - l < 2 ) || (len - l > 4)) return FALSE; /* extensions may not have a dot */ if (strchr( dot_pos+1, '.' )) return FALSE; /* must be in 8.3 format */ return TRUE; } /***************************************************************************** * do the actual mangling to 8.3 format * the buffer must be able to hold 13 characters (including the null) ***************************************************************************** */ void mangle_name_83(char *s) { int crc16, i; char *p, *q; char extension[4]; char base[9]; int baselen = 0; int extlen = 0; extension[0] = 0; base[0] = 0; /* * First, convert some common Unix extensions to extensions of 3 * characters. If none fits, don't change anything now. * FIXME: should be in an external file. */ if (strcmp(q = s + strlen(s) - strlen(".tar.gz"), ".tar.gz") == 0) { *q = '\0'; q = (char *)"tgz"; } else if (strcmp(q = s + strlen(s) - strlen(".tar.z"), ".tar.z") == 0) { *q = '\0'; q = (char *)"tgz"; } else if (strcmp(q = s + strlen(s) - strlen(".tar.Z"), ".tar.Z") == 0) { *q = '\0'; q = (char *)"taz"; } else if (strcmp(q = s + strlen(s) - strlen(".html"), ".html") == 0) { *q = '\0'; q = (char *)"htm"; } else if (strcmp(q = s + strlen(s) - strlen(".shtml"), ".shtml") == 0) { *q = '\0'; q = (char *)"stm"; } else if (strcmp(q = s + strlen(s) - strlen(".conf"), ".conf") == 0) { *q = '\0'; q = (char *)"cnf"; } else if (strcmp(q = s + strlen(s) - strlen(".mpeg"), ".mpeg") == 0) { *q = '\0'; q = (char *)"mpg"; } else if (strcmp(q = s + strlen(s) - strlen(".smil"), ".smil") == 0) { *q = '\0'; q = (char *)"smi"; } else if (strcmp(q = s + strlen(s) - strlen(".perl"), ".perl") == 0) { *q = '\0'; q = (char *)"pl"; } else if (strcmp(q = s + strlen(s) - strlen(".jpeg"), ".jpeg") == 0) { *q = '\0'; q = (char *)"jpg"; } else if (strcmp(q = s + strlen(s) - strlen(".tiff"), ".tiff") == 0) { *q = '\0'; q = (char *)"tif"; } else { q = NULL; } if (q) { /* * Extension is modified, apply changes */ p = s + strlen(s); *p++ = '.'; for (i = 0; i < strlen(q); i++) *p++ = q[i]; *p++ = '\0'; // Syslog('f', "name with new extension => \"%s\"", s); } /* * Now start name mangling */ p = strrchr(s,'.'); if (p && (strlen(p+1) < (size_t)4)) { int all_normal = (!strhaslower(p+1)); /* XXXXXXXXX */ if (all_normal && p[1] != 0) { *p = 0; crc16 = crc16xmodem(s, strlen(s)); *p = '.'; } else { crc16 = crc16xmodem(s, strlen(s)); } } else { crc16 = crc16xmodem(s, strlen(s)); } // Syslog('f', "crc16xmodem(%s) %d", s, crc16); tu(s); if (p) { if (p == s) safe_strcpy(extension, "___", 3); else { *p++ = 0; while (*p && extlen < 3) { if (*p != '.' ) extension[extlen++] = p[0]; p++; } extension[extlen] = 0; } } p = s; /* * Changed to baselen 4, original this is 5. * 24-11-2002 MB. */ while (*p && baselen < 4) { if (*p != '.' ) base[baselen++] = p[0]; p++; } base[baselen] = 0; if (crc16 > (MANGLE_BASE * MANGLE_BASE * MANGLE_BASE)) Syslog('!', "WARNING: mangle_name_83() crc16 overflow"); crc16 = crc16 % (MANGLE_BASE * MANGLE_BASE * MANGLE_BASE); sprintf(s, "%s%c%c%c%c", base, magic_char, mangle(crc16 / (MANGLE_BASE * MANGLE_BASE)), mangle(crc16 / MANGLE_BASE), mangle(crc16)); if ( *extension ) { (void)strcat(s, "."); (void)strcat(s, extension); } } /***************************************************************************** * Convert a filename to DOS format. * * Input: OutName - Source *and* destination buffer. * * NOTE that OutName must point to a memory space that * is at least 13 bytes in size! That should always be * the case of course. * * **************************************************************************** */ void name_mangle(char *OutName) { char *p; p = xstrcpy(OutName); /* * check if it's already in 8.3 format */ if (!is_8_3(OutName)) { mangle_name_83(OutName); // Syslog('f',"name_mangle(%s) ==> [%s]", p, OutName); } else { /* * No mangling needed, convert to uppercase */ tu(OutName); } // Syslog('f',"name_mangle(%s) ==> [%s]", p, OutName); free(p); }