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