001package ca.uhn.fhir.util; 002 003import static org.apache.commons.lang3.StringUtils.defaultIfBlank; 004import static org.apache.commons.lang3.StringUtils.isBlank; 005 006import java.io.UnsupportedEncodingException; 007import java.net.MalformedURLException; 008import java.net.URL; 009import java.net.URLDecoder; 010import java.net.URLEncoder; 011import java.util.ArrayList; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Map; 015import java.util.Map.Entry; 016import java.util.StringTokenizer; 017 018import ca.uhn.fhir.model.primitive.IdDt; 019import ca.uhn.fhir.rest.server.Constants; 020import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 021 022/* 023 * #%L 024 * HAPI FHIR - Core Library 025 * %% 026 * Copyright (C) 2014 - 2017 University Health Network 027 * %% 028 * Licensed under the Apache License, Version 2.0 (the "License"); 029 * you may not use this file except in compliance with the License. 030 * You may obtain a copy of the License at 031 * 032 * http://www.apache.org/licenses/LICENSE-2.0 033 * 034 * Unless required by applicable law or agreed to in writing, software 035 * distributed under the License is distributed on an "AS IS" BASIS, 036 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 037 * See the License for the specific language governing permissions and 038 * limitations under the License. 039 * #L% 040 */ 041 042public class UrlUtil { 043 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UrlUtil.class); 044 045 /** 046 * Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is invalid. 047 */ 048 public static String constructAbsoluteUrl(String theBase, String theEndpoint) { 049 if (theEndpoint == null) { 050 return null; 051 } 052 if (isAbsolute(theEndpoint)) { 053 return theEndpoint; 054 } 055 if (theBase == null) { 056 return theEndpoint; 057 } 058 059 try { 060 return new URL(new URL(theBase), theEndpoint).toString(); 061 } catch (MalformedURLException e) { 062 ourLog.warn("Failed to resolve relative URL[" + theEndpoint + "] against absolute base[" + theBase + "]", e); 063 return theEndpoint; 064 } 065 } 066 067 public static String constructRelativeUrl(String theParentExtensionUrl, String theExtensionUrl) { 068 if (theParentExtensionUrl == null) { 069 return theExtensionUrl; 070 } 071 if (theExtensionUrl == null) { 072 return theExtensionUrl; 073 } 074 075 int parentLastSlashIdx = theParentExtensionUrl.lastIndexOf('/'); 076 int childLastSlashIdx = theExtensionUrl.lastIndexOf('/'); 077 078 if (parentLastSlashIdx == -1 || childLastSlashIdx == -1) { 079 return theExtensionUrl; 080 } 081 082 if (parentLastSlashIdx != childLastSlashIdx) { 083 return theExtensionUrl; 084 } 085 086 if (!theParentExtensionUrl.substring(0, parentLastSlashIdx).equals(theExtensionUrl.substring(0, parentLastSlashIdx))) { 087 return theExtensionUrl; 088 } 089 090 if (theExtensionUrl.length() > parentLastSlashIdx) { 091 return theExtensionUrl.substring(parentLastSlashIdx + 1); 092 } 093 094 return theExtensionUrl; 095 } 096 097 /** 098 * URL encode a value 099 */ 100 public static String escape(String theValue) { 101 if (theValue == null) { 102 return null; 103 } 104 try { 105 return URLEncoder.encode(theValue, "UTF-8"); 106 } catch (UnsupportedEncodingException e) { 107 throw new Error("UTF-8 not supported on this platform"); 108 } 109 } 110 111 public static boolean isAbsolute(String theValue) { 112 String value = theValue.toLowerCase(); 113 return value.startsWith("http://") || value.startsWith("https://"); 114 } 115 116 public static boolean isValid(String theUrl) { 117 if (theUrl == null || theUrl.length() < 8) { 118 return false; 119 } 120 121 String url = theUrl.toLowerCase(); 122 if (url.charAt(0) != 'h') { 123 return false; 124 } 125 if (url.charAt(1) != 't') { 126 return false; 127 } 128 if (url.charAt(2) != 't') { 129 return false; 130 } 131 if (url.charAt(3) != 'p') { 132 return false; 133 } 134 int slashOffset; 135 if (url.charAt(4) == ':') { 136 slashOffset = 5; 137 } else if (url.charAt(4) == 's') { 138 if (url.charAt(5) != ':') { 139 return false; 140 } 141 slashOffset = 6; 142 } else { 143 return false; 144 } 145 146 if (url.charAt(slashOffset) != '/') { 147 return false; 148 } 149 if (url.charAt(slashOffset + 1) != '/') { 150 return false; 151 } 152 153 return true; 154 } 155 156 public static void main(String[] args) { 157 System.out.println(escape("http://snomed.info/sct?fhir_vs=isa/126851005")); 158 } 159 160 public static Map<String, String[]> parseQueryString(String theQueryString) { 161 HashMap<String, List<String>> map = new HashMap<String, List<String>>(); 162 parseQueryString(theQueryString, map); 163 return toQueryStringMap(map); 164 } 165 166 public static Map<String, String[]> parseQueryStrings(String... theQueryString) { 167 HashMap<String, List<String>> map = new HashMap<String, List<String>>(); 168 for (String next : theQueryString) { 169 parseQueryString(next, map); 170 } 171 return toQueryStringMap(map); 172 } 173 174 private static Map<String, String[]> toQueryStringMap(HashMap<String, List<String>> map) { 175 HashMap<String, String[]> retVal = new HashMap<String, String[]>(); 176 for (Entry<String, List<String>> nextEntry : map.entrySet()) { 177 retVal.put(nextEntry.getKey(), nextEntry.getValue().toArray(new String[nextEntry.getValue().size()])); 178 } 179 return retVal; 180 } 181 182 private static void parseQueryString(String theQueryString, HashMap<String, List<String>> map) { 183 String query = theQueryString; 184 if (query.startsWith("?")) { 185 query = query.substring(1); 186 } 187 188 189 StringTokenizer tok = new StringTokenizer(query, "&"); 190 while (tok.hasMoreTokens()) { 191 String nextToken = tok.nextToken(); 192 if (isBlank(nextToken)) { 193 continue; 194 } 195 196 int equalsIndex = nextToken.indexOf('='); 197 String nextValue; 198 String nextKey; 199 if (equalsIndex == -1) { 200 nextKey = nextToken; 201 nextValue = ""; 202 } else { 203 nextKey = nextToken.substring(0, equalsIndex); 204 nextValue = nextToken.substring(equalsIndex + 1); 205 } 206 207 nextKey = unescape(nextKey); 208 nextValue = unescape(nextValue); 209 210 List<String> list = map.get(nextKey); 211 if (list == null) { 212 list = new ArrayList<String>(); 213 map.put(nextKey, list); 214 } 215 list.add(nextValue); 216 } 217 } 218 219 //@formatter:off 220 /** 221 * Parse a URL in one of the following forms: 222 * <ul> 223 * <li>[Resource Type]?[Search Params] 224 * <li>[Resource Type]/[Resource ID] 225 * <li>[Resource Type]/[Resource ID]/_history/[Version ID] 226 * </ul> 227 */ 228 //@formatter:on 229 public static UrlParts parseUrl(String theUrl) { 230 String url = theUrl; 231 UrlParts retVal = new UrlParts(); 232 if (url.startsWith("http")) { 233 if (url.startsWith("/")) { 234 url = url.substring(1); 235 } 236 237 int qmIdx = url.indexOf('?'); 238 if (qmIdx != -1) { 239 retVal.setParams(defaultIfBlank(url.substring(qmIdx + 1), null)); 240 url = url.substring(0, qmIdx); 241 } 242 243 IdDt id = new IdDt(url); 244 retVal.setResourceType(id.getResourceType()); 245 retVal.setResourceId(id.getIdPart()); 246 retVal.setVersionId(id.getVersionIdPart()); 247 return retVal; 248 } 249 if (url.matches("\\/[a-zA-Z]+\\?.*")) { 250 url = url.substring(1); 251 } 252 int nextStart = 0; 253 boolean nextIsHistory = false; 254 255 for (int idx = 0; idx < url.length(); idx++) { 256 char nextChar = url.charAt(idx); 257 boolean atEnd = (idx + 1) == url.length(); 258 if (nextChar == '?' || nextChar == '/' || atEnd) { 259 int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx; 260 String nextSubstring = url.substring(nextStart, endIdx); 261 if (retVal.getResourceType() == null) { 262 retVal.setResourceType(nextSubstring); 263 } else if (retVal.getResourceId() == null) { 264 retVal.setResourceId(nextSubstring); 265 } else if (nextIsHistory) { 266 retVal.setVersionId(nextSubstring); 267 } else { 268 if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) { 269 nextIsHistory = true; 270 } else { 271 throw new InvalidRequestException("Invalid FHIR resource URL: " + url); 272 } 273 } 274 if (nextChar == '?') { 275 if (url.length() > idx + 1) { 276 retVal.setParams(url.substring(idx + 1, url.length())); 277 } 278 break; 279 } 280 nextStart = idx + 1; 281 } 282 } 283 284 return retVal; 285 286 } 287 288 public static String unescape(String theString) { 289 if (theString == null) { 290 return null; 291 } 292 for (int i = 0; i < theString.length(); i++) { 293 char nextChar = theString.charAt(i); 294 if (nextChar == '%' || nextChar == '+') { 295 try { 296 return URLDecoder.decode(theString, "UTF-8"); 297 } catch (UnsupportedEncodingException e) { 298 throw new Error("UTF-8 not supported, this shouldn't happen", e); 299 } 300 } 301 } 302 return theString; 303 } 304 305 public static class UrlParts { 306 private String myParams; 307 private String myResourceId; 308 private String myResourceType; 309 private String myVersionId; 310 311 public String getParams() { 312 return myParams; 313 } 314 315 public String getResourceId() { 316 return myResourceId; 317 } 318 319 public String getResourceType() { 320 return myResourceType; 321 } 322 323 public String getVersionId() { 324 return myVersionId; 325 } 326 327 public void setParams(String theParams) { 328 myParams = theParams; 329 } 330 331 public void setResourceId(String theResourceId) { 332 myResourceId = theResourceId; 333 } 334 335 public void setResourceType(String theResourceType) { 336 myResourceType = theResourceType; 337 } 338 339 public void setVersionId(String theVersionId) { 340 myVersionId = theVersionId; 341 } 342 } 343 344}