/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.net.url; import java.util.Locale; public final class UrlUtils { private UrlUtils() { } /** * Returns the path will relative path segments like ".." and "." resolved. * The returned path will not necessarily start with a "/" character. This * handles ".." and "." segments at both the beginning and end of the path. * * @param discardRelativePrefix true to remove leading ".." segments from * the path. This is appropriate for paths that are known to be * absolute. */ public static String canonicalizePath(String path, boolean discardRelativePrefix) { // the first character of the current path segment int segmentStart = 0; // the number of segments seen thus far that can be erased by sequences of '..'. int deletableSegments = 0; for (int i = 0; i <= path.length(); ) { int nextSegmentStart; if (i == path.length()) { nextSegmentStart = i; } else if (path.charAt(i) == '/') { nextSegmentStart = i + 1; } else { i++; continue; } /* * We've encountered either the end of a segment or the end of the * complete path. If the final segment was "." or "..", remove the * appropriate segments of the path. */ if (i == segmentStart + 1 && path.regionMatches(segmentStart, ".", 0, 1)) { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". path = path.substring(0, segmentStart) + path.substring(nextSegmentStart); i = segmentStart; } else if (i == segmentStart + 2 && path.regionMatches(segmentStart, "..", 0, 2)) { if (deletableSegments > 0 || discardRelativePrefix) { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". deletableSegments--; int prevSegmentStart = path.lastIndexOf('/', segmentStart - 2) + 1; path = path.substring(0, prevSegmentStart) + path.substring(nextSegmentStart); i = segmentStart = prevSegmentStart; } else { // There's no segment to delete; this ".." segment must be retained. i++; segmentStart = i; } } else { if (i > 0) { deletableSegments++; } i++; segmentStart = i; } } return path; } /** * Returns a path that can be safely concatenated with {@code authority}. If * the authority is null or empty, this can be any path. Otherwise the paths * run together like {@code http://android.comindex.html}. */ public static String authoritySafePath(String authority, String path) { if (authority != null && !authority.isEmpty() && !path.isEmpty() && !path.startsWith("/")) { return "/" + path; } return path; } /** * Returns the scheme prefix like "http" from the URL spec, or null if the * spec doesn't start with a scheme. Scheme prefixes match this pattern: * {@code alpha ( alpha | digit | '+' | '-' | '.' )* ':'} */ public static String getSchemePrefix(String spec) { int colon = spec.indexOf(':'); if (colon < 1) { return null; } for (int i = 0; i < colon; i++) { char c = spec.charAt(i); if (!isValidSchemeChar(i, c)) { return null; } } return spec.substring(0, colon).toLowerCase(Locale.US); } public static boolean isValidSchemeChar(int index, char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { return true; } if (index > 0 && ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')) { return true; } return false; } /** * Returns the index of the first char of {@code chars} in {@code string} * bounded between {@code start} and {@code end}. This returns {@code end} * if none of the characters exist in the requested range. */ public static int findFirstOf(String string, String chars, int start, int end) { for (int i = start; i < end; i++) { char c = string.charAt(i); if (chars.indexOf(c) != -1) { return i; } } return end; } }