2 * Copyright (c) 2002. Joe Marcus Clarke (marcus@marcuscom.com)
3 * All Rights Reserved. See COPYRIGHT.
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.
12 #endif /* HAVE_CONFIG_H */
17 #include <atalk/util.h>
18 #include <atalk/bstradd.h>
24 #define hextoint( c ) ( isdigit( c ) ? c - '0' : c + 10 - 'A' )
25 #define isuxdigit(x) (isdigit(x) || (isupper(x) && isxdigit(x)))
27 static size_t mangle_extension(const struct vol *vol, const char* uname,
28 char* extension, charset_t charset)
30 char *p = strrchr(uname, '.');
32 if (p && p != uname) {
33 uint16_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);
38 if (len != (size_t)-1) return len;
43 static char *demangle_checks(const struct vol *vol, char* uname, char * mfilename, size_t prefix, char * ext)
46 static char buffer[MAXPATHLEN +2]; /* for convert_charset dest_len parameter +2 */
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 */
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);
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 .. */
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)) ) {
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;
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 */
90 /* if we only checked if "prefix" number of characters match */
91 /* we get a false postive in above case */
93 if ( (flags & CONV_REQMANGLE) ) {
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)) ) {
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)) {
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)) {
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 */
119 if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, CH_UTF8_MAC, 0,
120 uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) {
124 if ( !strncmp (mfilename, buffer, prefix) ) {
132 /* -------------------------------------------------------
135 private_demangle(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *osx)
139 uint32_t id, file_id;
140 static char buffer[12 + MAXPATHLEN + 1];
141 int len = 12 + MAXPATHLEN + 1;
147 t = strchr(mfilename, MANGLE_CHAR);
151 prefix = t - mfilename;
153 * is prefix == 0 a valid mangled filename ?
155 /* may be a mangled filename */
157 if (*t == '0') { /* can't start with a 0 */
160 while(isuxdigit(*t)) {
161 id = (id *16) + hextoint(*t);
164 if ((*t != 0 && *t != '.') || strlen(t) > MAX_EXT_LENGTH || id < 17) {
168 file_id = id = htonl(id);
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
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);
187 return demangle_checks(vol, cfrombstr(dir->d_u_name), mfilename, prefix, t);
190 else if (NULL != (u_name = cnid_resolve(vol->v_cdb, &id, buffer, len)) ) {
195 /* convert back to mac name and check it's the same */
196 t = utompath(vol, u_name, file_id, utf8_encoding(vol->v_obj));
197 if (!strcmp(t, mfilename)) {
202 return demangle_checks (vol, u_name, mfilename, prefix, t);
209 /* -------------------------------------------------------
212 demangle(const struct vol *vol, char *mfilename, cnid_t did)
214 return private_demangle(vol, mfilename, did, NULL);
217 /* -------------------------------------------------------
221 demangle_osx(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *fileid)
223 return private_demangle(vol, mfilename, did, fileid);
226 /* -------------------------------------------------------
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
233 Recent Mac OS X (10.4.?-) don't have this limitation.
234 Desirable implementation is:
235 volcharset -> truncated 510 UTF16-MAC -> UTF8-MAC
237 ------------------------
238 with utf8 filename not always round trip
239 filename mac filename too long or first chars if unmatchable chars.
241 id file/folder ID or 0
244 mangle(const struct vol *vol, char *filename, size_t filenamelen, char *uname, cnid_t id, int flags) {
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 */
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) {
260 /* we don't have the file id! only catsearch call mangle with id == 0 */
263 /* First, attempt to locate a file extension. */
264 ext_len = mangle_extension(vol, uname, ext, (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset);
266 k = sprintf(mangle_suffix, "%c%X", MANGLE_CHAR, ntohl(id));
268 if (filenamelen + k + ext_len > maxlen) {
269 uint16_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;
276 strlcpy(m, filename, filenamelen + 1);
281 strcat(m, mangle_suffix);
283 strncat(m, ext, ext_len);