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