File: mangle.c

package info (click to toggle)
netatalk 2.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 9,716 kB
  • sloc: ansic: 85,115; sh: 10,385; perl: 1,703; makefile: 1,363
file content (287 lines) | stat: -rw-r--r-- 9,444 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/* 
 * Copyright (c) 2002. Joe Marcus Clarke (marcus@marcuscom.com)
 * All Rights Reserved.  See COPYRIGHT.
 *
 * mangle, demangle (filename):
 * mangle or demangle filenames if they are greater than the max allowed
 * characters for a given version of AFP.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <ctype.h>

#include <atalk/util.h>
#include <atalk/bstradd.h>

#include "mangle.h"
#include "desktop.h"


#define hextoint( c )   ( isdigit( c ) ? c - '0' : c + 10 - 'A' )
#define isuxdigit(x)    (isdigit(x) || (isupper(x) && isxdigit(x)))

static size_t mangle_extension(const struct vol *vol, const char* uname,
			       char* extension, charset_t charset)
{
  char *p = strrchr(uname, '.');

  if (p && p != uname) {
    u_int16_t flags = CONV_FORCE | CONV_UNESCAPEHEX;
    size_t len = convert_charset(vol->v_volcharset, charset,
				 vol->v_maccharset, p, strlen(p),
				 extension, MAX_EXT_LENGTH, &flags);

    if (len != (size_t)-1) return len;
  }
  return 0;
}

static char *demangle_checks(const struct vol *vol, char* uname, char * mfilename, size_t prefix, char * ext)
{
    u_int16_t flags;
    static char buffer[MAXPATHLEN +2];  /* for convert_charset dest_len parameter +2 */
    size_t len;
    size_t mfilenamelen;

    /* We need to check, whether we really need to demangle the filename 	*/
    /* i.e. it's not just a file with a valid #HEX in the name ...		*/
    /* but we don't want to miss valid demangle as well.			*/
    /* check whether file extensions match */

    char buf[MAX_EXT_LENGTH + 2];  /* for convert_charset dest_len parameter +2 */
    size_t ext_len = mangle_extension(vol, uname, buf, CH_UTF8_MAC);

    if (ext_len) {
        buf[ext_len] = '\0';
        if (strcmp(ext, buf))
            return mfilename;
    } else {
        if (*ext)
            return mfilename;
    }

    /* First we convert the unix name to our volume maccharset     	*/
    /* This assumes, OSX will not send us a mangled name for *any* 	*/
    /* other reason than a hint/filename mismatch on the OSX side!! */
    /* If the filename needed mangling, we'll get the mac filename	*/
    /* till the first unconvertable char, so these have to	match	*/
    /* the mangled name we got ..					*/

    flags = CONV_IGNORE | CONV_UNESCAPEHEX;
    if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, vol->v_maccharset, 0, 
				      uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) {
	return mfilename;
    }
    /* If the filename is too long we also needed to mangle */
    mfilenamelen = strlen(mfilename);
    if ( len >= vol->max_filename || mfilenamelen == MACFILELEN ) {
        flags |= CONV_REQMANGLE;
        len = prefix;
    }
    
    /* Ok, mangling was needed, now we do some further checks    */
    /* this is still necessary, as we might have a file abcde:xx */
    /* with id 12, mangled to abcde#12, and a passed filename    */
    /* abcd#12 						     */ 
    /* if we only checked if "prefix" number of characters match */
    /* we get a false postive in above case			     */

    if ( (flags & CONV_REQMANGLE) ) {
        if (len) { 
            /* convert the buffer to UTF8_MAC ... */
            if ((size_t) -1 == (len = convert_charset(vol->v_maccharset, CH_UTF8_MAC, 0, 
            			buffer, len, buffer, MAXPATHLEN, &flags)) ) {
                return mfilename;
            }
            /* Now compare the two names, they have to match the number of characters in buffer */
            /* prefix can be longer than len, OSX might send us the first character(s) of a     */
            /* decomposed char as the *last* character(s) before the #, so our match below will */
            /* still work, but leaves room for a race ... FIXME				    */
            if ( (prefix >= len || mfilenamelen == MACFILELEN) 
                 && !strncmp (mfilename, buffer, len)) {
                 return uname;
            }
        }
        else {
            /* We couldn't convert the name to maccharset at all, so we'd expect a name */
            /* in the "???#ID" form ... */
            if ( !strncmp("???", mfilename, prefix)) {
                return uname;
            }
            /* ..but OSX might send us only the first characters of a decomposed character. */
            /*  So convert to UTF8_MAC again, now at least the prefix number of 	  */
            /* characters have to match ... again a possible race FIXME			  */
            
            if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, CH_UTF8_MAC, 0, 
	                          uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) {
	        return mfilename;
	    }

            if ( !strncmp (mfilename, buffer, prefix) ) {
                return uname;
            }
        }
    }
    return mfilename;
}

