View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2022 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.handler.url.impl;
21  
22  import java.util.Enumeration;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.sling.api.SlingHttpServletRequest;
26  import org.apache.sling.api.adapter.Adaptable;
27  import org.jetbrains.annotations.NotNull;
28  import org.jetbrains.annotations.Nullable;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  /**
33   * Implements auto-detection for Site URL prefix (author or publish).
34   * This works only if the adaptable is of SlingHttpServletRequest.
35   */
36  final class UrlPrefix {
37  
38    /**
39     * String that is provided in site URL config to enable auto-detection.
40     */
41    public static final String AUTO_DETECTION = "<auto>";
42  
43    static final String HTTP_HEADER_X_FORWARDED_HOST = "X-Forwarded-Host";
44    static final String HTTP_HEADER_X_FORWARDED_PROTO = "X-Forwarded-Proto";
45    static final String HTTP_HEADER_HOST = "Host";
46    static final String HTTP_HEADER_X_FORWARDED_SSL = "X-Forwarded-SSL";
47    static final String VALUE_ON = "on";
48  
49    private static final Logger log = LoggerFactory.getLogger(UrlPrefix.class);
50  
51    private UrlPrefix() {
52      // static methods only
53    }
54  
55    /**
56     * Apply auto-detection to URL prefix.
57     * @param configuredUrlPrefix Configured URL prefix
58     * @param adaptable Adaptable
59     * @return Configured or auto-detected URL prefix
60     */
61    static @Nullable String applyAutoDetection(@Nullable String configuredUrlPrefix, @NotNull Adaptable adaptable) {
62      String urlPrefix = configuredUrlPrefix;
63      if (StringUtils.contains(configuredUrlPrefix, AUTO_DETECTION)) {
64        // remove auto marker (detection might not be possible if adaptable is not a request)
65        urlPrefix = StringUtils.trimToNull(StringUtils.remove(configuredUrlPrefix, AUTO_DETECTION));
66        // auto-detect based on request
67        if (adaptable instanceof SlingHttpServletRequest) {
68          SlingHttpServletRequest request = (SlingHttpServletRequest)adaptable;
69          urlPrefix = detectFromForwardedHeader(request);
70          if (urlPrefix == null) {
71            urlPrefix = detectFromServletRequest(request);
72          }
73        }
74      }
75      return urlPrefix;
76    }
77  
78    /**
79     * Try to get URL prefix from X-Forwarded header (e.g. in AEMaaCS environment).
80     * @param request Request
81     * @return Url prefix or null
82     */
83    private static @Nullable String detectFromForwardedHeader(@NotNull SlingHttpServletRequest request) {
84  
85      // output request headers to log in TRACE level
86      if (log.isTraceEnabled()) {
87        StringBuilder output = new StringBuilder();
88        Enumeration<String> headers = request.getHeaderNames();
89        while (headers.hasMoreElements()) {
90          String header = headers.nextElement();
91          if (output.length() > 0) {
92            output.append("; ");
93          }
94          output.append(header).append('=').append(request.getHeader(header));
95        }
96        log.trace("HTTP headers: {}", output);
97      }
98  
99      // this should work for AEMaaCS author
100     String forwardedHost = request.getHeader(HTTP_HEADER_X_FORWARDED_HOST);
101     String forwardedProto = request.getHeader(HTTP_HEADER_X_FORWARDED_PROTO);
102     if (StringUtils.isNotEmpty(forwardedHost) && StringUtils.isNotEmpty(forwardedProto)) {
103       return forwardedProto + "://" + forwardedHost;
104     }
105 
106     // this should work for AEMaaCS publish
107     String host = request.getHeader(HTTP_HEADER_HOST);
108     String forwardedSsl = request.getHeader(HTTP_HEADER_X_FORWARDED_SSL);
109     if (StringUtils.isNotEmpty(host) && StringUtils.equalsIgnoreCase(forwardedSsl, VALUE_ON)) {
110       return "https://" + host;
111     }
112 
113     return null;
114   }
115 
116   private static @NotNull String detectFromServletRequest(@NotNull SlingHttpServletRequest request) {
117     StringBuilder urlPrefix = new StringBuilder();
118     urlPrefix.append(request.getScheme()).append("://").append(request.getServerName());
119     int port = request.getServerPort();
120     if ((request.isSecure() && port != 443) || (!request.isSecure() && port != 80)) {
121       urlPrefix.append(':').append(Integer.toString(port));
122     }
123     return urlPrefix.toString();
124   }
125 
126 }