001package ca.uhn.fhir.util; 002 003import static org.apache.commons.lang3.StringUtils.defaultIfBlank; 004 005import java.io.UnsupportedEncodingException; 006import java.net.MalformedURLException; 007import java.net.URL; 008import java.net.URLDecoder; 009import java.net.URLEncoder; 010 011import ca.uhn.fhir.model.primitive.IdDt; 012import ca.uhn.fhir.rest.server.Constants; 013import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 014 015/* 016 * #%L 017 * HAPI FHIR - Core Library 018 * %% 019 * Copyright (C) 2014 - 2016 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 public static void main(String[] args) { 039 System.out.println(escape("http://snomed.info/sct?fhir_vs=isa/126851005")); 040 } 041 042 /** 043 * Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is invalid. 044 */ 045 public static String constructAbsoluteUrl(String theBase, String theEndpoint) { 046 if (theEndpoint == null) { 047 return null; 048 } 049 if (isAbsolute(theEndpoint)) { 050 return theEndpoint; 051 } 052 if (theBase == null) { 053 return theEndpoint; 054 } 055 056 try { 057 return new URL(new URL(theBase), theEndpoint).toString(); 058 } catch (MalformedURLException e) { 059 ourLog.warn("Failed to resolve relative URL[" + theEndpoint + "] against absolute base[" + theBase + "]", e); 060 return theEndpoint; 061 } 062 } 063 064 public static boolean isAbsolute(String theValue) { 065 String value = theValue.toLowerCase(); 066 return value.startsWith("http://") || value.startsWith("https://"); 067 } 068 069 public static String constructRelativeUrl(String theParentExtensionUrl, String theExtensionUrl) { 070 if (theParentExtensionUrl == null) { 071 return theExtensionUrl; 072 } 073 if (theExtensionUrl == null) { 074 return theExtensionUrl; 075 } 076 077 int parentLastSlashIdx = theParentExtensionUrl.lastIndexOf('/'); 078 int childLastSlashIdx = theExtensionUrl.lastIndexOf('/'); 079 080 if (parentLastSlashIdx == -1 || childLastSlashIdx == -1) { 081 return theExtensionUrl; 082 } 083 084 if (parentLastSlashIdx != childLastSlashIdx) { 085 return theExtensionUrl; 086 } 087 088 if (!theParentExtensionUrl.substring(0, parentLastSlashIdx).equals(theExtensionUrl.substring(0, parentLastSlashIdx))) { 089 return theExtensionUrl; 090 } 091 092 if (theExtensionUrl.length() > parentLastSlashIdx) { 093 return theExtensionUrl.substring(parentLastSlashIdx + 1); 094 } 095 096 return theExtensionUrl; 097 } 098 099 public static boolean isValid(String theUrl) { 100 if (theUrl == null || theUrl.length() < 8) { 101 return false; 102 } 103 104 String url = theUrl.toLowerCase(); 105 if (url.charAt(0) != 'h') { 106 return false; 107 } 108 if (url.charAt(1) != 't') { 109 return false; 110 } 111 if (url.charAt(2) != 't') { 112 return false; 113 } 114 if (url.charAt(3) != 'p') { 115 return false; 116 } 117 int slashOffset; 118 if (url.charAt(4) == ':') { 119 slashOffset = 5; 120 } else if (url.charAt(4) == 's') { 121 if (url.charAt(5) != ':') { 122 return false; 123 } 124 slashOffset = 6; 125 } else { 126 return false; 127 } 128 129 if (url.charAt(slashOffset) != '/') { 130 return false; 131 } 132 if (url.charAt(slashOffset + 1) != '/') { 133 return false; 134 } 135 136 return true; 137 } 138 139 public static String unescape(String theString) { 140 if (theString == null) { 141 return null; 142 } 143 if (theString.indexOf('%') == -1) { 144 return theString; 145 } 146 try { 147 return URLDecoder.decode(theString, "UTF-8"); 148 } catch (UnsupportedEncodingException e) { 149 throw new Error("UTF-8 not supported, this shouldn't happen", e); 150 } 151 } 152 153 /** 154 * URL encode a value 155 */ 156 public static String escape(String theValue) { 157 if (theValue == null) { 158 return null; 159 } 160 try { 161 return URLEncoder.encode(theValue, "UTF-8"); 162 } catch (UnsupportedEncodingException e) { 163 throw new Error("UTF-8 not supported on this platform"); 164 } 165 } 166 167 //@formatter:off 168 /** 169 * Parse a URL in one of the following forms: 170 * <ul> 171 * <li>[Resource Type]?[Search Params] 172 * <li>[Resource Type]/[Resource ID] 173 * <li>[Resource Type]/[Resource ID]/_history/[Version ID] 174 * </ul> 175 */ 176 //@formatter:on 177 public static UrlParts parseUrl(String theUrl) { 178 String url = theUrl; 179 UrlParts retVal = new UrlParts(); 180 if (url.startsWith("http")) { 181 if (url.startsWith("/")) { 182 url = url.substring(1); 183 } 184 185 int qmIdx = url.indexOf('?'); 186 if (qmIdx != -1) { 187 retVal.setParams(defaultIfBlank(url.substring(qmIdx + 1), null)); 188 url = url.substring(0, qmIdx); 189 } 190 191 IdDt id = new IdDt(url); 192 retVal.setResourceType(id.getResourceType()); 193 retVal.setResourceId(id.getIdPart()); 194 retVal.setVersionId(id.getVersionIdPart()); 195 return retVal; 196 } else { 197 if (url.matches("\\/[a-zA-Z]+\\?.*")) { 198 url = url.substring(1); 199 } 200 int nextStart = 0; 201 boolean nextIsHistory = false; 202 203 for (int idx = 0; idx < url.length(); idx++) { 204 char nextChar = url.charAt(idx); 205 boolean atEnd = (idx + 1) == url.length(); 206 if (nextChar == '?' || nextChar == '/' || atEnd) { 207 int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx; 208 String nextSubstring = url.substring(nextStart, endIdx); 209 if (retVal.getResourceType() == null) { 210 retVal.setResourceType(nextSubstring); 211 } else if (retVal.getResourceId() == null) { 212 retVal.setResourceId(nextSubstring); 213 } else if (nextIsHistory) { 214 retVal.setVersionId(nextSubstring); 215 } else { 216 if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) { 217 nextIsHistory = true; 218 } else { 219 throw new InvalidRequestException("Invalid FHIR resource URL: " + url); 220 } 221 } 222 if (nextChar == '?') { 223 if (url.length() > idx + 1) { 224 retVal.setParams(url.substring(idx + 1, url.length())); 225 } 226 break; 227 } 228 nextStart = idx + 1; 229 } 230 } 231 232 return retVal; 233 } 234 } 235 236 public static class UrlParts { 237 private String myParams; 238 private String myResourceId; 239 private String myResourceType; 240 private String myVersionId; 241 242 public String getParams() { 243 return myParams; 244 } 245 246 public String getResourceId() { 247 return myResourceId; 248 } 249 250 public String getResourceType() { 251 return myResourceType; 252 } 253 254 public String getVersionId() { 255 return myVersionId; 256 } 257 258 public void setParams(String theParams) { 259 myParams = theParams; 260 } 261 262 public void setResourceId(String theResourceId) { 263 myResourceId = theResourceId; 264 } 265 266 public void setResourceType(String theResourceType) { 267 myResourceType = theResourceType; 268 } 269 270 public void setVersionId(String theVersionId) { 271 myVersionId = theVersionId; 272 } 273 } 274 275}