/* -------------------------------------------------------
*/
static char *
private_demangle(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *osx) 
{
    char *t;
    char *u_name;
    u_int32_t id, file_id;
    static char buffer[12 + MAXPATHLEN + 1];
    int len = 12 + MAXPATHLEN + 1;
    struct dir	*dir;
    size_t prefix;

    id = file_id = 0;

    t = strchr(mfilename, MANGLE_CHAR);
    if (t == NULL) {
        return mfilename;
    }
    prefix = t - mfilename;
    /* FIXME 
     * is prefix == 0 a valid mangled filename ?
    */
    /* may be a mangled filename */
    t++;
    if (*t == '0') { /* can't start with a 0 */
        return mfilename;
    }
    while(isuxdigit(*t)) {
        id = (id *16) + hextoint(*t);
        t++;
    }
    if ((*t != 0 && *t != '.') || strlen(t) > MAX_EXT_LENGTH || id < 17) {
        return mfilename;
    }

    file_id = id = htonl(id);
    if (osx) {
        *osx = id;
    }

    /* is it a dir?, there's a conflict with pre OSX 'trash #2'  */
    if ((dir = dirlookup(vol, id))) {
        if (dir->d_pdid != did) {
            /* not in the same folder, there's a race with outdate cache
             * but we have to live with it, hopefully client will recover
            */
            return mfilename;
        }
        if (!osx) {
            /* it's not from cname so mfilename and dir must be the same */
            if (strcmp(cfrombstr(dir->d_m_name), mfilename) == 0) {
                return cfrombstr(dir->d_u_name);
            }
        } else {
            return demangle_checks(vol, cfrombstr(dir->d_u_name), mfilename, prefix, t);
        }
    }
    else if (NULL != (u_name = cnid_resolve(vol->v_cdb, &id, buffer, len)) ) {
        if (id != did) {
            return mfilename;
        }
        if (!osx) {
            /* convert back to mac name and check it's the same */
            t = utompath(vol, u_name, file_id, utf8_encoding());
            if (!strcmp(t, mfilename)) {
                return u_name;
            }
        }
        else {
            return demangle_checks (vol, u_name, mfilename, prefix, t);
        }
    }

    return mfilename;
}

/* -------------------------------------------------------
*/
char *
demangle(const struct vol *vol, char *mfilename, cnid_t did)
{
    return private_demangle(vol, mfilename, did, NULL);
}

/* -------------------------------------------------------
 * OS X  
*/
char *
demangle_osx(const struct vol *vol, char *mfilename, cnid_t did, cnid_t *fileid) 
{
    return private_demangle(vol, mfilename, did, fileid);
}

/* -------------------------------------------------------
   FIXME !!!

   Early Mac OS X (10.0-10.4.?) had the limitation up to 255 Byte.
   Current implementation is:
      volcharset -> UTF16-MAC -> truncated 255 UTF8-MAC

   Recent Mac OS X (10.4.?-) don't have this limitation.
   Desirable implementation is:
      volcharset -> truncated 510 UTF16-MAC -> UTF8-MAC

   ------------------------
   with utf8 filename not always round trip
   filename   mac filename too long or first chars if unmatchable chars.
   uname      unix filename 
   id         file/folder ID or 0
*/
char *
mangle(const struct vol *vol, char *filename, size_t filenamelen, char *uname, cnid_t id, int flags) {
    char *m = NULL;
    static char mfilename[MAXPATHLEN]; /* way > maxlen */
    char mangle_suffix[MANGLE_LENGTH + 1];
    char ext[MAX_EXT_LENGTH +2];  /* for convert_charset dest_len parameter +2 */
    size_t ext_len;
    size_t maxlen;
    int k;
    
    maxlen = (flags & 2)?UTF8FILELEN_EARLY:MACFILELEN; /* was vol->max_filename */
    /* Do we really need to mangle this filename? */
    if (!(flags & 1) && filenamelen <= maxlen) {
	return filename;
    }

    if (!id) {
        /* we don't have the file id! only catsearch call mangle with id == 0 */
        return NULL;
    }
    /* First, attempt to locate a file extension. */
    ext_len = mangle_extension(vol, uname, ext, (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset);
    m = mfilename;
    k = sprintf(mangle_suffix, "%c%X", MANGLE_CHAR, ntohl(id));

    if (filenamelen + k + ext_len > maxlen) {
      u_int16_t opt = CONV_FORCE | CONV_UNESCAPEHEX;
      size_t n = convert_charset(vol->v_volcharset,
				 (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset,
				 vol->v_maccharset, uname, strlen(uname),
				 m, maxlen - k - ext_len, &opt);
      m[n != (size_t)-1 ? n : 0] = 0;
    } else {
      strlcpy(m, filename, filenamelen + 1);
    }
    if (*m == 0) {
        strcat(m, "???");
    }
    strcat(m, mangle_suffix);
    if (ext_len) {
	strncat(m, ext, ext_len);
    }

    return m;
}