com.chiorichan.http.HttpRequestWrapper.java Source code

Java tutorial

Introduction

Here is the source code for com.chiorichan.http.HttpRequestWrapper.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2016 Chiori Greene a.k.a. Chiori-chan <me@chiorichan.com>
 * All Right Reserved.
 */
package com.chiorichan.http;

import io.netty.channel.Channel;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.ssl.SslHandler;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;

import org.apache.commons.lang3.Validate;

import com.chiorichan.AppConfig;
import com.chiorichan.Loader;
import com.chiorichan.account.Account;
import com.chiorichan.account.AccountManager;
import com.chiorichan.account.auth.AccountAuthenticator;
import com.chiorichan.account.lang.AccountDescriptiveReason;
import com.chiorichan.account.lang.AccountException;
import com.chiorichan.account.lang.AccountResult;
import com.chiorichan.lang.EnumColor;
import com.chiorichan.logger.experimental.LogEvent;
import com.chiorichan.messaging.MessageSender;
import com.chiorichan.net.NetworkManager;
import com.chiorichan.session.Session;
import com.chiorichan.session.SessionContext;
import com.chiorichan.session.SessionManager;
import com.chiorichan.session.SessionWrapper;
import com.chiorichan.site.Site;
import com.chiorichan.site.SiteManager;
import com.chiorichan.site.SiteMapping;
import com.chiorichan.tasks.Timings;
import com.chiorichan.util.Namespace;
import com.chiorichan.util.NetworkFunc;
import com.chiorichan.util.ObjectFunc;
import com.chiorichan.util.Pair;
import com.chiorichan.util.StringFunc;
import com.chiorichan.util.Versioning;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Wraps the Netty HttpRequest and provides shortcut methods
 */
public class HttpRequestWrapper extends SessionWrapper implements SessionContext {
    private static final Map<Thread, WeakReference<HttpRequestWrapper>> references = Maps.newConcurrentMap();

    /**
     * Return maps as unmodifiable
     */
    private static boolean unmodifiableMaps = AppConfig.get()
            .getBoolean("advanced.security.unmodifiableMapsEnabled", true);

    public static HttpRequestWrapper getRequest() {
        if (!references.containsKey(Thread.currentThread()) || references.get(Thread.currentThread()).get() == null)
            throw new IllegalStateException("Thread '" + Thread.currentThread().getName()
                    + "' does not seem to currently link to any existing http requests, please try again or notify an administrator.");
        return references.get(Thread.currentThread()).get();
    }

    private static void putRequest(HttpRequestWrapper request) {
        references.put(Thread.currentThread(), new WeakReference<HttpRequestWrapper>(request));
    }

    /**
     * The original Netty Channel
     */
    private final Channel channel;

    /**
     * The size of the posted content
     */
    int contentSize = 0;

    /**
     * Cookie Cache
     */
    final Set<HttpCookie> cookies = Sets.newHashSet();

    /**
     * The Get Map
     */
    final Map<String, String> getMap = Maps.newTreeMap();

    /**
     * The {@link HttpHandler} for this request
     */
    final HttpHandler handler;

    /**
     * The original Netty Http Request
     */
    private final HttpRequest http;

    /**
     * Instance of LogEvent used by this request
     */
    final LogEvent log;

    /**
     * The Post Map
     */
    final Map<String, String> postMap = Maps.newTreeMap();

    /**
     * The time of this request
     */
    final int requestTime;

    /**
     * The paired HttpResponseWrapper
     */
    final HttpResponseWrapper response;

    /**
     * The URI Rewrite Map
     */
    final Map<String, String> rewriteMap = Maps.newTreeMap();

    /**
     * Server Cookie Cache
     */
    final Set<HttpCookie> serverCookies = Sets.newHashSet();

    /**
     * Server Variables
     */
    HttpVariableMapper vars = new HttpVariableMapper();

    /**
     * The Site associated with this request
     */
    Site site;

    /**
     * Is this a SSL request
     */
    final boolean ssl;

    /**
     * Files uploaded with this request
     */
    final Map<String, UploadedFile> uploadedFiles = new HashMap<String, UploadedFile>();

    /**
     * The requested URI
     */
    private String uri = null;

    /**
     * The requested root domain
     */
    private String parentDomain;

    /**
     * The requested child domain
     */
    private String childDomain = "";

