org.apache.dubbo.common.URLStrParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dubbo Show documentation
Show all versions of dubbo Show documentation
The all in one project of dubbo
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common;
import org.apache.dubbo.common.url.component.ServiceConfigURL;
import org.apache.dubbo.common.url.component.URLItemCache;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING;
import static org.apache.dubbo.common.utils.StringUtils.decodeHexByte;
import static org.apache.dubbo.common.utils.Utf8Utils.decodeUtf8;
public final class URLStrParser {
public static final String ENCODED_QUESTION_MARK = "%3F";
public static final String ENCODED_TIMESTAMP_KEY = "timestamp%3D";
public static final String ENCODED_PID_KEY = "pid%3D";
public static final String ENCODED_AND_MARK = "%26";
private static final char SPACE = 0x20;
private static final ThreadLocal DECODE_TEMP_BUF = ThreadLocal.withInitial(() -> new TempBuf(1024));
private URLStrParser() {
//empty
}
/**
* @param decodedURLStr : after {@link URL#decode} string
* decodedURLStr format: protocol://username:password@host:port/path?k1=v1&k2=v2
* [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
*/
public static URL parseDecodedStr(String decodedURLStr) {
Map parameters = null;
int pathEndIdx = decodedURLStr.indexOf('?');
if (pathEndIdx >= 0) {
parameters = parseDecodedParams(decodedURLStr, pathEndIdx + 1);
} else {
pathEndIdx = decodedURLStr.length();
}
String decodedBody = decodedURLStr.substring(0, pathEndIdx);
return parseURLBody(decodedURLStr, decodedBody, parameters);
}
private static Map parseDecodedParams(String str, int from) {
int len = str.length();
if (from >= len) {
return Collections.emptyMap();
}
TempBuf tempBuf = DECODE_TEMP_BUF.get();
Map params = new HashMap<>();
int nameStart = from;
int valueStart = -1;
int i;
for (i = from; i < len; i++) {
char ch = str.charAt(i);
switch (ch) {
case '=':
if (nameStart == i) {
nameStart = i + 1;
} else if (valueStart < nameStart) {
valueStart = i + 1;
}
break;
case ';':
case '&':
addParam(str, false, nameStart, valueStart, i, params, tempBuf);
nameStart = i + 1;
break;
default:
// continue
}
}
addParam(str, false, nameStart, valueStart, i, params, tempBuf);
return params;
}
/**
* @param fullURLStr : fullURLString
* @param decodedBody : format: [protocol://][username:password@][host:port]/[path]
* @param parameters :
* @return URL
*/
private static URL parseURLBody(String fullURLStr, String decodedBody, Map parameters) {
int starIdx = 0, endIdx = decodedBody.length();
// ignore the url content following '#'
int poundIndex = decodedBody.indexOf('#');
if (poundIndex != -1) {
endIdx = poundIndex;
}
String protocol = null;
int protoEndIdx = decodedBody.indexOf("://");
if (protoEndIdx >= 0) {
if (protoEndIdx == 0) {
throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\"");
}
protocol = decodedBody.substring(0, protoEndIdx);
starIdx = protoEndIdx + 3;
} else {
// case: file:/path/to/file.txt
protoEndIdx = decodedBody.indexOf(":/");
if (protoEndIdx >= 0) {
if (protoEndIdx == 0) {
throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\"");
}
protocol = decodedBody.substring(0, protoEndIdx);
starIdx = protoEndIdx + 1;
}
}
String path = null;
int pathStartIdx = indexOf(decodedBody, '/', starIdx, endIdx);
if (pathStartIdx >= 0) {
path = decodedBody.substring(pathStartIdx + 1, endIdx);
endIdx = pathStartIdx;
}
String username = null;
String password = null;
int pwdEndIdx = lastIndexOf(decodedBody, '@', starIdx, endIdx);
if (pwdEndIdx > 0) {
int passwordStartIdx = indexOf(decodedBody, ':', starIdx, pwdEndIdx);
if (passwordStartIdx != -1) {//tolerate incomplete user pwd input, like '1234@'
username = decodedBody.substring(starIdx, passwordStartIdx);
password = decodedBody.substring(passwordStartIdx + 1, pwdEndIdx);
} else {
username = decodedBody.substring(starIdx, pwdEndIdx);
}
starIdx = pwdEndIdx + 1;
}
String host = null;
int port = 0;
int hostEndIdx = lastIndexOf(decodedBody, ':', starIdx, endIdx);
if (hostEndIdx > 0 && hostEndIdx < decodedBody.length() - 1) {
if (lastIndexOf(decodedBody, '%', starIdx, endIdx) > hostEndIdx) {
// ipv6 address with scope id
// e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
// see https://howdoesinternetwork.com/2013/ipv6-zone-id
// ignore
} else {
port = Integer.parseInt(decodedBody.substring(hostEndIdx + 1, endIdx));
endIdx = hostEndIdx;
}
}
if (endIdx > starIdx) {
host = decodedBody.substring(starIdx, endIdx);
}
// check cache
protocol = URLItemCache.intern(protocol);
path = URLItemCache.checkPath(path);
return new ServiceConfigURL(protocol, username, password, host, port, path, parameters);
}
public static String[] parseRawURLToArrays(String rawURLStr, int pathEndIdx) {
String[] parts = new String[2];
int paramStartIdx = pathEndIdx + 3;//skip ENCODED_QUESTION_MARK
if (pathEndIdx == -1) {
pathEndIdx = rawURLStr.indexOf('?');
if (pathEndIdx == -1) {
// url with no params, decode anyway
rawURLStr = URL.decode(rawURLStr);
} else {
paramStartIdx = pathEndIdx + 1;
}
}
if (pathEndIdx >= 0) {
parts[0] = rawURLStr.substring(0, pathEndIdx);
parts[1] = rawURLStr.substring(paramStartIdx);
} else {
parts = new String[]{rawURLStr};
}
return parts;
}
public static Map parseParams(String rawParams, boolean encoded) {
if (encoded) {
return parseEncodedParams(rawParams, 0);
}
return parseDecodedParams(rawParams, 0);
}
/**
* @param encodedURLStr : after {@link URL#encode(String)} string
* encodedURLStr after decode format: protocol://username:password@host:port/path?k1=v1&k2=v2
* [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
*/
public static URL parseEncodedStr(String encodedURLStr) {
Map parameters = null;
int pathEndIdx = encodedURLStr.toUpperCase().indexOf("%3F");// '?'
if (pathEndIdx >= 0) {
parameters = parseEncodedParams(encodedURLStr, pathEndIdx + 3);
} else {
pathEndIdx = encodedURLStr.length();
}
//decodedBody format: [protocol://][username:password@][host:port]/[path]
String decodedBody = decodeComponent(encodedURLStr, 0, pathEndIdx, false, DECODE_TEMP_BUF.get());
return parseURLBody(encodedURLStr, decodedBody, parameters);
}
private static Map parseEncodedParams(String str, int from) {
int len = str.length();
if (from >= len) {
return Collections.emptyMap();
}
TempBuf tempBuf = DECODE_TEMP_BUF.get();
Map params = new HashMap<>();
int nameStart = from;
int valueStart = -1;
int i;
for (i = from; i < len; i++) {
char ch = str.charAt(i);
if (ch == '%') {
if (i + 3 > len) {
throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str);
}
ch = (char) decodeHexByte(str, i + 1);
i += 2;
}
switch (ch) {
case '=':
if (nameStart == i) {
nameStart = i + 1;
} else if (valueStart < nameStart) {
valueStart = i + 1;
}
break;
case ';':
case '&':
addParam(str, true, nameStart, valueStart, i - 2, params, tempBuf);
nameStart = i + 1;
break;
default:
// continue
}
}
addParam(str, true, nameStart, valueStart, i, params, tempBuf);
return params;
}
private static boolean addParam(String str, boolean isEncoded, int nameStart, int valueStart, int valueEnd, Map params,
TempBuf tempBuf) {
if (nameStart >= valueEnd) {
return false;
}
if (valueStart <= nameStart) {
valueStart = valueEnd + 1;
}
if (isEncoded) {
String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
String value;
if (valueStart >= valueEnd) {
value = name;
} else {
value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
}
URLItemCache.putParams(params,name, value);
// compatible with lower versions registering "default." keys
if (name.startsWith(DEFAULT_KEY_PREFIX)) {
params.putIfAbsent(name.substring(DEFAULT_KEY_PREFIX.length()), value);
}
} else {
String name = str.substring(nameStart, valueStart - 1);
String value;
if (valueStart >= valueEnd) {
value = name;
} else {
value = str.substring(valueStart, valueEnd);
}
URLItemCache.putParams(params, name, value);
// compatible with lower versions registering "default." keys
if (name.startsWith(DEFAULT_KEY_PREFIX)) {
params.putIfAbsent(name.substring(DEFAULT_KEY_PREFIX.length()), value);
}
}
return true;
}
private static String decodeComponent(String s, int from, int toExcluded, boolean isPath, TempBuf tempBuf) {
int len = toExcluded - from;
if (len <= 0) {
return EMPTY_STRING;
}
int firstEscaped = -1;
for (int i = from; i < toExcluded; i++) {
char c = s.charAt(i);
if (c == '%' || c == '+' && !isPath) {
firstEscaped = i;
break;
}
}
if (firstEscaped == -1) {
return s.substring(from, toExcluded);
}
// Each encoded byte takes 3 characters (e.g. "%20")
int decodedCapacity = (toExcluded - firstEscaped) / 3;
byte[] buf = tempBuf.byteBuf(decodedCapacity);
char[] charBuf = tempBuf.charBuf(len);
s.getChars(from, firstEscaped, charBuf, 0);
int charBufIdx = firstEscaped - from;
return decodeUtf8Component(s, firstEscaped, toExcluded, isPath, buf, charBuf, charBufIdx);
}
private static String decodeUtf8Component(String str, int firstEscaped, int toExcluded, boolean isPath, byte[] buf,
char[] charBuf, int charBufIdx) {
int bufIdx;
for (int i = firstEscaped; i < toExcluded; i++) {
char c = str.charAt(i);
if (c != '%') {
charBuf[charBufIdx++] = c != '+' || isPath ? c : SPACE;
continue;
}
bufIdx = 0;
do {
if (i + 3 > toExcluded) {
throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str);
}
buf[bufIdx++] = decodeHexByte(str, i + 1);
i += 3;
} while (i < toExcluded && str.charAt(i) == '%');
i--;
charBufIdx += decodeUtf8(buf, 0, bufIdx, charBuf, charBufIdx);
}
return new String(charBuf, 0, charBufIdx);
}
private static int indexOf(String str, char ch, int from, int toExclude) {
from = Math.max(from, 0);
toExclude = Math.min(toExclude, str.length());
if (from > toExclude) {
return -1;
}
for (int i = from; i < toExclude; i++) {
if (str.charAt(i) == ch) {
return i;
}
}
return -1;
}
private static int lastIndexOf(String str, char ch, int from, int toExclude) {
from = Math.max(from, 0);
toExclude = Math.min(toExclude, str.length() - 1);
if (from > toExclude) {
return -1;
}
for (int i = toExclude; i >= from; i--) {
if (str.charAt(i) == ch) {
return i;
}
}
return -1;
}
private static final class TempBuf {
private final char[] chars;
private final byte[] bytes;
TempBuf(int bufSize) {
this.chars = new char[bufSize];
this.bytes = new byte[bufSize];
}
public char[] charBuf(int size) {
char[] chars = this.chars;
if (size <= chars.length) {
return chars;
}
return new char[size];
}
public byte[] byteBuf(int size) {
byte[] bytes = this.bytes;
if (size <= bytes.length) {
return bytes;
}
return new byte[size];
}
}
}