]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/mangle.c
eb8cf78eb93d5558a57b4167d484d55cb0e61d41
[netatalk.git] / etc / afpd / mangle.c
1 /* 
2  * $Id: mangle.c,v 1.16.2.1.2.12.2.1 2005-01-31 19:50:37 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 isn't this stuff always false */
58     if ( len >= vol->max_filename || (mfilenamelen = strlen(mfilename)) == MACFILELEN ) {
59         flags |= CONV_REQMANGLE;
60         len = prefix;
61     }
62     
63     /* Ok, mangling was needed, now we do some further checks    */
64     /* this is still necessary, as we might have a file abcde:xx */
65     /* with id 12, mangled to abcde#12, and a passed filename    */
66     /* abcd#12                                               */ 
67     /* if we only checked if "prefix" number of characters match */
68     /* we get a false postive in above case                          */
69
70     if ( flags & CONV_REQMANGLE ) {
71         if (len) { 
72             /* convert the buffer to UTF8_MAC ... */
73             if ((size_t) -1 == (len = convert_charset(vol->v_maccharset, CH_UTF8_MAC, 0, 
74                                 buffer, len, buffer, MAXPATHLEN, &flags)) ) {
75                 return mfilename;
76             }
77             /* Now compare the two names, they have to match the number of characters in buffer */
78             /* prefix can be longer than len, OSX might send us the first character(s) of a     */
79             /* decomposed char as the *last* character(s) before the #, so our match below will */
80             /* still work, but leaves room for a race ... FIXME                             */
81             if ( (prefix >= len || mfilenamelen == MACFILELEN) 
82                  && !strncmp (mfilename, buffer, len)) {
83                  return uname;
84             }
85         }
86         else {
87             /* We couldn't convert the name to maccharset at all, so we'd expect a name */
88             /* in the "???#ID" form ... */
89             if ( !strncmp("???", mfilename, prefix)) {
90                 return uname;
91             }
92             /* ..but OSX might send us only the first characters of a decomposed character. */
93             /*  So convert to UTF8_MAC again, now at least the prefix number of           */
94             /* characters have to match ... again a possible race FIXME                   */
95             
96             if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, CH_UTF8_MAC, 0, 
97                                   uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) {
98                 return mfilename;
99             }
100
101             if ( !strncmp (mfilename, buffer, prefix) ) {
102                 return uname;
103             }
104         }
105     }
106     return mfilename;
107 }
108
109 /* -------------------------------------------------------
110 */
111 static char *
112 private_demangle(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *osx) 
113 {
114     char *t;
115     char *u_name;
116     u_int32_t id, file_id;
117     static char buffer[12 + MAXPATHLEN + 1];
118     int len = 12 + MAXPATHLEN + 1;
119     struct dir  *dir;
120     size_t prefix;
121
122     id = file_id = 0;
123
124     t = strchr(mfilename, MANGLE_CHAR);
125     if (t == NULL) {
126         return mfilename;
127     }
128     prefix = t - mfilename;
129     /* FIXME 
130      * is prefix == 0 a valid mangled filename ?
131     */
132     /* may be a mangled filename */
133     t++;
134     if (*t == '0') { /* can't start with a 0 */
135         return mfilename;
136     }
137     while(isuxdigit(*t)) {
138         id = (id *16) + hextoint(*t);
139         t++;
140     }
141     if ((*t != 0 && *t != '.') || strlen(t) > MAX_EXT_LENGTH || id < 17) {
142         return mfilename;
143     }
144
145     file_id = id = htonl(id);
146     if (osx) {
147         *osx = id;
148     }
149
150     /* is it a dir?, there's a conflict with pre OSX 'trash #2'  */
151     if ((dir = dirsearch(vol, id))) {
152         if (dir->d_parent && dir->d_parent->d_did != did) {
153             /* not in the same folder, there's a race with outdate cache
154              * but we have to live with it, hopefully client will recover
155             */
156             return mfilename;
157         }
158         if (!osx) {
159             /* it's not from cname so mfilename and dir must be the same */
160             if (!strcmp(dir->d_m_name, mfilename)) {
161                 return dir->d_u_name;
162             }
163         } 
164         else {
165             return demangle_checks (vol, dir->d_u_name, mfilename, prefix, t);
166         }
167     }
168     else if (NULL != (u_name = cnid_resolve(vol->v_cdb, &id, buffer, len)) ) {
169         if (id != did) {
170             return mfilename;
171         }
172         if (!osx) {
173             /* convert back to mac name and check it's the same */
174             t = utompath(vol, u_name, file_id, utf8_encoding());
175             if (!strcmp(t, mfilename)) {
176                 return u_name;
177             }
178         }
179         else {
180             return demangle_checks (vol, u_name, mfilename, prefix, t);
181         }
182     }
183
184     return mfilename;
185 }
186
187 /* -------------------------------------------------------
188 */
189 char *
190 demangle(const struct vol *vol, char *mfilename, cnid_t did)
191 {
192     return private_demangle(vol, mfilename, did, NULL);
193 }
194
195 /* -------------------------------------------------------
196  * OS X  
197 */
198 char *
199 demangle_osx(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *fileid) 
200 {
201     return private_demangle(vol, mfilename, did, fileid);
202 }
203
204
205 /* -------------------------------------------------------
206  * find the start of a utf8 character
207  */
208 static unsigned char *
209 utf8_mangle_validate(unsigned char *path, size_t len)
210 {
211     unsigned char *p = path + len;
212     int           dec = 0;
213
214     /* char matches with 10xxxxxx ? */
215     while ( len && *p && ((*p & 0xc0) == 0x80)) {
216         dec = 1;
217         len--;
218         p--;
219     }
220     if (dec)
221         *p = 0;
222
223     return path;
224 }
225
226 /* -----------------------
227    with utf8 filename not always round trip
228    filename   mac filename too long or first chars if unmatchable chars.
229    uname      unix filename 
230    id         file/folder ID or 0
231    
232 */
233 unsigned char *
234 mangle(const struct vol *vol, unsigned char *filename, size_t filenamelen, unsigned char *uname, cnid_t id, int flags) {
235     unsigned char *ext = NULL;
236     unsigned char *m = NULL;
237     static unsigned char mfilename[MAXPATHLEN + 1];
238     unsigned char mangle_suffix[MANGLE_LENGTH + 1];
239     size_t ext_len = 0;
240     size_t maxlen;
241     int k;
242     
243     maxlen = (flags & 2)?255:MACFILELEN; /* was vol->max_filename */
244     /* Do we really need to mangle this filename? */
245     if (!(flags & 1) && filenamelen <= maxlen) {
246         return filename;
247     }
248     /* First, attempt to locate a file extension. */
249     if (NULL != (ext = strrchr(uname, '.')) ) {
250         ext_len = strlen(ext);
251         if (ext_len > MAX_EXT_LENGTH) {
252             /* Do some bounds checking to prevent an extension overflow. */
253             ext_len = MAX_EXT_LENGTH;
254         }
255     }
256     m = mfilename;
257     k = sprintf(mangle_suffix, "%c%X", MANGLE_CHAR, ntohl(id));
258
259     strlcpy(m, filename, maxlen  - k - ext_len +1);
260     if (flags & 2)
261         m = utf8_mangle_validate(m, maxlen - k - ext_len +1);
262     if (*m == 0) {
263         strcat(m, "???");
264     }
265     strcat(m, mangle_suffix);
266     if (ext) {
267         strncat(m, ext, ext_len);
268     }
269
270     return m;
271 }