    private boolean nonceProcessed = false;

    private HttpAuthenticator auth = null;

    HttpRequestWrapper(Channel channel, HttpRequest http, HttpHandler handler, boolean ssl, LogEvent log)
            throws IOException {
        this.channel = channel;
        this.http = http;
        this.handler = handler;
        this.ssl = ssl;
        this.log = log;

        putRequest(this);

        // Set Time of this Request
        requestTime = Timings.epoch();

        // Create a matching HttpResponseWrapper
        response = new HttpResponseWrapper(this, log);

        String host = getHostDomain();

        if (host == null || host.length() == 0) {
            parentDomain = "";
            site = SiteManager.instance().getDefaultSite();
        } else if (NetworkFunc.isValidIPv4(host) || NetworkFunc.isValidIPv6(host)) {
            parentDomain = host;
            site = SiteManager.instance().getSiteByIp(host).get(0);
        } else {
            Pair<String, SiteMapping> match = SiteMapping.get(host);

            if (match == null) {
                parentDomain = host;
                site = SiteManager.instance().getDefaultSite();
            } else {
                parentDomain = match.getKey();
                Namespace hostNamespace = new Namespace(host);
                Namespace parentNamespace = new Namespace(parentDomain);
                Namespace childNamespace = hostNamespace.subNamespace(0,
                        hostNamespace.getNodeCount() - parentNamespace.getNodeCount());
                assert hostNamespace.getNodeCount() - parentNamespace.getNodeCount() == childNamespace
                        .getNodeCount();
                childDomain = childNamespace.getNamespace();

                site = match.getValue().getSite();
            }
        }

        if (site == null)
            site = SiteManager.instance().getDefaultSite();

        if (site == SiteManager.instance().getDefaultSite() && getUri().startsWith("/~")) {
            List<String> uris = Splitter.on("/").omitEmptyStrings().splitToList(getUri());
            String siteId = uris.get(0).substring(1);

            Site siteTmp = SiteManager.instance().getSiteById(siteId);
            if (!siteId.equals("wisp") && siteTmp != null) {
                site = siteTmp;
                uri = "/" + Joiner.on("/").join(uris.subList(1, uris.size()));

                // TODO Implement both a virtual and real URI for use in redirects and url_to()
                String[] domains = site.getDomains().keySet().toArray(new String[0]);
                parentDomain = domains.length == 0 ? host : domains[0];
            }
        }

        // log.log( Level.INFO, "SiteId: " + site.getSiteId() + ", ParentDomain: " + parentDomain + ", ChildDomain: " + childDomain );

        try {
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(http.uri());
            Map<String, List<String>> params = queryStringDecoder.parameters();
            if (!params.isEmpty())
                for (Entry<String, List<String>> p : params.entrySet()) {
                    // XXX This is overriding the key, why would their there be multiple values???
                    String key = p.getKey();
                    List<String> vals = p.getValue();
                    for (String val : vals)
                        getMap.put(key, val);
                }
        } catch (IllegalStateException e) {
            log.log(Level.SEVERE, "Failed to decode the GET map because " + e.getMessage());
        }

        // Decode Cookies
        // String var1 = URLDecoder.decode( http.headers().getAndConvert( "Cookie" ), Charsets.UTF_8.displayName() );
        String var1 = http.headers().getAndConvert("Cookie");

        // TODO Find a way to fix missing invalid stuff

        if (var1 != null)
            try {
                Set<Cookie> var2 = CookieDecoder.decode(var1);
                for (Cookie cookie : var2)
                    if (cookie.name().startsWith("_ws"))
                        serverCookies.add(new HttpCookie(cookie));
                    else
                        cookies.add(new HttpCookie(cookie));
            } catch (IllegalArgumentException | NullPointerException e) {
                //NetworkManager.getLogger().debug( var1 );

                NetworkManager.getLogger().severe("Failed to parse cookie for reason: " + e.getMessage());
                // NetworkManager.getLogger().warning( "There was a problem decoding the request cookie.", e );
                // NetworkManager.getLogger().debug( "Cookie: " + var1 );
                // NetworkManager.getLogger().debug( "Headers: " + Joiner.on( "," ).withKeyValueSeparator( "=" ).join( http.headers() ) );
            }

        initServerVars();
    }

    @Override
    protected void finish0() {
        // Do Nothing
    }

