ratpack.session.clientside.internal.DefaultClientSessionService.java Source code

Java tutorial

Introduction

Here is the source code for ratpack.session.clientside.internal.DefaultClientSessionService.java

Source

/*
 * Copyright 2014 the original author or authors.
 *
 * 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 ratpack.session.clientside.internal;

import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import io.netty.buffer.*;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.CharsetUtil;
import ratpack.session.clientside.Crypto;
import ratpack.session.clientside.SessionService;
import ratpack.session.clientside.Signer;
import ratpack.util.Exceptions;

import java.nio.CharBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class DefaultClientSessionService implements SessionService {

    private static final Base64.Encoder ENCODER = Base64.getUrlEncoder();
    private static final Base64.Decoder DECODER = Base64.getUrlDecoder();
    private static final Escaper ESCAPER = UrlEscapers.urlFormParameterEscaper();

    private static final ByteBuf EQUALS = Unpooled.unreleasableBuffer(
            ByteBufUtil.encodeString(UnpooledByteBufAllocator.DEFAULT, CharBuffer.wrap("="), CharsetUtil.UTF_8));
    private static final ByteBuf AMPERSAND = Unpooled.unreleasableBuffer(
            ByteBufUtil.encodeString(UnpooledByteBufAllocator.DEFAULT, CharBuffer.wrap("&"), CharsetUtil.UTF_8));

    private static final String SESSION_SEPARATOR = ":";

    private final Signer signer;
    private final Crypto crypto;

    public DefaultClientSessionService(Signer signer, Crypto crypto) {
        this.signer = signer;
        this.crypto = crypto;
    }

    @Override
    public String serializeSession(ByteBufAllocator bufferAllocator, Set<Map.Entry<String, Object>> entries) {
        ByteBuf[] buffers = new ByteBuf[3 * entries.size() + entries.size() - 1];
        try {

            int i = 0;

            for (Map.Entry<String, Object> entry : entries) {
                buffers[i++] = encode(bufferAllocator, entry.getKey());
                buffers[i++] = EQUALS;
                buffers[i++] = encode(bufferAllocator, entry.getValue().toString());

                if (i < buffers.length) {
                    buffers[i++] = AMPERSAND;
                }
            }

            ByteBuf payloadBuffer = Unpooled.wrappedBuffer(buffers.length, buffers);
            byte[] payloadBytes = new byte[payloadBuffer.readableBytes()];
            payloadBuffer.getBytes(0, payloadBytes);
            if (crypto != null) {
                payloadBytes = crypto.encrypt(payloadBuffer);
                payloadBuffer = Unpooled.wrappedBuffer(payloadBytes);
            }

            String payloadString = ENCODER.encodeToString(payloadBytes);

            byte[] digest = signer.sign(payloadBuffer);
            String digestString = ENCODER.encodeToString(digest);

            return payloadString + SESSION_SEPARATOR + digestString;

        } finally {
            for (ByteBuf buffer : buffers) {
                if (buffer != null) {
                    buffer.release();
                }
            }
        }
    }

    private ByteBuf encode(ByteBufAllocator bufferAllocator, String value) {
        String escaped = ESCAPER.escape(value);
        return ByteBufUtil.encodeString(bufferAllocator, CharBuffer.wrap(escaped), CharsetUtil.UTF_8);
    }

    @Override
    public ConcurrentMap<String, Object> deserializeSession(Cookie cookie) {
        ConcurrentMap<String, Object> sessionStorage = new ConcurrentHashMap<>();

        String encodedPairs = cookie == null ? null : cookie.value();
        if (encodedPairs != null) {
            String[] parts = encodedPairs.split(SESSION_SEPARATOR);
            if (parts.length == 2) {
                byte[] urlEncoded = DECODER.decode(parts[0]);
                byte[] digest = DECODER.decode(parts[1]);

                try {
                    byte[] expectedDigest = signer.sign(Unpooled.wrappedBuffer(urlEncoded));

                    if (Arrays.equals(digest, expectedDigest)) {
                        byte[] message;
                        if (crypto == null) {
                            message = urlEncoded;
                        } else {
                            message = crypto.decrypt(Unpooled.wrappedBuffer(urlEncoded));
                        }

                        String payload = new String(message, CharsetUtil.UTF_8);
                        QueryStringDecoder queryStringDecoder = new QueryStringDecoder(payload, CharsetUtil.UTF_8,
                                false);
                        Map<String, List<String>> decoded = queryStringDecoder.parameters();
                        for (Map.Entry<String, List<String>> entry : decoded.entrySet()) {
                            String value = entry.getValue().isEmpty() ? null : entry.getValue().get(0);
                            sessionStorage.put(entry.getKey(), value);
                        }
                    }
                } catch (Exception e) {
                    throw Exceptions.uncheck(e);
                }
            }
        }

        return sessionStorage;
    }
}