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