    public String getArgument(String key) {
        String val = getMap.get(key);

        if (val == null && postMap != null)
            val = postMap.get(key);

        if (val == null && rewriteMap != null)
            val = rewriteMap.get(key);

        return val;
    }

    public String getArgument(String key, String def) {
        String val = getArgument(key);
        return val == null ? def : val;
    }

    public boolean getArgumentBoolean(String key) {
        String rtn = getArgument(key, "0").toLowerCase();
        return StringFunc.isTrue(rtn);
    }

    public double getArgumentDouble(String key) {
        Object obj = getArgument(key, "-1.0");
        return ObjectFunc.castToDouble(obj);
    }

    public int getArgumentInt(String key) {
        Object obj = getArgument(key, "-1");
        return ObjectFunc.castToInt(obj);
    }

    public Set<String> getArgumentKeys() {
        Set<String> keys = Sets.newHashSet();
        keys.addAll(getMap.keySet());
        keys.addAll(postMap.keySet());
        keys.addAll(rewriteMap.keySet());
        return keys;
    }

    public long getArgumentLong(String key) {
        Object obj = getArgument(key, "-1");
        return ObjectFunc.castToLong(obj);
    }

    public HttpAuthenticator getAuth() {
        if (auth == null)
            initAuthorization();
        return auth;
    }

    public String getBaseUrl() {
        String url = getDomain();

        if (getSubdomain() != null && !getSubdomain().isEmpty())
            url = getSubdomain() + "." + url;

        return (isSecure() ? "https://" : "http://") + url;
    }

    public Channel getChannel() {
        return channel;
    }

    public int getContentLength() {
        return contentSize;
    }

    @Override
    public HttpCookie getCookie(String key) {
        for (HttpCookie cookie : cookies)
            if (cookie.getKey().equals(key))
                return cookie;
        return null;
    }

    @Override
    public Set<HttpCookie> getCookies() {
        return Collections.unmodifiableSet(cookies);
    }

    public String getDomain() {
        return parentDomain == null ? "" : parentDomain;
    }

    public String getFullDomain() {
        return getFullDomain(null, ssl);
    }

    public String getFullDomain(boolean ssl) {
        return getFullDomain(null, ssl);
    }

    public String getFullDomain(String subdomain) {
        return getFullDomain(subdomain, ssl);
    }

    public String getFullDomain(String subdomain, boolean ssl) {
        return (ssl ? "https://" : "http://") + (subdomain == null || subdomain.isEmpty() ? "" : subdomain + ".")
                + getDomain() + "/";
    }

    public String getFullUrl() {
        return getFullUrl(null, ssl);
    }

    public String getFullUrl(boolean ssl) {
        return getFullUrl(null, ssl);
    }

    public String getFullUrl(String subdomain) {
        return getFullUrl(subdomain, ssl);
    }

    public String getFullUrl(String subdomain, boolean ssl) {
        return getFullDomain(subdomain, ssl) + getUri().substring(1);
    }

    public Map<String, Object> getGetMap() {
        return parseMapArrays(getGetMapRaw());
    }

    public Map<String, String> getGetMapRaw() {
        if (unmodifiableMaps)
            return Collections.unmodifiableMap(getMap);

        return getMap;
    }

    public String getHeader(CharSequence key) {
        try {
            return http.headers().getAndConvert(key);
        } catch (NullPointerException | IndexOutOfBoundsException e) {
            return null;
        }
    }

    public HttpHeaders getHeaders() {
        return http.headers();
    }

    public String getHost() {
        return http.headers().getAndConvert("Host");
    }

    private String getHostDomain() {
        if (http.headers().contains("Host"))
            return http.headers().getAndConvert("Host").split("\\:")[0];
        return null;
    }

    public HttpVersion getHttpVersion() {
        return http.protocolVersion();
    }

    /**
     * Similar to {@link #getInetAddr()}
     *
     * @return
     *         the remote connections IP address
     */
    public InetAddress getInetAddr() {
        return getInetAddr(true);
    }

    /**
     * Similar to {@link #getInetAddr(boolean)}
     *
     * @param detectCDN
     *             Try to detect the use of CDNs, e.g., CloudFlare, IP headers when set to false.
     * @return
     *         the remote connections IP address
     */
    public InetAddress getInetAddr(boolean detectCDN) {
        if (detectCDN && http.headers().contains("CF-Connecting-IP"))
            try {
                return InetAddress.getByName(http.headers().getAndConvert("CF-Connecting-IP"));
            } catch (UnknownHostException e) {
                e.printStackTrace();
                return null;
            }

        return ((InetSocketAddress) channel.remoteAddress()).getAddress();
    }

