Skip to content

Commit

Permalink
WIP Do not review
Browse files Browse the repository at this point in the history
  • Loading branch information
trustin committed Apr 4, 2023
1 parent e91aa49 commit 9788547
Show file tree
Hide file tree
Showing 2 changed files with 403 additions and 458 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import java.util.BitSet;
import java.util.Objects;

import com.google.common.annotations.VisibleForTesting;

import com.linecorp.armeria.common.RequestTarget;
import com.linecorp.armeria.common.RequestTargetForm;
import com.linecorp.armeria.common.annotation.Nullable;
Expand Down Expand Up @@ -52,6 +50,8 @@ public final class DefaultRequestTarget implements RequestTarget {
*/
private static final BitSet MUST_PRESERVE_ENCODING_IN_QUERY = new BitSet();

private static final BitSet EMPTY_BITSET = new BitSet();

/**
* The table that converts a byte into a percent-encoded chars, e.g. 'A' -> "%41".
*/
Expand Down Expand Up @@ -116,7 +116,7 @@ public static RequestTarget forClient(String reqTarget, @Nullable String prefix)

// Concatenate `prefix` and `reqTarget` if necessary.
final String actualReqTarget;
if (prefix == null || prefix.isEmpty() || "*".equals(reqTarget)) {
if (prefix == null || "*".equals(reqTarget)) {
// No prefix was given or request target is `*`.
actualReqTarget = reqTarget;
} else {
Expand Down Expand Up @@ -275,16 +275,16 @@ private static RequestTarget slowForServer(String reqTarget, boolean allowDouble
final int queryPos = reqTarget.indexOf('?');
if (queryPos >= 0) {
if ((path = decodePercentsAndEncodeToUtf8(
reqTarget, 0, queryPos, true, SLASH_BYTES)) == null) {
reqTarget, 0, queryPos, ComponentType.CLIENT_PATH, null)) == null) {
return null;
}
if ((query = decodePercentsAndEncodeToUtf8(
reqTarget, queryPos + 1, reqTarget.length(), false, EMPTY_BYTES)) == null) {
reqTarget, queryPos + 1, reqTarget.length(), ComponentType.QUERY, EMPTY_BYTES)) == null) {
return null;
}
} else {
if ((path = decodePercentsAndEncodeToUtf8(
reqTarget, 0, reqTarget.length(), true, SLASH_BYTES)) == null) {
reqTarget, 0, reqTarget.length(), ComponentType.CLIENT_PATH, null)) == null) {
return null;
}
query = null;
Expand Down Expand Up @@ -370,8 +370,9 @@ private static RequestTarget slowForClient(String reqTarget,
// Find where a query string and a fragment starts.
final int queryPos;
final int fragmentPos;
final int maybeQueryPos = reqTarget.indexOf('?', pathPos + 1);
final int maybeFragmentPos = reqTarget.indexOf('#', pathPos + 1);
// Note: We don't start from `pathPos + 1` but from `pathPos` just in case path is empty.
final int maybeQueryPos = reqTarget.indexOf('?', pathPos);
final int maybeFragmentPos = reqTarget.indexOf('#', pathPos);
if (maybeQueryPos >= 0) {
// Found '?'.
if (maybeFragmentPos >= 0) {
Expand Down Expand Up @@ -399,24 +400,24 @@ private static RequestTarget slowForClient(String reqTarget,
// Split into path, query and fragment.
if (queryPos >= 0) {
if ((path = decodePercentsAndEncodeToUtf8(
reqTarget, pathPos, queryPos, true, EMPTY_BYTES)) == null) {
reqTarget, pathPos, queryPos, ComponentType.CLIENT_PATH, SLASH_BYTES)) == null) {
return null;
}

if (fragmentPos >= 0) {
// path?query#fragment
if ((query = decodePercentsAndEncodeToUtf8(
reqTarget, queryPos + 1, fragmentPos, false, EMPTY_BYTES)) == null) {
reqTarget, queryPos + 1, fragmentPos, ComponentType.QUERY, EMPTY_BYTES)) == null) {
return null;
}
if ((fragment = decodePercentsAndEncodeToUtf8(
reqTarget, fragmentPos + 1, reqTarget.length(), true, EMPTY_BYTES)) == null) {
reqTarget, fragmentPos + 1, reqTarget.length(), ComponentType.FRAGMENT, EMPTY_BYTES)) == null) {
return null;
}
} else {
// path?query
if ((query = decodePercentsAndEncodeToUtf8(
reqTarget, queryPos + 1, reqTarget.length(), false, EMPTY_BYTES)) == null) {
reqTarget, queryPos + 1, reqTarget.length(), ComponentType.QUERY, SLASH_BYTES)) == null) {
return null;
}
fragment = null;
Expand All @@ -425,43 +426,46 @@ private static RequestTarget slowForClient(String reqTarget,
if (fragmentPos >= 0) {
// path#fragment
if ((path = decodePercentsAndEncodeToUtf8(
reqTarget, pathPos, fragmentPos, true, EMPTY_BYTES)) == null) {
reqTarget, pathPos, fragmentPos, ComponentType.CLIENT_PATH, EMPTY_BYTES)) == null) {
return null;
}
query = null;
if ((fragment = decodePercentsAndEncodeToUtf8(
reqTarget, fragmentPos + 1, reqTarget.length(), true, EMPTY_BYTES)) == null) {
reqTarget, fragmentPos + 1, reqTarget.length(), ComponentType.FRAGMENT, EMPTY_BYTES)) == null) {
return null;
}
} else {
// path
if ((path = decodePercentsAndEncodeToUtf8(
reqTarget, pathPos, reqTarget.length(), true, EMPTY_BYTES)) == null) {
reqTarget, pathPos, reqTarget.length(), ComponentType.CLIENT_PATH, EMPTY_BYTES)) == null) {
return null;
}
query = null;
fragment = null;
}
}

