/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gravitee.am.gateway.handler.oauth2.security.web;

import io.gravitee.common.http.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author David BRASSELY (david.brassely at graviteesource.com)
 * @author GraviteeSource Team
 */
public class XForwardedAwareRedirectStrategy implements RedirectStrategy {

    private final Logger logger = LoggerFactory.getLogger(XForwardedAwareRedirectStrategy.class);

    private boolean contextRelative;

    @Override
    public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
        String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);

        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUrl);

        String scheme = request.getHeader(HttpHeaders.X_FORWARDED_PROTO);
        if (scheme != null && !scheme.isEmpty()) {
            builder.scheme(scheme);
        }

        String host = request.getHeader(HttpHeaders.X_FORWARDED_HOST);
        if (host != null && !host.isEmpty()) {
            if (host.contains(":")) {
                // Forwarded host contains both host and port
                String [] parts = host.split(":");
                builder.host(parts[0]);
                builder.port(parts[1]);
            } else {
                builder.host(host);
            }
        }

        // decode redirect_uri query param
        Map<String, String> parameters = getParameterMap(redirectUrl);
        builder.replaceQueryParam(OAuth2Utils.REDIRECT_URI, parameters.get(OAuth2Utils.REDIRECT_URI));

        redirectUrl = response.encodeRedirectURL(builder.toUriString());

        if (logger.isDebugEnabled()) {
            logger.debug("Redirecting to '{}'", redirectUrl);
        }

        response.sendRedirect(redirectUrl);
    }

    private String calculateRedirectUrl(String contextPath, String url) {
        if (!UrlUtils.isAbsoluteUrl(url)) {
            if (contextRelative) {
                return url;
            } else {
                return contextPath + url;
            }
        }

        // Full URL, including http(s)://

        if (!contextRelative) {
            return url;
        }

        // Calculate the relative URL from the fully qualified URL, minus the last
        // occurrence of the scheme and base context.
        url = url.substring(url.lastIndexOf("://") + 3); // strip off scheme
        url = url.substring(url.indexOf(contextPath) + contextPath.length());

        if (url.length() > 1 && url.charAt(0) == '/') {
            url = url.substring(1);
        }

        return url;
    }

    /**
     * If <tt>true</tt>, causes any redirection URLs to be calculated minus the protocol
     * and context path (defaults to <tt>false</tt>).
     */
    public void setContextRelative(boolean useRelativeContext) {
        this.contextRelative = useRelativeContext;
    }

    private Map<String, String> getParameterMap(String url) throws UnsupportedEncodingException {
        Map<String, String> queryPairs = new LinkedHashMap<>();
        String[] pairs = url.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf("=");
            queryPairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
        }
        return queryPairs;
    }
}

@@ -790,76 +790,8 @@
 ry;%0A
-import org.springframework.security.oauth2.common.util.OAuth2Utils;%0A
 impo
@@ -844,16 +844,16 @@
 rategy;%0A
+
 import o
@@ -1082,135 +1082,8 @@
 ion;
-%0Aimport java.io.UnsupportedEncodingException;%0Aimport java.net.URLDecoder;%0Aimport java.util.LinkedHashMap;%0Aimport java.util.Map;
 %0A%0A/*
@@ -2308,226 +2308,8 @@
  %7D%0A%0A
-        // decode redirect_uri query param%0A        Map%3CString, String%3E parameters = getParameterMap(redirectUrl);%0A        builder.replaceQueryParam(OAuth2Utils.REDIRECT_URI, parameters.get(OAuth2Utils.REDIRECT_URI));%0A%0A
     
@@ -2361,16 +2361,29 @@
 builder.
+build(false).
 toUriStr
@@ -3642,462 +3642,6 @@
  %7D%0A%0A
-    private Map%3CString, String%3E getParameterMap(String url) throws UnsupportedEncodingException %7B%0A        Map%3CString, String%3E queryPairs = new LinkedHashMap%3C%3E();%0A        String%5B%5D pairs = url.split(%22&%22);%0A        for (String pair : pairs) %7B%0A            int idx = pair.indexOf(%22=%22);%0A            queryPairs.put(URLDecoder.decode(pair.substring(0, idx), %22UTF-8%22), URLDecoder.decode(pair.substring(idx + 1), %22UTF-8%22));%0A        %7D%0A        return queryPairs;%0A    %7D%0A
 %7D%0A

handle encoded redirect_uri query parameter to avoid redirect_uri mismatch exception, input redirect_url must be kept untouched