001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.util; 019 020 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.util.Arrays; 024import java.util.Set; 025 026 027/** 028 * URI operations. 029 */ 030public final class URIUtils { 031 032 033 /** 034 * Gets the base part (schema, host, port and path) of the specified 035 * URI. 036 * 037 * @param uri The URI. May be {@code null}. 038 * 039 * @return The base part of the URI, {@code null} if the original URI 040 * is {@code null} or doesn't specify a protocol. 041 */ 042 public static URI getBaseURI(final URI uri) { 043 044 if (uri == null) 045 return null; 046 047 try { 048 return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null); 049 050 } catch (URISyntaxException e) { 051 052 return null; 053 } 054 } 055 056 057 /** 058 * Prepends the specified path component to a URI. The prepended and 059 * any existing path component are always joined with a single slash 060 * ('/') between them 061 * 062 * @param uri The URI, {@code null} if not specified. 063 * @param pathComponent The path component to prepend, {@code null} if 064 * not specified. 065 * 066 * @return The URI with prepended path component, {@code null} if the 067 * original URI wasn't specified. 068 */ 069 public static URI prependPath(final URI uri, final String pathComponent) { 070 071 if (uri == null) { 072 return null; 073 } 074 075 if (StringUtils.isBlank(pathComponent)) { 076 return uri; 077 } 078 079 String origPath = uri.getPath(); 080 if (origPath == null || origPath.isEmpty() || origPath.equals("/")) { 081 origPath = null; 082 } 083 String joinedPath = joinPathComponents(pathComponent, origPath); 084 joinedPath = prependLeadingSlashIfMissing(joinedPath); 085 086 try { 087 return new URI( 088 uri.getScheme(), null, uri.getHost(), uri.getPort(), 089 joinedPath, 090 uri.getQuery(), uri.getFragment()); 091 } catch (URISyntaxException e) { 092 // should never happen when starting from legal URI 093 return null; 094 } 095 } 096 097 098 /** 099 * Prepends a leading slash `/` if missing to the specified string. 100 * 101 * @param s The string, {@code null} if not specified. 102 * 103 * @return The string with leading slash, {@code null} if not 104 * originally specified. 105 */ 106 public static String prependLeadingSlashIfMissing(String s) { 107 if (s == null) { 108 return null; 109 } 110 if (s.startsWith("/")) { 111 return s; 112 } 113 return "/" + s; 114 } 115 116 117 /** 118 * Strips any leading slashes '/' if present from the specified string. 119 * 120 * @param s The string, {@code null} if not specified. 121 * 122 * @return The string with no leading slash, {@code null} if not 123 * originally specified. 124 */ 125 public static String stripLeadingSlashIfPresent(final String s) { 126 if (StringUtils.isBlank(s)) { 127 return s; 128 } 129 if (s.startsWith("/")) { 130 String tmp = s; 131 while (tmp.startsWith("/")) { 132 tmp = tmp.substring(1); 133 } 134 return tmp; 135 } 136 return s; 137 } 138 139 140 /** 141 * Joins two path components. If the two path components are not 142 * {@code null} or empty they are joined so that there is only a single 143 * slash ('/') between them. 144 * 145 * @param c1 The first path component, {@code null} if not specified. 146 * @param c2 The second path component, {@code null} if not specified. 147 * 148 * @return The joined path components, {@code null} if both are not 149 * specified, or if one is {@code null} the other unmodified. 150 */ 151 public static String joinPathComponents(final String c1, final String c2) { 152 153 if (c1 == null && c2 == null) { 154 return null; 155 } 156 157 if (c1 == null || c1.isEmpty()) { 158 return c2; 159 } 160 161 if (c2 == null || c2.isEmpty()) { 162 return c1; 163 } 164 165 if (c1.endsWith("/") && ! c2.startsWith("/")) { 166 return c1 + c2; 167 } 168 if (! c1.endsWith("/") && c2.startsWith("/")) { 169 return c1 + c2; 170 } 171 if (c1.endsWith("/") && c2.startsWith("/")) { 172 return c1 + stripLeadingSlashIfPresent(c2); 173 } 174 return c1 + "/" + c2; 175 } 176 177 178 /** 179 * Strips the query string from the specified URI. 180 * 181 * @param uri The URI. May be {@code null}.' 182 * 183 * @return The URI with stripped query string, {@code null} if the 184 * original URI is {@code null} or doesn't specify a protocol. 185 */ 186 public static URI stripQueryString(final URI uri) { 187 188 if (uri == null) 189 return null; 190 191 try { 192 return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, uri.getFragment()); 193 194 } catch (URISyntaxException e) { 195 return null; 196 } 197 } 198 199 200 /** 201 * Removes the trailing slash ("/") from the specified URI, if present. 202 * 203 * @param uri The URI. May be {@code null}. 204 * 205 * @return The URI with no trailing slash, {@code null} if the original 206 * URI is {@code null}. 207 */ 208 public static URI removeTrailingSlash(final URI uri) { 209 210 if (uri == null) 211 return null; 212 213 String uriString = uri.toString(); 214 215 if (uriString.charAt(uriString.length() - 1 ) == '/') { 216 return URI.create(uriString.substring(0, uriString.length() - 1)); 217 } 218 219 return uri; 220 } 221 222 223 /** 224 * Ensures the scheme of the specified URI is https. 225 * 226 * @param uri The URI to check, {@code null} if not specified. 227 * 228 * @throws IllegalArgumentException If the URI is specified and the 229 * scheme is not https. 230 */ 231 public static void ensureSchemeIsHTTPS(final URI uri) { 232 233 if (uri == null) { 234 return; 235 } 236 237 if (uri.getScheme() == null || ! "https".equalsIgnoreCase(uri.getScheme())) { 238 throw new IllegalArgumentException("The URI scheme must be https"); 239 } 240 } 241 242 243 /** 244 * Ensures the scheme of the specified URI is https or http. 245 * 246 * @param uri The URI to check, {@code null} if not specified. 247 * 248 * @throws IllegalArgumentException If the URI is specified and the 249 * scheme is not https or http. 250 */ 251 public static void ensureSchemeIsHTTPSorHTTP(final URI uri) { 252 253 if (uri == null) { 254 return; 255 } 256 257 if (uri.getScheme() == null || ! Arrays.asList("http", "https").contains(uri.getScheme().toLowerCase())) { 258 throw new IllegalArgumentException("The URI scheme must be https or http"); 259 } 260 } 261 262 263 /** 264 * Ensures the scheme of the specified URI is not prohibited. 265 * 266 * @param uri The URI to check, {@code null} if not 267 * specified. 268 * @param prohibitedURISchemes The prohibited URI schemes (should be in 269 * lower case), empty or {@code null} if 270 * not specified. 271 * 272 * @throws IllegalArgumentException If the URI is specified and its 273 * scheme is prohibited. 274 */ 275 public static void ensureSchemeIsNotProhibited(final URI uri, final Set<String> prohibitedURISchemes) { 276 277 if (uri == null || uri.getScheme() == null || prohibitedURISchemes == null || prohibitedURISchemes.isEmpty()) { 278 return; 279 } 280 281 if (prohibitedURISchemes.contains(uri.getScheme().toLowerCase())) { 282 throw new IllegalArgumentException("The URI scheme " + uri.getScheme() + " is prohibited"); 283 } 284 } 285 286 287 /** 288 * Prevents public instantiation. 289 */ 290 private URIUtils() {} 291}