/** * Copyright Microsoft Corporation * * 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 com.microsoft.azure.storage.core; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map.Entry; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageUri; /** * RESERVED FOR INTERNAL USE. A class to help modify paths */ public final class PathUtility { /** * Adds a queryString to an URI. * * @param resourceURI * the URI of the resource * @param fieldCollection * the key/ values collection to append. * @return an appended URI. * @throws URISyntaxException * if the resulting URI is invalid. * @throws StorageException */ public static URI addToSingleUriQuery(final URI resourceURI, final HashMap<String, String[]> fieldCollection) throws URISyntaxException, StorageException { if (resourceURI == null) { return null; } final UriQueryBuilder outUri = new UriQueryBuilder(); // Generate new queryString for (final Entry<String, String[]> entry : fieldCollection.entrySet()) { for (final String val : entry.getValue()) { outUri.add(entry.getKey(), val); } } return outUri.addToURI(resourceURI); } /** * Adds a queryString to an URI. * * @param resourceURI * the URI of the resource * @param queryString * the query string to add * @return an appended URI. * @throws URISyntaxException * if the resulting URI is invalid. * @throws StorageException */ public static StorageUri addToQuery(final StorageUri resourceURI, final String queryString) throws URISyntaxException, StorageException { return new StorageUri(addToSingleUriQuery(resourceURI.getPrimaryUri(), parseQueryString(queryString)), addToSingleUriQuery(resourceURI.getSecondaryUri(), parseQueryString(queryString))); } /** * Adds a queryString to an URI. * * @param resourceURI * the URI of the resource * @param queryString * the query string to add * @return an appended URI. * @throws URISyntaxException * if the resulting URI is invalid. * @throws StorageException */ public static URI addToQuery(final URI resourceURI, final String queryString) throws URISyntaxException, StorageException { return addToSingleUriQuery(resourceURI, parseQueryString(queryString)); } /** * Appends a path to a list of URIs correctly using "/" as separator. * * @param uriList * The base Uri. * @param relativeOrAbslouteUri * The relative or absloute URI. * @return The appended Uri. * @throws URISyntaxException */ public static StorageUri appendPathToUri(final StorageUri uriList, final String relativeOrAbsoluteUri) throws URISyntaxException { return appendPathToUri(uriList, relativeOrAbsoluteUri, "/"); } /** * Appends a path to a list of URIs correctly using "/" as separator. * * @param uriList * The base Uri. * @param relativeOrAbslouteUri * The relative or absloute URI. * @return The appended Uri. * @throws URISyntaxException */ public static StorageUri appendPathToUri(final StorageUri uriList, final String relativeOrAbsoluteUri, final String separator) throws URISyntaxException { return new StorageUri(appendPathToSingleUri(uriList.getPrimaryUri(), relativeOrAbsoluteUri, separator), appendPathToSingleUri(uriList.getSecondaryUri(), relativeOrAbsoluteUri, separator)); } /** * Appends a path to a URI correctly using "/" as separator. * * @param uriList * The base Uri. * @param relativeOrAbslouteUri * The relative or absloute URI. * @return The appended Uri. * @throws URISyntaxException */ public static URI appendPathToSingleUri(final URI uri, final String relativeOrAbsoluteUri) throws URISyntaxException { return appendPathToSingleUri(uri, relativeOrAbsoluteUri, "/"); } /** * Appends a path to a URI correctly using the given separator. * * @param uri * The base Uri. * @param relativeUri * The relative URI. * @param separator * the separator to use. * @return The appended Uri. * @throws URISyntaxException * a valid Uri cannot be constructed */ public static URI appendPathToSingleUri(final URI uri, final String relativeUri, final String separator) throws URISyntaxException { if (uri == null) { return null; } if (relativeUri == null || relativeUri.isEmpty()) { return uri; } if (uri.getPath().length() == 0 && relativeUri.startsWith(separator)) { return new URI(uri.getScheme(), uri.getAuthority(), relativeUri, uri.getRawQuery(), uri.getRawFragment()); } final StringBuilder pathString = new StringBuilder(uri.getPath()); if (uri.getPath().endsWith(separator)) { pathString.append(relativeUri); } else { pathString.append(separator); pathString.append(relativeUri); } return new URI(uri.getScheme(), uri.getAuthority(), pathString.toString(), uri.getQuery(), uri.getFragment()); } /** * Gets the blob name from the URI. * * @param inURI * the resource address * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return the blobs name * @throws URISyntaxException */ public static String getBlobNameFromURI(final URI inURI, final boolean usePathStyleUris) throws URISyntaxException { return Utility.safeRelativize(new URI(getContainerURI(new StorageUri(inURI), usePathStyleUris).getPrimaryUri() .toString().concat("/")), inURI); } /** * Gets the canonical path for an object from the credentials. * * @param credentials * the credentials to use. * @param absolutePath * the Absolute path of the object. * @return the canonical path for an object from the credentials */ public static String getCanonicalPathFromCredentials(final StorageCredentials credentials, final String absolutePath) { final String account = credentials.getAccountName(); if (account == null) { final String errorMessage = SR.CANNOT_CREATE_SAS_FOR_GIVEN_CREDENTIALS; throw new IllegalArgumentException(errorMessage); } final StringBuilder builder = new StringBuilder("/"); builder.append(account); builder.append(absolutePath); return builder.toString(); } /** * Get the container name from address from the URI. * * @param resourceAddress * The container Uri. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return container name from address from the URI. * @throws IllegalArgumentException */ public static String getContainerNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid blob address '%s', missing container information", resourceAddress)); } /** * Gets the file name from the URI. * * @param resourceAddress * the file URI * @param usePathStyleUris * a value indicating if the address is a path style URI * @return the file's name */ public static String getFileNameFromURI(final URI resourceAddress, final boolean usePathStyleUris) { // generate an array of the different levels of the path final String[] pathSegments = resourceAddress.getRawPath().split("/"); // usePathStyleUris ? baseuri/accountname/sharename/objectname : accountname.baseuri/sharename/objectname final int shareIndex = usePathStyleUris ? 2 : 1; if (pathSegments.length - 1 <= shareIndex) { // legal file addresses cannot end with or before the sharename throw new IllegalArgumentException(String.format("Invalid file address '%s'.", resourceAddress)); } else { // in a legal file address the lowest level is the filename return pathSegments[pathSegments.length - 1]; } } /** * Get the name of the lowest level directory from the given directory address. * * @param resourceAddress * the directory URI * @param usePathStyleUris * a value indicating if the address is a path style URI * @return directory name from address from the URI */ public static String getDirectoryNameFromURI(final URI resourceAddress, final boolean usePathStyleUris) { // generate an array of the different levels of the path final String[] pathSegments = resourceAddress.getRawPath().split("/"); // usePathStyleUris ? baseuri/accountname/sharename/objectname : accountname.baseuri/sharename/objectname final int shareIndex = usePathStyleUris ? 2 : 1; if (pathSegments.length - 1 < shareIndex) { // if the sharename is missing or too close to the end throw new IllegalArgumentException(String.format("Invalid directory address '%s'.", resourceAddress)); } else if (pathSegments.length - 1 == shareIndex) { // this is the root directory; it has no name return ""; } else { // in a legal directory address the lowest level is the directory return pathSegments[pathSegments.length - 1]; } } /** * Get the share name from address from the URI. * * @param resourceAddress * The share Uri. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return share name from address from the URI. * @throws IllegalArgumentException */ public static String getShareNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid file address '%s', missing share information", resourceAddress)); } /** * Get the table name from address from the URI. * * @param resourceAddress * The table Uri. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return table name from address from the URI. * @throws IllegalArgumentException */ public static String getTableNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid table address '%s', missing table information", resourceAddress)); } /** * Get the container, queue or table name from address from the URI. * * @param resourceAddress * The queue Uri. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return container name from address from the URI. * @throws IllegalArgumentException */ private static String getResourceNameFromUri(final URI resourceAddress, final boolean usePathStyleUris, final String error) { Utility.assertNotNull("resourceAddress", resourceAddress); final String[] pathSegments = resourceAddress.getRawPath().split("/"); final int expectedPartsLength = usePathStyleUris ? 3 : 2; if (pathSegments.length < expectedPartsLength) { throw new IllegalArgumentException(error); } final String resourceName = usePathStyleUris ? pathSegments[2] : pathSegments[1]; return Utility.trimEnd(resourceName, '/'); } /** * Gets the container URI from a blob address * * @param blobAddress * the blob address * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return the container URI from a blob address * @throws URISyntaxException */ public static StorageUri getContainerURI(final StorageUri blobAddress, final boolean usePathStyleUris) throws URISyntaxException { final String containerName = getContainerNameFromUri(blobAddress.getPrimaryUri(), usePathStyleUris); final StorageUri containerUri = appendPathToUri(getServiceClientBaseAddress(blobAddress, usePathStyleUris), containerName); return containerUri; } /** * Gets the share URI from a file address * * @param fileAddress * the file address * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return the share URI from a file address * @throws URISyntaxException */ public static StorageUri getShareURI(final StorageUri fileAddress, final boolean usePathStyleUris) throws URISyntaxException { final String shareName = getShareNameFromUri(fileAddress.getPrimaryUri(), usePathStyleUris); final StorageUri shareUri = appendPathToUri(getServiceClientBaseAddress(fileAddress, usePathStyleUris), shareName); return shareUri; } /** * Get the service client address from a complete Uri. * * @param address * Complete address of the resource. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return the service client address from a complete Uri. * @throws URISyntaxException */ public static String getServiceClientBaseAddress(final URI address, final boolean usePathStyleUris) throws URISyntaxException { if (address == null) { return null; } if (usePathStyleUris) { final String[] pathSegments = address.getRawPath().split("/"); if (pathSegments.length < 2) { final String error = String.format(SR.PATH_STYLE_URI_MISSING_ACCOUNT_INFORMATION); throw new IllegalArgumentException(error); } final StringBuilder completeAddress = new StringBuilder(new URI(address.getScheme(), address.getAuthority(), null, null, null).toString()); completeAddress.append("/"); completeAddress.append(Utility.trimEnd(pathSegments[1], '/')); return completeAddress.toString(); } else { return new URI(address.getScheme(), address.getAuthority(), null, null, null).toString(); } } /** * Get the service client address from a complete Uri. * * @param address * Complete address of the resource. * @param usePathStyleUris * a value indicating if the address is a path style uri. * @return the service client address from a complete Uri. * @throws URISyntaxException */ public static StorageUri getServiceClientBaseAddress(final StorageUri addressUri, final boolean usePathStyleUris) throws URISyntaxException { return new StorageUri(new URI(getServiceClientBaseAddress(addressUri.getPrimaryUri(), usePathStyleUris)), addressUri.getSecondaryUri() != null ? new URI(getServiceClientBaseAddress(addressUri.getSecondaryUri(), usePathStyleUris)) : null); } /** * Parses a query string into a one to many hashmap. * * @param parseString * the string to parse * @return a HashMap<String, String[]> of the key values. * @throws StorageException */ public static HashMap<String, String[]> parseQueryString(String parseString) throws StorageException { final HashMap<String, String[]> retVals = new HashMap<String, String[]>(); if (Utility.isNullOrEmpty(parseString)) { return retVals; } // 1. Remove ? if present final int queryDex = parseString.indexOf("?"); if (queryDex >= 0 && parseString.length() > 0) { parseString = parseString.substring(queryDex + 1); } // 2. split name value pairs by splitting on the 'c&' character final String[] valuePairs = parseString.contains("&") ? parseString.split("&") : parseString.split(";"); // 3. for each field value pair parse into appropriate map entries for (int m = 0; m < valuePairs.length; m++) { final int equalDex = valuePairs[m].indexOf("="); if (equalDex < 0 || equalDex == valuePairs[m].length() - 1) { continue; } String key = valuePairs[m].substring(0, equalDex); String value = valuePairs[m].substring(equalDex + 1); key = Utility.safeDecode(key); value = Utility.safeDecode(value); // 3.1 add to map String[] values = retVals.get(key); if (values == null) { values = new String[] { value }; if (!value.equals(Constants.EMPTY_STRING)) { retVals.put(key, values); } } else if (!value.equals(Constants.EMPTY_STRING)) { final String[] newValues = new String[values.length + 1]; for (int j = 0; j < values.length; j++) { newValues[j] = values[j]; } newValues[newValues.length] = value; } } return retVals; } /** * Strips the Query and Fragment from the uri. * * @param inUri * the uri to alter * @return the stripped uri. * @throws StorageException */ public static URI stripSingleURIQueryAndFragment(final URI inUri) throws StorageException { if (inUri == null) { return null; } try { return new URI(inUri.getScheme(), inUri.getAuthority(), inUri.getPath(), null, null); } catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } } /** * Strips the Query and Fragment from the uri. * * @param inUri * the uri to alter * @return the stripped uri. * @throws StorageException */ public static StorageUri stripURIQueryAndFragment(final StorageUri inUri) throws StorageException { return new StorageUri(stripSingleURIQueryAndFragment(inUri.getPrimaryUri()), stripSingleURIQueryAndFragment(inUri.getSecondaryUri())); } /** * Private Default Ctor. */ private PathUtility() { // No op } }