clean up util classes, add cookie signing helper class
This commit is contained in:
parent
3d944e417e
commit
816266e2b2
30 changed files with 396 additions and 608 deletions
|
@ -1,323 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE module PUBLIC
|
|
||||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
|
||||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
|
||||||
|
|
||||||
<!-- This is a checkstyle configuration file. For descriptions of
|
|
||||||
what the following rules do, please see the checkstyle configuration
|
|
||||||
page at http://checkstyle.sourceforge.net/config.html -->
|
|
||||||
|
|
||||||
<module name="Checker">
|
|
||||||
|
|
||||||
<module name="FileTabCharacter">
|
|
||||||
<!-- Checks that there are no tab characters in the file.
|
|
||||||
-->
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="NewlineAtEndOfFile">
|
|
||||||
<property name="lineSeparator" value="lf"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="RegexpSingleline">
|
|
||||||
<!-- Checks that FIXME is not used in comments. TODO is preferred.
|
|
||||||
-->
|
|
||||||
<property name="format" value="((//.*)|(\*.*))FIXME" />
|
|
||||||
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="RegexpSingleline">
|
|
||||||
<!-- Checks that TODOs are named. (Actually, just that they are followed
|
|
||||||
by an open paren.)
|
|
||||||
-->
|
|
||||||
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
|
|
||||||
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="JavadocPackage">
|
|
||||||
<!-- Checks that each Java package has a Javadoc file used for commenting.
|
|
||||||
Only allows a package-info.java, not package.html. -->
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<!-- All Java AST specific tests live under TreeWalker module. -->
|
|
||||||
<module name="TreeWalker">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
IMPORT CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<module name="RedundantImport">
|
|
||||||
<!-- Checks for redundant import statements. -->
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="ImportOrder">
|
|
||||||
<!-- Checks for out of order import statements. -->
|
|
||||||
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
<property name="groups" value="com,io,junit,net,org,javax,java"/>
|
|
||||||
<!-- This ensures that static imports go last. -->
|
|
||||||
<property name="option" value="under"/>
|
|
||||||
<property name="tokens" value="IMPORT, STATIC_IMPORT"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
JAVADOC CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Checks for Javadoc comments. -->
|
|
||||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
|
||||||
<module name="JavadocMethod">
|
|
||||||
<property name="scope" value="protected"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
<property name="allowMissingJavadoc" value="true"/>
|
|
||||||
<property name="allowMissingParamTags" value="true"/>
|
|
||||||
<property name="allowMissingReturnTag" value="true"/>
|
|
||||||
<property name="allowMissingThrowsTags" value="true"/>
|
|
||||||
<property name="allowThrowsTagsForSubclasses" value="true"/>
|
|
||||||
<property name="allowUndeclaredRTE" value="true"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="JavadocType">
|
|
||||||
<property name="scope" value="protected"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="JavadocStyle">
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
NAMING CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Item 38 - Adhere to generally accepted naming conventions -->
|
|
||||||
|
|
||||||
<module name="PackageName">
|
|
||||||
<!-- Validates identifiers for package names against the
|
|
||||||
supplied expression. -->
|
|
||||||
<!-- Here the default checkstyle rule restricts package name parts to
|
|
||||||
seven characters, this is not in line with common practice at Google.
|
|
||||||
-->
|
|
||||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="TypeNameCheck">
|
|
||||||
<!-- Validates static, final fields against the
|
|
||||||
expression "^[A-Z][a-zA-Z0-9]*$". -->
|
|
||||||
<metadata name="altname" value="TypeName"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="ConstantNameCheck">
|
|
||||||
<!-- Validates non-private, static, final fields against the supplied
|
|
||||||
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
|
|
||||||
<metadata name="altname" value="ConstantName"/>
|
|
||||||
<property name="applyToPublic" value="true"/>
|
|
||||||
<property name="applyToProtected" value="true"/>
|
|
||||||
<property name="applyToPackage" value="true"/>
|
|
||||||
<property name="applyToPrivate" value="false"/>
|
|
||||||
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
|
|
||||||
<message key="name.invalidPattern"
|
|
||||||
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="StaticVariableNameCheck">
|
|
||||||
<!-- Validates static, non-final fields against the supplied
|
|
||||||
expression "^[a-z][a-zA-Z0-9]*_?$". -->
|
|
||||||
<metadata name="altname" value="StaticVariableName"/>
|
|
||||||
<property name="applyToPublic" value="true"/>
|
|
||||||
<property name="applyToProtected" value="true"/>
|
|
||||||
<property name="applyToPackage" value="true"/>
|
|
||||||
<property name="applyToPrivate" value="true"/>
|
|
||||||
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="MemberNameCheck">
|
|
||||||
<!-- Validates non-static members against the supplied expression. -->
|
|
||||||
<metadata name="altname" value="MemberName"/>
|
|
||||||
<property name="applyToPublic" value="true"/>
|
|
||||||
<property name="applyToProtected" value="true"/>
|
|
||||||
<property name="applyToPackage" value="true"/>
|
|
||||||
<property name="applyToPrivate" value="true"/>
|
|
||||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="MethodNameCheck">
|
|
||||||
<!-- Validates identifiers for method names. -->
|
|
||||||
<metadata name="altname" value="MethodName"/>
|
|
||||||
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="ParameterName">
|
|
||||||
<!-- Validates identifiers for method parameters against the
|
|
||||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="LocalFinalVariableName">
|
|
||||||
<!-- Validates identifiers for local final variables against the
|
|
||||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="LocalVariableName">
|
|
||||||
<!-- Validates identifiers for local variables against the
|
|
||||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
LENGTH and CODING CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<module name="LineLength">
|
|
||||||
<!-- Checks if a line is too long. -->
|
|
||||||
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
The default ignore pattern exempts the following elements:
|
|
||||||
- import statements
|
|
||||||
- long URLs inside comments
|
|
||||||
-->
|
|
||||||
|
|
||||||
<property name="ignorePattern"
|
|
||||||
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
|
|
||||||
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="LeftCurly">
|
|
||||||
<!-- Checks for placement of the left curly brace ('{'). -->
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="RightCurly">
|
|
||||||
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
|
|
||||||
the same line. e.g., the following example is fine:
|
|
||||||
<pre>
|
|
||||||
if {
|
|
||||||
...
|
|
||||||
} else
|
|
||||||
</pre>
|
|
||||||
-->
|
|
||||||
<!-- This next example is not fine:
|
|
||||||
<pre>
|
|
||||||
if {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
else
|
|
||||||
</pre>
|
|
||||||
-->
|
|
||||||
<property name="option" value="same"/>
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<!-- Checks for braces around if and else blocks -->
|
|
||||||
<module name="NeedBraces">
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="UpperEll">
|
|
||||||
<!-- Checks that long constants are defined with an upper ell.-->
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="FallThrough">
|
|
||||||
<!-- Warn about falling through to the next case statement. Similar to
|
|
||||||
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
|
|
||||||
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
|
|
||||||
some other variants which we don't publicized to promote consistency).
|
|
||||||
-->
|
|
||||||
<property name="reliefPattern"
|
|
||||||
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
MODIFIERS CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<module name="ModifierOrder">
|
|
||||||
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
|
|
||||||
8.4.3. The prescribed order is:
|
|
||||||
public, protected, private, abstract, static, final, transient, volatile,
|
|
||||||
synchronized, native, strictfp
|
|
||||||
-->
|
|
||||||
</module>
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
WHITESPACE CHECKS
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<module name="WhitespaceAround">
|
|
||||||
<!-- Checks that various tokens are surrounded by whitespace.
|
|
||||||
This includes most binary operators and keywords followed
|
|
||||||
by regular or curly braces.
|
|
||||||
-->
|
|
||||||
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
|
|
||||||
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
|
|
||||||
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
|
||||||
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
|
||||||
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
|
|
||||||
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
|
|
||||||
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="WhitespaceAfter">
|
|
||||||
<!-- Checks that commas, semicolons and typecasts are followed by
|
|
||||||
whitespace.
|
|
||||||
-->
|
|
||||||
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="NoWhitespaceAfter">
|
|
||||||
<!-- Checks that there is no whitespace after various unary operators.
|
|
||||||
Linebreaks are allowed.
|
|
||||||
-->
|
|
||||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
|
|
||||||
UNARY_PLUS"/>
|
|
||||||
<property name="allowLineBreaks" value="true"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="NoWhitespaceBefore">
|
|
||||||
<!-- Checks that there is no whitespace before various unary operators.
|
|
||||||
Linebreaks are allowed.
|
|
||||||
-->
|
|
||||||
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
|
|
||||||
<property name="allowLineBreaks" value="true"/>
|
|
||||||
<property name="severity" value="error"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="ParenPad">
|
|
||||||
<!-- Checks that there is no whitespace before close parens or after
|
|
||||||
open parens.
|
|
||||||
-->
|
|
||||||
<property name="severity" value="warning"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
</module>
|
|
||||||
</module>
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.36.7
|
version = 4.1.36.8
|
||||||
|
|
||||||
# main packages
|
# main packages
|
||||||
netty.version = 4.1.36.Final
|
netty.version = 4.1.36.Final
|
||||||
|
@ -18,14 +18,13 @@ reactivestreams.version = 1.0.2
|
||||||
# rest
|
# rest
|
||||||
xbib-guice.version = 4.0.4
|
xbib-guice.version = 4.0.4
|
||||||
|
|
||||||
# xmlrpc-client
|
|
||||||
commons-httpclient.version = 3.1
|
|
||||||
|
|
||||||
# test packages
|
# test packages
|
||||||
junit.version = 5.4.2
|
junit.version = 5.4.2
|
||||||
junit4.version = 4.12
|
junit4.version = 4.12
|
||||||
conscrypt.version = 2.0.0
|
conscrypt.version = 2.0.0
|
||||||
jackson.version = 2.8.11.1
|
jackson.version = 2.9.9
|
||||||
|
|
||||||
|
# doc
|
||||||
asciidoclet.version = 1.5.4
|
asciidoclet.version = 1.5.4
|
||||||
|
|
||||||
org.gradle.warning.mode = all
|
org.gradle.warning.mode = all
|
||||||
|
|
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
#Mon Apr 22 17:45:04 CEST 2019
|
#Tue Aug 06 15:30:36 CEST 2019
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-all.zip
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.xbib.netty.http.client.transport.HttpTransport;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
|
|
||||||
import javax.net.ssl.SNIHostName;
|
import javax.net.ssl.SNIHostName;
|
||||||
import javax.net.ssl.SNIServerName;
|
import javax.net.ssl.SNIServerName;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import org.xbib.netty.http.client.retry.BackOff;
|
import org.xbib.netty.http.client.retry.BackOff;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
|
@ -4,7 +4,8 @@ import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
import org.xbib.netty.http.common.cookie.CookieDecoder;
|
import org.xbib.netty.http.common.cookie.CookieDecoder;
|
||||||
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtils;
|
import org.xbib.netty.http.common.cookie.SameSite;
|
||||||
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -42,7 +43,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
public Cookie decode(String header) {
|
public Cookie decode(String header) {
|
||||||
final int headerLen = Objects.requireNonNull(header, "header").length();
|
final int headerLen = Objects.requireNonNull(header, "header").length();
|
||||||
if (headerLen == 0) {
|
if (headerLen == 0) {
|
||||||
return null;
|
throw new IllegalArgumentException("header length is 0");
|
||||||
}
|
}
|
||||||
CookieBuilder cookieBuilder = null;
|
CookieBuilder cookieBuilder = null;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
@ -116,7 +117,10 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cookieBuilder != null ? cookieBuilder.cookie() : null;
|
if (cookieBuilder == null) {
|
||||||
|
throw new IllegalArgumentException("no cookie found");
|
||||||
|
}
|
||||||
|
return cookieBuilder.cookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CookieBuilder {
|
private static class CookieBuilder {
|
||||||
|
@ -139,7 +143,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
|
|
||||||
private boolean httpOnly;
|
private boolean httpOnly;
|
||||||
|
|
||||||
private Cookie.SameSite sameSite = Cookie.SameSite.STRICT;
|
private SameSite sameSite = SameSite.STRICT;
|
||||||
|
|
||||||
CookieBuilder(DefaultCookie cookie, String header) {
|
CookieBuilder(DefaultCookie cookie, String header) {
|
||||||
this.cookie = cookie;
|
this.cookie = cookie;
|
||||||
|
@ -208,7 +212,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
if (maxAge != Long.MIN_VALUE) {
|
if (maxAge != Long.MIN_VALUE) {
|
||||||
return maxAge;
|
return maxAge;
|
||||||
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
||||||
Instant expiresDate = DateTimeUtils.parseDate(header, expiresStart, expiresEnd);
|
Instant expiresDate = DateTimeUtil.parseDate(header, expiresStart, expiresEnd);
|
||||||
if (expiresDate != null) {
|
if (expiresDate != null) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
long maxAgeMillis = expiresDate.toEpochMilli() - now.toEpochMilli();
|
long maxAgeMillis = expiresDate.toEpochMilli() - now.toEpochMilli();
|
||||||
|
@ -233,7 +237,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) {
|
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) {
|
||||||
String string = computeValue(valueStart, valueEnd);
|
String string = computeValue(valueStart, valueEnd);
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
setSameSite(Cookie.SameSite.valueOf(string.toUpperCase(Locale.ROOT)));
|
setSameSite(SameSite.valueOf(string.toUpperCase(Locale.ROOT)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +250,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null;
|
return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSameSite(Cookie.SameSite value) {
|
private void setSameSite(SameSite value) {
|
||||||
sameSite = value;
|
sameSite = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@ package org.xbib.netty.http.client.test.cookie;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
|
import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtils;
|
import org.xbib.netty.http.common.cookie.SameSite;
|
||||||
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
@ -24,7 +25,7 @@ class ClientCookieDecoderTest {
|
||||||
void testDecodingSingleCookieV0() {
|
void testDecodingSingleCookieV0() {
|
||||||
long millis = System.currentTimeMillis() + 50000;
|
long millis = System.currentTimeMillis() + 50000;
|
||||||
String cookieString = "myCookie=myValue;expires=" +
|
String cookieString = "myCookie=myValue;expires=" +
|
||||||
DateTimeUtils.formatMillis(millis) +
|
DateTimeUtil.formatMillis(millis) +
|
||||||
";path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
";path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
assertNotNull(cookie);
|
assertNotNull(cookie);
|
||||||
|
@ -55,8 +56,8 @@ class ClientCookieDecoderTest {
|
||||||
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;domain=.adomainsomewhere" +
|
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;domain=.adomainsomewhere" +
|
||||||
";secure;comment=this is a comment;version=1;";
|
";secure;comment=this is a comment;version=1;";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
assertEquals("myValue", cookie.value());
|
|
||||||
assertNotNull(cookie);
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
assertEquals(".adomainsomewhere", cookie.domain());
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
assertEquals(50, cookie.maxAge());
|
assertEquals(50, cookie.maxAge());
|
||||||
assertEquals("/apathsomewhere", cookie.path());
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
@ -134,6 +135,7 @@ class ClientCookieDecoderTest {
|
||||||
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
|
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
|
||||||
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
|
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertEquals("ARPT", cookie.name());
|
assertEquals("ARPT", cookie.name());
|
||||||
assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value());
|
assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value());
|
||||||
}
|
}
|
||||||
|
@ -144,6 +146,7 @@ class ClientCookieDecoderTest {
|
||||||
long expectedMaxAge = ((zonedDateTime.toEpochSecond() * 1000L) - System.currentTimeMillis()) / 1000;
|
long expectedMaxAge = ((zonedDateTime.toEpochSecond() * 1000L) - System.currentTimeMillis()) / 1000;
|
||||||
String source = "Format=EU; expires=Fri, 31-Dec-2100 23:59:59 GMT; path=/";
|
String source = "Format=EU; expires=Fri, 31-Dec-2100 23:59:59 GMT; path=/";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
|
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +162,7 @@ class ClientCookieDecoderTest {
|
||||||
void testDecodingWeirdNames1() {
|
void testDecodingWeirdNames1() {
|
||||||
String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com";
|
String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertEquals("path", cookie.name());
|
assertEquals("path", cookie.name());
|
||||||
assertEquals("", cookie.value());
|
assertEquals("", cookie.value());
|
||||||
assertEquals("/", cookie.path());
|
assertEquals("/", cookie.path());
|
||||||
|
@ -168,15 +172,14 @@ class ClientCookieDecoderTest {
|
||||||
void testDecodingWeirdNames2() {
|
void testDecodingWeirdNames2() {
|
||||||
String src = "HTTPOnly=";
|
String src = "HTTPOnly=";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertEquals("HTTPOnly", cookie.name());
|
assertEquals("HTTPOnly", cookie.name());
|
||||||
assertEquals("", cookie.value());
|
assertEquals("", cookie.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDecodingValuesWithCommasAndEqualsFails() {
|
void testDecodingValuesWithCommasAndEqualsFails() {
|
||||||
String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E";
|
assertNull(ClientCookieDecoder.STRICT.decode( "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E"));
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
|
||||||
assertNull(cookie);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -226,6 +229,7 @@ class ClientCookieDecoderTest {
|
||||||
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
|
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
|
||||||
"'=KqtH";
|
"'=KqtH";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue + "\";");
|
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue + "\";");
|
||||||
|
assertNotNull(cookie);
|
||||||
assertEquals("bh", cookie.name());
|
assertEquals("bh", cookie.name());
|
||||||
assertEquals(longValue, cookie.value());
|
assertEquals(longValue, cookie.value());
|
||||||
}
|
}
|
||||||
|
@ -234,6 +238,7 @@ class ClientCookieDecoderTest {
|
||||||
void testIgnoreEmptyDomain() {
|
void testIgnoreEmptyDomain() {
|
||||||
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
|
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyDomain);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyDomain);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertNull(cookie.domain());
|
assertNull(cookie.domain());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +246,7 @@ class ClientCookieDecoderTest {
|
||||||
void testIgnoreEmptyPath() {
|
void testIgnoreEmptyPath() {
|
||||||
String emptyPath = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=";
|
String emptyPath = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyPath);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyPath);
|
||||||
|
assertNotNull(cookie);
|
||||||
assertNull(cookie.path());
|
assertNull(cookie.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,20 +254,23 @@ class ClientCookieDecoderTest {
|
||||||
void testSameSiteStrict() {
|
void testSameSiteStrict() {
|
||||||
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Strict";
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Strict";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
assertEquals(Cookie.SameSite.STRICT, cookie.sameSite());
|
assertNotNull(cookie);
|
||||||
|
assertEquals(SameSite.STRICT, cookie.sameSite());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSameSiteLax() {
|
void testSameSiteLax() {
|
||||||
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Lax";
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Lax";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
assertEquals(Cookie.SameSite.LAX, cookie.sameSite());
|
assertNotNull(cookie);
|
||||||
|
assertEquals(SameSite.LAX, cookie.sameSite());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmptySameSite() {
|
void testEmptySameSite() {
|
||||||
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=";
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=";
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
assertEquals(Cookie.SameSite.STRICT, cookie.sameSite());
|
assertNotNull(cookie);
|
||||||
|
assertEquals(SameSite.STRICT, cookie.sameSite());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,7 @@ class ClientCookieEncoderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRejectCookieValueWithSemicolon() {
|
void testRejectCookieValueWithSemicolon() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar"));
|
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar")));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,4 @@ public interface Cookie extends Comparable<Cookie> {
|
||||||
* @param sameSite the same site value
|
* @param sameSite the same site value
|
||||||
*/
|
*/
|
||||||
void setSameSite(SameSite sameSite);
|
void setSameSite(SameSite sameSite);
|
||||||
|
|
||||||
enum SameSite { STRICT, LAX }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
package org.xbib.netty.http.common.cookie;
|
||||||
|
|
||||||
|
import org.xbib.net.PercentDecoder;
|
||||||
|
import org.xbib.net.PercentEncoder;
|
||||||
|
import org.xbib.net.PercentEncoders;
|
||||||
|
import org.xbib.netty.http.common.security.Codec;
|
||||||
|
import org.xbib.netty.http.common.security.CryptUtil;
|
||||||
|
import org.xbib.netty.http.common.security.HMac;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.CodingErrorAction;
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
public class CookieSigner {
|
||||||
|
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
private final String publicValue;
|
||||||
|
|
||||||
|
private final String privateValue;
|
||||||
|
|
||||||
|
private String cookieValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct cookie for signing.
|
||||||
|
*
|
||||||
|
* @param charset the character set
|
||||||
|
* @param hmac the HMAC code
|
||||||
|
* @param codec the codec for the private value
|
||||||
|
* @param privateValue the private value
|
||||||
|
* @param publicValue the public value
|
||||||
|
* @param secret the secret
|
||||||
|
* @throws MalformedInputException if signing cookie fails
|
||||||
|
* @throws UnmappableCharacterException if signing cookie fails
|
||||||
|
* @throws NoSuchAlgorithmException if signing cookie fails
|
||||||
|
* @throws InvalidKeyException if signing cookie fails
|
||||||
|
*/
|
||||||
|
private CookieSigner(Charset charset, HMac hmac, Codec codec, String privateValue, String publicValue, String secret)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
PercentEncoder percentEncoder = PercentEncoders.getCookieEncoder(charset);
|
||||||
|
this.privateValue = privateValue;
|
||||||
|
this.publicValue = publicValue;
|
||||||
|
this.signature = CryptUtil.hmac(charset, hmac, codec, privateValue, secret);
|
||||||
|
this.cookieValue = percentEncoder.encode(String.join(":", publicValue, privateValue, signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse signed cookie value.
|
||||||
|
*
|
||||||
|
* @param charset the character set
|
||||||
|
* @param hmac the HMAC code
|
||||||
|
* @param codec the codec for the private value
|
||||||
|
* @param rawValue the raw value for parsing
|
||||||
|
* @param secret the secret
|
||||||
|
* @throws MalformedInputException if parsing failed
|
||||||
|
* @throws UnmappableCharacterException if parsing failed
|
||||||
|
* @throws NoSuchAlgorithmException if parsing failed
|
||||||
|
* @throws InvalidKeyException if parsing failed
|
||||||
|
* @throws SignatureException if signature is invalid
|
||||||
|
*/
|
||||||
|
private CookieSigner(Charset charset, HMac hmac, Codec codec, String rawValue, String secret)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException,
|
||||||
|
SignatureException {
|
||||||
|
PercentDecoder persentDecoder = new PercentDecoder(charset.newDecoder()
|
||||||
|
.onMalformedInput(CodingErrorAction.REPORT)
|
||||||
|
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||||
|
);
|
||||||
|
String[] s = persentDecoder.decode(rawValue).split(":", 3);
|
||||||
|
if (s.length != 3) {
|
||||||
|
throw new IllegalStateException("unable to find three colon-separated components in cookie value");
|
||||||
|
}
|
||||||
|
this.signature = CryptUtil.hmac(charset, hmac, codec, s[1], secret);
|
||||||
|
if (!s[2].equals(signature)) {
|
||||||
|
throw new SignatureException("HMAC signature does not match");
|
||||||
|
}
|
||||||
|
this.publicValue = s[0];
|
||||||
|
this.privateValue = s[1];
|
||||||
|
this.cookieValue = rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicValue() {
|
||||||
|
return publicValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrivateValue() {
|
||||||
|
return privateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCookieValue() {
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private Charset charset;
|
||||||
|
|
||||||
|
private HMac hmac;
|
||||||
|
|
||||||
|
private Codec codec;
|
||||||
|
|
||||||
|
private String privateValue;
|
||||||
|
|
||||||
|
private String publicValue;
|
||||||
|
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
private String rawValue;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
this.charset = StandardCharsets.UTF_8;
|
||||||
|
this.hmac = HMac.HMAC_SHA1;
|
||||||
|
this.codec = Codec.BASE64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCharset(Charset charset) {
|
||||||
|
this.charset = charset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withHMac(HMac hmac) {
|
||||||
|
this.hmac = hmac;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCodec(Codec codec) {
|
||||||
|
this.codec = codec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPrivateValue(String privateValue) {
|
||||||
|
this.privateValue = privateValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPublicValue(String publicValue) {
|
||||||
|
this.publicValue = publicValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSecret(String secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withRawValue(String rawValue) {
|
||||||
|
this.rawValue = rawValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CookieSigner build() {
|
||||||
|
try {
|
||||||
|
return rawValue != null ?
|
||||||
|
new CookieSigner(charset, hmac, codec, rawValue, secret) :
|
||||||
|
new CookieSigner(charset, hmac, codec, privateValue, publicValue, secret);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ public class DefaultCookie implements Cookie {
|
||||||
|
|
||||||
private SameSite sameSite;
|
private SameSite sameSite;
|
||||||
|
|
||||||
|
public DefaultCookie(String name, CookieSigner cookieSigner) {
|
||||||
|
this(name, cookieSigner.getCookieValue());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new cookie with the specified name and value.
|
* Creates a new cookie with the specified name and value.
|
||||||
* @param name name
|
* @param name name
|
||||||
|
@ -38,10 +42,6 @@ public class DefaultCookie implements Cookie {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultCookie(String name, Payload payload) {
|
|
||||||
this(name, payload.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
return name;
|
return name;
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
package org.xbib.netty.http.common.cookie;
|
|
||||||
|
|
||||||
import org.xbib.net.PercentDecoder;
|
|
||||||
import org.xbib.net.PercentEncoder;
|
|
||||||
import org.xbib.net.PercentEncoders;
|
|
||||||
import org.xbib.netty.http.common.util.Codec;
|
|
||||||
import org.xbib.netty.http.common.util.CryptUtils;
|
|
||||||
import org.xbib.netty.http.common.util.HMac;
|
|
||||||
|
|
||||||
import java.nio.charset.MalformedInputException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.charset.UnmappableCharacterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
|
|
||||||
public class Payload {
|
|
||||||
|
|
||||||
private static final PercentEncoder PERCENT_ENCODER = PercentEncoders.getCookieEncoder(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
private static final PercentDecoder PERCENT_DECODER = new PercentDecoder(StandardCharsets.UTF_8.newDecoder());
|
|
||||||
|
|
||||||
private final Codec codec;
|
|
||||||
|
|
||||||
private final HMac hmac;
|
|
||||||
|
|
||||||
private final String publicValue;
|
|
||||||
|
|
||||||
private final String privateValue;
|
|
||||||
|
|
||||||
private final String secret;
|
|
||||||
|
|
||||||
public Payload(Codec codec, HMac hmac, String publicValue, String privateValue, String secret) {
|
|
||||||
this.codec = codec;
|
|
||||||
this.hmac = hmac;
|
|
||||||
this.publicValue = publicValue;
|
|
||||||
this.privateValue = privateValue;
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Payload(Codec codec, HMac hmac, String rawValue, String secret)
|
|
||||||
throws MalformedInputException, UnmappableCharacterException, InvalidKeyException,
|
|
||||||
NoSuchAlgorithmException, SignatureException {
|
|
||||||
this.codec = codec;
|
|
||||||
this.hmac = hmac;
|
|
||||||
String[] s = PERCENT_DECODER.decode(rawValue).split(":", 3);
|
|
||||||
if (s.length != 3) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
this.publicValue = s[0];
|
|
||||||
this.privateValue = s[1];
|
|
||||||
this.secret = secret;
|
|
||||||
if (!s[2].equals(CryptUtils.hmac(codec, privateValue, secret, hmac))) {
|
|
||||||
throw new SignatureException("HMAC signature does not match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublicValue() {
|
|
||||||
return publicValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrivateValue() {
|
|
||||||
return privateValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSecret() {
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
try {
|
|
||||||
return PERCENT_ENCODER.encode(String.join(":", publicValue, privateValue,
|
|
||||||
CryptUtils.hmac(codec, privateValue, secret, hmac)));
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException | MalformedInputException | UnmappableCharacterException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.xbib.netty.http.common.cookie;
|
||||||
|
|
||||||
|
public enum SameSite {
|
||||||
|
STRICT, LAX
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.common.util;
|
package org.xbib.netty.http.common.security;
|
||||||
|
|
||||||
public enum Algo {
|
public enum Algo {
|
||||||
MD5("MD5", "md5"),
|
MD5("MD5", "md5"),
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.xbib.netty.http.common.security;
|
||||||
|
|
||||||
|
public enum Codec {
|
||||||
|
BASE64, HEX
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package org.xbib.netty.http.common.util;
|
package org.xbib.netty.http.common.security;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
@ -15,10 +16,13 @@ import java.util.Base64;
|
||||||
* A utility class for invoking encryption methods and returning password strings,
|
* A utility class for invoking encryption methods and returning password strings,
|
||||||
* using {@link java.security.MessageDigest} and {@link javax.crypto.Mac}.
|
* using {@link java.security.MessageDigest} and {@link javax.crypto.Mac}.
|
||||||
*/
|
*/
|
||||||
public class CryptUtils {
|
public class CryptUtil {
|
||||||
|
|
||||||
private static final Random random = new SecureRandom();
|
private static final Random random = new SecureRandom();
|
||||||
|
|
||||||
|
private CryptUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
public static String randomHex(int length) {
|
public static String randomHex(int length) {
|
||||||
byte[] b = new byte[length];
|
byte[] b = new byte[length];
|
||||||
random.nextBytes(b);
|
random.nextBytes(b);
|
||||||
|
@ -53,32 +57,32 @@ public class CryptUtils {
|
||||||
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA512.algo, Algo.SSHA512.prefix);
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA512.algo, Algo.SSHA512.prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA1(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
public static String hmacSHA1(Charset charset, String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1);
|
return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText.getBytes(charset), secret.getBytes(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA1(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
public static String hmacSHA1(Charset charset, byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1);
|
return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText, secret.getBytes(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA1(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
public static String hmacSHA1(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA1);
|
return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText, secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA256(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
public static String hmacSHA256(Charset charset, String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256);
|
return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText.getBytes(charset), secret.getBytes(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA256(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
public static String hmacSHA256(Charset charset, byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256);
|
return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText, secret.getBytes(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmacSHA256(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
public static String hmacSHA256(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA256);
|
return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText, secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmac(Codec codec, String plainText, String secret, HMac hmac) throws InvalidKeyException, NoSuchAlgorithmException {
|
public static String hmac(Charset charset, HMac hmac, Codec codec, String plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
return hmac(codec, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), hmac);
|
return hmac(hmac, codec, plainText.getBytes(charset), secret.getBytes(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String digest(Codec codec, byte[] plainText, byte[] salt, String algo, String prefix) throws NoSuchAlgorithmException {
|
public static String digest(Codec codec, byte[] plainText, byte[] salt, String algo, String prefix) throws NoSuchAlgorithmException {
|
||||||
|
@ -98,11 +102,11 @@ public class CryptUtils {
|
||||||
codec == Codec.HEX ? encodeHex(bytes) : null);
|
codec == Codec.HEX ? encodeHex(bytes) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hmac(Codec codec, byte[] plainText, byte[] secret, HMac hmac) throws NoSuchAlgorithmException, InvalidKeyException {
|
public static String hmac(HMac hmac, Codec codec, byte[] plainText, byte[] secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
Objects.requireNonNull(plainText);
|
Objects.requireNonNull(plainText);
|
||||||
Objects.requireNonNull(secret);
|
Objects.requireNonNull(secret);
|
||||||
Mac mac = Mac.getInstance(hmac.algo);
|
Mac mac = Mac.getInstance(hmac.getAlgo());
|
||||||
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, hmac.algo);
|
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, hmac.getAlgo());
|
||||||
mac.init(secretKeySpec);
|
mac.init(secretKeySpec);
|
||||||
return codec == Codec.BASE64 ? Base64.getEncoder().encodeToString(mac.doFinal(plainText)) :
|
return codec == Codec.BASE64 ? Base64.getEncoder().encodeToString(mac.doFinal(plainText)) :
|
||||||
codec == Codec.HEX ? encodeHex(mac.doFinal(plainText)) : null;
|
codec == Codec.HEX ? encodeHex(mac.doFinal(plainText)) : null;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.common.util;
|
package org.xbib.netty.http.common.security;
|
||||||
|
|
||||||
public enum HMac {
|
public enum HMac {
|
||||||
HMAC_SHA1("HMacSHA1"),
|
HMAC_SHA1("HMacSHA1"),
|
||||||
|
@ -9,4 +9,8 @@ public enum HMac {
|
||||||
HMac(String algo) {
|
HMac(String algo) {
|
||||||
this.algo = algo;
|
this.algo = algo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAlgo() {
|
||||||
|
return algo;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.common;
|
package org.xbib.netty.http.common.security;
|
||||||
|
|
||||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
||||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
|
@ -23,6 +23,9 @@ public class SecurityUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SecurityUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
public interface Defaults {
|
public interface Defaults {
|
||||||
|
|
||||||
List<String> OPENSSL_CIPHERS = Http2SecurityUtil.CIPHERS;
|
List<String> OPENSSL_CIPHERS = Http2SecurityUtil.CIPHERS;
|
|
@ -1,5 +0,0 @@
|
||||||
package org.xbib.netty.http.common.util;
|
|
||||||
|
|
||||||
public enum Codec {
|
|
||||||
BASE64, HEX
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class DateTimeUtils {
|
public class DateTimeUtil {
|
||||||
|
|
||||||
private static final ZoneId ZONE_UTC = ZoneId.of("UTC");
|
private static final ZoneId ZONE_UTC = ZoneId.of("UTC");
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@ public class DateTimeUtils {
|
||||||
|
|
||||||
private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy";
|
private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy";
|
||||||
|
|
||||||
|
private DateTimeUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
public static String formatInstant(Instant instant) {
|
public static String formatInstant(Instant instant) {
|
||||||
return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC));
|
return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC));
|
||||||
}
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.xbib.netty.http.common.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
import org.xbib.netty.http.common.cookie.CookieSigner;
|
||||||
|
import org.xbib.netty.http.common.security.HMac;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class CookieSignerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodeSignedCookie() {
|
||||||
|
String id = "dummy";
|
||||||
|
CookieSigner cookieSigner = CookieSigner.builder().withHMac(HMac.HMAC_SHA256)
|
||||||
|
.withPublicValue(id)
|
||||||
|
.withPrivateValue(Base64.getEncoder().encodeToString("Hello".getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb")
|
||||||
|
.build();
|
||||||
|
DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner);
|
||||||
|
assertEquals("dummy", cookieSigner.getPublicValue());
|
||||||
|
assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodeSignedCookie() {
|
||||||
|
String rawValue = "dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
||||||
|
CookieSigner cookieSigner = CookieSigner.builder()
|
||||||
|
.withHMac(HMac.HMAC_SHA256)
|
||||||
|
.withRawValue(rawValue)
|
||||||
|
.withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb")
|
||||||
|
.build();
|
||||||
|
DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner);
|
||||||
|
assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value());
|
||||||
|
assertEquals("dummy", cookieSigner.getPublicValue());
|
||||||
|
assertEquals("Hello", new String(Base64.getDecoder().decode(cookieSigner.getPrivateValue()),
|
||||||
|
StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodeInvalidTuple() {
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
String rawValue = "dummy%3JSGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
||||||
|
CookieSigner.builder()
|
||||||
|
.withHMac(HMac.HMAC_SHA256)
|
||||||
|
.withRawValue(rawValue)
|
||||||
|
.withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodeISOGivesMalformedInputException() {
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
String rawValue = "dummy%FCSGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
||||||
|
CookieSigner.builder()
|
||||||
|
.withHMac(HMac.HMAC_SHA256)
|
||||||
|
.withRawValue(rawValue)
|
||||||
|
.withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodeISO() {
|
||||||
|
String rawValue = "d%FCmmy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
||||||
|
CookieSigner cookieSigner = CookieSigner.builder()
|
||||||
|
.withCharset(StandardCharsets.ISO_8859_1)
|
||||||
|
.withHMac(HMac.HMAC_SHA256)
|
||||||
|
.withRawValue(rawValue)
|
||||||
|
.withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb")
|
||||||
|
.build();
|
||||||
|
DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner);
|
||||||
|
assertEquals("d%FCmmy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value());
|
||||||
|
assertEquals("dümmy", cookieSigner.getPublicValue());
|
||||||
|
assertEquals("Hello", new String(Base64.getDecoder().decode(cookieSigner.getPrivateValue()),
|
||||||
|
StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
package org.xbib.netty.http.common.test.cookie;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
|
||||||
import org.xbib.netty.http.common.cookie.Payload;
|
|
||||||
import org.xbib.netty.http.common.util.Codec;
|
|
||||||
import org.xbib.netty.http.common.util.HMac;
|
|
||||||
|
|
||||||
import java.nio.charset.MalformedInputException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.charset.UnmappableCharacterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class SignedCookieTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testEncodeDefaultCookie() {
|
|
||||||
Base64Codec codec = new Base64Codec();
|
|
||||||
String cookieName = "SESS";
|
|
||||||
String domain = ".hbz-nrw.de";
|
|
||||||
String path = "/";
|
|
||||||
String id = "dummy";
|
|
||||||
Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256,
|
|
||||||
id, new String(codec.encode("Hello"), StandardCharsets.UTF_8),
|
|
||||||
"d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb");
|
|
||||||
DefaultCookie cookie = new DefaultCookie(cookieName, payload);
|
|
||||||
cookie.setDomain(domain);
|
|
||||||
cookie.setPath(path);
|
|
||||||
cookie.setMaxAge(3600);
|
|
||||||
cookie.setHttpOnly(true);
|
|
||||||
cookie.setSecure(true);
|
|
||||||
cookie.setSameSite(Cookie.SameSite.LAX);
|
|
||||||
assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value());
|
|
||||||
assertEquals(domain, cookie.domain());
|
|
||||||
assertEquals(path, cookie.path());
|
|
||||||
assertEquals(3600, cookie.maxAge());
|
|
||||||
assertEquals(cookieName, cookie.name());
|
|
||||||
assertTrue(cookie.isHttpOnly());
|
|
||||||
assertTrue(cookie.isSecure());
|
|
||||||
assertEquals(Cookie.SameSite.LAX, cookie.sameSite());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCookieValue()
|
|
||||||
throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
|
||||||
Base64Codec codec = new Base64Codec();
|
|
||||||
String rawCookieValue = "dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
|
||||||
Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256,
|
|
||||||
rawCookieValue, "d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb");
|
|
||||||
DefaultCookie cookie = new DefaultCookie("SESS", payload);
|
|
||||||
assertEquals("dummy", payload.getPublicValue());
|
|
||||||
assertEquals("Hello", codec.decode(payload.getPrivateValue().getBytes(StandardCharsets.UTF_8)));
|
|
||||||
}
|
|
||||||
|
|
||||||
class Base64Codec {
|
|
||||||
|
|
||||||
byte[] encode(String payload) {
|
|
||||||
return Base64.getEncoder().encode(payload.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
String decode(byte[] bytes) {
|
|
||||||
return new String(Base64.getDecoder().decode(bytes), StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +1,33 @@
|
||||||
package org.xbib.netty.http.common.test;
|
package org.xbib.netty.http.common.test.security;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.common.util.CryptUtils;
|
import org.xbib.netty.http.common.security.CryptUtil;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
class CryptUtilsTest {
|
class CryptUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRfc2307() throws NoSuchAlgorithmException {
|
void testRfc2307() throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
assertEquals("{md5}ixqZU8RhEpaoJ6v4xHgE1w==",
|
assertEquals("{md5}ixqZU8RhEpaoJ6v4xHgE1w==",
|
||||||
CryptUtils.md5("Hello"));
|
CryptUtil.md5("Hello"));
|
||||||
assertEquals("{sha}9/+ei3uy4Jtwk1pdeF4MxdnQq/A=",
|
assertEquals("{sha}9/+ei3uy4Jtwk1pdeF4MxdnQq/A=",
|
||||||
CryptUtils.sha("Hello"));
|
CryptUtil.sha("Hello"));
|
||||||
assertEquals("{sha256}GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=",
|
assertEquals("{sha256}GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=",
|
||||||
CryptUtils.sha256("Hello"));
|
CryptUtil.sha256("Hello"));
|
||||||
assertEquals("{sha512}NhX4DJ0pPtdAJof5SyLVjlKbjMeRb4+sf933+9WvTPd309eVp6AKFr9+fz+5Vh7puq5IDan+ehh2nnGIawPzFQ==",
|
assertEquals("{sha512}NhX4DJ0pPtdAJof5SyLVjlKbjMeRb4+sf933+9WvTPd309eVp6AKFr9+fz+5Vh7puq5IDan+ehh2nnGIawPzFQ==",
|
||||||
CryptUtils.sha512("Hello"));
|
CryptUtil.sha512("Hello"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHmac() throws InvalidKeyException, NoSuchAlgorithmException {
|
void testHmac() throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
assertEquals("Wgxn2SLeDKU+MGJQ5oWMH20sSUM=",
|
assertEquals("Wgxn2SLeDKU+MGJQ5oWMH20sSUM=",
|
||||||
CryptUtils.hmacSHA1("hello", "world"));
|
CryptUtil.hmacSHA1(StandardCharsets.ISO_8859_1, "hello", "world"));
|
||||||
assertEquals("PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=",
|
assertEquals("PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=",
|
||||||
CryptUtils.hmacSHA256("hello", "world"));
|
CryptUtil.hmacSHA256(StandardCharsets.ISO_8859_1, "hello", "world"));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
@ -147,7 +147,7 @@ public class Domain {
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.aliases = new LinkedHashSet<>();
|
this.aliases = new LinkedHashSet<>();
|
||||||
this.httpEndpointResolvers = new ArrayList<>();
|
this.httpEndpointResolvers = new ArrayList<>();
|
||||||
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; // InsecureTrustManagerFactory.INSTANCE;
|
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
|
||||||
this.sslProvider = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER;
|
this.sslProvider = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER;
|
||||||
this.ciphers = SecurityUtil.Defaults.DEFAULT_CIPHERS;
|
this.ciphers = SecurityUtil.Defaults.DEFAULT_CIPHERS;
|
||||||
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.security.SecurityUtil;
|
||||||
import org.xbib.netty.http.server.transport.HttpTransport;
|
import org.xbib.netty.http.server.transport.HttpTransport;
|
||||||
import org.xbib.netty.http.server.transport.Http2Transport;
|
import org.xbib.netty.http.server.transport.Http2Transport;
|
||||||
import org.xbib.netty.http.server.transport.Transport;
|
import org.xbib.netty.http.server.transport.Transport;
|
||||||
|
@ -83,8 +83,7 @@ public final class Server {
|
||||||
Class<? extends ServerSocketChannel> socketChannelClass) {
|
Class<? extends ServerSocketChannel> socketChannelClass) {
|
||||||
Objects.requireNonNull(serverConfig);
|
Objects.requireNonNull(serverConfig);
|
||||||
this.serverConfig = serverConfig;
|
this.serverConfig = serverConfig;
|
||||||
this.byteBufAllocator = byteBufAllocator != null ?
|
this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
|
||||||
byteBufAllocator : ByteBufAllocator.DEFAULT;
|
|
||||||
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
||||||
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
|
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
|
||||||
this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass);
|
this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass);
|
||||||
|
@ -191,6 +190,7 @@ public final class Server {
|
||||||
parentEventLoopGroup.shutdownGracefully();
|
parentEventLoopGroup.shutdownGracefully();
|
||||||
try {
|
try {
|
||||||
if (channelFuture != null) {
|
if (channelFuture != null) {
|
||||||
|
// close channel and wait
|
||||||
channelFuture.channel().closeFuture().sync();
|
channelFuture.channel().closeFuture().sync();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import org.xbib.netty.http.common.cookie.CookieEncoder;
|
||||||
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
||||||
import org.xbib.netty.http.common.cookie.CookieUtil;
|
import org.xbib.netty.http.common.cookie.CookieUtil;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtils;
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -86,7 +86,7 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
||||||
Instant expires = Instant.ofEpochMilli(cookie.maxAge() * 1000 + System.currentTimeMillis());
|
Instant expires = Instant.ofEpochMilli(cookie.maxAge() * 1000 + System.currentTimeMillis());
|
||||||
buf.append(CookieHeaderNames.EXPIRES);
|
buf.append(CookieHeaderNames.EXPIRES);
|
||||||
buf.append(CookieUtil.EQUALS);
|
buf.append(CookieUtil.EQUALS);
|
||||||
buf.append(DateTimeUtils.formatMillis(expires.toEpochMilli()));
|
buf.append(DateTimeUtil.formatMillis(expires.toEpochMilli()));
|
||||||
buf.append(CookieUtil.SEMICOLON);
|
buf.append(CookieUtil.SEMICOLON);
|
||||||
buf.append(CookieUtil.SP);
|
buf.append(CookieUtil.SP);
|
||||||
}
|
}
|
||||||
|
@ -110,28 +110,6 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
||||||
return CookieUtil.stripTrailingSeparator(buf);
|
return CookieUtil.stripTrailingSeparator(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deduplicate a list of encoded cookies by keeping only the last instance with a given name.
|
|
||||||
*
|
|
||||||
* @param encoded The list of encoded cookies.
|
|
||||||
* @param nameToLastIndex A map from cookie name to index of last cookie instance.
|
|
||||||
* @return The encoded list with all but the last instance of a named cookie.
|
|
||||||
*/
|
|
||||||
private static List<String> dedup(List<String> encoded, Map<String, Integer> nameToLastIndex) {
|
|
||||||
boolean[] isLastInstance = new boolean[encoded.size()];
|
|
||||||
for (int idx : nameToLastIndex.values()) {
|
|
||||||
isLastInstance[idx] = true;
|
|
||||||
}
|
|
||||||
List<String> dedupd = new ArrayList<>(nameToLastIndex.size());
|
|
||||||
int n = encoded.size();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
if (isLastInstance[i]) {
|
|
||||||
dedupd.add(encoded.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dedupd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch encodes cookies into Set-Cookie header values.
|
* Batch encodes cookies into Set-Cookie header values.
|
||||||
*
|
*
|
||||||
|
@ -204,4 +182,26 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
||||||
}
|
}
|
||||||
return hasDupdName ? dedup(encoded, nameToIndex) : encoded;
|
return hasDupdName ? dedup(encoded, nameToIndex) : encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deduplicate a list of encoded cookies by keeping only the last instance with a given name.
|
||||||
|
*
|
||||||
|
* @param encoded The list of encoded cookies.
|
||||||
|
* @param nameToLastIndex A map from cookie name to index of last cookie instance.
|
||||||
|
* @return The encoded list with all but the last instance of a named cookie.
|
||||||
|
*/
|
||||||
|
private static List<String> dedup(List<String> encoded, Map<String, Integer> nameToLastIndex) {
|
||||||
|
boolean[] isLastInstance = new boolean[encoded.size()];
|
||||||
|
for (int idx : nameToLastIndex.values()) {
|
||||||
|
isLastInstance[idx] = true;
|
||||||
|
}
|
||||||
|
List<String> dedupd = new ArrayList<>(nameToLastIndex.size());
|
||||||
|
int n = encoded.size();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (isLastInstance[i]) {
|
||||||
|
dedupd.add(encoded.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dedupd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[EndpointInfo:path=" + path + ",method=" + method + ",contentType=" + contentType + "]";
|
return "[HttpEndpointDescriptor:path=" + path + ",method=" + method + ",contentType=" + contentType + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -11,7 +11,6 @@ import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -44,7 +43,8 @@ public class HttpEndpointResolver {
|
||||||
HttpEndpointDescriptor httpEndpointDescriptor = serverRequest.getEndpointDescriptor();
|
HttpEndpointDescriptor httpEndpointDescriptor = serverRequest.getEndpointDescriptor();
|
||||||
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
|
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
|
||||||
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
|
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
|
||||||
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath())).collect(Collectors.toList()));
|
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath()))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
List<HttpEndpoint> matchingEndpoints = endpointDescriptors.get(httpEndpointDescriptor);
|
List<HttpEndpoint> matchingEndpoints = endpointDescriptors.get(httpEndpointDescriptor);
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
if (logger.isLoggable(Level.FINE)) {
|
||||||
logger.log(Level.FINE, () -> "endpoint = " + httpEndpointDescriptor +
|
logger.log(Level.FINE, () -> "endpoint = " + httpEndpointDescriptor +
|
||||||
|
@ -83,7 +83,7 @@ public class HttpEndpointResolver {
|
||||||
return endpointDescriptors;
|
return endpointDescriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpEndpoint createDefaultEndpoint() {
|
private HttpEndpoint createDefaultEndpoint() {
|
||||||
return HttpEndpoint.builder()
|
return HttpEndpoint.builder()
|
||||||
.setPath("/**")
|
.setPath("/**")
|
||||||
.addMethod("GET")
|
.addMethod("GET")
|
||||||
|
@ -94,27 +94,6 @@ public class HttpEndpointResolver {
|
||||||
}).build();
|
}).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple LRU cache, based on a {@link LinkedHashMap}.
|
|
||||||
*
|
|
||||||
* @param <K> the key type parameter
|
|
||||||
* @param <V> the vale type parameter
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
|
||||||
|
|
||||||
private final int cacheSize;
|
|
||||||
|
|
||||||
LRUCache(int cacheSize) {
|
|
||||||
super(16, 0.75f, true);
|
|
||||||
this.cacheSize = cacheSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
|
||||||
return size() > cacheSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.stream.ChunkedNioStream;
|
import io.netty.handler.stream.ChunkedNioStream;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtils;
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
||||||
|
@ -55,14 +55,14 @@ public abstract class ResourceService implements Service {
|
||||||
long maxAgeSeconds = 24 * 3600;
|
long maxAgeSeconds = 24 * 3600;
|
||||||
long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds;
|
long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds;
|
||||||
if (isCacheResponseEnabled()) {
|
if (isCacheResponseEnabled()) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis))
|
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis))
|
||||||
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds);
|
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds);
|
||||||
}
|
}
|
||||||
boolean sent = false;
|
boolean sent = false;
|
||||||
if (isETagResponseEnabled()) {
|
if (isETagResponseEnabled()) {
|
||||||
Instant lastModifiedInstant = resource.getLastModified();
|
Instant lastModifiedInstant = resource.getLastModified();
|
||||||
String eTag = resource.getResourcePath().hashCode() + "/" + lastModifiedInstant.toEpochMilli() + "/" + resource.getLength();
|
String eTag = resource.getResourcePath().hashCode() + "/" + lastModifiedInstant.toEpochMilli() + "/" + resource.getLength();
|
||||||
Instant ifUnmodifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
||||||
if (ifUnmodifiedSinceInstant != null &&
|
if (ifUnmodifiedSinceInstant != null &&
|
||||||
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
||||||
|
@ -76,20 +76,20 @@ public abstract class ResourceService implements Service {
|
||||||
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
||||||
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
|
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis));
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Instant ifModifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
||||||
if (ifModifiedSinceInstant != null &&
|
if (ifModifiedSinceInstant != null &&
|
||||||
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
|
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis));
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtils.formatInstant(lastModifiedInstant));
|
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatInstant(lastModifiedInstant));
|
||||||
if (isRangeResponseEnabled()) {
|
if (isRangeResponseEnabled()) {
|
||||||
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
||||||
sent = true;
|
sent = true;
|
||||||
|
@ -119,7 +119,7 @@ public abstract class ResourceService implements Service {
|
||||||
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
||||||
if (ifRange != null && !ifRange.equals(eTag)) {
|
if (ifRange != null && !ifRange.equals(eTag)) {
|
||||||
try {
|
try {
|
||||||
Instant ifRangeTime = DateTimeUtils.parseDate(ifRange);
|
Instant ifRangeTime = DateTimeUtil.parseDate(ifRange);
|
||||||
if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) {
|
if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) {
|
||||||
ranges.add(full);
|
ranges.add(full);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
|
import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtils;
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
|
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -35,7 +35,7 @@ class ServerCookieEncoderTest {
|
||||||
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);
|
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);
|
||||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||||
assertTrue(matcher.find());
|
assertTrue(matcher.find());
|
||||||
Instant expire = DateTimeUtils.parseDate(matcher.group(1));
|
Instant expire = DateTimeUtil.parseDate(matcher.group(1));
|
||||||
long diff = (expire.toEpochMilli() - System.currentTimeMillis()) / 1000;
|
long diff = (expire.toEpochMilli() - System.currentTimeMillis()) / 1000;
|
||||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue