001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012package org.apache.tapestry5.http.internal.services; 013 014import java.util.Arrays; 015import java.util.List; 016import java.util.Optional; 017 018import javax.servlet.http.HttpServletRequest; 019import javax.servlet.http.HttpServletResponse; 020 021import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 022import org.apache.tapestry5.http.services.CorsHandler; 023import org.apache.tapestry5.http.services.CorsHandlerHelper; 024import org.apache.tapestry5.ioc.annotations.Symbol; 025 026/** 027 * Default {@link CorsHandlerHelper} implementation. 028 * 029 * @see CorsHandler 030 * @see TapestryHttpSymbolConstants#CORS_ENABLED 031 * @see TapestryHttpSymbolConstants#CORS_ALLOWED_ORIGINS 032 * @since 5.8.2 033 */ 034public class CorsHandlerHelperImpl implements CorsHandlerHelper 035{ 036 037 private final List<String> allowedOrigins; 038 039 private final boolean allowAllOrigins; 040 private final boolean allowCredentials; 041 private final String allowMethods; 042 private final String allowedHeaders; 043 private final String exposeHeaders; 044 private final String maxAge; 045 046 public CorsHandlerHelperImpl( 047 @Symbol(TapestryHttpSymbolConstants.CORS_ALLOWED_ORIGINS) String allowedOrigins, 048 @Symbol(TapestryHttpSymbolConstants.CORS_ALLOW_CREDENTIALS) boolean allowCredentials, 049 @Symbol(TapestryHttpSymbolConstants.CORS_ALLOW_METHODS) String allowMethods, 050 @Symbol(TapestryHttpSymbolConstants.CORS_ALLOWED_HEADERS) String allowedHeaders, 051 @Symbol(TapestryHttpSymbolConstants.CORS_EXPOSE_HEADERS) String exposeHeaders, 052 @Symbol(TapestryHttpSymbolConstants.CORS_MAX_AGE) String maxAge) 053 { 054 allowAllOrigins = ORIGIN_WILDCARD.equals(allowedOrigins); 055 this.allowedOrigins = !allowAllOrigins ? Arrays.asList(trim(allowedOrigins.split(","))) : null; 056 this.allowCredentials = allowCredentials; 057 this.allowMethods = allowMethods.trim(); 058 this.allowedHeaders = allowedHeaders.trim(); 059 this.exposeHeaders = exposeHeaders.trim(); 060 this.maxAge = maxAge.trim(); 061 } 062 063 @Override 064 public String getPath(HttpServletRequest request) 065 { 066 067 // Copied from RequestImpl.getRequest() 068 String pathInfo = request.getPathInfo(); 069 070 if (pathInfo == null) 071 { 072 return request.getServletPath(); 073 } 074 075 // Websphere 6.1 is a bit wonky (see TAPESTRY-1713), and tends to return the empty string 076 // for the servlet path, and return the true path in pathInfo. 077 078 return pathInfo.length() == 0 ? "/" : pathInfo; 079 } 080 081 @Override 082 public Optional<String> getAllowedOrigin(HttpServletRequest request) 083 { 084 final Optional<String> allowedOrigin; 085 final Optional<String> origin = getOrigin(request); 086 087 if (allowAllOrigins) 088 { 089 allowedOrigin = Optional.of(ORIGIN_WILDCARD); 090 } 091 else if (origin.isPresent() && allowedOrigins.contains(origin.get())) 092 { 093 allowedOrigin = origin; 094 } 095 else 096 { 097 allowedOrigin = Optional.empty(); 098 } 099 100 return allowedOrigin; 101 } 102 103 @Override 104 public Optional<String> getOrigin(HttpServletRequest request) 105 { 106 return Optional.ofNullable(request.getHeader(ORIGIN_HEADER)); 107 } 108 109 @Override 110 public boolean isPreflight(HttpServletRequest request) 111 { 112 boolean preflight = false; 113 if (OPTIONS_METHOD.equals(request.getMethod())) 114 { 115 final Optional<String> origin = getAllowedOrigin(request); 116 preflight = origin.isPresent() && !origin.get().trim().isEmpty(); 117 } 118 return preflight; 119 } 120 121 @Override 122 public void configureOrigin(HttpServletResponse response, String value) 123 { 124 response.setHeader(ALLOW_ORIGIN_HEADER, value); 125 addValueToVaryHeader(response, ORIGIN_HEADER); 126 } 127 128 @Override 129 public void configureCredentials(HttpServletResponse response) 130 { 131 if (allowCredentials) 132 { 133 response.setHeader(ALLOW_CREDENTIALS_HEADER, "true"); 134 } 135 } 136 137 @Override 138 public void configureMethods(HttpServletResponse response) 139 { 140 response.setHeader(ALLOW_METHODS_HEADER, allowMethods); 141 } 142 143 @Override 144 public void configureAllowedHeaders(HttpServletResponse response, HttpServletRequest request) 145 { 146 String value; 147 if (allowedHeaders.isEmpty()) 148 { 149 value = request.getHeader(REQUEST_HEADERS_HEADER); 150 addValueToVaryHeader(response, REQUEST_HEADERS_HEADER); 151 } 152 else 153 { 154 value = allowedHeaders; 155 } 156 if (value != null && !value.isEmpty()) 157 { 158 response.setHeader(ALLOW_HEADERS_HEADER, value); 159 } 160 } 161 162 163 @Override 164 public void configureExposeHeaders(HttpServletResponse response) 165 { 166 if (!exposeHeaders.isEmpty()) 167 { 168 response.setHeader(EXPOSE_HEADERS_HEADER, exposeHeaders); 169 } 170 } 171 172 @Override 173 public void configureMaxAge(HttpServletResponse response) 174 { 175 if (!maxAge.isEmpty()) 176 { 177 response.setHeader(MAX_AGE_HEADER, maxAge); 178 } 179 } 180 181 182 private static String[] trim(String[] strings) { 183 for (int i = 0; i < strings.length; i++) 184 { 185 strings[i] = strings[i].trim(); 186 } 187 return strings; 188 } 189 190}