    public WebInterpreter getInterpreter() {
        return handler.getInterpreter();
    }

    /**
     * Similar to {@link #getIpAddr(boolean)} except defaults to true
     *
     * @return
     *         the remote connections IP address as a string
     */
    @Override
    public String getIpAddr() {
        return getIpAddr(true);
    }

    /**
     * This method uses a checker that makes it possible for our server to get the correct remote IP even if using it with CloudFlare.
     * I believe there are other CDN services like CloudFlare. I'd love it if people could inform me, so I can implement similar methods.
     * https://support.cloudflare.com/hc/en-us/articles/200170786-Why-do-my-server-logs-show-CloudFlare-s-IPs-using-CloudFlare-
     *
     * @param detectCDN
     *             Try to detect the use of CDNs, e.g., CloudFlare, IP headers when set to false.
     * @return
     *         the remote connections IP address as a string
     */
    public String getIpAddr(boolean detectCDN) {
        // TODO Implement other CDNs
        if (detectCDN && http.headers().contains("CF-Connecting-IP"))
            return http.headers().getAndConvert("CF-Connecting-IP");

        return ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
    }

    public String getLocalHostName() {
        return ((InetSocketAddress) channel.localAddress()).getHostName();
    }

    public String getLocalIpAddr() {
        return ((InetSocketAddress) channel.localAddress()).getAddress().getHostAddress();
    }

    public int getLocalPort() {
        return ((InetSocketAddress) channel.localAddress()).getPort();
    }

    @Override
    public Site getLocation() {
        if (site == null)
            site = SiteManager.instance().getDefaultSite();
        return site;
    }

    public HttpRequest getOriginal() {
        return http;
    }

    public String getParameter(String key) {
        return null;
    }

    public Map<String, Object> getPostMap() {
        return parseMapArrays(getPostMapRaw());
    }

    public Map<String, String> getPostMapRaw() {
        if (unmodifiableMaps)
            return Collections.unmodifiableMap(postMap);

        return postMap;
    }

    public String getQuery() {
        if (getMap.isEmpty())
            return "";
        return "?" + Joiner.on("&").withKeyValueSeparator("=").join(getMap);
    }

    public String getRemoteHostname() {
        return ((InetSocketAddress) channel.remoteAddress()).getHostName();
    }

    public int getRemotePort() {
        return ((InetSocketAddress) channel.remoteAddress()).getPort();
    }

    public String getRequestHost() {
        return getHeader("Host");
    }

    public Map<String, Object> getRequestMap() {
        return parseMapArrays(getRequestMapRaw());
    }

    public Map<String, String> getRequestMapRaw() {
        Map<String, String> requestMap = new HashMap<String, String>();

        if (getMap != null)
            requestMap.putAll(getMap);

        if (postMap != null)
            requestMap.putAll(postMap);

        if (rewriteMap != null)
            requestMap.putAll(rewriteMap);

        if (unmodifiableMaps)
            return Collections.unmodifiableMap(requestMap);

        return requestMap;
    }

    public int getRequestTime() {
        return requestTime;
    }

    public HttpResponseWrapper getResponse() {
        return response;
    }

    public Map<String, String> getRewriteMap() {
        if (unmodifiableMaps)
            return Collections.unmodifiableMap(rewriteMap);

        return rewriteMap;
    }

    public HttpVariableMapper getServer() {
        return vars;
    }

    @Override
    protected HttpCookie getServerCookie(String key) {
        for (HttpCookie cookie : serverCookies)
            if (cookie.getKey().equals(key))
                return cookie;

        return null;
    }

    public Site getSite() {
        return getLocation();
    }

    public String getSubdomain() {
        return childDomain == null ? "" : childDomain;
    }

    public Map<String, UploadedFile> getUploadedFiles() {
        return Collections.unmodifiableMap(uploadedFiles);
    }

    Map<String, UploadedFile> getUploadedFilesRaw() {
        return uploadedFiles;
    }

