parent
ef0a21ec7a
commit
d6fcad9ad7
|
@ -18,12 +18,14 @@ package org.springframework.web.util;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.lang.Contract;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogDelegateFactory;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parser for URI's based on the syntax in RFC 3986.
|
* Parser for URI's based on RFC 3986 syntax.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
|
@ -32,6 +34,8 @@ import org.springframework.util.Assert;
|
||||||
*/
|
*/
|
||||||
abstract class RfcUriParser {
|
abstract class RfcUriParser {
|
||||||
|
|
||||||
|
private static final Log logger = LogDelegateFactory.getHiddenLog(RfcUriParser.class);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the given URI string.
|
* Parse the given URI string.
|
||||||
|
@ -44,19 +48,20 @@ abstract class RfcUriParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Contract("false, _ -> fail")
|
private static void verify(boolean expression, InternalParser parser, String message) {
|
||||||
private static void verify(boolean expression, String message) {
|
|
||||||
if (!expression) {
|
if (!expression) {
|
||||||
fail(message);
|
fail(parser, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verifyIsHexDigit(char c, String message) {
|
private static void verifyIsHexDigit(char c, InternalParser parser, String message) {
|
||||||
verify((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9'), message);
|
verify((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9'), parser, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Contract("_ -> fail")
|
private static void fail(InternalParser parser, String message) {
|
||||||
private static void fail(String message) {
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(InvalidUrlException.class.getSimpleName() + ": \"" + message + "\" " + parser);
|
||||||
|
}
|
||||||
throw new InvalidUrlException(message);
|
throw new InvalidUrlException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +207,7 @@ abstract class RfcUriParser {
|
||||||
parser.captureUser().componentIndex(i + 1);
|
parser.captureUser().componentIndex(i + 1);
|
||||||
break;
|
break;
|
||||||
case '[':
|
case '[':
|
||||||
verify(parser.isAtStartOfComponent(), "Bad authority");
|
verify(parser.isAtStartOfComponent(), parser, "Bad authority");
|
||||||
parser.advanceTo(IPV6);
|
parser.advanceTo(IPV6);
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
|
@ -212,7 +217,7 @@ abstract class RfcUriParser {
|
||||||
boolean isAllowed = (parser.processCurlyBrackets(c) ||
|
boolean isAllowed = (parser.processCurlyBrackets(c) ||
|
||||||
parser.countDownPercentEncodingInHost(c) ||
|
parser.countDownPercentEncodingInHost(c) ||
|
||||||
HierarchicalUriComponents.Type.URI.isUnreservedOrSubDelimiter(c));
|
HierarchicalUriComponents.Type.URI.isUnreservedOrSubDelimiter(c));
|
||||||
verify(isAllowed, "Bad authority");
|
verify(isAllowed, parser, "Bad authority");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,13 +247,13 @@ abstract class RfcUriParser {
|
||||||
case ':':
|
case ':':
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
verifyIsHexDigit(c, "Bad authority");
|
verifyIsHexDigit(c, parser, "Bad authority");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleEnd(InternalParser parser) {
|
public void handleEnd(InternalParser parser) {
|
||||||
verify(parser.hasHost(), "Bad authority"); // no closing ']'
|
verify(parser.hasHost(), parser, "Bad authority"); // no closing ']'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -257,7 +262,7 @@ abstract class RfcUriParser {
|
||||||
@Override
|
@Override
|
||||||
public void handleNext(InternalParser parser, char c, int i) {
|
public void handleNext(InternalParser parser, char c, int i) {
|
||||||
if (c == '@') {
|
if (c == '@') {
|
||||||
verify(!parser.hasUser(), "Bad authority");
|
verify(!parser.hasUser(), parser, "Bad authority");
|
||||||
parser.switchPortForFullPassword().advanceTo(HOST, i + 1);
|
parser.switchPortForFullPassword().advanceTo(HOST, i + 1);
|
||||||
}
|
}
|
||||||
else if (c == '/') {
|
else if (c == '/') {
|
||||||
|
@ -274,7 +279,7 @@ abstract class RfcUriParser {
|
||||||
parser.switchPortForPassword().advanceTo(HOST);
|
parser.switchPortForPassword().advanceTo(HOST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fail("Bad authority");
|
fail(parser, "Bad authority");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +347,7 @@ abstract class RfcUriParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNext(InternalParser parser, char c, int i) {
|
public void handleNext(InternalParser parser, char c, int i) {
|
||||||
fail("Bad character '*'");
|
fail(parser, "Bad character '*'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -422,31 +427,6 @@ abstract class RfcUriParser {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the input string and return a {@link UriRecord} with the results.
|
|
||||||
*/
|
|
||||||
public UriRecord parse() {
|
|
||||||
Assert.isTrue(this.state == State.START && this.index == 0, "Internal Error");
|
|
||||||
|
|
||||||
while (hasNext()) {
|
|
||||||
this.state.handleNext(this, charAtIndex(), this.index);
|
|
||||||
this.index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.handleEnd(this);
|
|
||||||
|
|
||||||
return new UriRecord(this.scheme, this.isOpaque,
|
|
||||||
this.user, this.host, this.port, this.path, this.query, this.fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasNext() {
|
|
||||||
return (this.index < this.uri.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
public char charAtIndex() {
|
|
||||||
return this.uri.charAt(this.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check internal state
|
// Check internal state
|
||||||
|
|
||||||
public boolean hasScheme() {
|
public boolean hasScheme() {
|
||||||
|
@ -469,15 +449,43 @@ abstract class RfcUriParser {
|
||||||
return (this.index == this.componentIndex);
|
return (this.index == this.componentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Top-level parse loop, iterate over chars and delegate to states
|
||||||
|
|
||||||
|
public UriRecord parse() {
|
||||||
|
Assert.isTrue(this.state == State.START && this.index == 0, "Internal Error");
|
||||||
|
|
||||||
|
while (hasNext()) {
|
||||||
|
this.state.handleNext(this, charAtIndex(), this.index);
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.handleEnd(this);
|
||||||
|
|
||||||
|
return new UriRecord(this.scheme, this.isOpaque,
|
||||||
|
this.user, this.host, this.port, this.path, this.query, this.fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return (this.index < this.uri.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public char charAtIndex() {
|
||||||
|
return this.uri.charAt(this.index);
|
||||||
|
}
|
||||||
|
|
||||||
// Transitions and index updates
|
// Transitions and index updates
|
||||||
|
|
||||||
public void advanceTo(State state) {
|
public void advanceTo(State state) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(this.state + " -> " + state + ", " +
|
||||||
|
"index=" + this.index + ", componentIndex=" + this.componentIndex);
|
||||||
|
}
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void advanceTo(State state, int componentIndex) {
|
public void advanceTo(State state, int componentIndex) {
|
||||||
this.state = state;
|
|
||||||
this.componentIndex = componentIndex;
|
this.componentIndex = componentIndex;
|
||||||
|
advanceTo(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser componentIndex(int componentIndex) {
|
public InternalParser componentIndex(int componentIndex) {
|
||||||
|
@ -498,19 +506,19 @@ abstract class RfcUriParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser captureScheme() {
|
public InternalParser captureScheme() {
|
||||||
this.scheme = captureComponent().toLowerCase();
|
this.scheme = captureComponent("scheme").toLowerCase();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser captureUser() {
|
public InternalParser captureUser() {
|
||||||
this.inPassword = false;
|
this.inPassword = false;
|
||||||
this.user = captureComponent();
|
this.user = captureComponent("user");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser captureHost() {
|
public InternalParser captureHost() {
|
||||||
verify(this.remainingPercentEncodedChars == 0 && !this.inPassword, "Bad authority");
|
verify(this.remainingPercentEncodedChars == 0 && !this.inPassword, this, "Bad authority");
|
||||||
this.host = captureComponent();
|
this.host = captureComponent("host");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,29 +530,32 @@ abstract class RfcUriParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser capturePort() {
|
public InternalParser capturePort() {
|
||||||
verify(this.openCurlyBracketCount == 0, "Bad authority");
|
verify(this.openCurlyBracketCount == 0, this, "Bad authority");
|
||||||
this.port = captureComponent();
|
this.port = captureComponent("port");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser capturePath() {
|
public InternalParser capturePath() {
|
||||||
this.path = captureComponent();
|
this.path = captureComponent("path");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser captureQuery() {
|
public InternalParser captureQuery() {
|
||||||
this.query = captureComponent();
|
this.query = captureComponent("query");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void captureFragmentIfNotEmpty() {
|
public void captureFragmentIfNotEmpty() {
|
||||||
if (this.index > this.componentIndex + 1) {
|
if (this.index > this.componentIndex + 1) {
|
||||||
this.fragment = captureComponent();
|
this.fragment = captureComponent("fragment");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser switchPortForFullPassword() {
|
public InternalParser switchPortForFullPassword() {
|
||||||
this.user = this.host + ":" + captureComponent();
|
this.user = this.host + ":" + captureComponent();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Switching from host/port to user=" + this.user);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,16 +564,27 @@ abstract class RfcUriParser {
|
||||||
if (this.host != null) {
|
if (this.host != null) {
|
||||||
this.componentIndex = (this.componentIndex - this.host.length() - 1);
|
this.componentIndex = (this.componentIndex - this.host.length() - 1);
|
||||||
this.host = null;
|
this.host = null;
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Switching from host/port to username/password");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String captureComponent(String logPrefix) {
|
||||||
|
String value = captureComponent();
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(logPrefix + " set to '" + value + "'");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private String captureComponent() {
|
private String captureComponent() {
|
||||||
return this.uri.substring(this.componentIndex, this.index);
|
return this.uri.substring(this.componentIndex, this.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalParser markPercentEncoding() {
|
public InternalParser markPercentEncoding() {
|
||||||
verify(this.remainingPercentEncodedChars == 0, "Bad encoding");
|
verify(this.remainingPercentEncodedChars == 0, this, "Bad encoding");
|
||||||
this.remainingPercentEncodedChars = 2;
|
this.remainingPercentEncodedChars = 2;
|
||||||
this.inUtf16Sequence = false;
|
this.inUtf16Sequence = false;
|
||||||
return this;
|
return this;
|
||||||
|
@ -578,7 +600,7 @@ abstract class RfcUriParser {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.remainingPercentEncodedChars--;
|
this.remainingPercentEncodedChars--;
|
||||||
verifyIsHexDigit(c, "Bad authority");
|
verifyIsHexDigit(c, this, "Bad authority");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,7 +617,7 @@ abstract class RfcUriParser {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.remainingPercentEncodedChars--;
|
this.remainingPercentEncodedChars--;
|
||||||
verifyIsHexDigit(c, "Bad path");
|
verifyIsHexDigit(c, this, "Bad path");
|
||||||
this.inUtf16Sequence &= (this.remainingPercentEncodedChars > 0);
|
this.inUtf16Sequence &= (this.remainingPercentEncodedChars > 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -615,6 +637,13 @@ abstract class RfcUriParser {
|
||||||
return (this.openCurlyBracketCount > 0);
|
return (this.openCurlyBracketCount > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[State=" + this.state + ", index=" + this.index + ", componentIndex=" + this.componentIndex +
|
||||||
|
", uri='" + this.uri + "', scheme='" + this.scheme + "', user='" + this.user +
|
||||||
|
"', host='" + this.host + "', path='" + this.path + "', port='" + this.port +
|
||||||
|
"', query='" + this.query + "', fragment='" + this.fragment + "']";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue