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.commons.lang3.Strings;
26  import org.apache.sling.api.SlingHttpServletRequest;
27  import org.apache.sling.api.adapter.Adaptable;
28  import org.jetbrains.annotations.NotNull;
29  import org.jetbrains.annotations.Nullable;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * Implements auto-detection for Site URL prefix (author or publish).
35   * This works only if the adaptable is of SlingHttpServletRequest.
36   */
37  final class UrlPrefix {
38  
39    /**
40     * String that is provided in site URL config to enable auto-detection.
41     */
42    public static final String AUTO_DETECTION = "<auto>";
43  
44    static final String HTTP_HEADER_X_FORWARDED_HOST = "X-Forwarded-Host";
45    static final String HTTP_HEADER_X_FORWARDED_PROTO = "X-Forwarded-Proto";
46    static final String HTTP_HEADER_HOST = "Host";
47    static final String HTTP_HEADER_X_FORWARDED_SSL = "X-Forwarded-SSL";
48    static final String VALUE_ON = "on";
49  
50    private static final Logger log = LoggerFactory.getLogger(UrlPrefix.class);
51  
52    private UrlPrefix() {
53      // static methods only
54    }
55  
56    /**
57     * Apply auto-detection to URL prefix.
58     * @param configuredUrlPrefix Configured URL prefix
59     * @param adaptable Adaptable
60     * @return Configured or auto-detected URL prefix
61     */
62    static @Nullable String applyAutoDetection(@Nullable String configuredUrlPrefix, @NotNull Adaptable adaptable) {
63      String urlPrefix = configuredUrlPrefix;
64      if (Strings.CS.contains(configuredUrlPrefix, AUTO_DETECTION)) {
65        // remove auto marker (detection might not be possible if adaptable is not a request)
66        urlPrefix = StringUtils.trimToNull(Strings.CS.remove(configuredUrlPrefix, AUTO_DETECTION));
67        // auto-detect based on request
68        if (adaptable instanceof SlingHttpServletRequest) {
69          SlingHttpServletRequest request = (SlingHttpServletRequest)adaptable;
70          urlPrefix = detectFromForwardedHeader(request);
71          if (urlPrefix == null) {
72            urlPrefix = detectFromServletRequest(request);
73          }
74        }
75      }
76      return urlPrefix;
77    }
78  
79    /**
80     * Try to get URL prefix from X-Forwarded header (e.g. in AEMaaCS environment).
81     * @param request Request
82     * @return Url prefix or null
83     */
84    private static @Nullable String detectFromForwardedHeader(@NotNull SlingHttpServletRequest request) {
85  
86      // output request headers to log in TRACE level
87      if (log.isTraceEnabled()) {
88        StringBuilder output = new StringBuilder();
89        Enumeration<String> headers = request.getHeaderNames();
90        while (headers.hasMoreElements()) {
91          String header = headers.nextElement();
92          if (output.length() > 0) {
93            output.append("; ");
94          }
95          output.append(header).append('=').append(request.getHeader(header));
96        }
97        log.trace("HTTP headers: {}", output);
98      }
99  
100     // this should work for AEMaaCS author
101     String forwardedHost = request.getHeader(HTTP_HEADER_X_FORWARDED_HOST);
102     String forwardedProto = request.getHeader(HTTP_HEADER_X_FORWARDED_PROTO);
103     if (StringUtils.isNotEmpty(forwardedHost) && StringUtils.isNotEmpty(forwardedProto)) {
104       return forwardedProto + "://" + forwardedHost;
105     }
106 
107     // this should work for AEMaaCS publish
108     String host = request.getHeader(HTTP_HEADER_HOST);
109     String forwardedSsl = request.getHeader(HTTP_HEADER_X_FORWARDED_SSL);
110     if (StringUtils.isNotEmpty(host) && Strings.CI.equals(forwardedSsl, VALUE_ON)) {
111       return "https://" + host;
112     }
113 
114     return null;
115   }
116 
117   private static @NotNull String detectFromServletRequest(@NotNull SlingHttpServletRequest request) {
118     StringBuilder urlPrefix = new StringBuilder();
119     urlPrefix.append(request.getScheme()).append("://").append(request.getServerName());
120     int port = request.getServerPort();
121     if ((request.isSecure() && port != 443) || (!request.isSecure() && port != 80)) {
122       urlPrefix.append(':').append(Integer.toString(port));
123     }
124     return urlPrefix.toString();
125   }
126 
127 }