    public String getUri() {
        if (uri == null) {
            uri = http.uri();

            try {
                uri = URLDecoder.decode(uri, Charsets.UTF_8.name());
            } catch (UnsupportedEncodingException e) {
                try {
                    uri = URLDecoder.decode(uri, Charsets.ISO_8859_1.name());
                } catch (UnsupportedEncodingException e1) {
                    throw new Error();
                }
            } catch (IllegalArgumentException e1) {
                // [ni..up-3-1] 02-05 00:17:10.273 [WARNING] [HttpHdl] WARNING THIS IS AN UNCAUGHT EXCEPTION! CAN YOU KINDLY REPORT THIS STACKTRACE TO THE DEVELOPER?
                // java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "im"
            }

            // if ( uri.contains( File.separator + '.' ) || uri.contains( '.' + File.separator ) || uri.startsWith( "." ) || uri.endsWith( "." ) || INSECURE_URI.matcher( uri ).matches() )
            // {
            // return "/";
            // }

            if (uri.contains("?"))
                uri = uri.substring(0, uri.indexOf("?"));

            if (!uri.startsWith("/"))
                uri = "/" + uri;
        }

        return uri;
    }

    public String getUserAgent() {
        return getHeader("User-Agent");
    }

    public String getWebSocketLocation(HttpObject req) {
        String location = getHost() + "/fw/websocket";
        if (ssl)
            return "wss://" + location;
        else
            return "ws://" + location;
    }

    public boolean hasArgument(String key) {
        return getMap.containsKey(key) || postMap.containsKey(key) || rewriteMap.containsKey(key);
    }

    private void initAuthorization() {
        if (auth == null && getHeader(HttpHeaderNames.AUTHORIZATION) != null)
            auth = new HttpAuthenticator(this);
    }

    /**
     * Initializes the serverVars with initial information from this request
     */
    private void initServerVars() {
        vars.put(ServerVars.SERVER_SOFTWARE, Versioning.getProduct());
        vars.put(ServerVars.SERVER_VERSION, Versioning.getVersion());
        vars.put(ServerVars.SERVER_ADMIN, AppConfig.get().getString("server.admin", "me@chiorichan.com"));
        vars.put(ServerVars.SERVER_SIGNATURE, Versioning.getProduct() + " Version " + Versioning.getVersion());
        vars.put(ServerVars.HTTP_VERSION, http.protocolVersion());
        vars.put(ServerVars.HTTP_ACCEPT, getHeader("Accept"));
        vars.put(ServerVars.HTTP_USER_AGENT, getUserAgent());
        vars.put(ServerVars.HTTP_CONNECTION, getHeader("Connection"));
        vars.put(ServerVars.HTTP_HOST, getLocalHostName());
        vars.put(ServerVars.HTTP_ACCEPT_ENCODING, getHeader("Accept-Encoding"));
        vars.put(ServerVars.HTTP_ACCEPT_LANGUAGE, getHeader("Accept-Language"));
        vars.put(ServerVars.HTTP_X_REQUESTED_WITH, getHeader("X-requested-with"));
        vars.put(ServerVars.REMOTE_HOST, getRemoteHostname());
        vars.put(ServerVars.REMOTE_ADDR, getIpAddr());
        vars.put(ServerVars.REMOTE_PORT, getRemotePort());
        vars.put(ServerVars.REQUEST_TIME, getRequestTime());
        vars.put(ServerVars.REQUEST_URI, getUri());
        vars.put(ServerVars.CONTENT_LENGTH, getContentLength());
        vars.put(ServerVars.SERVER_IP, getLocalIpAddr());
        vars.put(ServerVars.SERVER_NAME, Versioning.getProductSimple());
        vars.put(ServerVars.SERVER_PORT, getLocalPort());
        vars.put(ServerVars.HTTPS, isSecure());
        vars.put(ServerVars.DOCUMENT_ROOT, Loader.getWebRoot());
        vars.put(ServerVars.SESSION, this);

        if (getAuth() != null) {
            // Implement authorization as an optional builtin manageable feature, e.g., .htdigest.
            vars.put(ServerVars.AUTH_DIGEST, getAuth().getDigest());
            vars.put(ServerVars.AUTH_USER, getAuth().getUsername());
            vars.put(ServerVars.AUTH_PW, getAuth().getPassword());
            vars.put(ServerVars.AUTH_TYPE, getAuth().getType());
        }
    }

