1/* 2 * Copyright (c) 2002. Joe Marcus Clarke (marcus@marcuscom.com) 3 * All Rights Reserved. See COPYRIGHT. 4 * 5 * mangle, demangle (filename): 6 * mangle or demangle filenames if they are greater than the max allowed 7 * characters for a given version of AFP. 8 */ 9 10#ifdef HAVE_CONFIG_H 11#include "config.h" 12#endif /* HAVE_CONFIG_H */ 13 14#include <stdio.h> 15#include <ctype.h> 16 17#include <atalk/util.h> 18#include <atalk/bstradd.h> 19 20#include "mangle.h" 21#include "desktop.h" 22 23 24#define hextoint( c ) ( isdigit( c ) ? c - '0' : c + 10 - 'A' ) 25#define isuxdigit(x) (isdigit(x) || (isupper(x) && isxdigit(x))) 26 27static size_t mangle_extension(const struct vol *vol, const char* uname, 28 char* extension, charset_t charset) 29{ 30 char *p = strrchr(uname, '.'); 31 32 if (p && p != uname) { 33 u_int16_t flags = CONV_FORCE | CONV_UNESCAPEHEX; 34 size_t len = convert_charset(vol->v_volcharset, charset, 35 vol->v_maccharset, p, strlen(p), 36 extension, MAX_EXT_LENGTH, &flags); 37 38 if (len != (size_t)-1) return len; 39 } 40 return 0; 41} 42 43static char *demangle_checks(const struct vol *vol, char* uname, char * mfilename, size_t prefix, char * ext) 44{ 45 u_int16_t flags; 46 static char buffer[MAXPATHLEN +2]; /* for convert_charset dest_len parameter +2 */ 47 size_t len; 48 size_t mfilenamelen; 49 50 /* We need to check, whether we really need to demangle the filename */ 51 /* i.e. it's not just a file with a valid #HEX in the name ... */ 52 /* but we don't want to miss valid demangle as well. */ 53 /* check whether file extensions match */ 54 55 char buf[MAX_EXT_LENGTH + 2]; /* for convert_charset dest_len parameter +2 */ 56 size_t ext_len = mangle_extension(vol, uname, buf, CH_UTF8_MAC); 57 58 if (ext_len) { 59 buf[ext_len] = '\0'; 60 if (strcmp(ext, buf)) 61 return mfilename; 62 } else { 63 if (*ext) 64 return mfilename; 65 } 66 67 /* First we convert the unix name to our volume maccharset */ 68 /* This assumes, OSX will not send us a mangled name for *any* */ 69 /* other reason than a hint/filename mismatch on the OSX side!! */ 70 /* If the filename needed mangling, we'll get the mac filename */ 71 /* till the first unconvertable char, so these have to match */ 72 /* the mangled name we got .. */ 73 74 flags = CONV_IGNORE | CONV_UNESCAPEHEX; 75 if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, vol->v_maccharset, 0, 76 uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) { 77 return mfilename; 78 } 79 /* If the filename is too long we also needed to mangle */ 80 mfilenamelen = strlen(mfilename); 81 if ( len >= vol->max_filename || mfilenamelen == MACFILELEN ) { 82 flags |= CONV_REQMANGLE; 83 len = prefix; 84 } 85 86 /* Ok, mangling was needed, now we do some further checks */ 87 /* this is still necessary, as we might have a file abcde:xx */ 88 /* with id 12, mangled to abcde#12, and a passed filename */ 89 /* abcd#12 */ 90 /* if we only checked if "prefix" number of characters match */ 91 /* we get a false postive in above case */ 92 93 if ( (flags & CONV_REQMANGLE) ) { 94 if (len) { 95 /* convert the buffer to UTF8_MAC ... */ 96 if ((size_t) -1 == (len = convert_charset(vol->v_maccharset, CH_UTF8_MAC, 0, 97 buffer, len, buffer, MAXPATHLEN, &flags)) ) { 98 return mfilename; 99 } 100 /* Now compare the two names, they have to match the number of characters in buffer */ 101 /* prefix can be longer than len, OSX might send us the first character(s) of a */ 102 /* decomposed char as the *last* character(s) before the #, so our match below will */ 103 /* still work, but leaves room for a race ... FIXME */ 104 if ( (prefix >= len || mfilenamelen == MACFILELEN) 105 && !strncmp (mfilename, buffer, len)) { 106 return uname; 107 } 108 } 109 else { 110 /* We couldn't convert the name to maccharset at all, so we'd expect a name */ 111 /* in the "???#ID" form ... */ 112 if ( !strncmp("???", mfilename, prefix)) { 113 return uname; 114 } 115 /* ..but OSX might send us only the first characters of a decomposed character. */ 116 /* So convert to UTF8_MAC again, now at least the prefix number of */ 117 /* characters have to match ... again a possible race FIXME */ 118 119 if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, CH_UTF8_MAC, 0, 120 uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) { 121 return mfilename; 122 } 123 124 if ( !strncmp (mfilename, buffer, prefix) ) { 125 return uname; 126 } 127 } 128 } 129 return mfilename; 130} 131 132/* ------------------------------------------------------- 133*/ 134static char * 135private_demangle(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *osx) 136{ 137 char *t; 138 char *u_name; 139 u_int32_t id, file_id; 140 static char buffer[12 + MAXPATHLEN + 1]; 141 int len = 12 + MAXPATHLEN + 1; 142 struct dir *dir; 143 size_t prefix; 144 145 id = file_id = 0; 146 147 t = strchr(mfilename, MANGLE_CHAR); 148 if (t == NULL) { 149 return mfilename; 150 } 151 prefix = t - mfilename; 152 /* FIXME 153 * is prefix == 0 a valid mangled filename ? 154 */ 155 /* may be a mangled filename */ 156 t++; 157 if (*t == '0') { /* can't start with a 0 */ 158 return mfilename; 159 } 160 while(isuxdigit(*t)) { 161 id = (id *16) + hextoint(*t); 162 t++; 163 } 164 if ((*t != 0 && *t != '.') || strlen(t) > MAX_EXT_LENGTH || id < 17) { 165 return mfilename; 166 } 167 168 file_id = id = htonl(id); 169 if (osx) { 170 *osx = id; 171 } 172 173 /* is it a dir?, there's a conflict with pre OSX 'trash #2' */ 174 if ((dir = dirlookup(vol, id))) { 175 if (dir->d_pdid != did) { 176 /* not in the same folder, there's a race with outdate cache 177 * but we have to live with it, hopefully client will recover 178 */ 179 return mfilename; 180 } 181 if (!osx) { 182 /* it's not from cname so mfilename and dir must be the same */ 183 if (strcmp(cfrombstr(dir->d_m_name), mfilename) == 0) { 184 return cfrombstr(dir->d_u_name); 185 } 186 } else { 187 return demangle_checks(vol, cfrombstr(dir->d_u_name), mfilename, prefix, t); 188 } 189 } 190 else if (NULL != (u_name = cnid_resolve(vol->v_cdb, &id, buffer, len)) ) { 191 if (id != did) { 192 return mfilename; 193 } 194 if (!osx) { 195 /* convert back to mac name and check it's the same */ 196 t = utompath(vol, u_name, file_id, utf8_encoding()); 197 if (!strcmp(t, mfilename)) { 198 return u_name; 199 } 200 } 201 else { 202 return demangle_checks (vol, u_name, mfilename, prefix, t); 203 } 204 } 205 206 return mfilename; 207} 208 209/* ------------------------------------------------------- 210*/ 211char * 212demangle(const struct vol *vol, char *mfilename, cnid_t did) 213{ 214 return private_demangle(vol, mfilename, did, NULL); 215} 216 217/* ------------------------------------------------------- 218 * OS X 219*/ 220char * 221demangle_osx(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *fileid) 222{ 223 return private_demangle(vol, mfilename, did, fileid); 224} 225 226/* ------------------------------------------------------- 227 FIXME !!! 228 229 Early Mac OS X (10.0-10.4.?) had the limitation up to 255 Byte. 230 Current implementation is: 231 volcharset -> UTF16-MAC -> truncated 255 UTF8-MAC 232 233 Recent Mac OS X (10.4.?-) don't have this limitation. 234 Desirable implementation is: 235 volcharset -> truncated 510 UTF16-MAC -> UTF8-MAC 236 237 ------------------------ 238 with utf8 filename not always round trip 239 filename mac filename too long or first chars if unmatchable chars. 240 uname unix filename 241 id file/folder ID or 0 242*/ 243char * 244mangle(const struct vol *vol, char *filename, size_t filenamelen, char *uname, cnid_t id, int flags) { 245 char *m = NULL; 246 static char mfilename[MAXPATHLEN]; /* way > maxlen */ 247 char mangle_suffix[MANGLE_LENGTH + 1]; 248 char ext[MAX_EXT_LENGTH +2]; /* for convert_charset dest_len parameter +2 */ 249 size_t ext_len; 250 size_t maxlen; 251 int k; 252 253 maxlen = (flags & 2)?UTF8FILELEN_EARLY:MACFILELEN; /* was vol->max_filename */ 254 /* Do we really need to mangle this filename? */ 255 if (!(flags & 1) && filenamelen <= maxlen) { 256 return filename; 257 } 258 259 if (!id) { 260 /* we don't have the file id! only catsearch call mangle with id == 0 */ 261 return NULL; 262 } 263 /* First, attempt to locate a file extension. */ 264 ext_len = mangle_extension(vol, uname, ext, (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset); 265 m = mfilename; 266 k = sprintf(mangle_suffix, "%c%X", MANGLE_CHAR, ntohl(id)); 267 268 if (filenamelen + k + ext_len > maxlen) { 269 u_int16_t opt = CONV_FORCE | CONV_UNESCAPEHEX; 270 size_t n = convert_charset(vol->v_volcharset, 271 (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset, 272 vol->v_maccharset, uname, strlen(uname), 273 m, maxlen - k - ext_len, &opt); 274 m[n != (size_t)-1 ? n : 0] = 0; 275 } else { 276 strlcpy(m, filename, filenamelen + 1); 277 } 278 if (*m == 0) { 279 strcat(m, "???"); 280 } 281 strcat(m, mangle_suffix); 282 if (ext_len) { 283 strncat(m, ext, ext_len); 284 } 285 286 return m; 287} 288