/** * The MIT License (MIT) * * Copyright (c) 2014-2017 Yegor Bugayenko * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package org.takes.rs; import java.util.regex.Pattern; import lombok.EqualsAndHashCode; import lombok.ToString; import org.takes.Response; /** * Response decorator, with an additional cookie. * * The decorator validates cookie name according * to <a href="http://tools.ietf.org/html/rfc2616#section-2.2">RFC 2616</a> * and cookie value according * to <a href="http://tools.ietf.org/html/rfc6265#section-4.1.1">RFC 6265</a> * * <p>Use this decorator in order to return a response with a "Set-Cookie" * header inside, for example: * * <pre> return new RsWithCookie( * new RsText("hello, world!"), * "u", "Jeff", * "Path=/", "Expires=Wed, 13 Jan 2021 22:23:01 GMT" * );</pre> * * <p>This response will contain this header: * * <pre> Set-Cookie: u=Jeff;Path=/;Expires=Wed, 13 Jan 2021 22:23:01 GMT</pre> * * <p>The class is immutable and thread-safe. * * @author Yegor Bugayenko (yegor256@gmail.com) * @version $Id: bfb2aa25362ecc7ef94b7ee5f614c7123d463b16 $ * @since 0.1 */ @ToString(callSuper = true) @EqualsAndHashCode(callSuper = true) public final class RsWithCookie extends RsWrap { /** * Cookie value validation regexp. * @checkstyle LineLengthCheck (3 lines) */ private static final Pattern CVALUE_PTRN = Pattern.compile( "[\\x21\\x23-\\x2B\\x2D-\\x3A\\x3C-\\x5B\\x5D-\\x7E]*|\"[\\x21\\x23-\\x2B\\x2D-\\x3A\\x3C-\\x5B\\x5D-\\x7E]*\"" ); /** * Cookie name validation regexp. */ private static final Pattern CNAME_PTRN = Pattern.compile( "[\\x20-\\x7E&&[^()<>@,;:\\\"/\\[\\]?={} ]]+" ); /** * Cookie header name. */ private static final CharSequence SET_COOKIE = "Set-Cookie"; /** * Ctor. * @param name Cookie name * @param value Value of it * @param attrs Optional attributes, for example "Path=/" */ public RsWithCookie(final CharSequence name, final CharSequence value, final CharSequence... attrs) { this(new RsEmpty(), name, value, attrs); } /** * Ctor. * @param res Original response * @param name Cookie name * @param value Value of it * @param attrs Optional attributes, for example "Path=/" * @checkstyle ParameterNumberCheck (10 lines) */ public RsWithCookie(final Response res, final CharSequence name, final CharSequence value, final CharSequence... attrs) { super( new RsWithHeader( res, RsWithCookie.SET_COOKIE, RsWithCookie.make( RsWithCookie.validName(name), RsWithCookie.validValue(value), attrs ) ) ); } /** * Build cookie string. * @param name Cookie name * @param value Value of it * @param attrs Optional attributes, for example "Path=/" * @return Text */ private static String make(final CharSequence name, final CharSequence value, final CharSequence... attrs) { final StringBuilder text = new StringBuilder( String.format("%s=%s;", name, value) ); for (final CharSequence attr : attrs) { text.append(attr).append(';'); } return text.toString(); } /** * Checks value according RFC 6265 section 4.1.1. * @param value Cookie value * @return Cookie value */ private static CharSequence validValue(final CharSequence value) { if (!RsWithCookie.CVALUE_PTRN.matcher(value).matches()) { throw new IllegalArgumentException( String.format( "Cookie value \"%s\" contains invalid characters", value ) ); } return value; } /** * Checks name according RFC 2616, section 2.2. * @param name Cookie name; * @return Cookie name */ private static CharSequence validName(final CharSequence name) { if (!RsWithCookie.CNAME_PTRN.matcher(name).matches()) { throw new IllegalArgumentException( String.format( "Cookie name \"%s\" contains invalid characters", name ) ); } return name; } }