    /**
     * Tries to check the "X-requested-with" header.
     * Not a guaranteed method to determined if a request was made with AJAX since this header is not always set.
     *
     * @return Was the request made with AJAX
     */
    public boolean isAjaxRequest() {
        return getHeader("X-requested-with") == null ? false
                : getHeader("X-requested-with").equals("XMLHttpRequest");
    }

    public boolean isCDN() {
        // TODO Implement additional CDN detection methods
        return http.headers().contains("CF-Connecting-IP");
    }

    public boolean isSecure() {
        return channel.pipeline().get(SslHandler.class) != null;
    }

    public boolean isWebsocketRequest() {
        return "/fw/websocket".equals(getUri());
    }

    public HttpMethod method() {
        return http.method();
    }

    public String methodString() {
        return http.method().toString();
    }

    public boolean nonceProcessed() {
        return nonceProcessed;
    }

    void nonceProcessed(boolean processed) {
        nonceProcessed = processed;
    }

    private Map<String, Object> parseMapArrays(Map<String, String> origMap) {
        Map<String, Object> result = Maps.newLinkedHashMap();

        for (Entry<String, String> e : origMap.entrySet()) {
            String var = null;
            String key = null;
            String val = e.getValue();

            if (e.getKey().contains("[") && e.getKey().endsWith("]")) {
                var = e.getKey().substring(0, e.getKey().indexOf("["));

                if (e.getKey().length() - e.getKey().indexOf("[") > 1)
                    key = e.getKey().substring(e.getKey().indexOf("[") + 1, e.getKey().length() - 1);
                else
                    key = "";
            } else
                var = e.getKey();

            if (result.containsKey(var)) {
                Object o = result.get(var);
                if (o instanceof String) {
                    if (key == null || key.isEmpty())
                        key = "1";

                    Map<String, String> hash = Maps.newLinkedHashMap();
                    hash.put("0", (String) o);
                    hash.put(key, val);
                    result.put(var, hash);
                } else if (o instanceof Map) {
                    @SuppressWarnings("unchecked")
                    Map<String, String> map = (Map<String, String>) o;

                    if (key == null || key.isEmpty()) {
                        int cnt = 0;
                        while (map.containsKey(cnt))
                            cnt++;
                        key = "" + cnt;
                    }

                    map.put(key, val);
                } else if (key == null)
                    result.put(var, val);
                else {
                    if (key.isEmpty())
                        key = "0";

                    Map<String, String> hash = Maps.newLinkedHashMap();
                    hash.put(key, val);
                    result.put(var, hash);
                }

            } else if (key == null)
                result.put(e.getKey(), e.getValue());
            else {
                if (key.isEmpty())
                    key = "0";

                Map<String, String> hash = Maps.newLinkedHashMap();
                hash.put(key, val);
                result.put(var, hash);
            }
        }

        return result;
    }

    protected void putAllGetMap(Map<String, String> map) {
        getMap.putAll(map);
    }

    protected void putAllPostMap(Map<String, String> map) {
        postMap.putAll(map);
    }

    protected void putGetMap(String key, String value) {
        getMap.put(key, value);
    }

    protected void putPostMap(String key, String value) {
        postMap.put(key, value);
    }

    protected void putRewriteParam(String key, String val) {
        rewriteMap.put(key, val);
    }

    protected void putRewriteParams(Map<String, String> map) {
        rewriteMap.putAll(map);
    }

    protected void putUpload(String name, UploadedFile uploadedFile) {
        uploadedFiles.put(name, uploadedFile);
    }

    // XXX Better Implement
    public void requireLogin() throws IOException {
        requireLogin(null);
    }

    /**
     * First checks in an account is present, sends to login page if not.
     * Second checks if the present accounts has the specified permission.
     *
     * @param permission
     * @throws IOException
     */
    public void requireLogin(String permission) throws IOException {
        if (!getSession().isLoginPresent())
            getResponse().sendLoginPage();

        if (permission != null)
            if (!getSession().checkPermission(permission).isTrue())
                getResponse().sendError(HttpCode.HTTP_FORBIDDEN,
                        "You must have the permission `" + permission + "` in order to view this page!");
    }

    @Override
    public void sendMessage(MessageSender sender, Object... objs) {
        // Do Nothing
    }

    @Override
    public void sendMessage(Object... objs) {
        // Do Nothing
    }

