001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.server;
021
022import jakarta.servlet.ServletContext;
023import jakarta.servlet.http.HttpServletRequest;
024import org.apache.commons.lang3.StringUtils;
025
026/**
027 * Determines the server's base using the incoming request
028 */
029public class IncomingRequestAddressStrategy implements IServerAddressStrategy {
030
031        private String myServletPath;
032
033        @Override
034        public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) {
035                if (theRequest == null) {
036                        return null;
037                }
038                String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
039
040                String servletPath;
041                if (myServletPath != null) {
042                        servletPath = myServletPath;
043                } else {
044                        servletPath = StringUtils.defaultString(theRequest.getServletPath());
045                }
046
047                StringBuffer requestUrl = theRequest.getRequestURL();
048                String servletContextPath = StringUtils.defaultString(theRequest.getContextPath());
049
050                String requestPath = requestFullPath.substring(servletContextPath.length() + servletPath.length());
051                if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
052                        requestPath = requestPath.substring(1);
053                }
054
055                int startOfPath = requestUrl.indexOf("//");
056                int requestUrlLength = requestUrl.length();
057
058                if (startOfPath != -1 && (startOfPath + 2) < requestUrlLength) {
059                        startOfPath = requestUrl.indexOf("/", startOfPath + 2);
060                }
061                if (startOfPath == -1) {
062                        startOfPath = 0;
063                }
064
065                int contextIndex;
066                if (servletPath.length() == 0 || servletPath.equals("/")) {
067                        if (requestPath.length() == 0) {
068                                contextIndex = requestUrlLength;
069                        } else {
070                                contextIndex = requestUrl.indexOf(requestPath, startOfPath);
071                        }
072                } else {
073                        // servletContextPath can start with servletPath
074                        contextIndex = requestUrl.indexOf(servletPath + "/", startOfPath);
075                        if (contextIndex == -1) {
076                                contextIndex = requestUrl.indexOf(servletPath, startOfPath);
077                        }
078                }
079
080                String fhirServerBase;
081                int length = contextIndex + servletPath.length();
082                if (length > requestUrlLength) {
083                        length = requestUrlLength;
084                }
085                fhirServerBase = requestUrl.substring(0, length);
086                return fhirServerBase;
087        }
088
089        /**
090         * If set to a non-null value (default is <code>null</code>), this address strategy assumes that the FHIR endpoint is deployed to the given servlet path within the context. This is useful in some
091         * deployments where it isn't obvious to the servlet which part of the path is actually the root path to reach the servlet.
092         * <p>
093         * Example values could be:
094         * <ul>
095         * <li>null</li>
096         * <li>/</li>
097         * <li>/base</li>
098         * </ul>
099         * </p>
100         * <p>
101         * <b>Wildcards are not supported!</b>
102         * </p>
103         */
104        public void setServletPath(String theServletPath) {
105                myServletPath = theServletPath;
106        }
107
108        /**
109         * Determines the servlet's context path.
110         *
111         * This is here to try and deal with the wide variation in servers and what they return.
112         *
113         * getServletContext().getContextPath() is supposed to return the path to the specific servlet we are deployed as but it's not available everywhere. On some servers getServletContext() can return
114         * null (old Jetty seems to suffer from this, see hapi-fhir-base-test-mindeps-server) and on other old servers (Servlet 2.4) getServletContext().getContextPath() doesn't even exist.
115         *
116         * theRequest.getContextPath() returns the context for the specific incoming request. It should be available everywhere, but it's likely to be less predicable if there are multiple servlet mappings
117         * pointing to the same servlet, so we don't favour it. This is possibly not the best strategy (maybe we should just always use theRequest.getContextPath()?) but so far people seem happy with this
118         * behavour across a wide variety of platforms.
119         *
120         * If you are having troubles on a given platform/configuration and want to suggest a change or even report incompatibility here, we'd love to hear about it.
121         */
122        public static String determineServletContextPath(HttpServletRequest theRequest, RestfulServer server) {
123                String retVal;
124                if (server.getServletContext() != null) {
125                        if (server.getServletContext().getMajorVersion() >= 3
126                                        || (server.getServletContext().getMajorVersion() > 2
127                                                        && server.getServletContext().getMinorVersion() >= 5)) {
128                                retVal = server.getServletContext().getContextPath();
129                        } else {
130                                retVal = theRequest.getContextPath();
131                        }
132                } else {
133                        retVal = theRequest.getContextPath();
134                }
135                retVal = StringUtils.defaultString(retVal);
136                return retVal;
137        }
138}