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}