// Reject a relative path and accept an asterisk (e.g. OPTIONS * HTTP/1.1).
// Accept an asterisk (e.g. OPTIONS * HTTP/1.1).
if (query == null && path.length == 1 && path.data[0] == '*') {
return new DefaultRequestTarget(RequestTargetForm.ASTERISK,
null,
null,
"*",
null,
null);
}

final String encodedPath;
if (isRelativePath(path)) {
if (query == null && path.length == 1 && path.data[0] == '*') {
return new DefaultRequestTarget(RequestTargetForm.ASTERISK,
null,
null,
"*",
null,
null);
} else {
// Do not accept a relative path.
return null;
}
// Turn a relative path into an absolute one.
encodedPath = '/' + encodePathToPercents(path);
} else {
encodedPath = encodePathToPercents(path);
}

final String encodedPath = encodePathToPercents(path);
final String encodedQuery = encodeQueryToPercents(query);
final String encodedFragment = encodeFragmentToPercents(fragment);

if (schemeAndAuthority != null) {
return new DefaultRequestTarget(RequestTargetForm.ABSOLUTE,
schemeAndAuthority.getScheme(),
Expand Down Expand Up @@ -513,25 +517,16 @@ private static boolean isRelativePath(Bytes path) {
return path.length == 0 || path.data[0] != '/' || path.isEncoded(0);
}

/**
* Decodes a percent-encoded query string. This method is only used for {@code RequestTargetTest}.
*/
@Nullable
@VisibleForTesting
static String decodePercentEncodedQuery(String query) {
final Bytes bytes = decodePercentsAndEncodeToUtf8(query, 0, query.length(), false, EMPTY_BYTES);
return encodeQueryToPercents(bytes);
}

@Nullable
private static Bytes decodePercentsAndEncodeToUtf8(String value, int start, int end,
boolean isPath, Bytes whenEmpty) {
ComponentType type, @Nullable Bytes whenEmpty) {
final int length = end - start;
if (length == 0) {
return whenEmpty;
}

final Bytes buf = new Bytes(Math.max(length * 3 / 2, 4));
final boolean isPath = type.isPath();
boolean wasSlash = false;
for (final CodePointIterator i = new CodePointIterator(value, start, end);
i.hasNextCodePoint();/* noop */) {
Expand Down Expand Up @@ -562,19 +557,19 @@ private static Bytes decodePercentsAndEncodeToUtf8(String value, int start, int
buf.addEncoded((byte) '/');
wasSlash = false;
} else {
if (appendOneByte(buf, decoded, wasSlash, isPath)) {
if (appendOneByte(buf, decoded, wasSlash, type)) {
wasSlash = false;
} else {
return null;
}
}
} else {
// If query:
if (MUST_PRESERVE_ENCODING_IN_QUERY.get(decoded)) {
// If query or fragment:
if (type.mustPreserveEncoding.get(decoded)) {
buf.ensure(1);
buf.addEncoded((byte) decoded);
wasSlash = false;
} else if (appendOneByte(buf, decoded, wasSlash, isPath)) {
} else if (appendOneByte(buf, decoded, wasSlash, type)) {
wasSlash = decoded == '/';
} else {
return null;
Expand All @@ -585,15 +580,15 @@ private static Bytes decodePercentsAndEncodeToUtf8(String value, int start, int
continue;
}

if (cp == '+' && !isPath) {
if (cp == '+' && type == ComponentType.QUERY) {
buf.ensure(1);
buf.addEncoded((byte) ' ');
wasSlash = false;
continue;
}

if (cp <= 0x7F) {
if (!appendOneByte(buf, cp, wasSlash, isPath)) {
if (!appendOneByte(buf, cp, wasSlash, type)) {
return null;
}
wasSlash = cp == '/';
Expand Down Expand Up @@ -642,33 +637,32 @@ private static Bytes decodePercentsAndEncodeToUtf8(String value, int start, int
return buf;
}

private static boolean appendOneByte(Bytes buf, int cp, boolean wasSlash, boolean isPath) {
private static boolean appendOneByte(Bytes buf, int cp, boolean wasSlash, ComponentType type) {
if (cp == 0x7F) {
// Reject the control character: 0x7F
return false;
}

if (cp >>> 5 == 0) {
// Reject the control characters: 0x00..0x1F
if (isPath) {
if (type.isPath()) {
return false;
} else if (cp != 0x0A && cp != 0x0D && cp != 0x09) {
// .. except 0x0A (LF), 0x0D (CR) and 0x09 (TAB) because they are used in a form.
return false;
}
}

if (cp == '/' && isPath) {
if (cp == '/' && type.isPath()) {
if (!wasSlash) {
buf.ensure(1);
buf.add((byte) '/');
} else {
// Remove the consecutive slashes: '/path//with///consecutive////slashes'.
}
} else {
final BitSet allowedChars = isPath ? ALLOWED_PATH_CHARS : ALLOWED_QUERY_CHARS;
buf.ensure(1);
if (allowedChars.get(cp)) {
if (type.allowedChars.get(cp)) {
buf.add((byte) cp);
} else {
buf.addEncoded((byte) cp);
Expand Down Expand Up @@ -926,4 +920,26 @@ int nextCodePoint() {
return c1;
}
}

private enum ComponentType {
CLIENT_PATH(false, ALLOWED_PATH_CHARS, EMPTY_BITSET),
SERVER_PATH(false, ALLOWED_PATH_CHARS, EMPTY_BITSET),
QUERY(false, ALLOWED_QUERY_CHARS, MUST_PRESERVE_ENCODING_IN_QUERY),
FRAGMENT(false, ALLOWED_PATH_CHARS, EMPTY_BITSET);

final boolean plusIsSpace;
final BitSet allowedChars;
final BitSet mustPreserveEncoding;

ComponentType(boolean plusIsSpace, BitSet allowedChars, BitSet mustPreserveEncoding) {
this.plusIsSpace = plusIsSpace;
this.allowedChars = allowedChars;
this.mustPreserveEncoding = mustPreserveEncoding;
}

boolean isPath() {
// Only the first and second value will return true.
return (ordinal() & 0xFFFFFFFE) == 0;
}
}
}
Loading

0 comments on commit 9788547

Please sign in to comment.