1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package libcore.net.url;
18 
19 import java.util.Locale;
20 
21 public final class UrlUtils {
UrlUtils()22     private UrlUtils() {
23     }
24 
25     /**
26      * Returns the path will relative path segments like ".." and "." resolved.
27      * The returned path will not necessarily start with a "/" character. This
28      * handles ".." and "." segments at both the beginning and end of the path.
29      *
30      * @param discardRelativePrefix true to remove leading ".." segments from
31      *     the path. This is appropriate for paths that are known to be
32      *     absolute.
33      */
canonicalizePath(String path, boolean discardRelativePrefix)34     public static String canonicalizePath(String path, boolean discardRelativePrefix) {
35         // the first character of the current path segment
36         int segmentStart = 0;
37 
38         // the number of segments seen thus far that can be erased by sequences of '..'.
39         int deletableSegments = 0;
40 
41         for (int i = 0; i <= path.length(); ) {
42             int nextSegmentStart;
43             if (i == path.length()) {
44                 nextSegmentStart = i;
45             } else if (path.charAt(i) == '/') {
46                 nextSegmentStart = i + 1;
47             } else {
48                 i++;
49                 continue;
50             }
51 
52             /*
53              * We've encountered either the end of a segment or the end of the
54              * complete path. If the final segment was "." or "..", remove the
55              * appropriate segments of the path.
56              */
57             if (i == segmentStart + 1 && path.regionMatches(segmentStart, ".", 0, 1)) {
58                 // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
59                 path = path.substring(0, segmentStart) + path.substring(nextSegmentStart);
60                 i = segmentStart;
61             } else if (i == segmentStart + 2 && path.regionMatches(segmentStart, "..", 0, 2)) {
62                 if (deletableSegments > 0 || discardRelativePrefix) {
63                     // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
64                     deletableSegments--;
65                     int prevSegmentStart = path.lastIndexOf('/', segmentStart - 2) + 1;
66                     path = path.substring(0, prevSegmentStart) + path.substring(nextSegmentStart);
67                     i = segmentStart = prevSegmentStart;
68                 } else {
69                     // There's no segment to delete; this ".." segment must be retained.
70                     i++;
71                     segmentStart = i;
72                 }
73             } else {
74                 if (i > 0) {
75                     deletableSegments++;
76                 }
77                 i++;
78                 segmentStart = i;
79             }
80         }
81         return path;
82     }
83 
84     /**
85      * Returns a path that can be safely concatenated with {@code authority}. If
86      * the authority is null or empty, this can be any path. Otherwise the paths
87      * run together like {@code http://android.comindex.html}.
88      */
authoritySafePath(String authority, String path)89     public static String authoritySafePath(String authority, String path) {
90         if (authority != null && !authority.isEmpty() && !path.isEmpty() && !path.startsWith("/")) {
91             return "/" + path;
92         }
93         return path;
94     }
95 
96     /**
97      * Returns the scheme prefix like "http" from the URL spec, or null if the
98      * spec doesn't start with a scheme. Scheme prefixes match this pattern:
99      * {@code alpha ( alpha | digit | '+' | '-' | '.' )* ':'}
100      */
getSchemePrefix(String spec)101     public static String getSchemePrefix(String spec) {
102         int colon = spec.indexOf(':');
103 
104         if (colon < 1) {
105             return null;
106         }
107 
108         for (int i = 0; i < colon; i++) {
109             char c = spec.charAt(i);
110             if (!isValidSchemeChar(i, c)) {
111                 return null;
112             }
113         }
114 
115         return spec.substring(0, colon).toLowerCase(Locale.US);
116     }
117 
isValidSchemeChar(int index, char c)118     public static boolean isValidSchemeChar(int index, char c) {
119         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
120             return true;
121         }
122         if (index > 0 && ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')) {
123             return true;
124         }
125         return false;
126     }
127 
128     /**
129      * Returns the index of the first char of {@code chars} in {@code string}
130      * bounded between {@code start} and {@code end}. This returns {@code end}
131      * if none of the characters exist in the requested range.
132      */
findFirstOf(String string, String chars, int start, int end)133     public static int findFirstOf(String string, String chars, int start, int end) {
134         for (int i = start; i < end; i++) {
135             char c = string.charAt(i);
136             if (chars.indexOf(c) != -1) {
137                 return i;
138             }
139         }
140         return end;
141     }
142 }
143