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}