]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/mangle.c
Merge remote-tracking branch 'remotes/origin/branch-netatalk-2-1'
[netatalk.git] / etc / afpd / mangle.c
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
27 static 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
43 static 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 */
134 static char *
135 private_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 */
211 char *
212 demangle(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 */
220 char *
221 demangle_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 */
243 char *
244 mangle(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 }