    @Override
    public void sessionStarted() {
        getBinding().setVariable("request", this);
        getBinding().setVariable("response", getResponse());
    }

    protected void setSite(Site site) {
        Validate.notNull(site);
        this.site = site;
    }

    void setUri(String uri) {
        this.uri = uri;

        if (!uri.startsWith("/"))
            uri = "/" + uri;
    }

    protected boolean validateLogins() {
        Session session = getSession();

        if (getArgument("logout") != null) {
            AccountResult result = getSession().logout();

            if (result.isSuccess()) {
                getResponse().sendLoginPage(result.getMessage());
                return true;
            }
        }

        // TODO Implement One Time Tokens

        String username = getArgument("user");
        String password = getArgument("pass");
        boolean remember = getArgumentBoolean("remember");
        String target = getArgument("target");

        String loginPost = target == null || target.isEmpty()
                ? getLocation().getConfig().getString("scripts.login-post", "/")
                : target;

        if (loginPost.isEmpty())
            loginPost = "/";

        if (username != null && password != null) {
            try {
                if (!ssl)
                    AccountManager.getLogger().warning(
                            "It is highly recommended that account logins are submitted over SSL. Without SSL, passwords are at great risk.");

                if (!nonceProcessed() && AppConfig.get().getBoolean("accounts.requireLoginWithNonce"))
                    throw new AccountException(AccountDescriptiveReason.NONCE_REQUIRED, username);

                AccountResult result = getSession().loginWithException(AccountAuthenticator.PASSWORD, username,
                        password);

                Account acct = result.getAccountWithException();

                session.remember(remember);

                SessionManager.getLogger()
                        .info(EnumColor.GREEN + "Successful Login: [id='" + acct.getId() + "',siteId='"
                                + (acct.getLocation() == null ? null : acct.getLocation().getId())
                                + "',authenticator='plaintext']");

                if (site.getLoginPost() != null)
                    getResponse().sendRedirect(site.getLoginPost());
                else
                    getResponse().sendLoginPage("Your have been successfully logged in!", "success");
            } catch (AccountException e) {
                AccountResult result = e.getResult();

                String msg = result.getFormattedMessage();

                if (!result.isIgnorable() && result.hasCause()) {
                    result.getCause().printStackTrace();
                    msg = result.getCause().getMessage();
                }

                AccountManager.getLogger()
                        .warning(EnumColor.RED + "Failed Login [id='" + username + "',hasPassword='"
                                + (password != null && password.length() > 0)
                                + "',authenticator='plaintext'`,reason='" + msg + "']");
                getResponse().sendLoginPage(result.getMessage(), null, target);
            } catch (Throwable t) {
                AccountManager.getLogger().severe("Login has thrown an internal server error", t);
                getResponse().sendLoginPage(AccountDescriptiveReason.INTERNAL_ERROR.getMessage(), null, target);
            }
            return true;
        } else if (session.isLoginPresent()) {
            // XXX Should we revalidate logins with each request? It could be something worth considering for extra security. Maybe a config option?

            /*
             * Maybe make this a server configuration option, e.g., sessions.revalidateLogins
             *
             * try
             * {
             * session.currentAccount.reloadAndValidate(); // <- Is this being overly redundant?
             * Loader.getLogger().info( ChatColor.GREEN + "Current Login `Username \"" + session.currentAccount.getName() + "\", Password \"" + session.currentAccount.getMetaData().getPassword() + "\", UserId \"" +
             * session.currentAccount.getAccountId() + "\", Display Name \"" + session.currentAccount.getDisplayName() + "\"`" );
             * }
             * catch ( LoginException e )
             * {
             * session.currentAccount = null;
             * Loader.getLogger().warning( ChatColor.GREEN + "Login Failed `There was a login present but it failed validation with error: " + e.getMessage() + "`" );
             * }
             */
        }

        // Will we ever be using a session on more than one domains?
        if (!getDomain().isEmpty() && session.getSessionCookie() != null
                && !session.getSessionCookie().getDomain().isEmpty())
            if (!session.getSessionCookie().getDomain().endsWith(getDomain()))
                NetworkManager.getLogger()
                        .warning("The site `" + site.getId() + "` specifies the session cookie domain as `"
                                + session.getSessionCookie().getDomain() + "` but the request was made on domain `"
                                + getDomain() + "`. The session will not remain persistent.");

        return false;
    }
}