1/*
2 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#include <stdlib.h>
27#include <string.h>
28#include <malloc.h>
29
30#include "FileSystemSupport_md.h"
31
32/*
33 * Windows implementation of file system support functions
34 */
35
36#define slash           '\\'
37#define altSlash        '/'
38
39static int isSlash(char c) {
40    return (c == '\\') || (c == '/');
41}
42
43static int isLetter(char c) {
44    return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
45}
46
47char pathSeparator() {
48    return ';';
49}
50
51/* filename are case insensitive on windows */
52int filenameStrcmp(const char* s1, const char* s2) {
53    return strcasecmp(s1, s2);
54}
55
56char* basePath(const char* path) {
57    char* pos = strchr(path, slash);
58    char* last = NULL;
59    while (pos != NULL) {
60        last = pos;
61        pos++;
62        pos = strchr(pos, slash);
63    }
64    if (last == NULL) {
65        return (char*)path;
66    } else {
67        int len = (int)(last - path);
68        char* str = (char*)malloc(len+1);
69        if (len > 0) {
70            memcpy(str, path, len);
71        }
72        str[len] = '\0';
73        return str;
74    }
75}
76
77
78
79/* -- Normalization - src/windows/classes/java/io/Win32FileSystem.java */
80
81
82/* A normal Win32 pathname contains no duplicate slashes, except possibly
83 * for a UNC prefix, and does not end with a slash.  It may be the empty
84 * string.  Normalized Win32 pathnames have the convenient property that
85 * the length of the prefix almost uniquely identifies the type of the path
86 * and whether it is absolute or relative:
87 *
88 *      0  relative to both drive and directory
89 *      1  drive-relative (begins with '\\')
90 *      2  absolute UNC (if first char is '\\'),
91 *         else directory-relative (has form "z:foo")
92 *      3  absolute local pathname (begins with "z:\\")
93 */
94static int normalizePrefix(const char* path, int len, char* sb, int* sbLen) {
95    char c;
96    int src = 0;
97    while ((src < len) && isSlash(path[src])) src++;
98    if ((len - src >= 2)
99        && isLetter(c = path[src])
100        && path[src + 1] == ':') {
101        /* Remove leading slashes if followed by drive specifier.
102           This hack is necessary to support file URLs containing drive
103           specifiers (e.g., "file://c:/path").  As a side effect,
104           "/c:/path" can be used as an alternative to "c:/path". */
105        sb[(*sbLen)++] = c;
106        sb[(*sbLen)++] = ':';
107        src += 2;
108    } else {
109        src = 0;
110        if ((len >= 2)
111            && isSlash(path[0])
112            && isSlash(path[1])) {
113            /* UNC pathname: Retain first slash; leave src pointed at
114               second slash so that further slashes will be collapsed
115               into the second slash.  The result will be a pathname
116               beginning with "\\\\" followed (most likely) by a host
117               name. */
118            src = 1;
119            sb[(*sbLen)++] = slash;
120        }
121    }
122    return src;
123}
124
125/*
126 * Normalize the given pathname, whose length is len, starting at the given
127 * offset; everything before this offset is already normal.
128 */
129static char* normalizePath(const char* path, int len, int off) {
130    int src;
131    char* sb;
132    int sbLen;
133
134    if (len == 0) return (char*)path;
135    if (off < 3) off = 0;       /* Avoid fencepost cases with UNC pathnames */
136
137    sb = (char*)malloc(len+1);
138    sbLen = 0;
139
140    if (off == 0) {
141        /* Complete normalization, including prefix */
142        src = normalizePrefix(path, len, sb, &sbLen);
143    } else {
144        /* Partial normalization */
145        src = off;
146        memcpy(sb+sbLen, path, off);
147        sbLen += off;
148    }
149
150    /* Remove redundant slashes from the remainder of the path, forcing all
151       slashes into the preferred slash */
152    while (src < len) {
153        char c = path[src++];
154        if (isSlash(c)) {
155            while ((src < len) && isSlash(path[src])) src++;
156            if (src == len) {
157                /* Check for trailing separator */
158                if ((sbLen == 2) && (sb[1] == ':')) {
159                    /* "z:\\" */
160                    sb[sbLen++] = slash;
161                    break;
162                }
163                if (sbLen == 0) {
164                    /* "\\" */
165                    sb[sbLen++] = slash;
166                    break;
167                }
168                if ((sbLen == 1) && (isSlash(sb[0]))) {
169                    /* "\\\\" is not collapsed to "\\" because "\\\\" marks
170                       the beginning of a UNC pathname.  Even though it is
171                       not, by itself, a valid UNC pathname, we leave it as
172                       is in order to be consistent with the win32 APIs,
173                       which treat this case as an invalid UNC pathname
174                       rather than as an alias for the root directory of
175                       the current drive. */
176                    sb[sbLen++] = slash;
177                    break;
178                }
179                /* Path does not denote a root directory, so do not append
180                   trailing slash */
181                break;
182            } else {
183                sb[sbLen++] = slash;
184            }
185        } else {
186            sb[sbLen++] = c;
187        }
188    }
189
190    sb[sbLen] = '\0';
191    return sb;
192}
193
194/*
195 * Check that the given pathname is normal.  If not, invoke the real
196 * normalizer on the part of the pathname that requires normalization.
197 * This way we iterate through the whole pathname string only once.
198 */
199char* normalize(char* path) {
200    int n = (int)strlen(path);
201    int i;
202    char c = 0;
203    int prev = 0;
204    for (i = 0; i < n; i++) {
205        char c = path[i];
206        if (c == altSlash)
207            return normalizePath(path, n, (prev == slash) ? i - 1 : i);
208        if ((c == slash) && (prev == slash) && (i > 1))
209            return normalizePath(path, n, i - 1);
210        if ((c == ':') && (i > 1))
211            return normalizePath(path, n, 0);
212        prev = c;
213    }
214    if (prev == slash)
215        return normalizePath(path, n, n - 1);
216    return path;
217}
218
219
220/* -- Resolution - src/windows/classes/java/io/Win32FileSystem.java */
221
222
223char* resolve(const char* parent, const char* child) {
224    char* c;
225    char* theChars;
226    int parentEnd, childStart, len;
227
228    int pn = (int)strlen(parent);
229    int cn = (int)strlen(child);
230
231    if (pn == 0) return (char*)child;
232    if (cn == 0) return (char*)parent;
233
234    c = (char*)child;
235    childStart = 0;
236    parentEnd = pn;
237
238    if ((cn > 1) && (c[0] == slash)) {
239        if (c[1] == slash) {
240            /* Drop prefix when child is a UNC pathname */
241            childStart = 2;
242        } else {
243            /* Drop prefix when child is drive-relative */
244            childStart = 1;
245
246        }
247        if (cn == childStart) { // Child is double slash
248            if (parent[pn - 1] == slash) {
249                char* str = strdup(parent);
250                str[pn-1] = '\0';
251                return str;
252            }
253            return (char*)parent;
254        }
255    }
256
257    if (parent[pn - 1] == slash)
258        parentEnd--;
259
260    len = parentEnd + cn - childStart;
261
262    if (child[childStart] == slash) {
263        theChars = (char*)malloc(len+1);
264        memcpy(theChars, parent, parentEnd);
265        memcpy(theChars+parentEnd, child+childStart, (cn-childStart));
266        theChars[len] = '\0';
267    } else {
268        theChars = (char*)malloc(len+2);
269        memcpy(theChars, parent, parentEnd);
270        theChars[parentEnd] = slash;
271        memcpy(theChars+parentEnd+1, child+childStart, (cn-childStart));
272        theChars[len+1] = '\0';
273    }
274    return theChars;
275}
276
277
278static int prefixLength(const char* path) {
279    char c0, c1;
280
281    int n = (int)strlen(path);
282    if (n == 0) return 0;
283    c0 = path[0];
284    c1 = (n > 1) ? path[1] : 0;
285    if (c0 == slash) {
286        if (c1 == slash) return 2;      /* Absolute UNC pathname "\\\\foo" */
287        return 1;                       /* Drive-relative "\\foo" */
288    }
289    if (isLetter(c0) && (c1 == ':')) {
290        if ((n > 2) && (path[2] == slash))
291            return 3;           /* Absolute local pathname "z:\\foo" */
292        return 2;                       /* Directory-relative "z:foo" */
293    }
294    return 0;                   /* Completely relative */
295}
296
297
298int isAbsolute(const char* path) {
299    int pl = prefixLength(path);
300    return (((pl == 2) && (path[0] == slash)) || (pl == 3));
301}
302
303
304char* fromURIPath(const char* path) {
305    int start = 0;
306    int len = (int)strlen(path);
307
308    if ((len > 2) && (path[2] == ':')) {
309        // "/c:/foo" --> "c:/foo"
310        start = 1;
311        // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
312        if ((len > 3) && path[len-1] == '/')
313            len--;
314    } else if ((len > 1) && path[len-1] == '/') {
315        // "/foo/" --> "/foo"
316        len--;
317    }
318
319    if (start == 0 && len == (int)strlen(path)) {
320        return (char*)path;
321    } else {
322        char* p = (char*)malloc(len+1);
323        if (p != NULL) {
324            memcpy(p, path+start, len);
325            p[len] = '\0';
326        }
327        return p;
328    }
329}
330