    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}