// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See sibling License.txt file package hudson.plugins.tfs.util; import org.eclipse.jgit.transport.URIish; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Scanner; import java.util.TreeMap; public class UriHelper { private static final Map<String, Integer> SCHEMES_TO_DEFAULT_PORTS; public static final String UTF_8 = "UTF-8"; private static final String DEFAULT_COLLECTION = "DefaultCollection"; static { final Map<String, Integer> defaultPorts = new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER); defaultPorts.put("ftp", 21); defaultPorts.put("ssh", 22); defaultPorts.put("http", 80); defaultPorts.put("https", 443); SCHEMES_TO_DEFAULT_PORTS = Collections.unmodifiableMap(defaultPorts); } /** * Compares two {@link URI} instances to determine if they are equivalent. * For example, * {@code HTTP://WWW.EXAMPLE.COM:80/} * and * {@code http://www.example.com} * are considered equivalent. * This method handles a few more cases than {@link URI#equals(Object)}, such that the scheme's * default port number will be considered, as will the default path for hosts. * * @param a the first URI * @param b the second URI * @return {@code true} if a and b represent the same resource; {@code false} otherwise. */ public static boolean areSame(final URI a, final URI b) { if (a == null) { return b == null; } if (b == null) { return false; } if (!StringHelper.equalIgnoringCase(a.getScheme(), b.getScheme())) { return false; } if (!StringHelper.equalIgnoringCase(a.getHost(), b.getHost())) { return false; } final int aPort = normalizePort(a); final int bPort = normalizePort(b); if (aPort != bPort) { return false; } final String aPath = normalizePath(a); final String bPath = normalizePath(b); if (!StringHelper.equal(aPath, bPath)) { return false; } if (!StringHelper.equal(a.getQuery(), b.getQuery())) { return false; } if (!StringHelper.equal(a.getFragment(), b.getFragment())) { return false; } return true; } public static boolean areSameGitRepo(final URIish a, final URIish b) { final URI uriA = a == null ? null : URI.create(a.toString()); final URI uriB = b == null ? null : URI.create(b.toString()); return areSameGitRepo(uriA, uriB); } public static boolean areSameGitRepo(final URI a, final URI b) { if (a == null) { return b == null; } if (b == null) { return false; } if (StringHelper.equalIgnoringCase(a.getScheme(), b.getScheme())) { final int aPort = normalizePort(a); final int bPort = normalizePort(b); if (aPort != bPort) { return false; } } if (!StringHelper.equalIgnoringCase(a.getHost(), b.getHost())) { return false; } final String aPath = normalizePath(a); final String bPath = normalizePath(b); if (StringHelper.equal(aPath, bPath)) { return true; } final Iterator<String> aPathParts = decomposePath(aPath); boolean aSeenDefaultCollection = false; final Iterator<String> bPathParts = decomposePath(bPath); boolean bSeenDefaultCollection = false; while (aPathParts.hasNext() && bPathParts.hasNext()) { String aPart = aPathParts.next(); String bPart = bPathParts.next(); if (StringHelper.equalIgnoringCase(DEFAULT_COLLECTION, aPart) && aPathParts.hasNext() && !aSeenDefaultCollection) { aPart = aPathParts.next(); aSeenDefaultCollection = true; } if (StringHelper.equalIgnoringCase(DEFAULT_COLLECTION, bPart) && bPathParts.hasNext() && !bSeenDefaultCollection) { bPart = bPathParts.next(); bSeenDefaultCollection = true; } if (!StringHelper.equalIgnoringCase(aPart, bPart)) { return false; } } return true; } static int normalizePort(final URI uri) { int port = uri.getPort(); if (port == -1) { final String scheme = uri.getScheme(); if (scheme != null) { if (SCHEMES_TO_DEFAULT_PORTS.containsKey(scheme)) { port = SCHEMES_TO_DEFAULT_PORTS.get(scheme); } } } return port; } static String normalizePath(final URI uri) { String path = uri.getPath(); if (path == null) { path = "/"; } else { if (!path.endsWith("/")) { path = path + "/"; } } return path; } static Iterator<String> decomposePath(final String path) { return new Scanner(path).useDelimiter("/"); } public static boolean hasPath(final URI uri) { final String path = uri.getPath(); if (path != null) { if (path.length() > 0 && !path.equals("/")) { return true; } } return false; } public static boolean isWellFormedUriString(final String uriString) { try { new URI(uriString); return true; } catch (final URISyntaxException ignored) { return false; } } public static URI join(final URI collectionUri, final Object... components) { return join(collectionUri.toString(), components); } public static URI join(final String collectionUrl, final Object... components) { final StringBuilder sb = new StringBuilder(collectionUrl); final boolean baseEndedWithSlash = endsWithSlash(sb); boolean first = true; for (final Object component : components) { boolean hasSlash = false; if (component instanceof QueryString) { final QueryString queryString = (QueryString) component; if (first) { if (!baseEndedWithSlash) { sb.append('/'); } } sb.append("?"); sb.append(queryString.toString()); // a QueryString must be the last of the components break; } else { if (first) { first = false; if (!baseEndedWithSlash) { sb.append('/'); } } else { sb.append('/'); } try { final String encodedComponent = URLEncoder.encode(component.toString(), UTF_8); // URLEncoder#encode() encodes spaces as "+" but they should be "%20" final String correctlyEncodedComponent = encodedComponent.replaceAll("\\+", "%20"); sb.append(correctlyEncodedComponent); } catch (final UnsupportedEncodingException e) { throw new Error(e); } } } final String uriString = sb.toString(); return URI.create(uriString); } static boolean endsWithSlash(final StringBuilder stringBuilder) { final int length = stringBuilder.length(); return length > 0 && stringBuilder.charAt(length - 1) == '/'; } public static String serializeParameters(final Map<String, String> parameters) { try { final StringBuilder sb = new StringBuilder(); final Iterator<Map.Entry<String, String>> iterator = parameters.entrySet().iterator(); if (iterator.hasNext()) { Map.Entry<String, String> entry; String key; String encodedKey; String value; String encodedValue; entry = iterator.next(); key = entry.getKey(); encodedKey = URLEncoder.encode(key, UTF_8); sb.append(encodedKey); value = entry.getValue(); if (value != null) { encodedValue = URLEncoder.encode(value, UTF_8); sb.append('=').append(encodedValue); } while (iterator.hasNext()) { sb.append('&'); entry = iterator.next(); key = entry.getKey(); encodedKey = URLEncoder.encode(key, UTF_8); sb.append(encodedKey); value = entry.getValue(); if (value != null) { encodedValue = URLEncoder.encode(value, UTF_8); sb.append('=').append(encodedValue); } } } return sb.toString(); } catch (final UnsupportedEncodingException e) { throw new Error(e); } } }