mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-20 16:29:43 +01:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b960dd02a | ||
|
|
6b7d5fd58c | ||
|
|
ebcdeb7d80 | ||
|
|
67870df19e | ||
|
|
afd19dbefd | ||
|
|
a55097289b | ||
|
|
8de39b80cd | ||
|
|
0f038754e5 | ||
|
|
4aa278e4a0 | ||
|
|
e3562ecc99 | ||
|
|
b62d47da9c | ||
|
|
e748f39e20 | ||
|
|
428ade4fd8 | ||
|
|
e28ff71e97 | ||
|
|
f826eb992e | ||
|
|
ba3f14c83a | ||
|
|
82a3601748 | ||
|
|
eeab5252e6 | ||
|
|
838bbedd1a | ||
|
|
0e4ad056dd | ||
|
|
89163e73d0 | ||
|
|
665ebc5d47 | ||
|
|
32b1c35305 | ||
|
|
55c7be5fe9 | ||
|
|
5017e2e385 | ||
|
|
492c217125 | ||
|
|
269c9580fb | ||
|
|
5f691bb788 | ||
|
|
bcdd2242ef | ||
|
|
faee138574 | ||
|
|
fb69e212ba | ||
|
|
d8cc648a18 | ||
|
|
15d2b6217d | ||
|
|
b92f4a73b4 | ||
|
|
5f0e3bcd37 | ||
|
|
e6f336ae8b | ||
|
|
7c0f78edf1 | ||
|
|
a2ba1bd211 | ||
|
|
c95adeb8f2 | ||
|
|
c30306f779 |
14
.idea/vcs.xml
generated
14
.idea/vcs.xml
generated
@@ -1,5 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="IssueNavigationConfiguration">
|
||||||
|
<option name="links">
|
||||||
|
<list>
|
||||||
|
<IssueNavigationLink>
|
||||||
|
<option name="issueRegexp" value="[A-Z]+\-\d+" />
|
||||||
|
<option name="linkRegexp" value="http://youtrack.jetbrains.com/issue/$0" />
|
||||||
|
</IssueNavigationLink>
|
||||||
|
<IssueNavigationLink>
|
||||||
|
<option name="issueRegexp" value="(\d+)\:" />
|
||||||
|
<option name="linkRegexp" value="https://bugs.openjdk.java.net/browse/JDK-$1" />
|
||||||
|
</IssueNavigationLink>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
76
jb/project/tools/windows/scripts/mkimages_x64_fd.sh
Executable file
76
jb/project/tools/windows/scripts/mkimages_x64_fd.sh
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash -x
|
||||||
|
|
||||||
|
# The following parameters must be specified:
|
||||||
|
# JBSDK_VERSION - specifies the current version of OpenJDK e.g. 11_0_6
|
||||||
|
# JDK_BUILD_NUMBER - specifies the number of OpenJDK build or the value of --with-version-build argument to configure
|
||||||
|
# build_number - specifies the number of JetBrainsRuntime build
|
||||||
|
# bundle_type - specifies bundle to bu built; possible values:
|
||||||
|
# jcef - the bundles 1) jbr with jcef+javafx, 2) jbrsdk and 3) test will be created
|
||||||
|
# jfx - the bundle 1) jbr with javafx only will be created
|
||||||
|
#
|
||||||
|
# jbrsdk-${JBSDK_VERSION}-osx-x64-b${build_number}.tar.gz
|
||||||
|
# jbr-${JBSDK_VERSION}-osx-x64-b${build_number}.tar.gz
|
||||||
|
#
|
||||||
|
# $ ./java --version
|
||||||
|
# openjdk 11.0.6 2020-01-14
|
||||||
|
# OpenJDK Runtime Environment (build 11.0.6+${JDK_BUILD_NUMBER}-b${build_number})
|
||||||
|
# OpenJDK 64-Bit Server VM (build 11.0.6+${JDK_BUILD_NUMBER}-b${build_number}, mixed mode)
|
||||||
|
#
|
||||||
|
# Environment variables:
|
||||||
|
# MODULAR_SDK_PATH - specifies the path to the directory where imported modules are located.
|
||||||
|
# By default imported modules should be located in ./modular-sdk
|
||||||
|
# JCEF_PATH - specifies the path to the directory where JCEF binaries are located.
|
||||||
|
# By default imported modules should be located in ./jcef_win_x64
|
||||||
|
|
||||||
|
JBSDK_VERSION=$1
|
||||||
|
JDK_BUILD_NUMBER=$2
|
||||||
|
build_number=$3
|
||||||
|
|
||||||
|
JBSDK_VERSION_WITH_DOTS=$(echo $JBSDK_VERSION | sed 's/_/\./g')
|
||||||
|
WITH_IMPORT_MODULES="--with-import-modules=${MODULAR_SDK_PATH:=${WORK_DIR}/modular-sdk}"
|
||||||
|
JCEF_PATH=${JCEF_PATH:=./jcef_win_x64}
|
||||||
|
|
||||||
|
source jb/project/tools/common.sh
|
||||||
|
|
||||||
|
JBRSDK_BASE_NAME=jbrsdk-${JBSDK_VERSION}
|
||||||
|
WORK_DIR=$(pwd)
|
||||||
|
WITH_DEBUG_LEVEL="--with-debug-level=fastdebug"
|
||||||
|
RELEASE_NAME=windows-x86_64-normal-server-fastdebug
|
||||||
|
PATH="/usr/local/bin:/usr/bin:${PATH}"
|
||||||
|
./configure \
|
||||||
|
--disable-warnings-as-errors \
|
||||||
|
$WITH_DEBUG_LEVEL \
|
||||||
|
--with-target-bits=64 \
|
||||||
|
--with-vendor-name="${VENDOR_NAME}" \
|
||||||
|
--with-vendor-version-string="${VENDOR_VERSION_STRING}" \
|
||||||
|
--with-version-pre= \
|
||||||
|
--with-version-build=${JDK_BUILD_NUMBER} \
|
||||||
|
--with-version-opt=b${build_number} \
|
||||||
|
$WITH_IMPORT_MODULES \
|
||||||
|
--with-toolchain-version=2015 \
|
||||||
|
--with-boot-jdk=${BOOT_JDK} \
|
||||||
|
--disable-ccache \
|
||||||
|
--enable-cds=yes || exit 1
|
||||||
|
|
||||||
|
make LOG=info clean images CONF=$RELEASE_NAME || exit 1
|
||||||
|
|
||||||
|
JSDK=build/$RELEASE_NAME/images/jdk
|
||||||
|
JBSDK=${JBRSDK_BASE_NAME}-windows-x64-b${build_number}
|
||||||
|
BASE_DIR=build/$RELEASE_NAME/images
|
||||||
|
JBRSDK_BUNDLE=jbrsdk
|
||||||
|
|
||||||
|
rm -rf ${BASE_DIR}/${JBRSDK_BUNDLE} && rsync -a --exclude demo --exclude sample ${JSDK}/ ${JBRSDK_BUNDLE} || exit 1
|
||||||
|
cp -R ${JCEF_PATH}/* ${JBRSDK_BUNDLE}/bin
|
||||||
|
sed 's/JBR/JBRSDK/g' ${JSDK}/release > release
|
||||||
|
mv release ${JBRSDK_BUNDLE}/release
|
||||||
|
|
||||||
|
JBR_BUNDLE=jbr
|
||||||
|
JBR_BASE_NAME=jbr-$JBSDK_VERSION
|
||||||
|
rm -rf ${JBR_BUNDLE}
|
||||||
|
|
||||||
|
${JSDK}/bin/jlink \
|
||||||
|
--module-path ${JSDK}/jmods --no-man-pages --compress=2 \
|
||||||
|
--add-modules $(xargs < modules.list | sed s/" "//g) --output ${JBR_BUNDLE} || exit $?
|
||||||
|
cp -R ${JCEF_PATH}/* ${JBR_BUNDLE}/bin
|
||||||
|
echo Modifying release info ...
|
||||||
|
cat ${JSDK}/release | tr -d '\r' | grep -v 'JAVA_VERSION' | grep -v 'MODULES' >> ${JBR_BUNDLE}/release
|
||||||
44
jb/project/tools/windows/scripts/pack_x64_fd.sh
Executable file
44
jb/project/tools/windows/scripts/pack_x64_fd.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash -x
|
||||||
|
|
||||||
|
# The following parameters must be specified:
|
||||||
|
# JBSDK_VERSION - specifies the current version of OpenJDK e.g. 11_0_6
|
||||||
|
# JDK_BUILD_NUMBER - specifies the number of OpenJDK build or the value of --with-version-build argument to configure
|
||||||
|
# build_number - specifies the number of JetBrainsRuntime build
|
||||||
|
# bundle_type - specifies bundle to bu built; possible values:
|
||||||
|
# jcef - the bundles 1) jbr with jcef+javafx, 2) jbrsdk and 3) test will be created
|
||||||
|
# jfx - the bundle 1) jbr with javafx only will be created
|
||||||
|
#
|
||||||
|
# jbrsdk-${JBSDK_VERSION}-osx-x64-b${build_number}.tar.gz
|
||||||
|
# jbr-${JBSDK_VERSION}-osx-x64-b${build_number}.tar.gz
|
||||||
|
#
|
||||||
|
# $ ./java --version
|
||||||
|
# openjdk 11.0.6 2020-01-14
|
||||||
|
# OpenJDK Runtime Environment (build 11.0.6+${JDK_BUILD_NUMBER}-b${build_number})
|
||||||
|
# OpenJDK 64-Bit Server VM (build 11.0.6+${JDK_BUILD_NUMBER}-b${build_number}, mixed mode)
|
||||||
|
#
|
||||||
|
|
||||||
|
JBSDK_VERSION=$1
|
||||||
|
JDK_BUILD_NUMBER=$2
|
||||||
|
build_number=$3
|
||||||
|
|
||||||
|
RELEASE_NAME=windows-x86_64-normal-server-fastdebug
|
||||||
|
|
||||||
|
JBRSDK_BASE_NAME=jbrsdk-$JBSDK_VERSION
|
||||||
|
JBR_BASE_NAME=jbr-$JBSDK_VERSION
|
||||||
|
|
||||||
|
IMAGES_DIR=build/$RELEASE_NAME/images
|
||||||
|
JSDK=$IMAGES_DIR/jdk
|
||||||
|
JBSDK=$JBRSDK_BASE_NAME-windows-x64-fastdebug-b$build_number
|
||||||
|
BASE_DIR=.
|
||||||
|
|
||||||
|
JBRSDK_BUNDLE=jbrsdk
|
||||||
|
echo Creating $JBSDK.tar.gz ...
|
||||||
|
/usr/bin/tar -czf $JBSDK.tar.gz $JBRSDK_BUNDLE || exit 1
|
||||||
|
|
||||||
|
JBR_BUNDLE=jbr
|
||||||
|
JBR_BASE_NAME=jbr-${JBSDK_VERSION}
|
||||||
|
JBR=$JBR_BASE_NAME-windows-x64-fastdebug-b$build_number
|
||||||
|
echo Creating $JBR.tar.gz ...
|
||||||
|
cp -R ${BASE_DIR}/${JBR_BUNDLE} ${BASE_DIR}/jbr
|
||||||
|
|
||||||
|
/usr/bin/tar -czf $JBR.tar.gz -C $BASE_DIR jbr || exit 1
|
||||||
@@ -1531,12 +1531,87 @@ public final class Files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells whether or not a file is considered <em>hidden</em>. The exact
|
* Finds and returns the position of the first mismatched byte in the content
|
||||||
* definition of hidden is platform or provider dependent. On UNIX for
|
* of two files, or {@code -1L} if there is no mismatch. The position will be
|
||||||
* example a file is considered to be hidden if its name begins with a
|
* in the inclusive range of {@code 0L} up to the size (in bytes) of the
|
||||||
* period character ('.'). On Windows a file is considered hidden if it
|
* smaller file.
|
||||||
* isn't a directory and the DOS {@link DosFileAttributes#isHidden hidden}
|
*
|
||||||
* attribute is set.
|
* <p> Two files are considered to match if they satisfy one of the following
|
||||||
|
* conditions:
|
||||||
|
* <ul>
|
||||||
|
* <li> The two paths locate the {@linkplain #isSameFile(Path, Path) same file},
|
||||||
|
* even if two {@linkplain Path#equals(Object) equal} paths locate a file
|
||||||
|
* does not exist, or </li>
|
||||||
|
* <li> The two files are the same size, and every byte in the first file
|
||||||
|
* is identical to the corresponding byte in the second file. </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p> Otherwise there is a mismatch between the two files and the value
|
||||||
|
* returned by this method is:
|
||||||
|
* <ul>
|
||||||
|
* <li> The position of the first mismatched byte, or </li>
|
||||||
|
* <li> The size of the smaller file (in bytes) when the files are different
|
||||||
|
* sizes and every byte of the smaller file is identical to the
|
||||||
|
* corresponding byte of the larger file. </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p> This method may not be atomic with respect to other file system
|
||||||
|
* operations. This method is always <i>reflexive</i> (for {@code Path f},
|
||||||
|
* {@code mismatch(f,f)} returns {@code -1L}). If the file system and files
|
||||||
|
* remain static, then this method is <i>symmetric</i> (for two {@code Paths f}
|
||||||
|
* and {@code g}, {@code mismatch(f,g)} will return the same value as
|
||||||
|
* {@code mismatch(g,f)}).
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* the path to the first file
|
||||||
|
* @param path2
|
||||||
|
* the path to the second file
|
||||||
|
*
|
||||||
|
* @return the position of the first mismatch or {@code -1L} if no mismatch
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* if an I/O error occurs
|
||||||
|
* @throws SecurityException
|
||||||
|
* In the case of the default provider, and a security manager is
|
||||||
|
* installed, the {@link SecurityManager#checkRead(String) checkRead}
|
||||||
|
* method is invoked to check read access to both files.
|
||||||
|
*
|
||||||
|
* @since 12
|
||||||
|
*/
|
||||||
|
public static long mismatch(Path path, Path path2) throws IOException {
|
||||||
|
if (isSameFile(path, path2)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte[] buffer1 = new byte[BUFFER_SIZE];
|
||||||
|
byte[] buffer2 = new byte[BUFFER_SIZE];
|
||||||
|
try (InputStream in1 = Files.newInputStream(path);
|
||||||
|
InputStream in2 = Files.newInputStream(path2);) {
|
||||||
|
long totalRead = 0;
|
||||||
|
while (true) {
|
||||||
|
int nRead1 = in1.readNBytes(buffer1, 0, BUFFER_SIZE);
|
||||||
|
int nRead2 = in2.readNBytes(buffer2, 0, BUFFER_SIZE);
|
||||||
|
|
||||||
|
int i = Arrays.mismatch(buffer1, 0, nRead1, buffer2, 0, nRead2);
|
||||||
|
if (i > -1) {
|
||||||
|
return totalRead + i;
|
||||||
|
}
|
||||||
|
if (nRead1 < BUFFER_SIZE) {
|
||||||
|
// we've reached the end of the files, but found no mismatch
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
totalRead += nRead1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether or not a file is considered <em>hidden</em>.
|
||||||
|
*
|
||||||
|
* @apiNote
|
||||||
|
* The exact definition of hidden is platform or provider dependent. On UNIX
|
||||||
|
* for example a file is considered to be hidden if its name begins with a
|
||||||
|
* period character ('.'). On Windows a file is considered hidden if the DOS
|
||||||
|
* {@link DosFileAttributes#isHidden hidden} attribute is set.
|
||||||
*
|
*
|
||||||
* <p> Depending on the implementation this method may require to access
|
* <p> Depending on the implementation this method may require to access
|
||||||
* the file system to determine if the file is considered hidden.
|
* the file system to determine if the file is considered hidden.
|
||||||
|
|||||||
@@ -29,11 +29,17 @@ import java.net.URL;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.PasswordAuthentication;
|
import java.net.PasswordAuthentication;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import sun.net.www.HeaderParser;
|
import sun.net.www.HeaderParser;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BasicAuthentication: Encapsulate an http server authentication using
|
* BasicAuthentication: Encapsulate an http server authentication using
|
||||||
@@ -49,37 +55,18 @@ class BasicAuthentication extends AuthenticationInfo {
|
|||||||
|
|
||||||
/** The authentication string for this host, port, and realm. This is
|
/** The authentication string for this host, port, and realm. This is
|
||||||
a simple BASE64 encoding of "login:password". */
|
a simple BASE64 encoding of "login:password". */
|
||||||
String auth;
|
final String auth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a BasicAuthentication
|
* Create a BasicAuthentication
|
||||||
*/
|
*/
|
||||||
public BasicAuthentication(boolean isProxy, String host, int port,
|
public BasicAuthentication(boolean isProxy, String host, int port,
|
||||||
String realm, PasswordAuthentication pw,
|
String realm, PasswordAuthentication pw,
|
||||||
String authenticatorKey) {
|
boolean isUTF8, String authenticatorKey) {
|
||||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||||
AuthScheme.BASIC, host, port, realm,
|
AuthScheme.BASIC, host, port, realm,
|
||||||
Objects.requireNonNull(authenticatorKey));
|
Objects.requireNonNull(authenticatorKey));
|
||||||
String plain = pw.getUserName() + ":";
|
this.auth = authValueFrom(pw, isUTF8);
|
||||||
byte[] nameBytes = null;
|
|
||||||
try {
|
|
||||||
nameBytes = plain.getBytes("ISO-8859-1");
|
|
||||||
} catch (java.io.UnsupportedEncodingException uee) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get password bytes
|
|
||||||
char[] passwd = pw.getPassword();
|
|
||||||
byte[] passwdBytes = new byte[passwd.length];
|
|
||||||
for (int i=0; i<passwd.length; i++)
|
|
||||||
passwdBytes[i] = (byte)passwd[i];
|
|
||||||
|
|
||||||
// concatenate user name and password bytes and encode them
|
|
||||||
byte[] concat = new byte[nameBytes.length + passwdBytes.length];
|
|
||||||
System.arraycopy(nameBytes, 0, concat, 0, nameBytes.length);
|
|
||||||
System.arraycopy(passwdBytes, 0, concat, nameBytes.length,
|
|
||||||
passwdBytes.length);
|
|
||||||
this.auth = "Basic " + Base64.getEncoder().encodeToString(concat);
|
|
||||||
this.pw = pw;
|
this.pw = pw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,34 +86,30 @@ class BasicAuthentication extends AuthenticationInfo {
|
|||||||
* Create a BasicAuthentication
|
* Create a BasicAuthentication
|
||||||
*/
|
*/
|
||||||
public BasicAuthentication(boolean isProxy, URL url, String realm,
|
public BasicAuthentication(boolean isProxy, URL url, String realm,
|
||||||
PasswordAuthentication pw,
|
PasswordAuthentication pw, boolean isUTF8,
|
||||||
String authenticatorKey) {
|
String authenticatorKey) {
|
||||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||||
AuthScheme.BASIC, url, realm,
|
AuthScheme.BASIC, url, realm,
|
||||||
Objects.requireNonNull(authenticatorKey));
|
Objects.requireNonNull(authenticatorKey));
|
||||||
String plain = pw.getUserName() + ":";
|
this.auth = authValueFrom(pw, isUTF8);
|
||||||
byte[] nameBytes = null;
|
|
||||||
try {
|
|
||||||
nameBytes = plain.getBytes("ISO-8859-1");
|
|
||||||
} catch (java.io.UnsupportedEncodingException uee) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get password bytes
|
|
||||||
char[] passwd = pw.getPassword();
|
|
||||||
byte[] passwdBytes = new byte[passwd.length];
|
|
||||||
for (int i=0; i<passwd.length; i++)
|
|
||||||
passwdBytes[i] = (byte)passwd[i];
|
|
||||||
|
|
||||||
// concatenate user name and password bytes and encode them
|
|
||||||
byte[] concat = new byte[nameBytes.length + passwdBytes.length];
|
|
||||||
System.arraycopy(nameBytes, 0, concat, 0, nameBytes.length);
|
|
||||||
System.arraycopy(passwdBytes, 0, concat, nameBytes.length,
|
|
||||||
passwdBytes.length);
|
|
||||||
this.auth = "Basic " + Base64.getEncoder().encodeToString(concat);
|
|
||||||
this.pw = pw;
|
this.pw = pw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String authValueFrom(PasswordAuthentication pw, boolean isUTF8) {
|
||||||
|
String plain = pw.getUserName() + ":";
|
||||||
|
char[] password = pw.getPassword();
|
||||||
|
CharBuffer cbuf = CharBuffer.allocate(plain.length() + password.length);
|
||||||
|
cbuf.put(plain).put(password).flip();
|
||||||
|
Charset charset = isUTF8 ? UTF_8 : ISO_8859_1;
|
||||||
|
ByteBuffer buf = charset.encode(cbuf);
|
||||||
|
ByteBuffer enc = Base64.getEncoder().encode(buf);
|
||||||
|
String ret = "Basic " + new String(enc.array(), enc.position(), enc.remaining(), ISO_8859_1);
|
||||||
|
Arrays.fill(buf.array(), (byte) 0);
|
||||||
|
Arrays.fill(enc.array(), (byte) 0);
|
||||||
|
Arrays.fill(cbuf.array(), (char) 0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a BasicAuthentication
|
* Create a BasicAuthentication
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2274,6 +2274,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
|||||||
if (host != null && authhdr.isPresent()) {
|
if (host != null && authhdr.isPresent()) {
|
||||||
HeaderParser p = authhdr.headerParser();
|
HeaderParser p = authhdr.headerParser();
|
||||||
String realm = p.findValue("realm");
|
String realm = p.findValue("realm");
|
||||||
|
String charset = p.findValue("charset");
|
||||||
|
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||||
String scheme = authhdr.scheme();
|
String scheme = authhdr.scheme();
|
||||||
AuthScheme authScheme = UNKNOWN;
|
AuthScheme authScheme = UNKNOWN;
|
||||||
if ("basic".equalsIgnoreCase(scheme)) {
|
if ("basic".equalsIgnoreCase(scheme)) {
|
||||||
@@ -2319,7 +2321,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
|||||||
realm, scheme, url, RequestorType.PROXY);
|
realm, scheme, url, RequestorType.PROXY);
|
||||||
if (a != null) {
|
if (a != null) {
|
||||||
ret = new BasicAuthentication(true, host, port, realm, a,
|
ret = new BasicAuthentication(true, host, port, realm, a,
|
||||||
getAuthenticatorKey());
|
isUTF8, getAuthenticatorKey());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DIGEST:
|
case DIGEST:
|
||||||
@@ -2437,6 +2439,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
|||||||
HeaderParser p = authhdr.headerParser();
|
HeaderParser p = authhdr.headerParser();
|
||||||
String realm = p.findValue("realm");
|
String realm = p.findValue("realm");
|
||||||
String scheme = authhdr.scheme();
|
String scheme = authhdr.scheme();
|
||||||
|
String charset = p.findValue("charset");
|
||||||
|
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||||
AuthScheme authScheme = UNKNOWN;
|
AuthScheme authScheme = UNKNOWN;
|
||||||
if ("basic".equalsIgnoreCase(scheme)) {
|
if ("basic".equalsIgnoreCase(scheme)) {
|
||||||
authScheme = BASIC;
|
authScheme = BASIC;
|
||||||
@@ -2488,7 +2492,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
|||||||
realm, scheme, url, RequestorType.SERVER);
|
realm, scheme, url, RequestorType.SERVER);
|
||||||
if (a != null) {
|
if (a != null) {
|
||||||
ret = new BasicAuthentication(false, url, realm, a,
|
ret = new BasicAuthentication(false, url, realm, a,
|
||||||
getAuthenticatorKey());
|
isUTF8, getAuthenticatorKey());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DIGEST:
|
case DIGEST:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2002, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -29,7 +29,7 @@ import java.io.*;
|
|||||||
import java.nio.*;
|
import java.nio.*;
|
||||||
import java.nio.channels.*;
|
import java.nio.channels.*;
|
||||||
import java.nio.channels.spi.*;
|
import java.nio.channels.spi.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is defined here rather than in java.nio.channels.Channels
|
* This class is defined here rather than in java.nio.channels.Channels
|
||||||
@@ -87,10 +87,8 @@ public class ChannelInputStream
|
|||||||
public synchronized int read(byte[] bs, int off, int len)
|
public synchronized int read(byte[] bs, int off, int len)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
if ((off < 0) || (off > bs.length) || (len < 0) ||
|
Objects.checkFromIndexSize(off, len, bs.length);
|
||||||
((off + len) > bs.length) || ((off + len) < 0)) {
|
if (len == 0)
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
} else if (len == 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ByteBuffer bb = ((this.bs == bs)
|
ByteBuffer bb = ((this.bs == bs)
|
||||||
@@ -119,6 +117,26 @@ public class ChannelInputStream
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized long skip(long n) throws IOException {
|
||||||
|
// special case where the channel is to a file
|
||||||
|
if (ch instanceof SeekableByteChannel && n > 0) {
|
||||||
|
SeekableByteChannel sbc = (SeekableByteChannel)ch;
|
||||||
|
try {
|
||||||
|
long pos = sbc.position();
|
||||||
|
long size = sbc.size();
|
||||||
|
if (pos >= size) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
n = Math.min(n, size - pos);
|
||||||
|
sbc.position(pos + n);
|
||||||
|
return sbc.position() - pos;
|
||||||
|
} catch (ClosedChannelException cce) {
|
||||||
|
throw new IOException(cce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.skip(n);
|
||||||
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
ch.close();
|
ch.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -75,6 +75,7 @@ class WindowsConstants {
|
|||||||
public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C;
|
public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C;
|
||||||
public static final int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
|
public static final int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
|
||||||
public static final int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1;
|
public static final int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1;
|
||||||
|
public static final int SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2;
|
||||||
|
|
||||||
// volume flags
|
// volume flags
|
||||||
public static final int FILE_CASE_SENSITIVE_SEARCH = 0x00000001;
|
public static final int FILE_CASE_SENSITIVE_SEARCH = 0x00000001;
|
||||||
@@ -104,6 +105,7 @@ class WindowsConstants {
|
|||||||
public static final int ERROR_MORE_DATA = 234;
|
public static final int ERROR_MORE_DATA = 234;
|
||||||
public static final int ERROR_DIRECTORY = 267;
|
public static final int ERROR_DIRECTORY = 267;
|
||||||
public static final int ERROR_NOTIFY_ENUM_DIR = 1022;
|
public static final int ERROR_NOTIFY_ENUM_DIR = 1022;
|
||||||
|
public static final int ERROR_PRIVILEGE_NOT_HELD = 1314;
|
||||||
public static final int ERROR_NONE_MAPPED = 1332;
|
public static final int ERROR_NONE_MAPPED = 1332;
|
||||||
public static final int ERROR_NOT_A_REPARSE_POINT = 4390;
|
public static final int ERROR_NOT_A_REPARSE_POINT = 4390;
|
||||||
public static final int ERROR_INVALID_REPARSE_DATA = 4392;
|
public static final int ERROR_INVALID_REPARSE_DATA = 4392;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -470,9 +470,6 @@ class WindowsFileSystemProvider
|
|||||||
} catch (WindowsException x) {
|
} catch (WindowsException x) {
|
||||||
x.rethrowAsIOException(file);
|
x.rethrowAsIOException(file);
|
||||||
}
|
}
|
||||||
// DOS hidden attribute not meaningful when set on directories
|
|
||||||
if (attrs.isDirectory())
|
|
||||||
return false;
|
|
||||||
return attrs.isHidden();
|
return attrs.isHidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -29,6 +29,8 @@ import java.security.AccessController;
|
|||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
|
|
||||||
|
import static sun.nio.fs.WindowsConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Win32 and library calls.
|
* Win32 and library calls.
|
||||||
*/
|
*/
|
||||||
@@ -920,6 +922,12 @@ class WindowsNativeDispatcher {
|
|||||||
* LPCWSTR lpTargetFileName,
|
* LPCWSTR lpTargetFileName,
|
||||||
* DWORD dwFlags
|
* DWORD dwFlags
|
||||||
* )
|
* )
|
||||||
|
*
|
||||||
|
* Creates a symbolic link, conditionally retrying with the addition of
|
||||||
|
* the flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE if the initial
|
||||||
|
* attempt fails with ERROR_PRIVILEGE_NOT_HELD. If the retry fails, throw
|
||||||
|
* the original exception due to ERROR_PRIVILEGE_NOT_HELD. The retry will
|
||||||
|
* succeed only on Windows build 14972 or later if Developer Mode is on.
|
||||||
*/
|
*/
|
||||||
static void CreateSymbolicLink(String link, String target, int flags)
|
static void CreateSymbolicLink(String link, String target, int flags)
|
||||||
throws WindowsException
|
throws WindowsException
|
||||||
@@ -929,6 +937,19 @@ class WindowsNativeDispatcher {
|
|||||||
try {
|
try {
|
||||||
CreateSymbolicLink0(linkBuffer.address(), targetBuffer.address(),
|
CreateSymbolicLink0(linkBuffer.address(), targetBuffer.address(),
|
||||||
flags);
|
flags);
|
||||||
|
} catch (WindowsException x) {
|
||||||
|
if (x.lastError() == ERROR_PRIVILEGE_NOT_HELD) {
|
||||||
|
flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||||
|
try {
|
||||||
|
CreateSymbolicLink0(linkBuffer.address(),
|
||||||
|
targetBuffer.address(), flags);
|
||||||
|
return;
|
||||||
|
} catch (WindowsException ignored) {
|
||||||
|
// Will fail with ERROR_INVALID_PARAMETER for Windows
|
||||||
|
// builds older than 14972.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw x;
|
||||||
} finally {
|
} finally {
|
||||||
targetBuffer.release();
|
targetBuffer.release();
|
||||||
linkBuffer.release();
|
linkBuffer.release();
|
||||||
|
|||||||
@@ -1056,17 +1056,7 @@ Java_sun_nio_fs_WindowsNativeDispatcher_CreateSymbolicLink0(JNIEnv* env,
|
|||||||
LPCWSTR link = jlong_to_ptr(linkAddress);
|
LPCWSTR link = jlong_to_ptr(linkAddress);
|
||||||
LPCWSTR target = jlong_to_ptr(targetAddress);
|
LPCWSTR target = jlong_to_ptr(targetAddress);
|
||||||
|
|
||||||
// Allow creation of symbolic links when the process is not elevated.
|
if (CreateSymbolicLinkW(link, target, (DWORD)flags) == 0)
|
||||||
// Developer Mode must be enabled for this option to function, otherwise
|
|
||||||
// it will be ignored. Check that symbol is available in current build SDK.
|
|
||||||
DWORD dwFlags = (DWORD)flags;
|
|
||||||
#ifdef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
|
||||||
dwFlags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// On Windows 64-bit this appears to succeed even when there are
|
|
||||||
// insufficient privileges
|
|
||||||
if (CreateSymbolicLinkW(link, target, dwFlags) == 0)
|
|
||||||
throwWindowsException(env, GetLastError());
|
throwWindowsException(env, GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ import java.awt.Robot;
|
|||||||
import java.awt.peer.RobotPeer;
|
import java.awt.peer.RobotPeer;
|
||||||
|
|
||||||
import sun.awt.CGraphicsDevice;
|
import sun.awt.CGraphicsDevice;
|
||||||
|
import sun.security.action.GetIntegerAction;
|
||||||
|
|
||||||
final class CRobot implements RobotPeer {
|
final class CRobot implements RobotPeer {
|
||||||
|
|
||||||
private static final int MOUSE_LOCATION_UNKNOWN = -1;
|
private static final int MOUSE_LOCATION_UNKNOWN = -1;
|
||||||
|
private static final int DEFAULT_SAFE_DELAY_MILLIS = 50;
|
||||||
|
|
||||||
private final CGraphicsDevice fDevice;
|
private final CGraphicsDevice fDevice;
|
||||||
private int mouseLastX = MOUSE_LOCATION_UNKNOWN;
|
private int mouseLastX = MOUSE_LOCATION_UNKNOWN;
|
||||||
@@ -51,7 +53,12 @@ final class CRobot implements RobotPeer {
|
|||||||
*/
|
*/
|
||||||
public CRobot(Robot r, CGraphicsDevice d) {
|
public CRobot(Robot r, CGraphicsDevice d) {
|
||||||
fDevice = d;
|
fDevice = d;
|
||||||
initRobot();
|
int safeDelayMillis = GetIntegerAction.privilegedGetProperty(
|
||||||
|
"sun.awt.osx.RobotSafeDelayMillis", DEFAULT_SAFE_DELAY_MILLIS);
|
||||||
|
if (safeDelayMillis < 0) {
|
||||||
|
safeDelayMillis = DEFAULT_SAFE_DELAY_MILLIS;
|
||||||
|
}
|
||||||
|
initRobot(safeDelayMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -193,7 +200,7 @@ final class CRobot implements RobotPeer {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
private native void initRobot();
|
private native void initRobot(int safeDelayMillis);
|
||||||
private native void mouseEvent(int lastX, int lastY, int buttonsState,
|
private native void mouseEvent(int lastX, int lastY, int buttonsState,
|
||||||
boolean isButtonsDownState,
|
boolean isButtonsDownState,
|
||||||
boolean isMouseMove);
|
boolean isMouseMove);
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
|
|
||||||
#import "java_awt_event_InputEvent.h"
|
#import "java_awt_event_InputEvent.h"
|
||||||
#import "java_awt_event_KeyEvent.h"
|
#import "java_awt_event_KeyEvent.h"
|
||||||
#import "sun_awt_event_KeyEvent.h"
|
|
||||||
#import "LWCToolkit.h"
|
#import "LWCToolkit.h"
|
||||||
|
|
||||||
#import "jni_util.h"
|
#import "jni_util.h"
|
||||||
@@ -446,53 +445,13 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary* getDiacriticUnicharToVkCodeDictionary() {
|
static const int UNICODE_OFFSET = 0x01000000;
|
||||||
|
|
||||||
static NSDictionary* diacriticUnicharToVkCodeDictionary = nil;
|
static BOOL isLatinUnicode(unichar ch) {
|
||||||
static dispatch_once_t onceToken;
|
// Latin-1 Supplement 0x0080 - 0x00FF
|
||||||
|
// Latin Extended-A 0x0100 - 0x017F
|
||||||
dispatch_once(&onceToken, ^{
|
// Latin Extended-B 0x0180 - 0x024F
|
||||||
diacriticUnicharToVkCodeDictionary =
|
return 0x0080 <= ch && ch <= 0x024F;
|
||||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_GRAVE], @"à",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_ACUTE], @"á",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_CIRCUMFLEX], @"â",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_TILDE], @"ã",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_DIAERESIS], @"ä",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_A_WITH_RING_ABOVE], @"å",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_AE], @"æ",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_C_WITH_CEDILLA], @"ç",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_E_WITH_GRAVE], @"è",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_E_WITH_ACUTE], @"é",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_E_WITH_CIRCUMFLEX], @"ê",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_E_WITH_DIAERESIS], @"ë",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_I_WITH_GRAVE], @"ì",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_I_WITH_ACUTE], @"í",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_I_WITH_CIRCUMFLEX], @"î",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_I_WITH_DIAERESIS], @"ï",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_ETH], @"ð",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_N_WITH_TILDE], @"ñ",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_GRAVE], @"ò",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_ACUTE], @"ó",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_CIRCUMFLEX], @"ô",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_TILDE], @"õ",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_DIAERESIS], @"ö",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_DIVISION_SIGN], @"÷",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_O_WITH_SLASH], @"ø",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_U_WITH_GRAVE], @"ù",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_U_WITH_ACUTE], @"ú",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_U_WITH_CIRCUMFLEX], @"û",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_U_WITH_DIAERESIS], @"ü",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_Y_WITH_ACUTE], @"ý",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_THORN], @"þ",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_Y_WITH_DIAERESIS], @"ÿ",
|
|
||||||
[NSNumber numberWithInt:sun_awt_event_KeyEvent_VK_ESZETT], @"ß",
|
|
||||||
nil
|
|
||||||
];
|
|
||||||
// This is ok to retain a singleton object
|
|
||||||
[diacriticUnicharToVkCodeDictionary retain];
|
|
||||||
});
|
|
||||||
return diacriticUnicharToVkCodeDictionary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary* getUnicharToVkCodeDictionary() {
|
static NSDictionary* getUnicharToVkCodeDictionary() {
|
||||||
@@ -525,10 +484,19 @@ static NSDictionary* getUnicharToVkCodeDictionary() {
|
|||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_SLASH], @"/",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_SLASH], @"/",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PERIOD], @".",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PERIOD], @".",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_MULTIPLY], @"*",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_ASTERISK], @"*",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_ADD], @"+",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PLUS], @"+",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
|
||||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_NUMBER_SIGN], @"#",
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_NUMBER_SIGN], @"#",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_DOLLAR], @"$",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_CIRCUMFLEX], @"^",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS], @"(",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS], @")",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_UNDERSCORE], @"_",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_AMPERSAND], @"&",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_QUOTEDBL], @"\"",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_EXCLAMATION_MARK], @"!",
|
||||||
|
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_LESS], @"<",
|
||||||
nil
|
nil
|
||||||
];
|
];
|
||||||
// This is ok to retain a singleton object
|
// This is ok to retain a singleton object
|
||||||
@@ -614,17 +582,21 @@ NsCharToJavaVirtualKeyCode(unichar ch, BOOL isDeadChar,
|
|||||||
if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:ch] ||
|
if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:ch] ||
|
||||||
[[NSCharacterSet symbolCharacterSet] characterIsMember:ch])
|
[[NSCharacterSet symbolCharacterSet] characterIsMember:ch])
|
||||||
{
|
{
|
||||||
*keyCode = [[unicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]] intValue];
|
// punctuationCharacterSet and symbolCharacterSet are too big
|
||||||
// we cannot find key location from a char, so let's use key code
|
// to store them all in UnicharToVkCodeDictionary
|
||||||
*postsTyped = YES;
|
int tmpKeyCode = [[unicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]] intValue];
|
||||||
*keyLocation = keyTable[key].javaKeyLocation;
|
if (tmpKeyCode != 0) {
|
||||||
return;
|
*keyCode = tmpKeyCode;
|
||||||
|
// we cannot find key location from a char, so let's use key code
|
||||||
|
*postsTyped = YES;
|
||||||
|
*keyLocation = keyTable[key].javaKeyLocation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary* diacriticUnicharToVkCodeDictionary = getDiacriticUnicharToVkCodeDictionary();
|
// Latin-1 suplement & Latin Extended A & B
|
||||||
NSNumber * jkc = [diacriticUnicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]];
|
if (isLatinUnicode(ch)) {
|
||||||
if (jkc != nil) {
|
*keyCode = ((int) ch) + UNICODE_OFFSET;
|
||||||
*keyCode = [jkc intValue];
|
|
||||||
// we cannot find key location from a char, so let's use key code
|
// we cannot find key location from a char, so let's use key code
|
||||||
*postsTyped = YES;
|
*postsTyped = YES;
|
||||||
*keyLocation = keyTable[key].javaKeyLocation;
|
*keyLocation = keyTable[key].javaKeyLocation;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ static NSTimeInterval gsLastClickTime;
|
|||||||
static int gsEventNumber;
|
static int gsEventNumber;
|
||||||
static int* gsButtonEventNumber;
|
static int* gsButtonEventNumber;
|
||||||
static NSTimeInterval gNextKeyEventTime;
|
static NSTimeInterval gNextKeyEventTime;
|
||||||
|
static NSTimeInterval safeDelay;
|
||||||
|
|
||||||
static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
|
static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
|
||||||
|
|
||||||
@@ -104,17 +105,17 @@ static inline void autoDelay(BOOL isMove) {
|
|||||||
[NSThread sleepForTimeInterval:delay];
|
[NSThread sleepForTimeInterval:delay];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + 0.050;
|
gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + safeDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: sun_lwawt_macosx_CRobot
|
* Class: sun_lwawt_macosx_CRobot
|
||||||
* Method: initRobot
|
* Method: initRobot
|
||||||
* Signature: (V)V
|
* Signature: (I)V
|
||||||
*/
|
*/
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_sun_lwawt_macosx_CRobot_initRobot
|
Java_sun_lwawt_macosx_CRobot_initRobot
|
||||||
(JNIEnv *env, jobject peer)
|
(JNIEnv *env, jobject peer, jint safeDelayMillis)
|
||||||
{
|
{
|
||||||
// Set things up to let our app act like a synthetic keyboard and mouse.
|
// Set things up to let our app act like a synthetic keyboard and mouse.
|
||||||
// Always set all states, in case Apple ever changes default behaviors.
|
// Always set all states, in case Apple ever changes default behaviors.
|
||||||
@@ -143,6 +144,7 @@ Java_sun_lwawt_macosx_CRobot_initRobot
|
|||||||
gsClickCount = 0;
|
gsClickCount = 0;
|
||||||
gsLastClickTime = 0;
|
gsLastClickTime = 0;
|
||||||
gNextKeyEventTime = 0;
|
gNextKeyEventTime = 0;
|
||||||
|
safeDelay = (NSTimeInterval)safeDelayMillis/1000;
|
||||||
gsEventNumber = ROBOT_EVENT_NUMBER_START;
|
gsEventNumber = ROBOT_EVENT_NUMBER_START;
|
||||||
|
|
||||||
gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons);
|
gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons);
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ import sun.awt.AppContext;
|
|||||||
import sun.awt.ComponentFactory;
|
import sun.awt.ComponentFactory;
|
||||||
import sun.security.action.GetBooleanAction;
|
import sun.security.action.GetBooleanAction;
|
||||||
import sun.security.action.GetPropertyAction;
|
import sun.security.action.GetPropertyAction;
|
||||||
import sun.awt.AppContext;
|
|
||||||
import sun.awt.AWTAccessor;
|
|
||||||
import sun.awt.ConstrainableGraphics;
|
import sun.awt.ConstrainableGraphics;
|
||||||
import sun.awt.EmbeddedFrame;
|
import sun.awt.EmbeddedFrame;
|
||||||
import sun.awt.RequestFocusController;
|
import sun.awt.RequestFocusController;
|
||||||
@@ -112,7 +110,6 @@ import sun.java2d.SunGraphics2D;
|
|||||||
import sun.java2d.SunGraphicsEnvironment;
|
import sun.java2d.SunGraphicsEnvironment;
|
||||||
import sun.java2d.pipe.Region;
|
import sun.java2d.pipe.Region;
|
||||||
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
|
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
|
||||||
import sun.security.action.GetPropertyAction;
|
|
||||||
import sun.swing.SwingAccessor;
|
import sun.swing.SwingAccessor;
|
||||||
import sun.util.logging.PlatformLogger;
|
import sun.util.logging.PlatformLogger;
|
||||||
|
|
||||||
@@ -226,6 +223,7 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
|||||||
private static final PlatformLogger eventLog = PlatformLogger.getLogger("java.awt.event.Component");
|
private static final PlatformLogger eventLog = PlatformLogger.getLogger("java.awt.event.Component");
|
||||||
private static final PlatformLogger focusLog = PlatformLogger.getLogger("java.awt.focus.Component");
|
private static final PlatformLogger focusLog = PlatformLogger.getLogger("java.awt.focus.Component");
|
||||||
private static final PlatformLogger mixingLog = PlatformLogger.getLogger("java.awt.mixing.Component");
|
private static final PlatformLogger mixingLog = PlatformLogger.getLogger("java.awt.mixing.Component");
|
||||||
|
private static final PlatformLogger focusRequestLog = PlatformLogger.getLogger("jb.focus.requests");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The peer of the component. The peer implements the component's
|
* The peer of the component. The peer implements the component's
|
||||||
@@ -7958,6 +7956,12 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
|||||||
boolean focusedWindowChangeAllowed,
|
boolean focusedWindowChangeAllowed,
|
||||||
FocusEvent.Cause cause)
|
FocusEvent.Cause cause)
|
||||||
{
|
{
|
||||||
|
if (focusRequestLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||||
|
focusRequestLog.fine("requestFocus("
|
||||||
|
+ (temporary ? "temporary," : "")
|
||||||
|
+ (focusedWindowChangeAllowed ? "" : "inWindow,")
|
||||||
|
+ cause + ") for " + this, new Throwable());
|
||||||
|
}
|
||||||
// 1) Check if the event being dispatched is a system-generated mouse event.
|
// 1) Check if the event being dispatched is a system-generated mouse event.
|
||||||
AWTEvent currentEvent = EventQueue.getCurrentEvent();
|
AWTEvent currentEvent = EventQueue.getCurrentEvent();
|
||||||
if (currentEvent instanceof MouseEvent &&
|
if (currentEvent instanceof MouseEvent &&
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ public class Window extends Container implements Accessible {
|
|||||||
private static final long serialVersionUID = 4497834738069338734L;
|
private static final long serialVersionUID = 4497834738069338734L;
|
||||||
|
|
||||||
private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Window");
|
private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Window");
|
||||||
|
private static final PlatformLogger focusRequestLog = PlatformLogger.getLogger("jb.focus.requests");
|
||||||
|
|
||||||
private static final boolean locationByPlatformProp;
|
private static final boolean locationByPlatformProp;
|
||||||
|
|
||||||
@@ -1308,6 +1309,9 @@ public class Window extends Container implements Accessible {
|
|||||||
// This functionality is implemented in a final package-private method
|
// This functionality is implemented in a final package-private method
|
||||||
// to insure that it cannot be overridden by client subclasses.
|
// to insure that it cannot be overridden by client subclasses.
|
||||||
final void toFront_NoClientCode() {
|
final void toFront_NoClientCode() {
|
||||||
|
if (focusRequestLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||||
|
focusRequestLog.fine("toFront() for" + this, new Throwable());
|
||||||
|
}
|
||||||
if (visible) {
|
if (visible) {
|
||||||
WindowPeer peer = (WindowPeer)this.peer;
|
WindowPeer peer = (WindowPeer)this.peer;
|
||||||
if (peer != null) {
|
if (peer != null) {
|
||||||
@@ -1351,6 +1355,9 @@ public class Window extends Container implements Accessible {
|
|||||||
// This functionality is implemented in a final package-private method
|
// This functionality is implemented in a final package-private method
|
||||||
// to insure that it cannot be overridden by client subclasses.
|
// to insure that it cannot be overridden by client subclasses.
|
||||||
final void toBack_NoClientCode() {
|
final void toBack_NoClientCode() {
|
||||||
|
if (focusRequestLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||||
|
focusRequestLog.fine("toBack() for " + this, new Throwable());
|
||||||
|
}
|
||||||
if(isAlwaysOnTop()) {
|
if(isAlwaysOnTop()) {
|
||||||
try {
|
try {
|
||||||
setAlwaysOnTop(false);
|
setAlwaysOnTop(false);
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package sun.awt.event;
|
|
||||||
|
|
||||||
import java.lang.annotation.Native;
|
|
||||||
|
|
||||||
public class KeyEvent {
|
|
||||||
/** */
|
|
||||||
@Native
|
|
||||||
public static final int START_OF_LATIN_DIACRITIC_LETTERS = 0x01000000;
|
|
||||||
|
|
||||||
/** */
|
|
||||||
public static final int VK_ESZETT = START_OF_LATIN_DIACRITIC_LETTERS + 0xDF;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_GRAVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE0;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE1;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_CIRCUMFLEX = START_OF_LATIN_DIACRITIC_LETTERS + 0xE2;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_TILDE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE3;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xE4;
|
|
||||||
/** */
|
|
||||||
public static final int VK_A_WITH_RING_ABOVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE5;
|
|
||||||
/** */
|
|
||||||
public static final int VK_AE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE6;
|
|
||||||
/** */
|
|
||||||
public static final int VK_C_WITH_CEDILLA = START_OF_LATIN_DIACRITIC_LETTERS + 0xE7;
|
|
||||||
/** */
|
|
||||||
public static final int VK_E_WITH_GRAVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE8;
|
|
||||||
/** */
|
|
||||||
public static final int VK_E_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xE9;
|
|
||||||
/** */
|
|
||||||
public static final int VK_E_WITH_CIRCUMFLEX = START_OF_LATIN_DIACRITIC_LETTERS + 0xEA;
|
|
||||||
/** */
|
|
||||||
public static final int VK_E_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xEB;
|
|
||||||
/** */
|
|
||||||
public static final int VK_I_WITH_GRAVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xEC;
|
|
||||||
/** */
|
|
||||||
public static final int VK_I_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xED;
|
|
||||||
/** */
|
|
||||||
public static final int VK_I_WITH_CIRCUMFLEX = START_OF_LATIN_DIACRITIC_LETTERS + 0xEE;
|
|
||||||
/** */
|
|
||||||
public static final int VK_I_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xEF;
|
|
||||||
/** */
|
|
||||||
public static final int VK_ETH = START_OF_LATIN_DIACRITIC_LETTERS + 0xF0;
|
|
||||||
/** */
|
|
||||||
public static final int VK_N_WITH_TILDE = START_OF_LATIN_DIACRITIC_LETTERS + 0xF1;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_GRAVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xF2;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xF3;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_CIRCUMFLEX = START_OF_LATIN_DIACRITIC_LETTERS + 0xF4;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_TILDE = START_OF_LATIN_DIACRITIC_LETTERS + 0xF5;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xF6;
|
|
||||||
/** */
|
|
||||||
public static final int VK_DIVISION_SIGN = START_OF_LATIN_DIACRITIC_LETTERS + 0xF7;
|
|
||||||
/** */
|
|
||||||
public static final int VK_O_WITH_SLASH = START_OF_LATIN_DIACRITIC_LETTERS + 0xF8;
|
|
||||||
/** */
|
|
||||||
public static final int VK_U_WITH_GRAVE = START_OF_LATIN_DIACRITIC_LETTERS + 0xF9;
|
|
||||||
/** */
|
|
||||||
public static final int VK_U_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xFA;
|
|
||||||
/** */
|
|
||||||
public static final int VK_U_WITH_CIRCUMFLEX = START_OF_LATIN_DIACRITIC_LETTERS + 0xFB;
|
|
||||||
/** */
|
|
||||||
public static final int VK_U_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xFC;
|
|
||||||
/** */
|
|
||||||
public static final int VK_Y_WITH_ACUTE = START_OF_LATIN_DIACRITIC_LETTERS + 0xFD;
|
|
||||||
/** */
|
|
||||||
public static final int VK_THORN = START_OF_LATIN_DIACRITIC_LETTERS + 0xFE;
|
|
||||||
/** */
|
|
||||||
public static final int VK_Y_WITH_DIAERESIS = START_OF_LATIN_DIACRITIC_LETTERS + 0xFF;
|
|
||||||
}
|
|
||||||
@@ -99,15 +99,16 @@ static GLhandleARB lcdTextProgram = 0;
|
|||||||
static GLhandleARB grayTextProgram = 0;
|
static GLhandleARB grayTextProgram = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this gamma if gray gamma shader is enabled
|
* Use this hints if gray gamma shader is enabled
|
||||||
* GRAY_LIGHT_GAMMA for light text on dark background
|
|
||||||
* GRAY_DARK_GAMMA for dark text on light backfround
|
|
||||||
* GRAY_GAMMA_THRESHOLD a threshold to switch gamma
|
|
||||||
* depending on text brightness
|
|
||||||
*/
|
*/
|
||||||
#define GRAY_LIGHT_GAMMA 220
|
typedef struct {
|
||||||
#define GRAY_DARK_GAMMA 140
|
float light_gamma; // brightness of light text
|
||||||
#define GRAY_GAMMA_THRESHOLD 0.5f
|
float dark_gamma; // brightness of dark text
|
||||||
|
float light_exp; // thickness of light text
|
||||||
|
float dark_exp; // thickness of dark text
|
||||||
|
} GrayRenderHints;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This value tracks the previous LCD contrast setting, so if the contrast
|
* This value tracks the previous LCD contrast setting, so if the contrast
|
||||||
@@ -325,29 +326,32 @@ static const char *lcdTextShaderSource =
|
|||||||
"}";
|
"}";
|
||||||
|
|
||||||
static const char *grayGammaTextShaderSource =
|
static const char *grayGammaTextShaderSource =
|
||||||
"uniform vec3 src_adj;"
|
"uniform vec4 src_adj;"
|
||||||
"uniform sampler2D glyph_tex;"
|
"uniform sampler2D glyph_tex;"
|
||||||
"uniform float inv_light_gamma;"
|
"uniform float inv_light_gamma;"
|
||||||
"uniform float inv_dark_gamma;"
|
"uniform float inv_dark_gamma;"
|
||||||
"uniform float threshold;"
|
"uniform float inv_light_exp;"
|
||||||
|
"uniform float inv_dark_exp;"
|
||||||
"void main(void)"
|
"void main(void)"
|
||||||
"{"
|
"{"
|
||||||
" float glyph_clr = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
"float a = src_adj.a;"
|
||||||
" if (dot(src_adj, vec3(1.0/3.0, 1.0/3.0, 1.0/3.0)) > threshold) {"
|
"vec3 col = src_adj.rgb;"
|
||||||
" glyph_clr = pow(glyph_clr, inv_light_gamma);"
|
|
||||||
" } else { "
|
|
||||||
" glyph_clr = pow(glyph_clr, inv_dark_gamma);"
|
|
||||||
" }"
|
|
||||||
" gl_FragColor = vec4(src_adj, glyph_clr);"
|
|
||||||
"}";
|
|
||||||
|
|
||||||
static const char *grayTextShaderSource =
|
// calculate brightness of the fragment
|
||||||
"uniform vec3 src_adj;"
|
"float b = dot(col, vec3(1.0/3.0, 1.0/3.0, 1.0/3.0))*a;"
|
||||||
"uniform sampler2D glyph_tex;"
|
|
||||||
"void main(void)"
|
// adjust fragment coverage
|
||||||
"{"
|
"float frag_cov = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
||||||
" float glyph_clr = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
"float exp = mix(inv_dark_exp, inv_light_exp, b);"
|
||||||
" gl_FragColor = vec4(src_adj, glyph_clr);"
|
"frag_cov = pow(frag_cov, exp);"
|
||||||
|
|
||||||
|
// adjust fragment color and alpha for alpha < 1.0
|
||||||
|
"if (a < 1.0) {"
|
||||||
|
"float g = mix(inv_dark_gamma, inv_light_gamma,b);"
|
||||||
|
"col = pow(col, vec3(g));"
|
||||||
|
"a = pow(a, exp);"
|
||||||
|
"}"
|
||||||
|
"gl_FragColor = vec4(col, a*frag_cov);"
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -384,21 +388,113 @@ OGLTR_CreateLCDTextProgram()
|
|||||||
|
|
||||||
return lcdTextProgram;
|
return lcdTextProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int JVM_GetIntProperty(const char* name, int defaultValue) {
|
||||||
|
JNIEnv *env = (JNIEnv *) JNU_GetEnv(jvm, JNI_VERSION_1_2);
|
||||||
|
static jclass systemCls = NULL;
|
||||||
|
if (systemCls == NULL) {
|
||||||
|
systemCls = (*env)->FindClass(env, "java/lang/System");
|
||||||
|
if (systemCls == NULL) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static jmethodID mid = NULL;
|
||||||
|
|
||||||
|
if (mid == NULL) {
|
||||||
|
mid = (*env)->GetStaticMethodID(env, systemCls, "getProperty",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/String;");
|
||||||
|
if (mid == NULL) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring jName = (*env)->NewStringUTF(env, name);
|
||||||
|
if (jName == NULL) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = defaultValue;
|
||||||
|
jstring jvalue = (*env)->CallStaticObjectMethod(env, systemCls, mid, jName);
|
||||||
|
if (jvalue != NULL) {
|
||||||
|
const char *utf8string = (*env)->GetStringUTFChars(env, jvalue, NULL);
|
||||||
|
if (utf8string != NULL) {
|
||||||
|
const int parsedVal = atoi(utf8string);
|
||||||
|
if (parsedVal > 0) {
|
||||||
|
result = parsedVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jvalue, utf8string);
|
||||||
|
}
|
||||||
|
(*env)->DeleteLocalRef(env, jName);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GrayRenderHints* getGrayRenderHints() {
|
||||||
|
static GrayRenderHints *hints = NULL;
|
||||||
|
static GrayRenderHints defaultRenderHints[] = {
|
||||||
|
// hints for "use font smoothing" option
|
||||||
|
// disabled
|
||||||
|
{1.666f, 0.333f, 1.0f, 1.25f},
|
||||||
|
// enabled
|
||||||
|
{1.666f, 0.333f, 0.454f, 1.4f}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hints == NULL) {
|
||||||
|
// read from VM-properties
|
||||||
|
int val = JVM_GetIntProperty("awt.font.nosm.light_gamma", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[0].light_gamma = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.nosm.dark_gamma", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[0].dark_gamma = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.nosm.light_exp", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[0].light_exp = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.nosm.dark_exp", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[0].dark_exp = val / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = JVM_GetIntProperty("awt.font.sm.light_gamma", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[1].light_gamma = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.sm.dark_gamma", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[1].dark_gamma = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.sm.light_exp", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[1].light_exp = val / 1000.0;
|
||||||
|
}
|
||||||
|
val = JVM_GetIntProperty("awt.font.sm.dark_exp", 0);
|
||||||
|
if (val > 0) {
|
||||||
|
defaultRenderHints[1].dark_exp = val / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hints = defaultRenderHints;
|
||||||
|
}
|
||||||
|
return hints;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles and links the LCD text shader program. If successful, this
|
* Compiles and links the LCD text shader program. If successful, this
|
||||||
* function returns a handle to the newly created shader program; otherwise
|
* function returns a handle to the newly created shader program; otherwise
|
||||||
* returns 0.
|
* returns 0.
|
||||||
*/
|
*/
|
||||||
static GLhandleARB
|
static GLhandleARB
|
||||||
OGLTR_CreateGrayTextProgram(jint lightContrast, jint darkContrast, jfloat threshold)
|
OGLTR_CreateGrayTextProgram(jboolean useFontSmoothing)
|
||||||
{
|
{
|
||||||
GLhandleARB grayTextProgram;
|
GLhandleARB grayTextProgram;
|
||||||
GLint loc;
|
GLint loc;
|
||||||
|
|
||||||
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateGrayTextProgram");
|
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateGrayTextProgram");
|
||||||
|
|
||||||
grayTextProgram = OGLContext_CreateFragmentProgram(
|
grayTextProgram = OGLContext_CreateFragmentProgram(grayGammaTextShaderSource);
|
||||||
lightContrast > 0 ? grayGammaTextShaderSource : grayTextShaderSource);
|
|
||||||
|
|
||||||
if (grayTextProgram == 0) {
|
if (grayTextProgram == 0) {
|
||||||
J2dRlsTraceLn(J2D_TRACE_ERROR,
|
J2dRlsTraceLn(J2D_TRACE_ERROR,
|
||||||
@@ -409,20 +505,21 @@ OGLTR_CreateGrayTextProgram(jint lightContrast, jint darkContrast, jfloat thresh
|
|||||||
// "use" the program object temporarily so that we can set the uniforms
|
// "use" the program object temporarily so that we can set the uniforms
|
||||||
j2d_glUseProgramObjectARB(grayTextProgram);
|
j2d_glUseProgramObjectARB(grayTextProgram);
|
||||||
|
|
||||||
if (lightContrast > 0) {
|
GrayRenderHints *hints = &(getGrayRenderHints()[useFontSmoothing]);
|
||||||
double ilg = 1.0 / (((double) lightContrast) / 100.0);
|
J2dTraceLn5(J2D_TRACE_INFO,
|
||||||
double idg = 1.0 / (((double) darkContrast) / 100.0);
|
"OGLTR_CreateGrayTextProgram: useFontSmoothing=%d "
|
||||||
J2dTraceLn1(J2D_TRACE_INFO,
|
"light_gamma=%f dark_gamma=%f light_exp=%f dark_exp=%f",
|
||||||
"OGLTR_CreateGrayTextProgram: contrast=%d", lightContrast);
|
useFontSmoothing, hints->light_gamma, hints->dark_gamma,
|
||||||
|
hints->light_exp, hints->dark_exp);
|
||||||
|
|
||||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_gamma");
|
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_gamma");
|
||||||
j2d_glUniform1fARB(loc, ilg);
|
j2d_glUniform1fARB(loc, hints->light_gamma);
|
||||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_dark_gamma");
|
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_dark_gamma");
|
||||||
j2d_glUniform1fARB(loc, idg);
|
j2d_glUniform1fARB(loc, hints->dark_gamma);
|
||||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "threshold");
|
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_exp");
|
||||||
j2d_glUniform1fARB(loc, threshold);
|
j2d_glUniform1fARB(loc, hints->light_exp);
|
||||||
|
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_dark_exp");
|
||||||
}
|
j2d_glUniform1fARB(loc, hints->dark_exp);
|
||||||
|
|
||||||
// "unuse" the program object; it will be re-bound later as needed
|
// "unuse" the program object; it will be re-bound later as needed
|
||||||
j2d_glUseProgramObjectARB(0);
|
j2d_glUseProgramObjectARB(0);
|
||||||
@@ -506,7 +603,7 @@ OGLTR_UpdateLCDTextColor(jint contrast)
|
|||||||
static jboolean
|
static jboolean
|
||||||
OGLTR_UpdateGrayTextColor()
|
OGLTR_UpdateGrayTextColor()
|
||||||
{
|
{
|
||||||
GLfloat radj, gadj, badj;
|
GLfloat radj, gadj, badj, aadj;
|
||||||
GLfloat clr[4];
|
GLfloat clr[4];
|
||||||
GLint loc;
|
GLint loc;
|
||||||
|
|
||||||
@@ -525,10 +622,11 @@ OGLTR_UpdateGrayTextColor()
|
|||||||
radj = (GLfloat)clr[0];
|
radj = (GLfloat)clr[0];
|
||||||
gadj = (GLfloat)clr[1];
|
gadj = (GLfloat)clr[1];
|
||||||
badj = (GLfloat)clr[2];
|
badj = (GLfloat)clr[2];
|
||||||
|
aadj = (GLfloat)clr[3];
|
||||||
|
|
||||||
// update the "src_adj" parameter of the shader program with this value
|
// update the "src_adj" parameter of the shader program with this value
|
||||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "src_adj");
|
loc = j2d_glGetUniformLocationARB(grayTextProgram, "src_adj");
|
||||||
j2d_glUniform3fARB(loc, radj, gadj, badj);
|
j2d_glUniform4fARB(loc, radj, gadj, badj, aadj);
|
||||||
|
|
||||||
return JNI_TRUE;
|
return JNI_TRUE;
|
||||||
}
|
}
|
||||||
@@ -598,9 +696,7 @@ OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID,
|
|||||||
* Enables the GrayScale text shader and updates any related states
|
* Enables the GrayScale text shader and updates any related states
|
||||||
*/
|
*/
|
||||||
static jboolean
|
static jboolean
|
||||||
OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID,
|
OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID, jboolean useFontSmoothing)
|
||||||
jint lightContrast, jint darkContrast,
|
|
||||||
jfloat threshold)
|
|
||||||
{
|
{
|
||||||
// bind the texture containing glyph data to texture unit 0
|
// bind the texture containing glyph data to texture unit 0
|
||||||
j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
|
j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
|
||||||
@@ -614,8 +710,7 @@ OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID,
|
|||||||
|
|
||||||
// create the Gray text shader, if necessary
|
// create the Gray text shader, if necessary
|
||||||
if (grayTextProgram == 0) {
|
if (grayTextProgram == 0) {
|
||||||
grayTextProgram = OGLTR_CreateGrayTextProgram(
|
grayTextProgram = OGLTR_CreateGrayTextProgram(useFontSmoothing);
|
||||||
lightContrast, darkContrast, threshold);
|
|
||||||
if (grayTextProgram == 0) {
|
if (grayTextProgram == 0) {
|
||||||
return JNI_FALSE;
|
return JNI_FALSE;
|
||||||
}
|
}
|
||||||
@@ -716,8 +811,7 @@ OGLTR_DisableGlyphModeState()
|
|||||||
}
|
}
|
||||||
|
|
||||||
static jboolean
|
static jboolean
|
||||||
OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo,
|
OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo, jint x, jint y, jboolean useFontSmoothing)
|
||||||
jint x, jint y, jint lightContrast, jint darkContrast, jfloat threshold)
|
|
||||||
{
|
{
|
||||||
CacheCellInfo *cell;
|
CacheCellInfo *cell;
|
||||||
jfloat x1, y1, x2, y2;
|
jfloat x1, y1, x2, y2;
|
||||||
@@ -732,8 +826,7 @@ OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!OGLTR_EnableGrayGlyphModeState(
|
if (!OGLTR_EnableGrayGlyphModeState(glyphCacheAA->cacheID, useFontSmoothing))
|
||||||
glyphCacheAA->cacheID, lightContrast, darkContrast, threshold))
|
|
||||||
{
|
{
|
||||||
return JNI_FALSE;
|
return JNI_FALSE;
|
||||||
}
|
}
|
||||||
@@ -1234,9 +1327,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
|
|||||||
int glyphCounter;
|
int glyphCounter;
|
||||||
GLuint dstTextureID = 0;
|
GLuint dstTextureID = 0;
|
||||||
jlong time;
|
jlong time;
|
||||||
jint lighGamma = 0;
|
jboolean fontSmoothing = JNI_FALSE;
|
||||||
jint darkGamma = 0;
|
|
||||||
jfloat threshold = 0;
|
|
||||||
|
|
||||||
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList");
|
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList");
|
||||||
if (graphicsPrimitive_traceflags & J2D_PTRACE_TIME) {
|
if (graphicsPrimitive_traceflags & J2D_PTRACE_TIME) {
|
||||||
@@ -1276,11 +1367,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
subPixPos = lcdSubPixelPosSupported ? subPixPos : 0;
|
subPixPos = lcdSubPixelPosSupported ? subPixPos : 0;
|
||||||
if (useFontSmoothing) {
|
fontSmoothing = useFontSmoothing;
|
||||||
lighGamma = GRAY_LIGHT_GAMMA;
|
|
||||||
darkGamma = GRAY_DARK_GAMMA;
|
|
||||||
threshold = GRAY_GAMMA_THRESHOLD;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
|
for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) {
|
||||||
@@ -1322,8 +1409,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
|
|||||||
if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
|
if (ginfo->width <= OGLTR_CACHE_CELL_WIDTH &&
|
||||||
ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
|
ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
|
||||||
{
|
{
|
||||||
ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y,
|
ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y, fontSmoothing);
|
||||||
lighGamma, darkGamma, threshold);
|
|
||||||
} else {
|
} else {
|
||||||
ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
|
ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1726,7 +1726,7 @@ static jlong
|
|||||||
if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD && width > 0) {
|
if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD && width > 0) {
|
||||||
glyphInfo->width = width/3;
|
glyphInfo->width = width/3;
|
||||||
glyphInfo->topLeftX -= 1;
|
glyphInfo->topLeftX -= 1;
|
||||||
glyphInfo->width += 1;
|
glyphInfo->width += 2;
|
||||||
} else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) {
|
} else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) {
|
||||||
glyphInfo->height = glyphInfo->height/3;
|
glyphInfo->height = glyphInfo->height/3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ public class XBaseWindow {
|
|||||||
|
|
||||||
private static XAtom wm_client_leader;
|
private static XAtom wm_client_leader;
|
||||||
|
|
||||||
private long userTime;
|
|
||||||
private static long globalUserTime;
|
private static long globalUserTime;
|
||||||
|
|
||||||
static enum InitialiseState {
|
static enum InitialiseState {
|
||||||
@@ -669,7 +668,7 @@ public class XBaseWindow {
|
|||||||
try {
|
try {
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setUserTimeFromGlobal();
|
setUserTimeBeforeShowing();
|
||||||
XlibWrapper.XMapWindow(XToolkit.getDisplay(), getWindow());
|
XlibWrapper.XMapWindow(XToolkit.getDisplay(), getWindow());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1029,7 +1028,7 @@ public class XBaseWindow {
|
|||||||
public void handleVisibilityEvent(XEvent xev) {
|
public void handleVisibilityEvent(XEvent xev) {
|
||||||
}
|
}
|
||||||
public void handleKeyPress(XEvent xev) {
|
public void handleKeyPress(XEvent xev) {
|
||||||
setUserTime(xev.get_xkey().get_time());
|
setUserTime(xev.get_xkey().get_time(), true);
|
||||||
}
|
}
|
||||||
public void handleKeyRelease(XEvent xev) {
|
public void handleKeyRelease(XEvent xev) {
|
||||||
}
|
}
|
||||||
@@ -1060,7 +1059,7 @@ public class XBaseWindow {
|
|||||||
if (!isWheel) {
|
if (!isWheel) {
|
||||||
switch (xev.get_type()) {
|
switch (xev.get_type()) {
|
||||||
case XConstants.ButtonPress:
|
case XConstants.ButtonPress:
|
||||||
setUserTime(xbe.get_time());
|
setUserTime(xbe.get_time(), true);
|
||||||
if (buttonState == 0) {
|
if (buttonState == 0) {
|
||||||
XWindowPeer parent = getToplevelXWindow();
|
XWindowPeer parent = getToplevelXWindow();
|
||||||
// See 6385277, 6981400.
|
// See 6385277, 6981400.
|
||||||
@@ -1299,15 +1298,12 @@ public class XBaseWindow {
|
|||||||
return x >= getAbsoluteX() && y >= getAbsoluteY() && x < (getAbsoluteX()+getWidth()) && y < (getAbsoluteY()+getHeight());
|
return x >= getAbsoluteX() && y >= getAbsoluteY() && x < (getAbsoluteX()+getWidth()) && y < (getAbsoluteY()+getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUserTimeFromGlobal() {
|
void setUserTimeBeforeShowing() {
|
||||||
setUserTime(globalUserTime);
|
if (globalUserTime != 0) setUserTime(globalUserTime, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUserTime(long time) {
|
protected void setUserTime(long time, boolean updateGlobalTime) {
|
||||||
if (time == userTime) return;
|
if (updateGlobalTime && (int)time - (int)globalUserTime > 0 /* accounting for wrap-around */) {
|
||||||
|
|
||||||
userTime = time;
|
|
||||||
if ((int)time - (int)globalUserTime > 0 /* accounting for wrap-around */) {
|
|
||||||
globalUserTime = time;
|
globalUserTime = time;
|
||||||
}
|
}
|
||||||
XNETProtocol netProtocol = XWM.getWM().getNETProtocol();
|
XNETProtocol netProtocol = XWM.getWM().getNETProtocol();
|
||||||
|
|||||||
@@ -1071,20 +1071,31 @@ abstract class XDecoratedPeer extends XWindowPeer {
|
|||||||
focusLog.fine("WM_TAKE_FOCUS on {0}", this);
|
focusLog.fine("WM_TAKE_FOCUS on {0}", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long requestTimeStamp = cl.get_data(1);
|
||||||
|
if (requestTimeStamp == 0) {
|
||||||
|
// KDE window manager always sends 0 ('CurrentTime') as timestamp,
|
||||||
|
// even though it seems to violate ICCCM specification
|
||||||
|
// (https://bugs.kde.org/show_bug.cgi?id=347153)
|
||||||
|
requestTimeStamp = XToolkit.getCurrentServerTime();
|
||||||
|
}
|
||||||
|
// we should treat WM_TAKE_FOCUS message as user interaction, as it can originate e.g. from user clicking
|
||||||
|
// on window title bar (there will be no ButtonPress/ButtonRelease events in this case)
|
||||||
|
setUserTime(requestTimeStamp, true);
|
||||||
|
|
||||||
if (XWM.getWMID() == XWM.UNITY_COMPIZ_WM) {
|
if (XWM.getWMID() == XWM.UNITY_COMPIZ_WM) {
|
||||||
// JDK-8159460
|
// JDK-8159460
|
||||||
Window focusedWindow = XKeyboardFocusManagerPeer.getInstance()
|
Window focusedWindow = XKeyboardFocusManagerPeer.getInstance()
|
||||||
.getCurrentFocusedWindow();
|
.getCurrentFocusedWindow();
|
||||||
Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow);
|
Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow);
|
||||||
if (activeWindow != target) {
|
if (activeWindow != target) {
|
||||||
requestWindowFocus(cl.get_data(1), true);
|
requestWindowFocus(requestTimeStamp, true);
|
||||||
} else {
|
} else {
|
||||||
WindowEvent we = new WindowEvent(focusedWindow,
|
WindowEvent we = new WindowEvent(focusedWindow,
|
||||||
WindowEvent.WINDOW_GAINED_FOCUS);
|
WindowEvent.WINDOW_GAINED_FOCUS);
|
||||||
sendEvent(we);
|
sendEvent(we);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
requestWindowFocus(cl.get_data(1), true);
|
requestWindowFocus(requestTimeStamp, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ final class XWM
|
|||||||
MUTTER_WM = 15,
|
MUTTER_WM = 15,
|
||||||
UNITY_COMPIZ_WM = 16,
|
UNITY_COMPIZ_WM = 16,
|
||||||
XMONAD_WM = 17,
|
XMONAD_WM = 17,
|
||||||
AWESOME_WM = 18;
|
AWESOME_WM = 18,
|
||||||
|
I3_WM = 19;
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
switch (WMID) {
|
switch (WMID) {
|
||||||
@@ -621,6 +622,10 @@ final class XWM
|
|||||||
return isNetWMName("awesome");
|
return isNetWMName("awesome");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isI3() {
|
||||||
|
return isNetWMName("i3");
|
||||||
|
}
|
||||||
|
|
||||||
static int awtWMNonReparenting = -1;
|
static int awtWMNonReparenting = -1;
|
||||||
static boolean isNonReparentingWM() {
|
static boolean isNonReparentingWM() {
|
||||||
if (awtWMNonReparenting == -1) {
|
if (awtWMNonReparenting == -1) {
|
||||||
@@ -824,6 +829,8 @@ final class XWM
|
|||||||
awt_wmgr = XWM.XMONAD_WM;
|
awt_wmgr = XWM.XMONAD_WM;
|
||||||
} else if (isAwesome()) {
|
} else if (isAwesome()) {
|
||||||
awt_wmgr = XWM.AWESOME_WM;
|
awt_wmgr = XWM.AWESOME_WM;
|
||||||
|
} else if (isI3()) {
|
||||||
|
awt_wmgr = XWM.I3_WM;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* We don't check for legacy WM when we already know that WM
|
* We don't check for legacy WM when we already know that WM
|
||||||
|
|||||||
@@ -1216,6 +1216,12 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
|||||||
return (uni > 0? sun.awt.ExtendedKeyCodes.getExtendedKeyCodeForChar(uni) : 0);
|
return (uni > 0? sun.awt.ExtendedKeyCodes.getExtendedKeyCodeForChar(uni) : 0);
|
||||||
//return (uni > 0? uni + 0x01000000 : 0);
|
//return (uni > 0? uni + 0x01000000 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// java keycodes for unicode values consistent with MacOS and Windows
|
||||||
|
private static int addUnicodeOffset(int uni) {
|
||||||
|
return uni > 0 ? uni + 0x01000000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
void logIncomingKeyEvent(XKeyEvent ev) {
|
void logIncomingKeyEvent(XKeyEvent ev) {
|
||||||
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE)) {
|
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||||
keyEventLog.fine("--XWindow.java:handleKeyEvent:"+ev);
|
keyEventLog.fine("--XWindow.java:handleKeyEvent:"+ev);
|
||||||
@@ -1304,17 +1310,18 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
||||||
|
primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) :
|
||||||
|
jkc.getJavaKeycode();
|
||||||
|
|
||||||
int jkeyToReturn;
|
int jkeyToReturn;
|
||||||
if (KeyEventProcessing.useNationalLayouts) {
|
if (KeyEventProcessing.useNationalLayouts) {
|
||||||
// if jkeyToReturn is VK_UNDEFINED then look for keycode in extended key code
|
jkeyToReturn = getNationalKeyCode(jkc, unicodeFromPrimaryKeysym);
|
||||||
jkeyToReturn = jkc.getJavaKeycode();
|
jkeyExtended = jkeyToReturn;
|
||||||
} else {
|
} else {
|
||||||
jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible
|
jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible
|
||||||
}
|
}
|
||||||
|
|
||||||
int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
|
||||||
primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) :
|
|
||||||
jkc.getJavaKeycode();
|
|
||||||
postKeyEvent( java.awt.event.KeyEvent.KEY_PRESSED,
|
postKeyEvent( java.awt.event.KeyEvent.KEY_PRESSED,
|
||||||
ev.get_time(),
|
ev.get_time(),
|
||||||
isDeadKey ? jkeyExtended : jkeyToReturn,
|
isDeadKey ? jkeyExtended : jkeyToReturn,
|
||||||
@@ -1395,16 +1402,18 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
|||||||
// is undefined, we still will have a guess of what was engraved on a keytop.
|
// is undefined, we still will have a guess of what was engraved on a keytop.
|
||||||
int unicodeFromPrimaryKeysym = keysymToUnicode( xkeycodeToPrimaryKeysym(ev) ,0);
|
int unicodeFromPrimaryKeysym = keysymToUnicode( xkeycodeToPrimaryKeysym(ev) ,0);
|
||||||
|
|
||||||
|
int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
||||||
|
primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) :
|
||||||
|
jkc.getJavaKeycode();
|
||||||
|
|
||||||
int jkeyToReturn;
|
int jkeyToReturn;
|
||||||
if (KeyEventProcessing.useNationalLayouts) {
|
if (KeyEventProcessing.useNationalLayouts) {
|
||||||
// if jkeyToReturn is VK_UNDEFINED then look for keycode in extended key code
|
jkeyToReturn = getNationalKeyCode(jkc, unicodeFromPrimaryKeysym);
|
||||||
jkeyToReturn = jkc.getJavaKeycode();
|
jkeyExtended = jkeyToReturn;
|
||||||
} else {
|
} else {
|
||||||
jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible
|
jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible
|
||||||
}
|
}
|
||||||
int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
|
||||||
primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) :
|
|
||||||
jkc.getJavaKeycode();
|
|
||||||
postKeyEvent( java.awt.event.KeyEvent.KEY_RELEASED,
|
postKeyEvent( java.awt.event.KeyEvent.KEY_RELEASED,
|
||||||
ev.get_time(),
|
ev.get_time(),
|
||||||
isDeadKey ? jkeyExtended : jkeyToReturn,
|
isDeadKey ? jkeyExtended : jkeyToReturn,
|
||||||
@@ -1417,6 +1426,14 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getNationalKeyCode(XKeysym.Keysym2JavaKeycode jkc, int unicodeFromPrimaryKeysym) {
|
||||||
|
// use this key code for both keyCode and extendedKeyCode
|
||||||
|
// compatible with MacOS and Windows
|
||||||
|
return jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
||||||
|
addUnicodeOffset(unicodeFromPrimaryKeysym) :
|
||||||
|
jkc.getJavaKeycode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isDeadKey(long keysym){
|
private boolean isDeadKey(long keysym){
|
||||||
return XKeySymConstants.XK_dead_grave <= keysym && keysym <= XKeySymConstants.XK_dead_semivoiced_sound;
|
return XKeySymConstants.XK_dead_grave <= keysym && keysym <= XKeySymConstants.XK_dead_semivoiced_sound;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ package sun.awt.X11;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
import java.awt.event.InvocationEvent;
|
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.awt.peer.ComponentPeer;
|
import java.awt.peer.ComponentPeer;
|
||||||
import java.awt.peer.WindowPeer;
|
import java.awt.peer.WindowPeer;
|
||||||
@@ -36,13 +35,7 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.Condition;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.BooleanSupplier;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import sun.awt.AWTAccessor;
|
import sun.awt.AWTAccessor;
|
||||||
import sun.awt.AWTAccessor.ComponentAccessor;
|
import sun.awt.AWTAccessor.ComponentAccessor;
|
||||||
@@ -1112,7 +1105,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
|||||||
if (!isVisible() && vis) {
|
if (!isVisible() && vis) {
|
||||||
isBeforeFirstMapNotify = true;
|
isBeforeFirstMapNotify = true;
|
||||||
winAttr.initialFocus = isAutoRequestFocus();
|
winAttr.initialFocus = isAutoRequestFocus();
|
||||||
if (!winAttr.initialFocus) {
|
if (!winAttr.initialFocus && XWM.getWMID() != XWM.I3_WM) {
|
||||||
/*
|
/*
|
||||||
* It's easier and safer to temporary suppress WM_TAKE_FOCUS
|
* It's easier and safer to temporary suppress WM_TAKE_FOCUS
|
||||||
* protocol itself than to ignore WM_TAKE_FOCUS client message.
|
* protocol itself than to ignore WM_TAKE_FOCUS client message.
|
||||||
@@ -1120,6 +1113,13 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
|||||||
* the message come after showing and the message come after
|
* the message come after showing and the message come after
|
||||||
* activation. Also, on Metacity, for some reason, we have _two_
|
* activation. Also, on Metacity, for some reason, we have _two_
|
||||||
* WM_TAKE_FOCUS client messages when showing a frame/dialog.
|
* WM_TAKE_FOCUS client messages when showing a frame/dialog.
|
||||||
|
*
|
||||||
|
* i3 window manager doesn't track updates to WM_TAKE_FOCUS
|
||||||
|
* property, so this approach won't work for it, breaking
|
||||||
|
* focus behaviour completely. So another way is used to
|
||||||
|
* suppress focus take over - via setting _NET_WM_USER_TIME
|
||||||
|
* to 0, as specified in EWMH spec (see
|
||||||
|
* 'setUserTimeBeforeShowing' method).
|
||||||
*/
|
*/
|
||||||
suppressWmTakeFocus(true);
|
suppressWmTakeFocus(true);
|
||||||
}
|
}
|
||||||
@@ -1184,6 +1184,16 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
|||||||
protected void suppressWmTakeFocus(boolean doSuppress) {
|
protected void suppressWmTakeFocus(boolean doSuppress) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setUserTimeBeforeShowing() {
|
||||||
|
if (winAttr.initialFocus || XWM.getWMID() != XWM.I3_WM) {
|
||||||
|
super.setUserTimeBeforeShowing();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setUserTime(0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final boolean isSimpleWindow() {
|
final boolean isSimpleWindow() {
|
||||||
return !(target instanceof Frame || target instanceof Dialog);
|
return !(target instanceof Frame || target instanceof Dialog);
|
||||||
}
|
}
|
||||||
@@ -1425,7 +1435,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
|||||||
isUnhiding |= isWMStateNetHidden();
|
isUnhiding |= isWMStateNetHidden();
|
||||||
|
|
||||||
super.handleMapNotifyEvent(xev);
|
super.handleMapNotifyEvent(xev);
|
||||||
if (!winAttr.initialFocus) {
|
if (!winAttr.initialFocus && XWM.getWMID() != XWM.I3_WM) {
|
||||||
suppressWmTakeFocus(false); // restore the protocol.
|
suppressWmTakeFocus(false); // restore the protocol.
|
||||||
/*
|
/*
|
||||||
* For some reason, on Metacity, a frame/dialog being shown
|
* For some reason, on Metacity, a frame/dialog being shown
|
||||||
@@ -2053,7 +2063,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
|||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
applyWindowType();
|
applyWindowType();
|
||||||
setUserTimeFromGlobal();
|
setUserTimeBeforeShowing();
|
||||||
XlibWrapper.XMapRaised(XToolkit.getDisplay(), getWindow());
|
XlibWrapper.XMapRaised(XToolkit.getDisplay(), getWindow());
|
||||||
} else {
|
} else {
|
||||||
XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), getWindow());
|
XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), getWindow());
|
||||||
|
|||||||
@@ -700,6 +700,20 @@ void AwtDesktopProperties::GetOtherParameters() {
|
|||||||
}
|
}
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add property for light/dark theme detection
|
||||||
|
value = getWindowsPropFromReg(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
|
||||||
|
TEXT("AppsUseLightTheme"), &valueType);
|
||||||
|
if (value != NULL) {
|
||||||
|
if (valueType == REG_DWORD) {
|
||||||
|
SetBooleanProperty(TEXT("win.lightTheme.on"), (BOOL)((int)*value == 1));
|
||||||
|
}
|
||||||
|
free(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SetBooleanProperty(TEXT("win.lightTheme.on"), TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (std::bad_alloc&) {
|
catch (std::bad_alloc&) {
|
||||||
if (value != NULL) {
|
if (value != NULL) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -34,6 +34,7 @@ import java.net.URISyntaxException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.net.http.HttpHeaders;
|
import java.net.http.HttpHeaders;
|
||||||
@@ -42,6 +43,7 @@ import jdk.internal.net.http.common.Utils;
|
|||||||
import static java.net.Authenticator.RequestorType.PROXY;
|
import static java.net.Authenticator.RequestorType.PROXY;
|
||||||
import static java.net.Authenticator.RequestorType.SERVER;
|
import static java.net.Authenticator.RequestorType.SERVER;
|
||||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of Http Basic authentication.
|
* Implementation of Http Basic authentication.
|
||||||
@@ -154,8 +156,8 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
if (proxyURI != null) {
|
if (proxyURI != null) {
|
||||||
CacheEntry ca = cache.get(proxyURI, true);
|
CacheEntry ca = cache.get(proxyURI, true);
|
||||||
if (ca != null) {
|
if (ca != null) {
|
||||||
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
|
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||||
addBasicCredentials(r, true, ca.value);
|
addBasicCredentials(r, true, ca.value, ca.isUTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,8 +166,8 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
if (exchange.serverauth == null) {
|
if (exchange.serverauth == null) {
|
||||||
CacheEntry ca = cache.get(r.uri(), false);
|
CacheEntry ca = cache.get(r.uri(), false);
|
||||||
if (ca != null) {
|
if (ca != null) {
|
||||||
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
|
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||||
addBasicCredentials(r, false, ca.value);
|
addBasicCredentials(r, false, ca.value, ca.isUTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,11 +175,13 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
// TODO: refactor into per auth scheme class
|
// TODO: refactor into per auth scheme class
|
||||||
private static void addBasicCredentials(HttpRequestImpl r,
|
private static void addBasicCredentials(HttpRequestImpl r,
|
||||||
boolean proxy,
|
boolean proxy,
|
||||||
PasswordAuthentication pw) {
|
PasswordAuthentication pw,
|
||||||
|
boolean isUTF8) {
|
||||||
String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
|
String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
|
||||||
StringBuilder sb = new StringBuilder(128);
|
StringBuilder sb = new StringBuilder(128);
|
||||||
sb.append(pw.getUserName()).append(':').append(pw.getPassword());
|
sb.append(pw.getUserName()).append(':').append(pw.getPassword());
|
||||||
String s = encoder.encodeToString(sb.toString().getBytes(ISO_8859_1));
|
var charset = isUTF8 ? UTF_8 : ISO_8859_1;
|
||||||
|
String s = encoder.encodeToString(sb.toString().getBytes(charset));
|
||||||
String value = "Basic " + s;
|
String value = "Basic " + s;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
if (r.isConnect()) {
|
if (r.isConnect()) {
|
||||||
@@ -202,35 +206,36 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
int retries;
|
int retries;
|
||||||
PasswordAuthentication credentials; // used in request
|
PasswordAuthentication credentials; // used in request
|
||||||
CacheEntry cacheEntry; // if used
|
CacheEntry cacheEntry; // if used
|
||||||
|
final boolean isUTF8; //
|
||||||
|
|
||||||
AuthInfo(boolean fromcache,
|
AuthInfo(boolean fromcache,
|
||||||
String scheme,
|
String scheme,
|
||||||
PasswordAuthentication credentials) {
|
PasswordAuthentication credentials, boolean isUTF8) {
|
||||||
this.fromcache = fromcache;
|
this.fromcache = fromcache;
|
||||||
this.scheme = scheme;
|
this.scheme = scheme;
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.retries = 1;
|
this.retries = 1;
|
||||||
|
this.isUTF8 = isUTF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthInfo(boolean fromcache,
|
AuthInfo(boolean fromcache,
|
||||||
String scheme,
|
String scheme,
|
||||||
PasswordAuthentication credentials,
|
PasswordAuthentication credentials,
|
||||||
CacheEntry ca) {
|
CacheEntry ca, boolean isUTF8) {
|
||||||
this(fromcache, scheme, credentials);
|
this(fromcache, scheme, credentials, isUTF8);
|
||||||
assert credentials == null || (ca != null && ca.value == null);
|
assert credentials == null || (ca != null && ca.value == null);
|
||||||
cacheEntry = ca;
|
cacheEntry = ca;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthInfo retryWithCredentials(PasswordAuthentication pw) {
|
AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) {
|
||||||
// If the info was already in the cache we need to create a new
|
// If the info was already in the cache we need to create a new
|
||||||
// instance with fromCache==false so that it's put back in the
|
// instance with fromCache==false so that it's put back in the
|
||||||
// cache if authentication succeeds
|
// cache if authentication succeeds
|
||||||
AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw) : this;
|
AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw, isUTF8) : this;
|
||||||
res.credentials = Objects.requireNonNull(pw);
|
res.credentials = Objects.requireNonNull(pw);
|
||||||
res.retries = retries;
|
res.retries = retries;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -240,41 +245,45 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
HttpHeaders hdrs = r.headers();
|
HttpHeaders hdrs = r.headers();
|
||||||
HttpRequestImpl req = r.request();
|
HttpRequestImpl req = r.request();
|
||||||
|
|
||||||
if (status != UNAUTHORIZED && status != PROXY_UNAUTHORIZED) {
|
if (status != PROXY_UNAUTHORIZED) {
|
||||||
// check if any authentication succeeded for first time
|
|
||||||
if (exchange.serverauth != null && !exchange.serverauth.fromcache) {
|
|
||||||
AuthInfo au = exchange.serverauth;
|
|
||||||
cache.store(au.scheme, req.uri(), false, au.credentials);
|
|
||||||
}
|
|
||||||
if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
|
if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
|
||||||
AuthInfo au = exchange.proxyauth;
|
AuthInfo au = exchange.proxyauth;
|
||||||
URI proxyURI = getProxyURI(req);
|
URI proxyURI = getProxyURI(req);
|
||||||
if (proxyURI != null) {
|
if (proxyURI != null) {
|
||||||
cache.store(au.scheme, proxyURI, true, au.credentials);
|
exchange.proxyauth = null;
|
||||||
|
cache.store(au.scheme, proxyURI, true, au.credentials, au.isUTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
if (status != UNAUTHORIZED) {
|
||||||
}
|
// check if any authentication succeeded for first time
|
||||||
|
if (exchange.serverauth != null && !exchange.serverauth.fromcache) {
|
||||||
boolean proxy = status == PROXY_UNAUTHORIZED;
|
AuthInfo au = exchange.serverauth;
|
||||||
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
cache.store(au.scheme, req.uri(), false, au.credentials, au.isUTF8);
|
||||||
String authval = hdrs.firstValue(authname).orElse(null);
|
}
|
||||||
if (authval == null) {
|
|
||||||
if (exchange.client().authenticator().isPresent()) {
|
|
||||||
throw new IOException(authname + " header missing for response code " + status);
|
|
||||||
} else {
|
|
||||||
// No authenticator? let the caller deal with this.
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HeaderParser parser = new HeaderParser(authval);
|
boolean proxy = status == PROXY_UNAUTHORIZED;
|
||||||
String scheme = parser.findKey(0);
|
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||||
|
List<String> authvals = hdrs.allValues(authname);
|
||||||
// TODO: Need to generalise from Basic only. Delegate to a provider class etc.
|
if (authvals.isEmpty() && exchange.client().authenticator().isPresent()) {
|
||||||
|
throw new IOException(authname + " header missing for response code " + status);
|
||||||
if (!scheme.equalsIgnoreCase("Basic")) {
|
}
|
||||||
return null; // error gets returned to app
|
String authval = null;
|
||||||
|
boolean isUTF8 = false;
|
||||||
|
for (String aval : authvals) {
|
||||||
|
HeaderParser parser = new HeaderParser(aval);
|
||||||
|
String scheme = parser.findKey(0);
|
||||||
|
if (scheme.equalsIgnoreCase("Basic")) {
|
||||||
|
authval = aval;
|
||||||
|
var charset = parser.findValue("charset");
|
||||||
|
isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (authval == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
@@ -303,14 +312,14 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
throw new IOException("No credentials provided");
|
throw new IOException("No credentials provided");
|
||||||
}
|
}
|
||||||
// No authentication in request. Get credentials from user
|
// No authentication in request. Get credentials from user
|
||||||
au = new AuthInfo(false, "Basic", pw);
|
au = new AuthInfo(false, "Basic", pw, isUTF8);
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
exchange.proxyauth = au;
|
exchange.proxyauth = au;
|
||||||
} else {
|
} else {
|
||||||
exchange.serverauth = au;
|
exchange.serverauth = au;
|
||||||
}
|
}
|
||||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||||
addBasicCredentials(req, proxy, pw);
|
addBasicCredentials(req, proxy, pw, isUTF8);
|
||||||
return req;
|
return req;
|
||||||
} else if (au.retries > retry_limit) {
|
} else if (au.retries > retry_limit) {
|
||||||
throw new IOException("too many authentication attempts. Limit: " +
|
throw new IOException("too many authentication attempts. Limit: " +
|
||||||
@@ -329,14 +338,14 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
if (pw == null) {
|
if (pw == null) {
|
||||||
throw new IOException("No credentials provided");
|
throw new IOException("No credentials provided");
|
||||||
}
|
}
|
||||||
au = au.retryWithCredentials(pw);
|
au = au.retryWithCredentials(pw, isUTF8);
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
exchange.proxyauth = au;
|
exchange.proxyauth = au;
|
||||||
} else {
|
} else {
|
||||||
exchange.serverauth = au;
|
exchange.serverauth = au;
|
||||||
}
|
}
|
||||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||||
addBasicCredentials(req, proxy, au.credentials);
|
addBasicCredentials(req, proxy, au.credentials, isUTF8);
|
||||||
au.retries++;
|
au.retries++;
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -373,10 +382,18 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean equalsIgnoreCase(String s1, String s2) {
|
||||||
|
return s1 == s2 || (s1 != null && s1.equalsIgnoreCase(s2));
|
||||||
|
}
|
||||||
|
|
||||||
synchronized void remove(String authscheme, URI domain, boolean proxy) {
|
synchronized void remove(String authscheme, URI domain, boolean proxy) {
|
||||||
for (CacheEntry entry : entries) {
|
var iterator = entries.iterator();
|
||||||
if (entry.equalsKey(domain, proxy)) {
|
while (iterator.hasNext()) {
|
||||||
entries.remove(entry);
|
var entry = iterator.next();
|
||||||
|
if (equalsIgnoreCase(entry.scheme, authscheme)) {
|
||||||
|
if (entry.equalsKey(domain, proxy)) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,9 +405,9 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
synchronized void store(String authscheme,
|
synchronized void store(String authscheme,
|
||||||
URI domain,
|
URI domain,
|
||||||
boolean proxy,
|
boolean proxy,
|
||||||
PasswordAuthentication value) {
|
PasswordAuthentication value, boolean isUTF8) {
|
||||||
remove(authscheme, domain, proxy);
|
remove(authscheme, domain, proxy);
|
||||||
entries.add(new CacheEntry(authscheme, domain, proxy, value));
|
entries.add(new CacheEntry(authscheme, domain, proxy, value, isUTF8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,15 +435,17 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
final String scheme;
|
final String scheme;
|
||||||
final boolean proxy;
|
final boolean proxy;
|
||||||
final PasswordAuthentication value;
|
final PasswordAuthentication value;
|
||||||
|
final boolean isUTF8;
|
||||||
|
|
||||||
CacheEntry(String authscheme,
|
CacheEntry(String authscheme,
|
||||||
URI uri,
|
URI uri,
|
||||||
boolean proxy,
|
boolean proxy,
|
||||||
PasswordAuthentication value) {
|
PasswordAuthentication value, boolean isUTF8) {
|
||||||
this.scheme = authscheme;
|
this.scheme = authscheme;
|
||||||
this.root = normalize(uri, true).toString(); // remove extraneous components
|
this.root = normalize(uri, true).toString(); // remove extraneous components
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.isUTF8 = isUTF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PasswordAuthentication value() {
|
public PasswordAuthentication value() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -142,6 +142,7 @@ final class ConnectionPool {
|
|||||||
HttpConnection c = secure ? findConnection(key, sslPool)
|
HttpConnection c = secure ? findConnection(key, sslPool)
|
||||||
: findConnection(key, plainPool);
|
: findConnection(key, plainPool);
|
||||||
//System.out.println ("getConnection returning: " + c);
|
//System.out.println ("getConnection returning: " + c);
|
||||||
|
assert c == null || c.isSecure() == secure;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +156,10 @@ final class ConnectionPool {
|
|||||||
// Called also by whitebox tests
|
// Called also by whitebox tests
|
||||||
void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
|
void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
|
||||||
|
|
||||||
|
assert (conn instanceof PlainHttpConnection) || conn.isSecure()
|
||||||
|
: "Attempting to return unsecure connection to SSL pool: "
|
||||||
|
+ conn.getClass();
|
||||||
|
|
||||||
// Don't call registerCleanupTrigger while holding a lock,
|
// Don't call registerCleanupTrigger while holding a lock,
|
||||||
// but register it before the connection is added to the pool,
|
// but register it before the connection is added to the pool,
|
||||||
// since we don't want to trigger the cleanup if the connection
|
// since we don't want to trigger the cleanup if the connection
|
||||||
@@ -450,7 +455,7 @@ final class ConnectionPool {
|
|||||||
if (c instanceof PlainHttpConnection) {
|
if (c instanceof PlainHttpConnection) {
|
||||||
removeFromPool(c, plainPool);
|
removeFromPool(c, plainPool);
|
||||||
} else {
|
} else {
|
||||||
assert c.isSecure();
|
assert c.isSecure() : "connection " + c + " is not secure!";
|
||||||
removeFromPool(c, sslPool);
|
removeFromPool(c, sslPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -263,7 +263,7 @@ class Http1Response<T> {
|
|||||||
connection.close();
|
connection.close();
|
||||||
return MinimalFuture.completedFuture(null); // not treating as error
|
return MinimalFuture.completedFuture(null); // not treating as error
|
||||||
} else {
|
} else {
|
||||||
return readBody(discarding(), true, executor);
|
return readBody(discarding(), !request.isWebSocket(), executor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +378,14 @@ class Http1Response<T> {
|
|||||||
public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
|
public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
|
||||||
boolean return2Cache,
|
boolean return2Cache,
|
||||||
Executor executor) {
|
Executor executor) {
|
||||||
|
if (debug.on()) {
|
||||||
|
debug.log("readBody: return2Cache: " + return2Cache);
|
||||||
|
if (request.isWebSocket() && return2Cache && connection != null) {
|
||||||
|
debug.log("websocket connection will be returned to cache: "
|
||||||
|
+ connection.getClass() + "/" + connection );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert !return2Cache || !request.isWebSocket();
|
||||||
this.return2Cache = return2Cache;
|
this.return2Cache = return2Cache;
|
||||||
final Http1BodySubscriber<U> subscriber = new Http1BodySubscriber<>(p);
|
final Http1BodySubscriber<U> subscriber = new Http1BodySubscriber<>(p);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -43,6 +43,7 @@ import java.net.http.HttpHeaders;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||||
import jdk.internal.net.http.common.Utils;
|
import jdk.internal.net.http.common.Utils;
|
||||||
|
import jdk.internal.net.http.websocket.OpeningHandshake;
|
||||||
import jdk.internal.net.http.websocket.WebSocketRequest;
|
import jdk.internal.net.http.websocket.WebSocketRequest;
|
||||||
|
|
||||||
import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
|
import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
|
||||||
@@ -157,7 +158,11 @@ public class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
|||||||
|
|
||||||
/** Returns a new instance suitable for authentication. */
|
/** Returns a new instance suitable for authentication. */
|
||||||
public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) {
|
public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) {
|
||||||
return new HttpRequestImpl(other.uri(), other.method(), other);
|
HttpRequestImpl request = new HttpRequestImpl(other.uri(), other.method(), other);
|
||||||
|
if (request.isWebSocket()) {
|
||||||
|
Utils.setWebSocketUpgradeHeaders(request);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -44,16 +44,13 @@ import jdk.internal.net.http.websocket.RawChannel;
|
|||||||
class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
||||||
|
|
||||||
final int responseCode;
|
final int responseCode;
|
||||||
final Exchange<T> exchange;
|
|
||||||
final HttpRequest initialRequest;
|
final HttpRequest initialRequest;
|
||||||
final Optional<HttpResponse<T>> previousResponse;
|
final Optional<HttpResponse<T>> previousResponse;
|
||||||
final HttpHeaders headers;
|
final HttpHeaders headers;
|
||||||
final Optional<SSLSession> sslSession;
|
final Optional<SSLSession> sslSession;
|
||||||
final URI uri;
|
final URI uri;
|
||||||
final HttpClient.Version version;
|
final HttpClient.Version version;
|
||||||
RawChannel rawchan;
|
final RawChannelProvider rawChannelProvider;
|
||||||
final HttpConnection connection;
|
|
||||||
final Stream<T> stream;
|
|
||||||
final T body;
|
final T body;
|
||||||
|
|
||||||
public HttpResponseImpl(HttpRequest initialRequest,
|
public HttpResponseImpl(HttpRequest initialRequest,
|
||||||
@@ -62,7 +59,6 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
|||||||
T body,
|
T body,
|
||||||
Exchange<T> exch) {
|
Exchange<T> exch) {
|
||||||
this.responseCode = response.statusCode();
|
this.responseCode = response.statusCode();
|
||||||
this.exchange = exch;
|
|
||||||
this.initialRequest = initialRequest;
|
this.initialRequest = initialRequest;
|
||||||
this.previousResponse = Optional.ofNullable(previousResponse);
|
this.previousResponse = Optional.ofNullable(previousResponse);
|
||||||
this.headers = response.headers();
|
this.headers = response.headers();
|
||||||
@@ -70,23 +66,10 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
|||||||
this.sslSession = Optional.ofNullable(response.getSSLSession());
|
this.sslSession = Optional.ofNullable(response.getSSLSession());
|
||||||
this.uri = response.request().uri();
|
this.uri = response.request().uri();
|
||||||
this.version = response.version();
|
this.version = response.version();
|
||||||
this.connection = connection(exch);
|
this.rawChannelProvider = RawChannelProvider.create(response, exch);
|
||||||
this.stream = null;
|
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpConnection connection(Exchange<?> exch) {
|
|
||||||
if (exch == null || exch.exchImpl == null) {
|
|
||||||
assert responseCode == 407;
|
|
||||||
return null; // case of Proxy 407
|
|
||||||
}
|
|
||||||
return exch.exchImpl.connection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExchangeImpl<?> exchangeImpl() {
|
|
||||||
return exchange != null ? exchange.exchImpl : stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int statusCode() {
|
public int statusCode() {
|
||||||
return responseCode;
|
return responseCode;
|
||||||
@@ -141,23 +124,35 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized RawChannel rawChannel() throws IOException {
|
public synchronized RawChannel rawChannel() throws IOException {
|
||||||
if (rawchan == null) {
|
if (rawChannelProvider == null) {
|
||||||
ExchangeImpl<?> exchImpl = exchangeImpl();
|
throw new UnsupportedOperationException(
|
||||||
if (!(exchImpl instanceof Http1Exchange)) {
|
"RawChannel is only supported for WebSocket creation");
|
||||||
// RawChannel is only used for WebSocket - and WebSocket
|
|
||||||
// is not supported over HTTP/2 yet, so we should not come
|
|
||||||
// here. Getting a RawChannel over HTTP/2 might be supported
|
|
||||||
// in the future, but it would entail retrieving any left over
|
|
||||||
// bytes that might have been read but not consumed by the
|
|
||||||
// HTTP/2 connection.
|
|
||||||
throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
|
|
||||||
}
|
|
||||||
// Http1Exchange may have some remaining bytes in its
|
|
||||||
// internal buffer.
|
|
||||||
Supplier<ByteBuffer> initial = ((Http1Exchange<?>)exchImpl)::drainLeftOverBytes;
|
|
||||||
rawchan = new RawChannelTube(connection, initial);
|
|
||||||
}
|
}
|
||||||
return rawchan;
|
return rawChannelProvider.rawChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the RawChannel that may have been used for WebSocket protocol.
|
||||||
|
*
|
||||||
|
* @apiNote This method should be called to close the connection
|
||||||
|
* if an exception occurs during the websocket handshake, in cases where
|
||||||
|
* {@link #rawChannel() rawChannel().close()} would have been called.
|
||||||
|
* An unsuccessful handshake may prevent the creation of the RawChannel:
|
||||||
|
* if a RawChannel has already been created, this method wil close it.
|
||||||
|
* Otherwise, it will close the connection.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if getting a RawChannel over
|
||||||
|
* this connection is not supported.
|
||||||
|
* @throws IOException if an I/O exception occurs while closing
|
||||||
|
* the channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void closeRawChannel() throws IOException {
|
||||||
|
if (rawChannelProvider == null) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"RawChannel is only supported for WebSocket creation");
|
||||||
|
}
|
||||||
|
rawChannelProvider.closeRawChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -174,4 +169,68 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
|||||||
.append(statusCode());
|
.append(statusCode());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An auxiliary class used for RawChannel creation when creating a WebSocket.
|
||||||
|
* This avoids keeping around references to connection/exchange in the
|
||||||
|
* regular HttpResponse case. Only those responses corresponding to an
|
||||||
|
* initial WebSocket request have a RawChannelProvider.
|
||||||
|
*/
|
||||||
|
private static final class RawChannelProvider implements RawChannel.Provider {
|
||||||
|
private final HttpConnection connection;
|
||||||
|
private final Exchange<?> exchange;
|
||||||
|
private RawChannel rawchan;
|
||||||
|
RawChannelProvider(HttpConnection conn, Exchange<?> exch) {
|
||||||
|
connection = conn;
|
||||||
|
exchange = exch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RawChannelProvider create(Response resp, Exchange<?> exch) {
|
||||||
|
if (resp.request().isWebSocket()) {
|
||||||
|
return new RawChannelProvider(connection(resp, exch), exch);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized RawChannel rawChannel() {
|
||||||
|
if (rawchan == null) {
|
||||||
|
ExchangeImpl<?> exchImpl = exchangeImpl();
|
||||||
|
if (!(exchImpl instanceof Http1Exchange)) {
|
||||||
|
// RawChannel is only used for WebSocket - and WebSocket
|
||||||
|
// is not supported over HTTP/2 yet, so we should not come
|
||||||
|
// here. Getting a RawChannel over HTTP/2 might be supported
|
||||||
|
// in the future, but it would entail retrieving any left over
|
||||||
|
// bytes that might have been read but not consumed by the
|
||||||
|
// HTTP/2 connection.
|
||||||
|
throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
|
||||||
|
}
|
||||||
|
// Http1Exchange may have some remaining bytes in its
|
||||||
|
// internal buffer.
|
||||||
|
Supplier<ByteBuffer> initial = ((Http1Exchange<?>) exchImpl)::drainLeftOverBytes;
|
||||||
|
rawchan = new RawChannelTube(connection, initial);
|
||||||
|
}
|
||||||
|
return rawchan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void closeRawChannel() throws IOException {
|
||||||
|
// close the rawChannel, if created, or the
|
||||||
|
// connection, if not.
|
||||||
|
if (rawchan != null) rawchan.close();
|
||||||
|
else connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpConnection connection(Response resp, Exchange<?> exch) {
|
||||||
|
if (exch == null || exch.exchImpl == null) {
|
||||||
|
assert resp.statusCode == 407;
|
||||||
|
return null; // case of Proxy 407
|
||||||
|
}
|
||||||
|
return exch.exchImpl.connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExchangeImpl<?> exchangeImpl() {
|
||||||
|
return exchange != null ? exchange.exchImpl : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -362,6 +362,10 @@ class MultiExchange<T> {
|
|||||||
this.response =
|
this.response =
|
||||||
new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
|
new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
|
||||||
Exchange<T> oldExch = exch;
|
Exchange<T> oldExch = exch;
|
||||||
|
if (currentreq.isWebSocket()) {
|
||||||
|
// need to close the connection and open a new one.
|
||||||
|
exch.exchImpl.connection().close();
|
||||||
|
}
|
||||||
return exch.ignoreBody().handle((r,t) -> {
|
return exch.ignoreBody().handle((r,t) -> {
|
||||||
previousreq = currentreq;
|
previousreq = currentreq;
|
||||||
currentreq = newrequest;
|
currentreq = newrequest;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -73,7 +73,7 @@ public class RawChannelTube implements RawChannel {
|
|||||||
this.initial = initial;
|
this.initial = initial;
|
||||||
this.writePublisher = new WritePublisher();
|
this.writePublisher = new WritePublisher();
|
||||||
this.readSubscriber = new ReadSubscriber();
|
this.readSubscriber = new ReadSubscriber();
|
||||||
dbgTag = "[WebSocket] RawChannelTube(" + tube.toString() +")";
|
dbgTag = "[WebSocket] RawChannelTube(" + tube +")";
|
||||||
debug = Utils.getWebSocketLogger(dbgTag::toString, Utils.DEBUG_WS);
|
debug = Utils.getWebSocketLogger(dbgTag::toString, Utils.DEBUG_WS);
|
||||||
connection.client().webSocketOpen();
|
connection.client().webSocketOpen();
|
||||||
connectFlows();
|
connectFlows();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -41,7 +41,7 @@ import javax.net.ssl.SNIServerName;
|
|||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -Djava.net.HttpClient.log=
|
* -Djdk.httpclient.HttpClient.log=
|
||||||
* errors,requests,headers,
|
* errors,requests,headers,
|
||||||
* frames[:control:data:window:all..],content,ssl,trace,channel
|
* frames[:control:data:window:all..],content,ssl,trace,channel
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import java.util.function.Predicate;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import jdk.internal.net.http.HttpRequestImpl;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -240,6 +241,15 @@ public final class Utils {
|
|||||||
: ! PROXY_AUTH_DISABLED_SCHEMES.isEmpty();
|
: ! PROXY_AUTH_DISABLED_SCHEMES.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebSocket connection Upgrade headers
|
||||||
|
private static final String HEADER_CONNECTION = "Connection";
|
||||||
|
private static final String HEADER_UPGRADE = "Upgrade";
|
||||||
|
|
||||||
|
public static final void setWebSocketUpgradeHeaders(HttpRequestImpl request) {
|
||||||
|
request.setSystemHeader(HEADER_UPGRADE, "websocket");
|
||||||
|
request.setSystemHeader(HEADER_CONNECTION, "Upgrade");
|
||||||
|
}
|
||||||
|
|
||||||
public static IllegalArgumentException newIAE(String message, Object... args) {
|
public static IllegalArgumentException newIAE(String message, Object... args) {
|
||||||
return new IllegalArgumentException(format(message, args));
|
return new IllegalArgumentException(format(message, args));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -143,8 +143,7 @@ public class OpeningHandshake {
|
|||||||
requestBuilder.version(Version.HTTP_1_1).GET();
|
requestBuilder.version(Version.HTTP_1_1).GET();
|
||||||
request = requestBuilder.buildForWebSocket();
|
request = requestBuilder.buildForWebSocket();
|
||||||
request.isWebSocket(true);
|
request.isWebSocket(true);
|
||||||
request.setSystemHeader(HEADER_UPGRADE, "websocket");
|
Utils.setWebSocketUpgradeHeaders(request);
|
||||||
request.setSystemHeader(HEADER_CONNECTION, "Upgrade");
|
|
||||||
request.setProxy(proxy);
|
request.setProxy(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +188,7 @@ public class OpeningHandshake {
|
|||||||
|
|
||||||
public CompletableFuture<Result> send() {
|
public CompletableFuture<Result> send() {
|
||||||
PrivilegedAction<CompletableFuture<Result>> pa = () ->
|
PrivilegedAction<CompletableFuture<Result>> pa = () ->
|
||||||
client.sendAsync(this.request, BodyHandlers.discarding())
|
client.sendAsync(this.request, BodyHandlers.ofString())
|
||||||
.thenCompose(this::resultFrom);
|
.thenCompose(this::resultFrom);
|
||||||
return AccessController.doPrivileged(pa);
|
return AccessController.doPrivileged(pa);
|
||||||
}
|
}
|
||||||
@@ -216,19 +215,26 @@ public class OpeningHandshake {
|
|||||||
//
|
//
|
||||||
// See https://tools.ietf.org/html/rfc6455#section-7.4.1
|
// See https://tools.ietf.org/html/rfc6455#section-7.4.1
|
||||||
Result result = null;
|
Result result = null;
|
||||||
Exception exception = null;
|
Throwable exception = null;
|
||||||
try {
|
try {
|
||||||
result = handleResponse(response);
|
result = handleResponse(response);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
exception = new WebSocketHandshakeException(response).initCause(e);
|
exception = new WebSocketHandshakeException(response).initCause(e);
|
||||||
|
} catch (Error e) {
|
||||||
|
// We should attempt to close the connection and relay
|
||||||
|
// the error through the completable future even in this
|
||||||
|
// case.
|
||||||
|
exception = e;
|
||||||
}
|
}
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
return MinimalFuture.completedFuture(result);
|
return MinimalFuture.completedFuture(result);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
((RawChannel.Provider) response).rawChannel().close();
|
// calling this method will close the rawChannel, if created,
|
||||||
|
// or the connection, if not.
|
||||||
|
((RawChannel.Provider) response).closeRawChannel();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
exception.addSuppressed(e);
|
exception.addSuppressed(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -40,6 +40,7 @@ public interface RawChannel extends Closeable {
|
|||||||
interface Provider {
|
interface Provider {
|
||||||
|
|
||||||
RawChannel rawChannel() throws IOException;
|
RawChannel rawChannel() throws IOException;
|
||||||
|
void closeRawChannel() throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RawEvent {
|
interface RawEvent {
|
||||||
|
|||||||
@@ -25,7 +25,11 @@
|
|||||||
|
|
||||||
package com.sun.net.httpserver;
|
package com.sun.net.httpserver;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BasicAuthenticator provides an implementation of HTTP Basic
|
* BasicAuthenticator provides an implementation of HTTP Basic
|
||||||
@@ -35,15 +39,44 @@ import java.util.Base64;
|
|||||||
*/
|
*/
|
||||||
public abstract class BasicAuthenticator extends Authenticator {
|
public abstract class BasicAuthenticator extends Authenticator {
|
||||||
|
|
||||||
protected String realm;
|
protected final String realm;
|
||||||
|
protected final Charset charset;
|
||||||
|
private final boolean isUTF8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BasicAuthenticator for the given HTTP realm
|
* Creates a BasicAuthenticator for the given HTTP realm.
|
||||||
|
* The Basic authentication credentials (username and password) are decoded
|
||||||
|
* using the platform's {@link Charset#defaultCharset() default character set}.
|
||||||
|
*
|
||||||
* @param realm The HTTP Basic authentication realm
|
* @param realm The HTTP Basic authentication realm
|
||||||
* @throws NullPointerException if the realm is an empty string
|
* @throws NullPointerException if realm is {@code null}
|
||||||
|
* @throws IllegalArgumentException if realm is an empty string
|
||||||
*/
|
*/
|
||||||
public BasicAuthenticator (String realm) {
|
public BasicAuthenticator (String realm) {
|
||||||
|
this(realm, Charset.defaultCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BasicAuthenticator for the given HTTP realm and using the
|
||||||
|
* given {@link Charset} to decode the Basic authentication credentials
|
||||||
|
* (username and password).
|
||||||
|
*
|
||||||
|
* @apiNote {@code UTF-8} is the recommended charset because its usage is
|
||||||
|
* communicated to the client, and therefore more likely to be used also
|
||||||
|
* by the client.
|
||||||
|
*
|
||||||
|
* @param realm The HTTP Basic authentication realm
|
||||||
|
* @param charset The Charset to decode incoming credentials from the client
|
||||||
|
* @throws NullPointerException if realm or charset are {@code null}
|
||||||
|
* @throws IllegalArgumentException if realm is an empty string
|
||||||
|
*/
|
||||||
|
public BasicAuthenticator (String realm, Charset charset) {
|
||||||
|
Objects.requireNonNull(charset);
|
||||||
|
if (realm.isEmpty()) // implicit NPE check
|
||||||
|
throw new IllegalArgumentException("realm must not be empty");
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
this.charset = charset;
|
||||||
|
this.isUTF8 = charset.equals(UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +96,9 @@ public abstract class BasicAuthenticator extends Authenticator {
|
|||||||
String auth = rmap.getFirst ("Authorization");
|
String auth = rmap.getFirst ("Authorization");
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
Headers map = t.getResponseHeaders();
|
Headers map = t.getResponseHeaders();
|
||||||
map.set ("WWW-Authenticate", "Basic realm=" + "\""+realm+"\"");
|
var authString = "Basic realm=" + "\"" + realm + "\"" +
|
||||||
|
(isUTF8 ? " charset=\"UTF-8\"" : "");
|
||||||
|
map.set ("WWW-Authenticate", authString);
|
||||||
return new Authenticator.Retry (401);
|
return new Authenticator.Retry (401);
|
||||||
}
|
}
|
||||||
int sp = auth.indexOf (' ');
|
int sp = auth.indexOf (' ');
|
||||||
@@ -71,7 +106,7 @@ public abstract class BasicAuthenticator extends Authenticator {
|
|||||||
return new Authenticator.Failure (401);
|
return new Authenticator.Failure (401);
|
||||||
}
|
}
|
||||||
byte[] b = Base64.getDecoder().decode(auth.substring(sp+1));
|
byte[] b = Base64.getDecoder().decode(auth.substring(sp+1));
|
||||||
String userpass = new String (b);
|
String userpass = new String (b, charset);
|
||||||
int colon = userpass.indexOf (':');
|
int colon = userpass.indexOf (':');
|
||||||
String uname = userpass.substring (0, colon);
|
String uname = userpass.substring (0, colon);
|
||||||
String pass = userpass.substring (colon+1);
|
String pass = userpass.substring (colon+1);
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8199849
|
||||||
|
* @library /test/lib
|
||||||
|
* @run main/othervm/timeout=6000 BasicAuthenticatorCharset
|
||||||
|
* @summary Check for correct use of character sets with BasicAuthenticator() authentication
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.*;
|
||||||
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test authentication
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BasicAuthenticatorCharset {
|
||||||
|
|
||||||
|
public static volatile int failCount = 0;
|
||||||
|
|
||||||
|
static final String TEST_USER = "test";
|
||||||
|
static final String UNICODE_PW = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||||
|
|
||||||
|
static Handler testHandler;
|
||||||
|
static HttpServer testHttpServer;
|
||||||
|
static java.net.Authenticator clientAuth;
|
||||||
|
static HttpClient client;
|
||||||
|
|
||||||
|
static class Handler implements HttpHandler {
|
||||||
|
public void handle(HttpExchange t) throws IOException {
|
||||||
|
InputStream is = t.getRequestBody();
|
||||||
|
while (is.read() != -1) ;
|
||||||
|
is.close();
|
||||||
|
t.sendResponseHeaders(200, -1);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ClientAuthenticator extends java.net.Authenticator {
|
||||||
|
public PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(TEST_USER, UNICODE_PW.toCharArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setAuthenticationPW(String path, String realm, String testPW, Charset charset) {
|
||||||
|
HttpContext ctx = testHttpServer.createContext(path, testHandler);
|
||||||
|
BasicAuthenticator auth;
|
||||||
|
if (charset != null) {
|
||||||
|
auth = new BasicAuthenticator(realm, charset) {
|
||||||
|
public boolean checkCredentials(String username, String pw) {
|
||||||
|
return username.equals(TEST_USER) && pw.equals(testPW);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
auth = new BasicAuthenticator(realm) {
|
||||||
|
public boolean checkCredentials(String username, String pw) {
|
||||||
|
return username.equals(TEST_USER) && pw.equals(testPW);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ctx.setAuthenticator(auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connectAndAuth(String path, int expectedStatus) throws Exception {
|
||||||
|
// path is prepended with /old or /new for old and new http client
|
||||||
|
URL oldurl = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(testHttpServer.getAddress().getPort())
|
||||||
|
.path("/old" + path)
|
||||||
|
.toURL();
|
||||||
|
|
||||||
|
URI newuri = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(testHttpServer.getAddress().getPort())
|
||||||
|
.path("/new" + path)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// check old client
|
||||||
|
|
||||||
|
HttpURLConnection testConnection = (HttpURLConnection) oldurl.openConnection(Proxy.NO_PROXY);
|
||||||
|
|
||||||
|
// Check for successful authentication
|
||||||
|
int status = testConnection.getResponseCode();
|
||||||
|
if (status != 401) {
|
||||||
|
InputStream is = testConnection.getInputStream();
|
||||||
|
while (is.read() != -1) ;
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
if (status != expectedStatus) {
|
||||||
|
System.err.println("Error (old): " + path);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(newuri)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
status = -1;
|
||||||
|
try {
|
||||||
|
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
|
||||||
|
status = response.statusCode();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("NEW: " + e);
|
||||||
|
status = 401; // limitation in new API.
|
||||||
|
}
|
||||||
|
if (status != expectedStatus) {
|
||||||
|
System.err.println("Error (new): " + path);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
clientAuth = new ClientAuthenticator();
|
||||||
|
client = HttpClient.newBuilder()
|
||||||
|
.authenticator(clientAuth)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String defaultCharset = System.getProperty("file.encoding");
|
||||||
|
boolean isUTF8 = defaultCharset.equalsIgnoreCase("UTF-8");
|
||||||
|
testHandler = new Handler();
|
||||||
|
InetSocketAddress addr = new InetSocketAddress(0);
|
||||||
|
testHttpServer = HttpServer.create(addr, 0);
|
||||||
|
|
||||||
|
// Set the passing credentials OLD client
|
||||||
|
setAuthenticationPW("/old/test1/", "passingCharset@test.realm", UNICODE_PW, UTF_8);
|
||||||
|
setAuthenticationPW("/old/test2/", "failingCharset@test.realm", UNICODE_PW, ISO_8859_1);
|
||||||
|
setAuthenticationPW("/old/test3/", "defaultCharset@test.realm", UNICODE_PW, null);
|
||||||
|
|
||||||
|
// Set the passing credentials NEW client
|
||||||
|
setAuthenticationPW("/new/test1/", "passingCharset@test.realm", UNICODE_PW, UTF_8);
|
||||||
|
setAuthenticationPW("/new/test2/", "failingCharset@test.realm", UNICODE_PW, ISO_8859_1);
|
||||||
|
setAuthenticationPW("/new/test3/", "defaultCharset@test.realm", UNICODE_PW, null);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
testHttpServer.setExecutor(executor);
|
||||||
|
testHttpServer.start();
|
||||||
|
java.net.Authenticator.setDefault(clientAuth);
|
||||||
|
|
||||||
|
connectAndAuth("/test1/passingCharset.html", 200);
|
||||||
|
connectAndAuth("/test2/failingCharset.html", 401);
|
||||||
|
if (isUTF8) {
|
||||||
|
connectAndAuth("/test3/defaultCharset.html", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
testHttpServer.stop(2);
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
// should fail once with UNICODE_PW and unsupporting character set
|
||||||
|
if (failCount > 0)
|
||||||
|
throw new RuntimeException("Fail count : " + failCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
229
test/jdk/com/sun/net/httpserver/bugs/8199849/ParamTest.java
Normal file
229
test/jdk/com/sun/net/httpserver/bugs/8199849/ParamTest.java
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8199849
|
||||||
|
* @summary
|
||||||
|
* @library /test/lib
|
||||||
|
* @run main/othervm ParamTest
|
||||||
|
* @run main/othervm -Djava.net.preferIPv6Addresses=true ParamTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ParamTest {
|
||||||
|
|
||||||
|
static final String[] variants = {
|
||||||
|
" charset=utf-8",
|
||||||
|
" charset=UtF-8",
|
||||||
|
" charset=\"utF-8\"",
|
||||||
|
" charset=\"UtF-8\""
|
||||||
|
};
|
||||||
|
|
||||||
|
static final int LOOPS = variants.length;
|
||||||
|
|
||||||
|
volatile static boolean error = false;
|
||||||
|
|
||||||
|
static class BasicServer extends Thread {
|
||||||
|
|
||||||
|
final ServerSocket server;
|
||||||
|
|
||||||
|
Socket s;
|
||||||
|
InputStream is;
|
||||||
|
OutputStream os;
|
||||||
|
|
||||||
|
static final String realm = "wallyworld";
|
||||||
|
|
||||||
|
String reply1 = "HTTP/1.1 401 Unauthorized\r\n"+
|
||||||
|
"WWW-Authenticate: Basic realm=\""+realm+"\"\r\n";
|
||||||
|
|
||||||
|
String reply2 = "HTTP/1.1 200 OK\r\n"+
|
||||||
|
"Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
|
||||||
|
"Server: Apache/1.3.14 (Unix)\r\n" +
|
||||||
|
"Connection: close\r\n" +
|
||||||
|
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||||
|
"Content-Length: 10\r\n\r\n";
|
||||||
|
|
||||||
|
BasicServer(ServerSocket s) {
|
||||||
|
server = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
String readHeaders(Socket sock) throws IOException {
|
||||||
|
InputStream is = sock.getInputStream();
|
||||||
|
String s = "";
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
while (!s.endsWith("\r\n\r\n")) {
|
||||||
|
int c = is.read(buf);
|
||||||
|
if (c == -1)
|
||||||
|
return s;
|
||||||
|
String f = new String(buf, 0, c, StandardCharsets.ISO_8859_1);
|
||||||
|
s = s + f;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check(String s, int iteration) {
|
||||||
|
if (s.indexOf(encodedAuthString) == -1) {
|
||||||
|
System.err.printf("On iteration %d, wrong auth string received %s\n", iteration, s);
|
||||||
|
error = true;
|
||||||
|
} else {
|
||||||
|
System.err.println("check: correct auth string received");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
for (int j = 0; j < 2; j++)
|
||||||
|
for (int i = 0; i < LOOPS; i++) {
|
||||||
|
System.out.println("Server 1: accept");
|
||||||
|
s = server.accept();
|
||||||
|
readHeaders(s);
|
||||||
|
System.out.println("accepted");
|
||||||
|
os = s.getOutputStream();
|
||||||
|
String str = reply1 + variants[i] + "\r\n\r\n";
|
||||||
|
os.write(str.getBytes());
|
||||||
|
|
||||||
|
System.out.println("Server 2: accept");
|
||||||
|
Socket s1 = server.accept();
|
||||||
|
String request = readHeaders(s1);
|
||||||
|
check(request, i);
|
||||||
|
System.out.println("accepted");
|
||||||
|
os = s1.getOutputStream();
|
||||||
|
os.write((reply2 + "HelloWorld").getBytes());
|
||||||
|
os.flush();
|
||||||
|
s.close();
|
||||||
|
s1.close();
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void finished() {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String password = "Selam D\u00fcnya.";
|
||||||
|
|
||||||
|
// "user : <password above>" encoded in UTF-8 and converted to Base 64
|
||||||
|
|
||||||
|
static final String encodedAuthString = "dXNlcjpTZWxhbSBEw7xueWEu";
|
||||||
|
|
||||||
|
static class MyAuthenticator extends Authenticator {
|
||||||
|
MyAuthenticator() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
System.out.println("Auth called");
|
||||||
|
return (new PasswordAuthentication ("user", password.toCharArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void read(InputStream is) throws IOException {
|
||||||
|
int c;
|
||||||
|
System.out.println("reading");
|
||||||
|
while ((c=is.read()) != -1) {
|
||||||
|
System.out.write(c);
|
||||||
|
}
|
||||||
|
System.out.println("");
|
||||||
|
System.out.println("finished reading");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) throws Exception {
|
||||||
|
MyAuthenticator auth = new MyAuthenticator();
|
||||||
|
Authenticator.setDefault(auth);
|
||||||
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
|
ServerSocket ss = new ServerSocket();
|
||||||
|
ss.bind(new InetSocketAddress(loopback, 0));
|
||||||
|
int port = ss.getLocalPort();
|
||||||
|
BasicServer server = new BasicServer(ss);
|
||||||
|
synchronized (server) {
|
||||||
|
server.start();
|
||||||
|
System.out.println("client 1");
|
||||||
|
String base = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(port)
|
||||||
|
.path("/")
|
||||||
|
.build()
|
||||||
|
.toString();
|
||||||
|
URL url = new URL(base + "d1/d2/d3/foo.html");
|
||||||
|
|
||||||
|
for (int i = 0; i < LOOPS; i++) {
|
||||||
|
URLConnection urlc = url.openConnection(Proxy.NO_PROXY);
|
||||||
|
InputStream is = urlc.getInputStream();
|
||||||
|
read(is);
|
||||||
|
System.out.println("Client: waiting for notify");
|
||||||
|
server.wait();
|
||||||
|
System.out.println("Client: continue");
|
||||||
|
// check if authenticator was called once (ok) or twice (not)
|
||||||
|
if (error) {
|
||||||
|
System.err.println("Error old client iteration " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
URI uri = url.toURI();
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.authenticator(auth)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest
|
||||||
|
.newBuilder(uri)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (int i = 0; i < LOOPS; i++) {
|
||||||
|
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
|
||||||
|
int status = response.statusCode();
|
||||||
|
if (status != 200) {
|
||||||
|
System.err.printf("Error new client (%d) iteration ",
|
||||||
|
status, i);
|
||||||
|
error = true;
|
||||||
|
} else
|
||||||
|
System.err.println("New client ok iteration " + i);
|
||||||
|
System.out.println("New Client: waiting for notify");
|
||||||
|
server.wait();
|
||||||
|
System.out.println("New Client: continue");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8199849
|
||||||
|
* @library /test/lib
|
||||||
|
* @summary Checks that unicode bytes are being handled correctly
|
||||||
|
* @run main/othervm -Dfile.encoding=UTF_8 TestHttpUnicode
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.*;
|
||||||
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
public class TestHttpUnicode {
|
||||||
|
|
||||||
|
private static final String TEST_USER = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||||
|
private static final String TEST_PW = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||||
|
|
||||||
|
static class ClientAuthenticator extends java.net.Authenticator {
|
||||||
|
public PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(TEST_USER, TEST_PW.toCharArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Handler implements HttpHandler {
|
||||||
|
public void handle(HttpExchange t) throws IOException {
|
||||||
|
InputStream is = t.getRequestBody();
|
||||||
|
while (is.read() != -1) ;
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
HttpPrincipal p = t.getPrincipal();
|
||||||
|
if (p.getUsername().equals(TEST_USER)) {
|
||||||
|
t.sendResponseHeaders(200, -1);
|
||||||
|
}
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
HttpServer testHttpServer = null;
|
||||||
|
try {
|
||||||
|
InetSocketAddress loopbackAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
||||||
|
testHttpServer = HttpServer.create(loopbackAddress, 0);
|
||||||
|
HttpContext context = testHttpServer.createContext("/test", new Handler());
|
||||||
|
System.setProperty("http.maxRedirects", "3");
|
||||||
|
|
||||||
|
BasicAuthenticator serverAuthenticator = new BasicAuthenticator("authCharacterSet@test.realm") {
|
||||||
|
public boolean checkCredentials(String username, String pw) {
|
||||||
|
return username.equals(TEST_USER) && pw.equals(TEST_PW);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
context.setAuthenticator(serverAuthenticator);
|
||||||
|
java.net.Authenticator.setDefault(new ClientAuthenticator());
|
||||||
|
|
||||||
|
testHttpServer.start();
|
||||||
|
URL url = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(testHttpServer.getAddress().getPort())
|
||||||
|
.path("/test/authCharacterSet.html")
|
||||||
|
.toURL();
|
||||||
|
HttpURLConnection testConnection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
|
||||||
|
|
||||||
|
// Authenication CHECK
|
||||||
|
if (testConnection.getResponseCode() == 401) {
|
||||||
|
throw new RuntimeException("Test Authentication failed with HTTP Status 401.");
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream is = testConnection.getInputStream();
|
||||||
|
while (is.read() != -1) ;
|
||||||
|
} finally {
|
||||||
|
testHttpServer.stop(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
298
test/jdk/java/net/httpclient/AuthFilterCacheTest.java
Normal file
298
test/jdk/java/net/httpclient/AuthFilterCacheTest.java
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.*;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import com.sun.net.httpserver.HttpsConfigurator;
|
||||||
|
import com.sun.net.httpserver.HttpsServer;
|
||||||
|
import org.testng.annotations.AfterClass;
|
||||||
|
import org.testng.annotations.BeforeClass;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8232853
|
||||||
|
* @summary AuthenticationFilter.Cache::remove may throw ConcurrentModificationException
|
||||||
|
* @library /lib/testlibrary/ http2/server
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer
|
||||||
|
* @modules java.net.http/jdk.internal.net.http.common
|
||||||
|
* java.net.http/jdk.internal.net.http.frame
|
||||||
|
* java.net.http/jdk.internal.net.http.hpack
|
||||||
|
* java.logging
|
||||||
|
* java.base/sun.net.www.http
|
||||||
|
* java.base/sun.net.www
|
||||||
|
* java.base/sun.net
|
||||||
|
* @run testng/othervm -Dtest.requiresHost=true
|
||||||
|
* -Djdk.httpclient.HttpClient.log=headers
|
||||||
|
* -Djdk.internal.httpclient.debug=false
|
||||||
|
* AuthFilterCacheTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class AuthFilterCacheTest implements HttpServerAdapters {
|
||||||
|
|
||||||
|
static final String RESPONSE_BODY = "Hello World!";
|
||||||
|
static final int REQUEST_COUNT = 5;
|
||||||
|
static final int URI_COUNT = 6;
|
||||||
|
static final CyclicBarrier barrier = new CyclicBarrier(REQUEST_COUNT * URI_COUNT);
|
||||||
|
static final SSLContext context;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
context = new jdk.testlibrary.SimpleSSLContext().get();
|
||||||
|
SSLContext.setDefault(context);
|
||||||
|
} catch (Exception x) {
|
||||||
|
throw new ExceptionInInitializerError(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpTestServer http1Server;
|
||||||
|
HttpTestServer http2Server;
|
||||||
|
HttpTestServer https1Server;
|
||||||
|
HttpTestServer https2Server;
|
||||||
|
DigestEchoServer.TunnelingProxy proxy;
|
||||||
|
URI http1URI;
|
||||||
|
URI https1URI;
|
||||||
|
URI http2URI;
|
||||||
|
URI https2URI;
|
||||||
|
InetSocketAddress proxyAddress;
|
||||||
|
ProxySelector proxySelector;
|
||||||
|
MyAuthenticator auth;
|
||||||
|
HttpClient client;
|
||||||
|
Executor executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
@DataProvider(name = "uris")
|
||||||
|
Object[][] testURIs() {
|
||||||
|
Object[][] uris = new Object[][]{
|
||||||
|
{List.of(http1URI.resolve("direct/orig/"),
|
||||||
|
https1URI.resolve("direct/orig/"),
|
||||||
|
https1URI.resolve("proxy/orig/"),
|
||||||
|
http2URI.resolve("direct/orig/"),
|
||||||
|
https2URI.resolve("direct/orig/"),
|
||||||
|
https2URI.resolve("proxy/orig/"))}
|
||||||
|
};
|
||||||
|
return uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient newHttpClient(ProxySelector ps, Authenticator auth) {
|
||||||
|
HttpClient.Builder builder = HttpClient
|
||||||
|
.newBuilder()
|
||||||
|
.sslContext(context)
|
||||||
|
.authenticator(auth)
|
||||||
|
.proxy(ps);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
try {
|
||||||
|
InetSocketAddress sa =
|
||||||
|
new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
||||||
|
auth = new MyAuthenticator();
|
||||||
|
|
||||||
|
// HTTP/1.1
|
||||||
|
HttpServer server1 = HttpServer.create(sa, 0);
|
||||||
|
server1.setExecutor(executor);
|
||||||
|
http1Server = HttpTestServer.of(server1);
|
||||||
|
http1Server.addHandler(new TestHandler(), "/AuthFilterCacheTest/http1/");
|
||||||
|
http1Server.start();
|
||||||
|
http1URI = new URI("http://" + http1Server.serverAuthority()
|
||||||
|
+ "/AuthFilterCacheTest/http1/");
|
||||||
|
|
||||||
|
// HTTPS/1.1
|
||||||
|
HttpsServer sserver1 = HttpsServer.create(sa, 100);
|
||||||
|
sserver1.setExecutor(executor);
|
||||||
|
sserver1.setHttpsConfigurator(new HttpsConfigurator(context));
|
||||||
|
https1Server = HttpTestServer.of(sserver1);
|
||||||
|
https1Server.addHandler(new TestHandler(), "/AuthFilterCacheTest/https1/");
|
||||||
|
https1Server.start();
|
||||||
|
https1URI = new URI("https://" + https1Server.serverAuthority()
|
||||||
|
+ "/AuthFilterCacheTest/https1/");
|
||||||
|
|
||||||
|
// HTTP/2.0
|
||||||
|
http2Server = HttpTestServer.of(
|
||||||
|
new Http2TestServer("localhost", false, 0));
|
||||||
|
http2Server.addHandler(new TestHandler(), "/AuthFilterCacheTest/http2/");
|
||||||
|
http2Server.start();
|
||||||
|
http2URI = new URI("http://" + http2Server.serverAuthority()
|
||||||
|
+ "/AuthFilterCacheTest/http2/");
|
||||||
|
|
||||||
|
// HTTPS/2.0
|
||||||
|
https2Server = HttpTestServer.of(
|
||||||
|
new Http2TestServer("localhost", true, 0));
|
||||||
|
https2Server.addHandler(new TestHandler(), "/AuthFilterCacheTest/https2/");
|
||||||
|
https2Server.start();
|
||||||
|
https2URI = new URI("https://" + https2Server.serverAuthority()
|
||||||
|
+ "/AuthFilterCacheTest/https2/");
|
||||||
|
|
||||||
|
proxy = DigestEchoServer.createHttpsProxyTunnel(
|
||||||
|
DigestEchoServer.HttpAuthSchemeType.NONE);
|
||||||
|
proxyAddress = proxy.getProxyAddress();
|
||||||
|
proxySelector = new HttpProxySelector(proxyAddress);
|
||||||
|
client = newHttpClient(proxySelector, auth);
|
||||||
|
|
||||||
|
System.out.println("Setup: done");
|
||||||
|
} catch (Exception x) {
|
||||||
|
tearDown();
|
||||||
|
throw x;
|
||||||
|
} catch (Error e) {
|
||||||
|
tearDown();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public void tearDown() {
|
||||||
|
proxy = stop(proxy, DigestEchoServer.TunnelingProxy::stop);
|
||||||
|
http1Server = stop(http1Server, HttpTestServer::stop);
|
||||||
|
https1Server = stop(https1Server, HttpTestServer::stop);
|
||||||
|
http2Server = stop(http2Server, HttpTestServer::stop);
|
||||||
|
https2Server = stop(https2Server, HttpTestServer::stop);
|
||||||
|
client = null;
|
||||||
|
|
||||||
|
System.out.println("Teardown: done");
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Stoppable<T> {
|
||||||
|
void stop(T service) throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> T stop(T service, Stoppable<T> stop) {
|
||||||
|
try {
|
||||||
|
if (service != null) stop.stop(service);
|
||||||
|
} catch (Throwable x) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HttpProxySelector extends ProxySelector {
|
||||||
|
private static final List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
|
||||||
|
private final List<Proxy> proxyList;
|
||||||
|
|
||||||
|
HttpProxySelector(InetSocketAddress proxyAddress) {
|
||||||
|
proxyList = List.of(new Proxy(Proxy.Type.HTTP, proxyAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Proxy> select(URI uri) {
|
||||||
|
// Our proxy only supports tunneling
|
||||||
|
if (uri.getScheme().equalsIgnoreCase("https")) {
|
||||||
|
if (uri.getPath().contains("/proxy/")) {
|
||||||
|
return proxyList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_PROXY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||||
|
System.err.println("Connection to proxy failed: " + ioe);
|
||||||
|
System.err.println("Proxy: " + sa);
|
||||||
|
System.err.println("\tURI: " + uri);
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestHandler implements HttpTestHandler {
|
||||||
|
static final AtomicLong respCounter = new AtomicLong();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpTestExchange t) throws IOException {
|
||||||
|
var count = respCounter.incrementAndGet();
|
||||||
|
System.out.println("Responses handled: " + count);
|
||||||
|
t.getRequestBody().readAllBytes();
|
||||||
|
|
||||||
|
if (t.getRequestMethod().equalsIgnoreCase("GET")) {
|
||||||
|
if (!t.getRequestHeaders().containsKey("Authorization")) {
|
||||||
|
t.getResponseHeaders()
|
||||||
|
.addHeader("WWW-Authenticate", "Basic realm=\"Earth\"");
|
||||||
|
t.sendResponseHeaders(401, 0);
|
||||||
|
} else {
|
||||||
|
byte[] resp = RESPONSE_BODY.getBytes(StandardCharsets.UTF_8);
|
||||||
|
t.sendResponseHeaders(200, resp.length);
|
||||||
|
try {
|
||||||
|
barrier.await();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
t.getResponseBody().write(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void doClient(List<URI> uris) {
|
||||||
|
assert uris.size() == URI_COUNT;
|
||||||
|
barrier.reset();
|
||||||
|
System.out.println("Client opening connection to: " + uris.toString());
|
||||||
|
|
||||||
|
List<CompletableFuture<HttpResponse<String>>> cfs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < REQUEST_COUNT; i++) {
|
||||||
|
for (URI uri : uris) {
|
||||||
|
HttpRequest req = HttpRequest.newBuilder()
|
||||||
|
.uri(uri)
|
||||||
|
.build();
|
||||||
|
cfs.add(client.sendAsync(req, HttpResponse.BodyHandlers.ofString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(cfs.toArray(new CompletableFuture[0])).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyAuthenticator extends Authenticator {
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
MyAuthenticator() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
System.out.println("Authenticator called: " + ++count);
|
||||||
|
return (new PasswordAuthentication("user" + count,
|
||||||
|
("passwordNotCheckedAnyway" + count).toCharArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "uris")
|
||||||
|
public void test(List<URI> uris) throws Exception {
|
||||||
|
System.out.println("Server listening at " + uris.toString());
|
||||||
|
doClient(uris);
|
||||||
|
}
|
||||||
|
}
|
||||||
159
test/jdk/java/net/httpclient/AuthSchemesTest.java
Normal file
159
test/jdk/java/net/httpclient/AuthSchemesTest.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8217237
|
||||||
|
* @modules java.net.http
|
||||||
|
* @run main/othervm AuthSchemesTest
|
||||||
|
* @summary HttpClient does not deal well with multi-valued WWW-Authenticate challenge headers
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.*;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
|
||||||
|
public class AuthSchemesTest {
|
||||||
|
static class BasicServer extends Thread {
|
||||||
|
|
||||||
|
ServerSocket server;
|
||||||
|
|
||||||
|
Socket s;
|
||||||
|
InputStream is;
|
||||||
|
OutputStream os;
|
||||||
|
static final String RESPONSE = "Hello world";
|
||||||
|
static final String respLength = Integer.toString(RESPONSE.length());
|
||||||
|
static final String realm = "wally world";
|
||||||
|
|
||||||
|
String reply1 = "HTTP/1.1 401 Unauthorized\r\n"+
|
||||||
|
"WWW-Authenticate: BarScheme\r\n" +
|
||||||
|
"WWW-Authenticate: FooScheme realm=\""+realm+"\"\r\n" +
|
||||||
|
"WWW-Authenticate: Basic realm=\""+realm+"\"\r\n" +
|
||||||
|
"WWW-Authenticate: WoofScheme\r\n\r\n";
|
||||||
|
|
||||||
|
String reply2 = "HTTP/1.1 200 OK\r\n"+
|
||||||
|
"Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
|
||||||
|
"Server: Apache/1.3.14 (Unix)\r\n" +
|
||||||
|
"Connection: close\r\n" +
|
||||||
|
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||||
|
"Content-Length: " + respLength + "\r\n\r\n";
|
||||||
|
|
||||||
|
BasicServer(ServerSocket s) {
|
||||||
|
server = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
String response() {
|
||||||
|
return RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void readAll(Socket s) throws IOException {
|
||||||
|
byte[] buf = new byte [128];
|
||||||
|
InputStream is = s.getInputStream();
|
||||||
|
s.setSoTimeout(1000);
|
||||||
|
try {
|
||||||
|
while (is.read(buf) > 0) ;
|
||||||
|
} catch (SocketTimeoutException x) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
System.out.println("Server 1: accept");
|
||||||
|
s = server.accept();
|
||||||
|
System.out.println("accepted");
|
||||||
|
os = s.getOutputStream();
|
||||||
|
os.write(reply1.getBytes());
|
||||||
|
readAll(s);
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
System.out.println("Server 2: accept");
|
||||||
|
s = server.accept();
|
||||||
|
System.out.println("accepted");
|
||||||
|
os = s.getOutputStream();
|
||||||
|
os.write((reply2+RESPONSE).getBytes());
|
||||||
|
readAll(s);
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isfinished = false;
|
||||||
|
|
||||||
|
public synchronized void finished() {
|
||||||
|
isfinished = true;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void waitforfinish() {
|
||||||
|
while (!isfinished) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Auth extends Authenticator {
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication("user", new char[] {'a','b','c'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
ServerSocket serversocket = null;
|
||||||
|
BasicServer server = null;
|
||||||
|
Auth authenticator = new Auth();
|
||||||
|
|
||||||
|
serversocket = new ServerSocket(0, 10, InetAddress.getLoopbackAddress());
|
||||||
|
int port = serversocket.getLocalPort();
|
||||||
|
server = new BasicServer(serversocket);
|
||||||
|
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.authenticator(authenticator)
|
||||||
|
.build();
|
||||||
|
server.start();
|
||||||
|
URI uri = URI.create("http://127.0.0.1:" + port + "/foo");
|
||||||
|
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200 || !response.body().equals(server.response())) {
|
||||||
|
System.out.println("Status code = " + response.statusCode());
|
||||||
|
serversocket.close();
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
serversocket.close();
|
||||||
|
server.waitforfinish();
|
||||||
|
System.out.println("OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -25,6 +25,9 @@ import java.net.*;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A minimal proxy server that supports CONNECT tunneling. It does not do
|
* A minimal proxy server that supports CONNECT tunneling. It does not do
|
||||||
@@ -37,6 +40,18 @@ public class ProxyServer extends Thread implements Closeable {
|
|||||||
ServerSocket listener;
|
ServerSocket listener;
|
||||||
int port;
|
int port;
|
||||||
volatile boolean debug;
|
volatile boolean debug;
|
||||||
|
private final Credentials credentials; // may be null
|
||||||
|
|
||||||
|
private static class Credentials {
|
||||||
|
private final String name;
|
||||||
|
private final String password;
|
||||||
|
private Credentials(String name, String password) {
|
||||||
|
this.name = name;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
public String name() { return name; }
|
||||||
|
public String password() { return password; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create proxy on port (zero means don't care). Call getPort()
|
* Create proxy on port (zero means don't care). Call getPort()
|
||||||
@@ -46,19 +61,42 @@ public class ProxyServer extends Thread implements Closeable {
|
|||||||
this(port, false);
|
this(port, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProxyServer(Integer port, Boolean debug) throws IOException {
|
public ProxyServer(Integer port,
|
||||||
|
Boolean debug,
|
||||||
|
String username,
|
||||||
|
String password)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(port, debug, new Credentials(username, password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyServer(Integer port,
|
||||||
|
Boolean debug)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(port, debug, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyServer(Integer port,
|
||||||
|
Boolean debug,
|
||||||
|
Credentials credentials)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
this.debug = debug;
|
this.debug = debug;
|
||||||
listener = new ServerSocket();
|
listener = new ServerSocket();
|
||||||
listener.setReuseAddress(false);
|
listener.setReuseAddress(false);
|
||||||
listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
|
listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
|
||||||
this.port = listener.getLocalPort();
|
this.port = listener.getLocalPort();
|
||||||
|
this.credentials = credentials;
|
||||||
setName("ProxyListener");
|
setName("ProxyListener");
|
||||||
setDaemon(true);
|
setDaemon(true);
|
||||||
connections = new LinkedList<>();
|
connections = new LinkedList<>();
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProxyServer(String s) { }
|
public ProxyServer(String s) {
|
||||||
|
credentials = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the port number this proxy is listening on
|
* Returns the port number this proxy is listening on
|
||||||
@@ -194,16 +232,69 @@ public class ProxyServer extends Thread implements Closeable {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks credentials in the request against those allowable by the proxy.
|
||||||
|
private boolean authorized(Credentials credentials,
|
||||||
|
List<String> requestHeaders) {
|
||||||
|
List<String> authorization = requestHeaders.stream()
|
||||||
|
.filter(n -> n.toLowerCase(Locale.US).startsWith("proxy-authorization"))
|
||||||
|
.collect(toList());
|
||||||
|
|
||||||
|
if (authorization.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (authorization.size() != 1) {
|
||||||
|
throw new IllegalStateException("Authorization unexpected count:" + authorization);
|
||||||
|
}
|
||||||
|
String value = authorization.get(0).substring("proxy-authorization".length()).trim();
|
||||||
|
if (!value.startsWith(":"))
|
||||||
|
throw new IllegalStateException("Authorization malformed: " + value);
|
||||||
|
value = value.substring(1).trim();
|
||||||
|
|
||||||
|
if (!value.startsWith("Basic "))
|
||||||
|
throw new IllegalStateException("Authorization not Basic: " + value);
|
||||||
|
|
||||||
|
value = value.substring("Basic ".length());
|
||||||
|
String values = new String(Base64.getDecoder().decode(value), UTF_8);
|
||||||
|
int sep = values.indexOf(':');
|
||||||
|
if (sep < 1) {
|
||||||
|
throw new IllegalStateException("Authorization no colon: " + values);
|
||||||
|
}
|
||||||
|
String name = values.substring(0, sep);
|
||||||
|
String password = values.substring(sep + 1);
|
||||||
|
|
||||||
|
if (name.equals(credentials.name()) && password.equals(credentials.password()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
try {
|
try {
|
||||||
byte[] buf = readHeaders(clientIn);
|
byte[] buf;
|
||||||
int p = findCRLF(buf);
|
while (true) {
|
||||||
if (p == -1) {
|
buf = readHeaders(clientIn);
|
||||||
close();
|
if (findCRLF(buf) == -1) {
|
||||||
return;
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> headers = asList(new String(buf, UTF_8).split("\r\n"));
|
||||||
|
// check authorization credentials, if required by the server
|
||||||
|
if (credentials != null && !authorized(credentials, headers)) {
|
||||||
|
String resp = "HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
||||||
|
"Content-Length: 0\r\n" +
|
||||||
|
"Proxy-Authenticate: Basic realm=\"proxy realm\"\r\n\r\n";
|
||||||
|
|
||||||
|
clientOut.write(resp.getBytes(UTF_8));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int p = findCRLF(buf);
|
||||||
String cmd = new String(buf, 0, p, "US-ASCII");
|
String cmd = new String(buf, 0, p, "US-ASCII");
|
||||||
String[] params = cmd.split(" ");
|
String[] params = cmd.split(" ");
|
||||||
|
|
||||||
if (params[0].equals("CONNECT")) {
|
if (params[0].equals("CONNECT")) {
|
||||||
doTunnel(params[1]);
|
doTunnel(params[1]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,610 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import javax.net.ServerSocketFactory;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketOption;
|
||||||
|
import java.net.StandardSocketOptions;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.charset.CharacterCodingException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.lang.System.err;
|
||||||
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy WebSocket Server, which supports TLS.
|
||||||
|
* By default the dummy webserver uses a plain TCP connection,
|
||||||
|
* but it can use a TLS connection if secure() is called before
|
||||||
|
* open(). It will use the default SSL context.
|
||||||
|
*
|
||||||
|
* Performs simpler version of the WebSocket Opening Handshake over HTTP (i.e.
|
||||||
|
* no proxying, cookies, etc.) Supports sequential connections, one at a time,
|
||||||
|
* i.e. in order for a client to connect to the server the previous client must
|
||||||
|
* disconnect first.
|
||||||
|
*
|
||||||
|
* Expected client request:
|
||||||
|
*
|
||||||
|
* GET /chat HTTP/1.1
|
||||||
|
* Host: server.example.com
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||||
|
* Origin: http://example.com
|
||||||
|
* Sec-WebSocket-Protocol: chat, superchat
|
||||||
|
* Sec-WebSocket-Version: 13
|
||||||
|
*
|
||||||
|
* This server response:
|
||||||
|
*
|
||||||
|
* HTTP/1.1 101 Switching Protocols
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
* Sec-WebSocket-Protocol: chat
|
||||||
|
*/
|
||||||
|
public class DummySecureWebSocketServer implements Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates some of the SocketChannel APIs over a Socket
|
||||||
|
* instance.
|
||||||
|
*/
|
||||||
|
public static class WebSocketChannel implements AutoCloseable {
|
||||||
|
interface Reader {
|
||||||
|
int read(ByteBuffer buf) throws IOException;
|
||||||
|
}
|
||||||
|
interface Writer {
|
||||||
|
void write(ByteBuffer buf) throws IOException;
|
||||||
|
}
|
||||||
|
interface Config {
|
||||||
|
<T> void setOption(SocketOption<T> option, T value) throws IOException;
|
||||||
|
}
|
||||||
|
interface Closer {
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
|
final AutoCloseable channel;
|
||||||
|
final Reader reader;
|
||||||
|
final Writer writer;
|
||||||
|
final Config config;
|
||||||
|
final Closer closer;
|
||||||
|
WebSocketChannel(AutoCloseable channel, Reader reader, Writer writer, Config config, Closer closer) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.reader = reader;
|
||||||
|
this.writer = writer;
|
||||||
|
this.config = config;
|
||||||
|
this.closer = closer;
|
||||||
|
}
|
||||||
|
public void close() throws IOException {
|
||||||
|
closer.close();
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
return channel.toString();
|
||||||
|
}
|
||||||
|
public int read(ByteBuffer bb) throws IOException {
|
||||||
|
return reader.read(bb);
|
||||||
|
}
|
||||||
|
public void write(ByteBuffer bb) throws IOException {
|
||||||
|
writer.write(bb);
|
||||||
|
}
|
||||||
|
public <T> void setOption(SocketOption<T> option, T value) throws IOException {
|
||||||
|
config.setOption(option, value);
|
||||||
|
}
|
||||||
|
public static WebSocketChannel of(Socket s) {
|
||||||
|
Reader reader = (bb) -> DummySecureWebSocketServer.read(s.getInputStream(), bb);
|
||||||
|
Writer writer = (bb) -> DummySecureWebSocketServer.write(s.getOutputStream(), bb);
|
||||||
|
return new WebSocketChannel(s, reader, writer, s::setOption, s::close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates some of the ServerSocketChannel APIs over a ServerSocket
|
||||||
|
* instance.
|
||||||
|
*/
|
||||||
|
public static class WebServerSocketChannel implements AutoCloseable {
|
||||||
|
interface Accepter {
|
||||||
|
WebSocketChannel accept() throws IOException;
|
||||||
|
}
|
||||||
|
interface Binder {
|
||||||
|
void bind(SocketAddress address) throws IOException;
|
||||||
|
}
|
||||||
|
interface Config {
|
||||||
|
<T> void setOption(SocketOption<T> option, T value) throws IOException;
|
||||||
|
}
|
||||||
|
interface Closer {
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
|
interface Addressable {
|
||||||
|
SocketAddress getLocalAddress() throws IOException;
|
||||||
|
}
|
||||||
|
final AutoCloseable server;
|
||||||
|
final Accepter accepter;
|
||||||
|
final Binder binder;
|
||||||
|
final Addressable address;
|
||||||
|
final Config config;
|
||||||
|
final Closer closer;
|
||||||
|
WebServerSocketChannel(AutoCloseable server,
|
||||||
|
Accepter accepter,
|
||||||
|
Binder binder,
|
||||||
|
Addressable address,
|
||||||
|
Config config,
|
||||||
|
Closer closer) {
|
||||||
|
this.server = server;
|
||||||
|
this.accepter = accepter;
|
||||||
|
this.binder = binder;
|
||||||
|
this.address = address;
|
||||||
|
this.config = config;
|
||||||
|
this.closer = closer;
|
||||||
|
}
|
||||||
|
public void close() throws IOException {
|
||||||
|
closer.close();
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
return server.toString();
|
||||||
|
}
|
||||||
|
public WebSocketChannel accept() throws IOException {
|
||||||
|
return accepter.accept();
|
||||||
|
}
|
||||||
|
public void bind(SocketAddress address) throws IOException {
|
||||||
|
binder.bind(address);
|
||||||
|
}
|
||||||
|
public <T> void setOption(SocketOption<T> option, T value) throws IOException {
|
||||||
|
config.setOption(option, value);
|
||||||
|
}
|
||||||
|
public SocketAddress getLocalAddress() throws IOException {
|
||||||
|
return address.getLocalAddress();
|
||||||
|
}
|
||||||
|
public static WebServerSocketChannel of(ServerSocket ss) {
|
||||||
|
Accepter a = () -> WebSocketChannel.of(ss.accept());
|
||||||
|
return new WebServerSocketChannel(ss, a, ss::bind, ss::getLocalSocketAddress, ss::setOption, ss::close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a secure WebServerSocketChannel
|
||||||
|
static WebServerSocketChannel openWSS() throws IOException {
|
||||||
|
return WebServerSocketChannel.of(SSLServerSocketFactory.getDefault().createServerSocket());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a plain WebServerSocketChannel
|
||||||
|
static WebServerSocketChannel openWS() throws IOException {
|
||||||
|
return WebServerSocketChannel.of(ServerSocketFactory.getDefault().createServerSocket());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int read(InputStream str, ByteBuffer buffer) throws IOException {
|
||||||
|
int len = Math.min(buffer.remaining(), 1024);
|
||||||
|
if (len <= 0) return 0;
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
int res = 0;
|
||||||
|
if (buffer.hasRemaining()) {
|
||||||
|
len = Math.min(len, buffer.remaining());
|
||||||
|
int n = str.read(bytes, 0, len);
|
||||||
|
if (n > 0) {
|
||||||
|
buffer.put(bytes, 0, n);
|
||||||
|
res += n;
|
||||||
|
} else if (res > 0) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write(OutputStream str, ByteBuffer buffer) throws IOException {
|
||||||
|
int len = Math.min(buffer.remaining(), 1024);
|
||||||
|
if (len <= 0) return;
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
int res = 0;
|
||||||
|
int pos = buffer.position();
|
||||||
|
while (buffer.hasRemaining()) {
|
||||||
|
len = Math.min(len, buffer.remaining());
|
||||||
|
buffer.get(bytes, 0, len);
|
||||||
|
str.write(bytes, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicBoolean started = new AtomicBoolean();
|
||||||
|
private final Thread thread;
|
||||||
|
private volatile WebServerSocketChannel ss;
|
||||||
|
private volatile InetSocketAddress address;
|
||||||
|
private volatile boolean secure;
|
||||||
|
private ByteBuffer read = ByteBuffer.allocate(16384);
|
||||||
|
private final CountDownLatch readReady = new CountDownLatch(1);
|
||||||
|
private volatile boolean done;
|
||||||
|
|
||||||
|
private static class Credentials {
|
||||||
|
private final String name;
|
||||||
|
private final String password;
|
||||||
|
private Credentials(String name, String password) {
|
||||||
|
this.name = name;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
public String name() { return name; }
|
||||||
|
public String password() { return password; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummySecureWebSocketServer() {
|
||||||
|
this(defaultMapping(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummySecureWebSocketServer(String username, String password) {
|
||||||
|
this(defaultMapping(), username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummySecureWebSocketServer(BiFunction<List<String>,Credentials,List<String>> mapping,
|
||||||
|
String username,
|
||||||
|
String password) {
|
||||||
|
requireNonNull(mapping);
|
||||||
|
Credentials credentials = username != null ?
|
||||||
|
new Credentials(username, password) : null;
|
||||||
|
|
||||||
|
thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
while (!Thread.currentThread().isInterrupted() && !done) {
|
||||||
|
err.println("Accepting next connection at: " + ss);
|
||||||
|
WebSocketChannel channel = ss.accept();
|
||||||
|
err.println("Accepted: " + channel);
|
||||||
|
try {
|
||||||
|
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||||
|
while (!done) {
|
||||||
|
StringBuilder request = new StringBuilder();
|
||||||
|
if (!readRequest(channel, request)) {
|
||||||
|
throw new IOException("Bad request:[" + request + "]");
|
||||||
|
}
|
||||||
|
List<String> strings = asList(request.toString().split("\r\n"));
|
||||||
|
List<String> response = mapping.apply(strings, credentials);
|
||||||
|
writeResponse(channel, response);
|
||||||
|
|
||||||
|
if (response.get(0).startsWith("HTTP/1.1 401")) {
|
||||||
|
err.println("Sent 401 Authentication response " + channel);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
serve(channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (!done) {
|
||||||
|
err.println("Error in connection: " + channel + ", " + e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
err.println("Closed: " + channel);
|
||||||
|
close(channel);
|
||||||
|
readReady.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ClosedByInterruptException ignored) {
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (!done) {
|
||||||
|
e.printStackTrace(err);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
done = true;
|
||||||
|
close(ss);
|
||||||
|
err.println("Stopped at: " + getURI());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setName("DummySecureWebSocketServer");
|
||||||
|
thread.setDaemon(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be called before open()
|
||||||
|
public DummySecureWebSocketServer secure() {
|
||||||
|
secure = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void read(WebSocketChannel ch) throws IOException {
|
||||||
|
// Read until the thread is interrupted or an error occurred
|
||||||
|
// or the input is shutdown
|
||||||
|
ByteBuffer b = ByteBuffer.allocate(65536);
|
||||||
|
while (ch.read(b) != -1) {
|
||||||
|
b.flip();
|
||||||
|
if (read.remaining() < b.remaining()) {
|
||||||
|
int required = read.capacity() - read.remaining() + b.remaining();
|
||||||
|
int log2required = 32 - Integer.numberOfLeadingZeros(required - 1);
|
||||||
|
ByteBuffer newBuffer = ByteBuffer.allocate(1 << log2required);
|
||||||
|
newBuffer.put(read.flip());
|
||||||
|
read = newBuffer;
|
||||||
|
}
|
||||||
|
read.put(b);
|
||||||
|
b.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void write(WebSocketChannel ch) throws IOException { }
|
||||||
|
|
||||||
|
protected final void serve(WebSocketChannel channel)
|
||||||
|
throws InterruptedException
|
||||||
|
{
|
||||||
|
Thread reader = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
read(channel);
|
||||||
|
} catch (IOException ignored) { }
|
||||||
|
});
|
||||||
|
Thread writer = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
write(channel);
|
||||||
|
} catch (IOException ignored) { }
|
||||||
|
});
|
||||||
|
reader.start();
|
||||||
|
writer.start();
|
||||||
|
try {
|
||||||
|
while (!done) {
|
||||||
|
try {
|
||||||
|
reader.join(500);
|
||||||
|
} catch (InterruptedException x) {
|
||||||
|
if (done) {
|
||||||
|
close(channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.interrupt();
|
||||||
|
try {
|
||||||
|
while (!done) {
|
||||||
|
try {
|
||||||
|
writer.join(500);
|
||||||
|
} catch (InterruptedException x) {
|
||||||
|
if (done) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
writer.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer read() throws InterruptedException {
|
||||||
|
readReady.await();
|
||||||
|
return read.duplicate().asReadOnlyBuffer().flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() throws IOException {
|
||||||
|
err.println("Starting");
|
||||||
|
if (!started.compareAndSet(false, true)) {
|
||||||
|
throw new IllegalStateException("Already started");
|
||||||
|
}
|
||||||
|
ss = secure ? openWSS() : openWS();
|
||||||
|
try {
|
||||||
|
ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
|
||||||
|
address = (InetSocketAddress) ss.getLocalAddress();
|
||||||
|
thread.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
done = true;
|
||||||
|
close(ss);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
err.println("Started at: " + getURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
err.println("Stopping: " + getURI());
|
||||||
|
done = true;
|
||||||
|
thread.interrupt();
|
||||||
|
close(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
URI getURI() {
|
||||||
|
if (!started.get()) {
|
||||||
|
throw new IllegalStateException("Not yet started");
|
||||||
|
}
|
||||||
|
if (!secure) {
|
||||||
|
return URI.create("ws://localhost:" + address.getPort());
|
||||||
|
} else {
|
||||||
|
return URI.create("wss://localhost:" + address.getPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean readRequest(WebSocketChannel channel, StringBuilder request)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(512);
|
||||||
|
while (channel.read(buffer) != -1) {
|
||||||
|
// read the complete HTTP request headers, there should be no body
|
||||||
|
CharBuffer decoded;
|
||||||
|
buffer.flip();
|
||||||
|
try {
|
||||||
|
decoded = ISO_8859_1.newDecoder().decode(buffer);
|
||||||
|
} catch (CharacterCodingException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
request.append(decoded);
|
||||||
|
if (Pattern.compile("\r\n\r\n").matcher(request).find())
|
||||||
|
return true;
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeResponse(WebSocketChannel channel, List<String> response)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
String s = response.stream().collect(Collectors.joining("\r\n"))
|
||||||
|
+ "\r\n\r\n";
|
||||||
|
ByteBuffer encoded;
|
||||||
|
try {
|
||||||
|
encoded = ISO_8859_1.newEncoder().encode(CharBuffer.wrap(s));
|
||||||
|
} catch (CharacterCodingException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
while (encoded.hasRemaining()) {
|
||||||
|
channel.write(encoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BiFunction<List<String>,Credentials,List<String>> defaultMapping() {
|
||||||
|
return (request, credentials) -> {
|
||||||
|
List<String> response = new LinkedList<>();
|
||||||
|
Iterator<String> iterator = request.iterator();
|
||||||
|
if (!iterator.hasNext()) {
|
||||||
|
throw new IllegalStateException("The request is empty");
|
||||||
|
}
|
||||||
|
String statusLine = iterator.next();
|
||||||
|
if (!(statusLine.startsWith("GET /") && statusLine.endsWith(" HTTP/1.1"))) {
|
||||||
|
throw new IllegalStateException
|
||||||
|
("Unexpected status line: " + request.get(0));
|
||||||
|
}
|
||||||
|
response.add("HTTP/1.1 101 Switching Protocols");
|
||||||
|
Map<String, List<String>> requestHeaders = new HashMap<>();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
String header = iterator.next();
|
||||||
|
String[] split = header.split(": ");
|
||||||
|
if (split.length != 2) {
|
||||||
|
throw new IllegalStateException
|
||||||
|
("Unexpected header: " + header
|
||||||
|
+ ", split=" + Arrays.toString(split));
|
||||||
|
}
|
||||||
|
requestHeaders.computeIfAbsent(split[0], k -> new ArrayList<>()).add(split[1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (requestHeaders.containsKey("Sec-WebSocket-Protocol")) {
|
||||||
|
throw new IllegalStateException("Subprotocols are not expected");
|
||||||
|
}
|
||||||
|
if (requestHeaders.containsKey("Sec-WebSocket-Extensions")) {
|
||||||
|
throw new IllegalStateException("Extensions are not expected");
|
||||||
|
}
|
||||||
|
expectHeader(requestHeaders, "Connection", "Upgrade");
|
||||||
|
response.add("Connection: Upgrade");
|
||||||
|
expectHeader(requestHeaders, "Upgrade", "websocket");
|
||||||
|
response.add("Upgrade: websocket");
|
||||||
|
expectHeader(requestHeaders, "Sec-WebSocket-Version", "13");
|
||||||
|
List<String> key = requestHeaders.get("Sec-WebSocket-Key");
|
||||||
|
if (key == null || key.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Sec-WebSocket-Key is missing");
|
||||||
|
}
|
||||||
|
if (key.size() != 1) {
|
||||||
|
throw new IllegalStateException("Sec-WebSocket-Key has too many values : " + key);
|
||||||
|
}
|
||||||
|
MessageDigest sha1 = null;
|
||||||
|
try {
|
||||||
|
sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
String x = key.get(0) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
sha1.update(x.getBytes(ISO_8859_1));
|
||||||
|
String v = Base64.getEncoder().encodeToString(sha1.digest());
|
||||||
|
response.add("Sec-WebSocket-Accept: " + v);
|
||||||
|
|
||||||
|
// check authorization credentials, if required by the server
|
||||||
|
if (credentials != null && !authorized(credentials, requestHeaders)) {
|
||||||
|
response.clear();
|
||||||
|
response.add("HTTP/1.1 401 Unauthorized");
|
||||||
|
response.add("Content-Length: 0");
|
||||||
|
response.add("WWW-Authenticate: Basic realm=\"dummy server realm\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks credentials in the request against those allowable by the server.
|
||||||
|
private static boolean authorized(Credentials credentials,
|
||||||
|
Map<String,List<String>> requestHeaders) {
|
||||||
|
List<String> authorization = requestHeaders.get("Authorization");
|
||||||
|
if (authorization == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (authorization.size() != 1) {
|
||||||
|
throw new IllegalStateException("Authorization unexpected count:" + authorization);
|
||||||
|
}
|
||||||
|
String header = authorization.get(0);
|
||||||
|
if (!header.startsWith("Basic "))
|
||||||
|
throw new IllegalStateException("Authorization not Basic: " + header);
|
||||||
|
|
||||||
|
header = header.substring("Basic ".length());
|
||||||
|
String values = new String(Base64.getDecoder().decode(header), UTF_8);
|
||||||
|
int sep = values.indexOf(':');
|
||||||
|
if (sep < 1) {
|
||||||
|
throw new IllegalStateException("Authorization not colon: " + values);
|
||||||
|
}
|
||||||
|
String name = values.substring(0, sep);
|
||||||
|
String password = values.substring(sep + 1);
|
||||||
|
|
||||||
|
if (name.equals(credentials.name()) && password.equals(credentials.password()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String expectHeader(Map<String, List<String>> headers,
|
||||||
|
String name,
|
||||||
|
String value) {
|
||||||
|
List<String> v = headers.get(name);
|
||||||
|
if (v == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("Expected '%s' header, not present in %s",
|
||||||
|
name, headers));
|
||||||
|
}
|
||||||
|
if (!v.contains(value)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("Expected '%s: %s', actual: '%s: %s'",
|
||||||
|
name, value, name, v)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void close(AutoCloseable... acs) {
|
||||||
|
for (AutoCloseable ac : acs) {
|
||||||
|
try {
|
||||||
|
ac.close();
|
||||||
|
} catch (Exception ignored) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -46,13 +46,14 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Function;
|
import java.util.function.BiFunction;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.lang.System.err;
|
import static java.lang.System.err;
|
||||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@@ -92,12 +93,32 @@ public class DummyWebSocketServer implements Closeable {
|
|||||||
private ByteBuffer read = ByteBuffer.allocate(16384);
|
private ByteBuffer read = ByteBuffer.allocate(16384);
|
||||||
private final CountDownLatch readReady = new CountDownLatch(1);
|
private final CountDownLatch readReady = new CountDownLatch(1);
|
||||||
|
|
||||||
public DummyWebSocketServer() {
|
private static class Credentials {
|
||||||
this(defaultMapping());
|
private final String name;
|
||||||
|
private final String password;
|
||||||
|
private Credentials(String name, String password) {
|
||||||
|
this.name = name;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
public String name() { return name; }
|
||||||
|
public String password() { return password; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public DummyWebSocketServer(Function<List<String>, List<String>> mapping) {
|
public DummyWebSocketServer() {
|
||||||
|
this(defaultMapping(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummyWebSocketServer(String username, String password) {
|
||||||
|
this(defaultMapping(), username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummyWebSocketServer(BiFunction<List<String>,Credentials,List<String>> mapping,
|
||||||
|
String username,
|
||||||
|
String password) {
|
||||||
requireNonNull(mapping);
|
requireNonNull(mapping);
|
||||||
|
Credentials credentials = username != null ?
|
||||||
|
new Credentials(username, password) : null;
|
||||||
|
|
||||||
thread = new Thread(() -> {
|
thread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
@@ -107,14 +128,23 @@ public class DummyWebSocketServer implements Closeable {
|
|||||||
try {
|
try {
|
||||||
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||||
channel.configureBlocking(true);
|
channel.configureBlocking(true);
|
||||||
StringBuilder request = new StringBuilder();
|
while (true) {
|
||||||
if (!readRequest(channel, request)) {
|
StringBuilder request = new StringBuilder();
|
||||||
throw new IOException("Bad request:" + request);
|
if (!readRequest(channel, request)) {
|
||||||
|
throw new IOException("Bad request:[" + request + "]");
|
||||||
|
}
|
||||||
|
List<String> strings = asList(request.toString().split("\r\n"));
|
||||||
|
List<String> response = mapping.apply(strings, credentials);
|
||||||
|
writeResponse(channel, response);
|
||||||
|
|
||||||
|
if (response.get(0).startsWith("HTTP/1.1 401")) {
|
||||||
|
err.println("Sent 401 Authentication response " + channel);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
serve(channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
List<String> strings = asList(request.toString().split("\r\n"));
|
|
||||||
List<String> response = mapping.apply(strings);
|
|
||||||
writeResponse(channel, response);
|
|
||||||
serve(channel);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
err.println("Error in connection: " + channel + ", " + e);
|
err.println("Error in connection: " + channel + ", " + e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -125,7 +155,7 @@ public class DummyWebSocketServer implements Closeable {
|
|||||||
}
|
}
|
||||||
} catch (ClosedByInterruptException ignored) {
|
} catch (ClosedByInterruptException ignored) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
err.println(e);
|
e.printStackTrace(err);
|
||||||
} finally {
|
} finally {
|
||||||
close(ssc);
|
close(ssc);
|
||||||
err.println("Stopped at: " + getURI());
|
err.println("Stopped at: " + getURI());
|
||||||
@@ -256,8 +286,8 @@ public class DummyWebSocketServer implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Function<List<String>, List<String>> defaultMapping() {
|
private static BiFunction<List<String>,Credentials,List<String>> defaultMapping() {
|
||||||
return request -> {
|
return (request, credentials) -> {
|
||||||
List<String> response = new LinkedList<>();
|
List<String> response = new LinkedList<>();
|
||||||
Iterator<String> iterator = request.iterator();
|
Iterator<String> iterator = request.iterator();
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
@@ -309,14 +339,57 @@ public class DummyWebSocketServer implements Closeable {
|
|||||||
sha1.update(x.getBytes(ISO_8859_1));
|
sha1.update(x.getBytes(ISO_8859_1));
|
||||||
String v = Base64.getEncoder().encodeToString(sha1.digest());
|
String v = Base64.getEncoder().encodeToString(sha1.digest());
|
||||||
response.add("Sec-WebSocket-Accept: " + v);
|
response.add("Sec-WebSocket-Accept: " + v);
|
||||||
|
|
||||||
|
// check authorization credentials, if required by the server
|
||||||
|
if (credentials != null && !authorized(credentials, requestHeaders)) {
|
||||||
|
response.clear();
|
||||||
|
response.add("HTTP/1.1 401 Unauthorized");
|
||||||
|
response.add("Content-Length: 0");
|
||||||
|
response.add("WWW-Authenticate: Basic realm=\"dummy server realm\"");
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks credentials in the request against those allowable by the server.
|
||||||
|
private static boolean authorized(Credentials credentials,
|
||||||
|
Map<String,List<String>> requestHeaders) {
|
||||||
|
List<String> authorization = requestHeaders.get("Authorization");
|
||||||
|
if (authorization == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (authorization.size() != 1) {
|
||||||
|
throw new IllegalStateException("Authorization unexpected count:" + authorization);
|
||||||
|
}
|
||||||
|
String header = authorization.get(0);
|
||||||
|
if (!header.startsWith("Basic "))
|
||||||
|
throw new IllegalStateException("Authorization not Basic: " + header);
|
||||||
|
|
||||||
|
header = header.substring("Basic ".length());
|
||||||
|
String values = new String(Base64.getDecoder().decode(header), UTF_8);
|
||||||
|
int sep = values.indexOf(':');
|
||||||
|
if (sep < 1) {
|
||||||
|
throw new IllegalStateException("Authorization not colon: " + values);
|
||||||
|
}
|
||||||
|
String name = values.substring(0, sep);
|
||||||
|
String password = values.substring(sep + 1);
|
||||||
|
|
||||||
|
if (name.equals(credentials.name()) && password.equals(credentials.password()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected static String expectHeader(Map<String, List<String>> headers,
|
protected static String expectHeader(Map<String, List<String>> headers,
|
||||||
String name,
|
String name,
|
||||||
String value) {
|
String value) {
|
||||||
List<String> v = headers.get(name);
|
List<String> v = headers.get(name);
|
||||||
|
if (v == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("Expected '%s' header, not present in %s",
|
||||||
|
name, headers));
|
||||||
|
}
|
||||||
if (!v.contains(value)) {
|
if (!v.contains(value)) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
format("Expected '%s: %s', actual: '%s: %s'",
|
format("Expected '%s: %s', actual: '%s: %s'",
|
||||||
|
|||||||
172
test/jdk/java/net/httpclient/websocket/SecureSupport.java
Normal file
172
test/jdk/java/net/httpclient/websocket/SecureSupport.java
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertThrows;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to create instances of DummySecureWebSocketServer which
|
||||||
|
* can support both plain and secure connections.
|
||||||
|
* The caller should invoke DummySecureWebSocketServer::secure before
|
||||||
|
* DummySecureWebSocketServer::open in order to enable secure connection.
|
||||||
|
* When secure, the DummySecureWebSocketServer currently only support using the
|
||||||
|
* default SSLEngine through the default SSLSocketServerFacrtory.
|
||||||
|
*/
|
||||||
|
public class SecureSupport {
|
||||||
|
|
||||||
|
private SecureSupport() { }
|
||||||
|
|
||||||
|
public static DummySecureWebSocketServer serverWithCannedData(int... data) {
|
||||||
|
return serverWithCannedDataAndAuthentication(null, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummySecureWebSocketServer serverWithCannedDataAndAuthentication(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
int... data)
|
||||||
|
{
|
||||||
|
byte[] copy = new byte[data.length];
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
copy[i] = (byte) data[i];
|
||||||
|
}
|
||||||
|
return serverWithCannedDataAndAuthentication(username, password, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummySecureWebSocketServer serverWithCannedData(byte... data) {
|
||||||
|
return serverWithCannedDataAndAuthentication(null, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummySecureWebSocketServer serverWithCannedDataAndAuthentication(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
byte... data)
|
||||||
|
{
|
||||||
|
byte[] copy = Arrays.copyOf(data, data.length);
|
||||||
|
return new DummySecureWebSocketServer(username, password) {
|
||||||
|
@Override
|
||||||
|
protected void write(WebSocketChannel ch) throws IOException {
|
||||||
|
int off = 0; int n = 1; // 1 byte at a time
|
||||||
|
while (off + n < copy.length + n) {
|
||||||
|
int len = Math.min(copy.length - off, n);
|
||||||
|
ByteBuffer bytes = ByteBuffer.wrap(copy, off, len);
|
||||||
|
off += len;
|
||||||
|
ch.write(bytes);
|
||||||
|
}
|
||||||
|
super.write(ch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This server does not read from the wire, allowing its client to fill up
|
||||||
|
* their send buffer. Used to test scenarios with outstanding send
|
||||||
|
* operations.
|
||||||
|
*/
|
||||||
|
public static DummySecureWebSocketServer notReadingServer() {
|
||||||
|
return new DummySecureWebSocketServer() {
|
||||||
|
@Override
|
||||||
|
protected void read(WebSocketChannel ch) throws IOException {
|
||||||
|
try {
|
||||||
|
Thread.sleep(Long.MAX_VALUE);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummySecureWebSocketServer writingServer(int... data) {
|
||||||
|
byte[] copy = new byte[data.length];
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
copy[i] = (byte) data[i];
|
||||||
|
}
|
||||||
|
return new DummySecureWebSocketServer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void read(WebSocketChannel ch) throws IOException {
|
||||||
|
try {
|
||||||
|
Thread.sleep(Long.MAX_VALUE);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(WebSocketChannel ch) throws IOException {
|
||||||
|
int off = 0; int n = 1; // 1 byte at a time
|
||||||
|
while (off + n < copy.length + n) {
|
||||||
|
int len = Math.min(copy.length - off, n);
|
||||||
|
ByteBuffer bytes = ByteBuffer.wrap(copy, off, len);
|
||||||
|
off += len;
|
||||||
|
ch.write(bytes);
|
||||||
|
}
|
||||||
|
super.write(ch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stringWith2NBytes(int n) {
|
||||||
|
// -- Russian Alphabet (33 characters, 2 bytes per char) --
|
||||||
|
char[] abc = {
|
||||||
|
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416,
|
||||||
|
0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E,
|
||||||
|
0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426,
|
||||||
|
0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E,
|
||||||
|
0x042F,
|
||||||
|
};
|
||||||
|
// repeat cyclically
|
||||||
|
StringBuilder sb = new StringBuilder(n);
|
||||||
|
for (int i = 0, j = 0; i < n; i++, j = (j + 1) % abc.length) {
|
||||||
|
sb.append(abc[j]);
|
||||||
|
}
|
||||||
|
String s = sb.toString();
|
||||||
|
assert s.length() == n && s.getBytes(StandardCharsets.UTF_8).length == 2 * n;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String malformedString() {
|
||||||
|
return new String(new char[]{0xDC00, 0xD800});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String incompleteString() {
|
||||||
|
return new String(new char[]{0xD800});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stringWithNBytes(int n) {
|
||||||
|
char[] chars = new char[n];
|
||||||
|
Arrays.fill(chars, 'A');
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -79,16 +79,32 @@ public class Support {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static DummyWebSocketServer serverWithCannedData(int... data) {
|
public static DummyWebSocketServer serverWithCannedData(int... data) {
|
||||||
|
return serverWithCannedDataAndAuthentication(null, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummyWebSocketServer serverWithCannedDataAndAuthentication(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
int... data)
|
||||||
|
{
|
||||||
byte[] copy = new byte[data.length];
|
byte[] copy = new byte[data.length];
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
copy[i] = (byte) data[i];
|
copy[i] = (byte) data[i];
|
||||||
}
|
}
|
||||||
return serverWithCannedData(copy);
|
return serverWithCannedDataAndAuthentication(username, password, copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DummyWebSocketServer serverWithCannedData(byte... data) {
|
public static DummyWebSocketServer serverWithCannedData(byte... data) {
|
||||||
|
return serverWithCannedDataAndAuthentication(null, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DummyWebSocketServer serverWithCannedDataAndAuthentication(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
byte... data)
|
||||||
|
{
|
||||||
byte[] copy = Arrays.copyOf(data, data.length);
|
byte[] copy = Arrays.copyOf(data, data.length);
|
||||||
return new DummyWebSocketServer() {
|
return new DummyWebSocketServer(username, password) {
|
||||||
@Override
|
@Override
|
||||||
protected void write(SocketChannel ch) throws IOException {
|
protected void write(SocketChannel ch) throws IOException {
|
||||||
int off = 0; int n = 1; // 1 byte at a time
|
int off = 0; int n = 1; // 1 byte at a time
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
|
* @bug 8240666
|
||||||
* @summary Basic test for WebSocketHandshakeException
|
* @summary Basic test for WebSocketHandshakeException
|
||||||
* @library /lib/testlibrary
|
* @library /lib/testlibrary
|
||||||
* @build jdk.testlibrary.SimpleSSLContext
|
* @build jdk.testlibrary.SimpleSSLContext
|
||||||
@@ -55,7 +56,9 @@ import java.util.concurrent.Executors;
|
|||||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertNotNull;
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertTrue;
|
||||||
import static org.testng.Assert.fail;
|
import static org.testng.Assert.fail;
|
||||||
|
import static java.lang.System.out;
|
||||||
|
|
||||||
public class WSHandshakeExceptionTest {
|
public class WSHandshakeExceptionTest {
|
||||||
|
|
||||||
@@ -107,6 +110,9 @@ public class WSHandshakeExceptionTest {
|
|||||||
}
|
}
|
||||||
WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
|
WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
|
||||||
assertNotNull(wse.getResponse());
|
assertNotNull(wse.getResponse());
|
||||||
|
out.println("Status code is " + wse.getResponse().statusCode());
|
||||||
|
out.println("Response is " + wse.getResponse().body());
|
||||||
|
assertTrue(((String)wse.getResponse().body()).contains("404"));
|
||||||
assertEquals(wse.getResponse().statusCode(), 404);
|
assertEquals(wse.getResponse().statusCode(), 404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
373
test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java
Normal file
373
test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8217429 8236859
|
||||||
|
* @summary WebSocket proxy tunneling tests
|
||||||
|
* @library /lib/testlibrary/
|
||||||
|
* @compile SecureSupport.java DummySecureWebSocketServer.java ../ProxyServer.java
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext WebSocketProxyTest
|
||||||
|
* @run testng/othervm
|
||||||
|
* -Djdk.internal.httpclient.debug=true
|
||||||
|
* -Djdk.internal.httpclient.websocket.debug=true
|
||||||
|
* -Djdk.httpclient.HttpClient.log=errors,requests,headers
|
||||||
|
* -Djdk.http.auth.tunneling.disabledSchemes=
|
||||||
|
* WebSocketProxyTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.PasswordAuthentication;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.net.http.WebSocket;
|
||||||
|
import java.net.http.WebSocketHandshakeException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
import org.testng.annotations.BeforeMethod;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import static java.net.http.HttpClient.newBuilder;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.FileAssert.fail;
|
||||||
|
|
||||||
|
public class WebSocketProxyTest {
|
||||||
|
|
||||||
|
// Used to verify a proxy/websocket server requiring Authentication
|
||||||
|
private static final String USERNAME = "wally";
|
||||||
|
private static final String PASSWORD = "xyz987";
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
SSLContext.setDefault(new SimpleSSLContext().get());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ExceptionInInitializerError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class WSAuthenticator extends Authenticator {
|
||||||
|
@Override
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Function<int[],DummySecureWebSocketServer> SERVER_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummySecureWebSocketServer apply(int[] data) {
|
||||||
|
return SecureSupport.serverWithCannedData(data); }
|
||||||
|
@Override public String toString() { return "SERVER_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Function<int[],DummySecureWebSocketServer> SSL_SERVER_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummySecureWebSocketServer apply(int[] data) {
|
||||||
|
return SecureSupport.serverWithCannedData(data).secure(); }
|
||||||
|
@Override public String toString() { return "SSL_SERVER_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Function<int[],DummySecureWebSocketServer> AUTH_SERVER_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummySecureWebSocketServer apply(int[] data) {
|
||||||
|
return SecureSupport.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data); }
|
||||||
|
@Override public String toString() { return "AUTH_SERVER_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Function<int[],DummySecureWebSocketServer> AUTH_SSL_SVR_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummySecureWebSocketServer apply(int[] data) {
|
||||||
|
return SecureSupport.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data).secure(); }
|
||||||
|
@Override public String toString() { return "AUTH_SSL_SVR_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Supplier<ProxyServer> TUNNELING_PROXY_SERVER =
|
||||||
|
new Supplier<>() {
|
||||||
|
@Override public ProxyServer get() {
|
||||||
|
try { return new ProxyServer(0, true);}
|
||||||
|
catch(IOException e) { throw new UncheckedIOException(e); } }
|
||||||
|
@Override public String toString() { return "TUNNELING_PROXY_SERVER"; }
|
||||||
|
};
|
||||||
|
static final Supplier<ProxyServer> AUTH_TUNNELING_PROXY_SERVER =
|
||||||
|
new Supplier<>() {
|
||||||
|
@Override public ProxyServer get() {
|
||||||
|
try { return new ProxyServer(0, true, USERNAME, PASSWORD);}
|
||||||
|
catch(IOException e) { throw new UncheckedIOException(e); } }
|
||||||
|
@Override public String toString() { return "AUTH_TUNNELING_PROXY_SERVER"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
@DataProvider(name = "servers")
|
||||||
|
public Object[][] servers() {
|
||||||
|
return new Object[][] {
|
||||||
|
{ SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER },
|
||||||
|
{ SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER },
|
||||||
|
{ SSL_SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER },
|
||||||
|
{ SSL_SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER },
|
||||||
|
{ AUTH_SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER },
|
||||||
|
{ AUTH_SSL_SVR_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER },
|
||||||
|
{ AUTH_SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER },
|
||||||
|
{ AUTH_SSL_SVR_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "servers")
|
||||||
|
public void simpleAggregatingBinaryMessages
|
||||||
|
(Function<int[],DummySecureWebSocketServer> serverSupplier,
|
||||||
|
Supplier<ProxyServer> proxyServerSupplier)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
List<byte[]> expected = List.of("hello", "chegar")
|
||||||
|
.stream()
|
||||||
|
.map(s -> s.getBytes(StandardCharsets.US_ASCII))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int[] binary = new int[]{
|
||||||
|
0x82, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, // hello
|
||||||
|
0x82, 0x06, 0x63, 0x68, 0x65, 0x67, 0x61, 0x72, // chegar
|
||||||
|
0x88, 0x00 // <CLOSE>
|
||||||
|
};
|
||||||
|
CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
|
||||||
|
|
||||||
|
try (var proxyServer = proxyServerSupplier.get();
|
||||||
|
var server = serverSupplier.apply(binary)) {
|
||||||
|
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
server.open();
|
||||||
|
System.out.println("Server: " + server.getURI());
|
||||||
|
System.out.println("Proxy: " + proxyAddress);
|
||||||
|
|
||||||
|
WebSocket.Listener listener = new WebSocket.Listener() {
|
||||||
|
|
||||||
|
List<byte[]> collectedBytes = new ArrayList<>();
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onBinary(WebSocket webSocket,
|
||||||
|
ByteBuffer message,
|
||||||
|
boolean last) {
|
||||||
|
System.out.printf("onBinary(%s, %s)%n", message, last);
|
||||||
|
webSocket.request(1);
|
||||||
|
|
||||||
|
append(message);
|
||||||
|
if (last) {
|
||||||
|
buffer.flip();
|
||||||
|
byte[] bytes = new byte[buffer.remaining()];
|
||||||
|
buffer.get(bytes);
|
||||||
|
buffer.clear();
|
||||||
|
processWholeBinary(bytes);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(ByteBuffer message) {
|
||||||
|
if (buffer.remaining() < message.remaining()) {
|
||||||
|
assert message.remaining() > 0;
|
||||||
|
int cap = (buffer.capacity() + message.remaining()) * 2;
|
||||||
|
ByteBuffer b = ByteBuffer.allocate(cap);
|
||||||
|
b.put(buffer.flip());
|
||||||
|
buffer = b;
|
||||||
|
}
|
||||||
|
buffer.put(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processWholeBinary(byte[] bytes) {
|
||||||
|
String stringBytes = new String(bytes, UTF_8);
|
||||||
|
System.out.println("processWholeBinary: " + stringBytes);
|
||||||
|
collectedBytes.add(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onClose(WebSocket webSocket,
|
||||||
|
int statusCode,
|
||||||
|
String reason) {
|
||||||
|
actual.complete(collectedBytes);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(WebSocket webSocket, Throwable error) {
|
||||||
|
actual.completeExceptionally(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress))
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build().newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), listener)
|
||||||
|
.join();
|
||||||
|
|
||||||
|
List<byte[]> a = actual.join();
|
||||||
|
assertEquals(a, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- authentication specific tests
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication succeeds when an Authenticator set on client builder.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void clientAuthenticate() throws IOException {
|
||||||
|
try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get();
|
||||||
|
var server = new DummySecureWebSocketServer()){
|
||||||
|
server.open();
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress))
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { })
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication succeeds when an `Authorization` header is explicitly set.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void explicitAuthenticate() throws IOException {
|
||||||
|
try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get();
|
||||||
|
var server = new DummySecureWebSocketServer()) {
|
||||||
|
server.open();
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
|
||||||
|
String hv = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(USERNAME + ":" + PASSWORD).getBytes(UTF_8));
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress)).build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.header("Proxy-Authorization", hv)
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { })
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication succeeds when an `Authorization` header is explicitly set.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void explicitAuthenticate2() throws IOException {
|
||||||
|
try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get();
|
||||||
|
var server = new DummySecureWebSocketServer(USERNAME, PASSWORD).secure()) {
|
||||||
|
server.open();
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
|
||||||
|
String hv = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(USERNAME + ":" + PASSWORD).getBytes(UTF_8));
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress)).build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.header("Proxy-Authorization", hv)
|
||||||
|
.header("Authorization", hv)
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { })
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication does not succeed when no authenticator is present.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void failNoAuthenticator() throws IOException {
|
||||||
|
try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get();
|
||||||
|
var server = new DummySecureWebSocketServer(USERNAME, PASSWORD)) {
|
||||||
|
server.open();
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
|
||||||
|
CompletableFuture<WebSocket> cf = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress)).build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { });
|
||||||
|
|
||||||
|
try {
|
||||||
|
var webSocket = cf.join();
|
||||||
|
fail("Expected exception not thrown");
|
||||||
|
} catch (CompletionException expected) {
|
||||||
|
WebSocketHandshakeException e = (WebSocketHandshakeException)expected.getCause();
|
||||||
|
HttpResponse<?> response = e.getResponse();
|
||||||
|
assertEquals(response.statusCode(), 407);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication does not succeed when the authenticator presents
|
||||||
|
* unauthorized credentials.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void failBadCredentials() throws IOException {
|
||||||
|
try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get();
|
||||||
|
var server = new DummySecureWebSocketServer(USERNAME, PASSWORD)) {
|
||||||
|
server.open();
|
||||||
|
InetSocketAddress proxyAddress = new InetSocketAddress(
|
||||||
|
InetAddress.getLoopbackAddress(), proxyServer.getPort());
|
||||||
|
|
||||||
|
Authenticator authenticator = new Authenticator() {
|
||||||
|
@Override protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication("BAD"+USERNAME, "".toCharArray());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CompletableFuture<WebSocket> cf = newBuilder()
|
||||||
|
.proxy(ProxySelector.of(proxyAddress))
|
||||||
|
.authenticator(authenticator)
|
||||||
|
.build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { });
|
||||||
|
|
||||||
|
try {
|
||||||
|
var webSocket = cf.join();
|
||||||
|
fail("Expected exception not thrown");
|
||||||
|
} catch (CompletionException expected) {
|
||||||
|
System.out.println("caught expected exception:" + expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
|
* @bug 8217429
|
||||||
* @build DummyWebSocketServer
|
* @build DummyWebSocketServer
|
||||||
* @run testng/othervm
|
* @run testng/othervm
|
||||||
* WebSocketTest
|
* WebSocketTest
|
||||||
@@ -33,23 +34,32 @@ import org.testng.annotations.DataProvider;
|
|||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.PasswordAuthentication;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.WebSocket;
|
import java.net.http.WebSocket;
|
||||||
|
import java.net.http.WebSocketHandshakeException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.CompletionStage;
|
import java.util.concurrent.CompletionStage;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||||
import static java.net.http.HttpClient.newBuilder;
|
import static java.net.http.HttpClient.newBuilder;
|
||||||
import static java.net.http.WebSocket.NORMAL_CLOSURE;
|
import static java.net.http.WebSocket.NORMAL_CLOSURE;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertThrows;
|
import static org.testng.Assert.assertThrows;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
public class WebSocketTest {
|
public class WebSocketTest {
|
||||||
|
|
||||||
@@ -68,8 +78,11 @@ public class WebSocketTest {
|
|||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
server.close();
|
System.out.println("AFTER TEST");
|
||||||
webSocket.abort();
|
if (server != null)
|
||||||
|
server.close();
|
||||||
|
if (webSocket != null)
|
||||||
|
webSocket.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -134,6 +147,8 @@ public class WebSocketTest {
|
|||||||
assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
|
assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
|
||||||
assertThrows(IAE, () -> webSocket.request(-1));
|
assertThrows(IAE, () -> webSocket.request(-1));
|
||||||
assertThrows(IAE, () -> webSocket.request(0));
|
assertThrows(IAE, () -> webSocket.request(0));
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -149,6 +164,7 @@ public class WebSocketTest {
|
|||||||
// Pings & Pongs are fine
|
// Pings & Pongs are fine
|
||||||
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
||||||
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -165,6 +181,7 @@ public class WebSocketTest {
|
|||||||
// Pings & Pongs are fine
|
// Pings & Pongs are fine
|
||||||
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
||||||
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -198,6 +215,8 @@ public class WebSocketTest {
|
|||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataProvider(name = "sequence")
|
@DataProvider(name = "sequence")
|
||||||
@@ -318,6 +337,8 @@ public class WebSocketTest {
|
|||||||
listener.invocations();
|
listener.invocations();
|
||||||
violation.complete(null); // won't affect if completed exceptionally
|
violation.complete(null); // won't affect if completed exceptionally
|
||||||
violation.join();
|
violation.join();
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -372,10 +393,48 @@ public class WebSocketTest {
|
|||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
||||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// Used to verify a server requiring Authentication
|
||||||
public void simpleAggregatingBinaryMessages() throws IOException {
|
private static final String USERNAME = "chegar";
|
||||||
|
private static final String PASSWORD = "a1b2c3";
|
||||||
|
|
||||||
|
static class WSAuthenticator extends Authenticator {
|
||||||
|
@Override
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Function<int[],DummyWebSocketServer> SERVER_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummyWebSocketServer apply(int[] data) {
|
||||||
|
return Support.serverWithCannedData(data); }
|
||||||
|
@Override public String toString() { return "SERVER_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Function<int[],DummyWebSocketServer> AUTH_SERVER_WITH_CANNED_DATA =
|
||||||
|
new Function<>() {
|
||||||
|
@Override public DummyWebSocketServer apply(int[] data) {
|
||||||
|
return Support.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data); }
|
||||||
|
@Override public String toString() { return "AUTH_SERVER_WITH_CANNED_DATA"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
@DataProvider(name = "servers")
|
||||||
|
public Object[][] servers() {
|
||||||
|
return new Object[][] {
|
||||||
|
{ SERVER_WITH_CANNED_DATA },
|
||||||
|
{ AUTH_SERVER_WITH_CANNED_DATA },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "servers")
|
||||||
|
public void simpleAggregatingBinaryMessages
|
||||||
|
(Function<int[],DummyWebSocketServer> serverSupplier)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
List<byte[]> expected = List.of("alpha", "beta", "gamma", "delta")
|
List<byte[]> expected = List.of("alpha", "beta", "gamma", "delta")
|
||||||
.stream()
|
.stream()
|
||||||
.map(s -> s.getBytes(StandardCharsets.US_ASCII))
|
.map(s -> s.getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -399,7 +458,7 @@ public class WebSocketTest {
|
|||||||
};
|
};
|
||||||
CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
|
CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
|
||||||
|
|
||||||
server = Support.serverWithCannedData(binary);
|
server = serverSupplier.apply(binary);
|
||||||
server.open();
|
server.open();
|
||||||
|
|
||||||
WebSocket.Listener listener = new WebSocket.Listener() {
|
WebSocket.Listener listener = new WebSocket.Listener() {
|
||||||
@@ -437,7 +496,7 @@ public class WebSocketTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processWholeBinary(byte[] bytes) {
|
private void processWholeBinary(byte[] bytes) {
|
||||||
String stringBytes = new String(bytes, StandardCharsets.UTF_8);
|
String stringBytes = new String(bytes, UTF_8);
|
||||||
System.out.println("processWholeBinary: " + stringBytes);
|
System.out.println("processWholeBinary: " + stringBytes);
|
||||||
collectedBytes.add(bytes);
|
collectedBytes.add(bytes);
|
||||||
}
|
}
|
||||||
@@ -456,17 +515,24 @@ public class WebSocketTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
|
webSocket = newBuilder()
|
||||||
|
.proxy(NO_PROXY)
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build().newWebSocketBuilder()
|
||||||
.buildAsync(server.getURI(), listener)
|
.buildAsync(server.getURI(), listener)
|
||||||
.join();
|
.join();
|
||||||
|
|
||||||
List<byte[]> a = actual.join();
|
List<byte[]> a = actual.join();
|
||||||
assertEquals(a, expected);
|
assertEquals(a, expected);
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(dataProvider = "servers")
|
||||||
public void simpleAggregatingTextMessages() throws IOException {
|
public void simpleAggregatingTextMessages
|
||||||
|
(Function<int[],DummyWebSocketServer> serverSupplier)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
||||||
|
|
||||||
int[] binary = new int[]{
|
int[] binary = new int[]{
|
||||||
@@ -488,7 +554,7 @@ public class WebSocketTest {
|
|||||||
};
|
};
|
||||||
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
||||||
|
|
||||||
server = Support.serverWithCannedData(binary);
|
server = serverSupplier.apply(binary);
|
||||||
server.open();
|
server.open();
|
||||||
|
|
||||||
WebSocket.Listener listener = new WebSocket.Listener() {
|
WebSocket.Listener listener = new WebSocket.Listener() {
|
||||||
@@ -530,21 +596,28 @@ public class WebSocketTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
|
webSocket = newBuilder()
|
||||||
|
.proxy(NO_PROXY)
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build().newWebSocketBuilder()
|
||||||
.buildAsync(server.getURI(), listener)
|
.buildAsync(server.getURI(), listener)
|
||||||
.join();
|
.join();
|
||||||
|
|
||||||
List<String> a = actual.join();
|
List<String> a = actual.join();
|
||||||
assertEquals(a, expected);
|
assertEquals(a, expected);
|
||||||
|
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Exercises the scenario where requests for more messages are made prior to
|
* Exercises the scenario where requests for more messages are made prior to
|
||||||
* completing the returned CompletionStage instances.
|
* completing the returned CompletionStage instances.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test(dataProvider = "servers")
|
||||||
public void aggregatingTextMessages() throws IOException {
|
public void aggregatingTextMessages
|
||||||
|
(Function<int[],DummyWebSocketServer> serverSupplier)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
||||||
|
|
||||||
int[] binary = new int[]{
|
int[] binary = new int[]{
|
||||||
@@ -566,8 +639,7 @@ public class WebSocketTest {
|
|||||||
};
|
};
|
||||||
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
||||||
|
|
||||||
|
server = serverSupplier.apply(binary);
|
||||||
server = Support.serverWithCannedData(binary);
|
|
||||||
server.open();
|
server.open();
|
||||||
|
|
||||||
WebSocket.Listener listener = new WebSocket.Listener() {
|
WebSocket.Listener listener = new WebSocket.Listener() {
|
||||||
@@ -623,11 +695,111 @@ public class WebSocketTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
|
webSocket = newBuilder()
|
||||||
|
.proxy(NO_PROXY)
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build().newWebSocketBuilder()
|
||||||
.buildAsync(server.getURI(), listener)
|
.buildAsync(server.getURI(), listener)
|
||||||
.join();
|
.join();
|
||||||
|
|
||||||
List<String> a = actual.join();
|
List<String> a = actual.join();
|
||||||
assertEquals(a, expected);
|
assertEquals(a, expected);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- authentication specific tests
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication succeeds when an Authenticator set on client builder.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void clientAuthenticate() throws IOException {
|
||||||
|
try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)){
|
||||||
|
server.open();
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(NO_PROXY)
|
||||||
|
.authenticator(new WSAuthenticator())
|
||||||
|
.build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { })
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication succeeds when an `Authorization` header is explicitly set.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void explicitAuthenticate() throws IOException {
|
||||||
|
try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
|
||||||
|
server.open();
|
||||||
|
|
||||||
|
String hv = "Basic " + Base64.getEncoder().encodeToString(
|
||||||
|
(USERNAME + ":" + PASSWORD).getBytes(UTF_8));
|
||||||
|
|
||||||
|
var webSocket = newBuilder()
|
||||||
|
.proxy(NO_PROXY).build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.header("Authorization", hv)
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { })
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication does not succeed when no authenticator is present.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void failNoAuthenticator() throws IOException {
|
||||||
|
try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
|
||||||
|
server.open();
|
||||||
|
|
||||||
|
CompletableFuture<WebSocket> cf = newBuilder()
|
||||||
|
.proxy(NO_PROXY).build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { });
|
||||||
|
|
||||||
|
try {
|
||||||
|
var webSocket = cf.join();
|
||||||
|
fail("Expected exception not thrown");
|
||||||
|
} catch (CompletionException expected) {
|
||||||
|
WebSocketHandshakeException e = (WebSocketHandshakeException)expected.getCause();
|
||||||
|
HttpResponse<?> response = e.getResponse();
|
||||||
|
assertEquals(response.statusCode(), 401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures authentication does not succeed when the authenticator presents
|
||||||
|
* unauthorized credentials.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void failBadCredentials() throws IOException {
|
||||||
|
try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
|
||||||
|
server.open();
|
||||||
|
|
||||||
|
Authenticator authenticator = new Authenticator() {
|
||||||
|
@Override protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication("BAD"+USERNAME, "".toCharArray());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CompletableFuture<WebSocket> cf = newBuilder()
|
||||||
|
.proxy(NO_PROXY)
|
||||||
|
.authenticator(authenticator)
|
||||||
|
.build()
|
||||||
|
.newWebSocketBuilder()
|
||||||
|
.buildAsync(server.getURI(), new WebSocket.Listener() { });
|
||||||
|
|
||||||
|
try {
|
||||||
|
var webSocket = cf.join();
|
||||||
|
fail("Expected exception not thrown");
|
||||||
|
} catch (CompletionException expected) {
|
||||||
|
System.out.println("caught expected exception:" + expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@@ -22,11 +22,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* @test
|
/* @test
|
||||||
* @bug 4313887 6838333 8005566 8032220
|
* @bug 4313887 6838333 8005566 8032220 8215467 8227080
|
||||||
* @summary Unit test for miscellenous methods in java.nio.file.Files
|
* @summary Unit test for miscellenous methods in java.nio.file.Files
|
||||||
* @library ..
|
* @library ..
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import static java.nio.file.Files.*;
|
import static java.nio.file.Files.*;
|
||||||
import static java.nio.file.LinkOption.*;
|
import static java.nio.file.LinkOption.*;
|
||||||
@@ -44,6 +47,7 @@ public class Misc {
|
|||||||
testIsSameFile(dir);
|
testIsSameFile(dir);
|
||||||
testFileTypeMethods(dir);
|
testFileTypeMethods(dir);
|
||||||
testAccessMethods(dir);
|
testAccessMethods(dir);
|
||||||
|
testSkip(dir);
|
||||||
} finally {
|
} finally {
|
||||||
TestUtil.removeAll(dir);
|
TestUtil.removeAll(dir);
|
||||||
}
|
}
|
||||||
@@ -102,6 +106,18 @@ public class Misc {
|
|||||||
} finally {
|
} finally {
|
||||||
delete(file);
|
delete(file);
|
||||||
}
|
}
|
||||||
|
Path dir = tmpdir.resolve("hidden");
|
||||||
|
createDirectory(dir);
|
||||||
|
try {
|
||||||
|
setAttribute(dir, "dos:hidden", true);
|
||||||
|
try {
|
||||||
|
assertTrue(isHidden(dir));
|
||||||
|
} finally {
|
||||||
|
setAttribute(dir, "dos:hidden", false);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
delete(dir);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assertTrue(isHidden(file));
|
assertTrue(isHidden(file));
|
||||||
}
|
}
|
||||||
@@ -360,6 +376,38 @@ public class Misc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Files.newInputStream(Path).skip().
|
||||||
|
*/
|
||||||
|
static void testSkip(Path tmpdir) throws IOException {
|
||||||
|
Path file = createFile(tmpdir.resolve("foo"));
|
||||||
|
try (OutputStream out = Files.newOutputStream(file)) {
|
||||||
|
byte[] blah = new byte[8192];
|
||||||
|
Arrays.fill(blah, (byte)42);
|
||||||
|
out.write(blah);
|
||||||
|
out.close();
|
||||||
|
try (InputStream in = Files.newInputStream(file)) {
|
||||||
|
assertTrue(in.skip(-1) == 0);
|
||||||
|
assertTrue(in.skip(0) == 0);
|
||||||
|
assertTrue(in.skip(blah.length/4) == blah.length/4);
|
||||||
|
assertTrue(in.skip(blah.length/2) == blah.length/2);
|
||||||
|
assertTrue(in.skip(Long.MAX_VALUE) == blah.length/4);
|
||||||
|
in.close();
|
||||||
|
try {
|
||||||
|
long n = in.skip(1);
|
||||||
|
throw new RuntimeException("skip() did not fail");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (!(ioe.getCause() instanceof ClosedChannelException)) {
|
||||||
|
throw new RuntimeException
|
||||||
|
("IOException not caused by ClosedChannelException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void assertTrue(boolean okay) {
|
static void assertTrue(boolean okay) {
|
||||||
if (!okay)
|
if (!okay)
|
||||||
throw new RuntimeException("Assertion Failed");
|
throw new RuntimeException("Assertion Failed");
|
||||||
|
|||||||
118
test/jdk/jb/java/awt/Focus/TitleBarClickTest.java
Normal file
118
test/jdk/jb/java/awt/Focus/TitleBarClickTest.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2020 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @summary Regression test for JBR-2652 Window is not focused in some cases on Linux
|
||||||
|
* @key headful
|
||||||
|
*/
|
||||||
|
public class TitleBarClickTest {
|
||||||
|
private static final CompletableFuture<Boolean> f2Opened = new CompletableFuture<>();
|
||||||
|
private static final CompletableFuture<Boolean> t2Clicked = new CompletableFuture<>();
|
||||||
|
private static CompletableFuture<Boolean> t1Focused;
|
||||||
|
private static CompletableFuture<Boolean> t2Focused;
|
||||||
|
|
||||||
|
private static JFrame f1;
|
||||||
|
private static JFrame f2;
|
||||||
|
private static JTextField t1;
|
||||||
|
private static JTextField t2;
|
||||||
|
private static Robot robot;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
robot = new Robot();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SwingUtilities.invokeAndWait(TitleBarClickTest::initUI);
|
||||||
|
f2Opened.get(10, TimeUnit.SECONDS);
|
||||||
|
clickAt(t2);
|
||||||
|
t2Clicked.get(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
SwingUtilities.invokeAndWait(() -> t1Focused = new CompletableFuture<>());
|
||||||
|
clickAtTitle(f1);
|
||||||
|
t1Focused.get(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
SwingUtilities.invokeAndWait(() -> t2Focused = new CompletableFuture<>());
|
||||||
|
f2.toFront();
|
||||||
|
t2Focused.get(10, TimeUnit.SECONDS);
|
||||||
|
} finally {
|
||||||
|
SwingUtilities.invokeAndWait(TitleBarClickTest::disposeUI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initUI() {
|
||||||
|
f1 = new JFrame("first");
|
||||||
|
f1.add(t1 = new JTextField());
|
||||||
|
t1.addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
if (t1Focused != null) t1Focused.complete(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
f1.setBounds(100, 100, 300, 100);
|
||||||
|
f1.setVisible(true);
|
||||||
|
f2 = new JFrame("second");
|
||||||
|
f2.add(t2 = new JTextField());
|
||||||
|
t2.addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
if (t2Focused != null) t2Focused.complete(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t2.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
t2Clicked.complete(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
f2.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowOpened(WindowEvent e) {
|
||||||
|
f2Opened.complete(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
f2.setBounds(450, 100, 300, 100);
|
||||||
|
f2.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void disposeUI() {
|
||||||
|
if (f1 != null) f1.dispose();
|
||||||
|
if (f2 != null) f2.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clickAt(int x, int y) {
|
||||||
|
robot.mouseMove(x, y);
|
||||||
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||||||
|
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clickAt(Component component) {
|
||||||
|
Point location = component.getLocationOnScreen();
|
||||||
|
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clickAtTitle(Window window) {
|
||||||
|
int topInset = window.getInsets().top;
|
||||||
|
if (topInset <= 0) throw new RuntimeException("Window doesn't have title bar");
|
||||||
|
Point location = window.getLocationOnScreen();
|
||||||
|
clickAt(location.x + window.getWidth() / 2, location.y + topInset / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ public class JBCefBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
// myCefBrowser.close(false);
|
myCefBrowser.close(true);
|
||||||
myCefClient.dispose();
|
myCefClient.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import java.awt.event.KeyEvent;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import static java.awt.event.KeyEvent.*;
|
import static java.awt.event.KeyEvent.*;
|
||||||
import static sun.awt.event.KeyEvent.*; /* comment this line to compile with jbrsdk8 */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class containing common key functionality
|
* Class containing common key functionality
|
||||||
@@ -54,18 +53,23 @@ public class Key {
|
|||||||
int getKeyCode() {
|
int getKeyCode() {
|
||||||
KeyChar keyChar = mappedKeyChars.getKeyChar();
|
KeyChar keyChar = mappedKeyChars.getKeyChar();
|
||||||
char ch = keyChar.getChar();
|
char ch = keyChar.getChar();
|
||||||
if (latinKeyCodesMap.containsKey(ch)) {
|
if (keyChar.isDead() && deadKeyCodesMap.containsKey(ch)) {
|
||||||
// TODO Fix this in jbruntime
|
|
||||||
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for non-English keys
|
|
||||||
return latinKeyCodesMap.get(ch);
|
|
||||||
} else if (keyChar.isDead() && deadKeyCodesMap.containsKey(ch)) {
|
|
||||||
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for dead keys
|
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for dead keys
|
||||||
return deadKeyCodesMap.get(ch);
|
return deadKeyCodesMap.get(ch);
|
||||||
|
} else if (isLatinUnicode(ch)) {
|
||||||
|
// Please see JBR-2672
|
||||||
|
final int UNICODE_OFFSET = 0x01000000;
|
||||||
|
return UNICODE_OFFSET + (int) ch;
|
||||||
} else {
|
} else {
|
||||||
return KeyEvent.getExtendedKeyCodeForChar(ch);
|
return KeyEvent.getExtendedKeyCodeForChar(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLatinUnicode(char ch) {
|
||||||
|
// Latin-1 Supplement & Latin Extended A & B
|
||||||
|
return ch >= 0x0080 && ch <= 0x024F;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns key char for the current layout
|
// Returns key char for the current layout
|
||||||
public char getChar(Modifier modifier) {
|
public char getChar(Modifier modifier) {
|
||||||
return mappedKeyChars.getKeyChar(modifier).getChar();
|
return mappedKeyChars.getKeyChar(modifier).getChar();
|
||||||
@@ -91,49 +95,6 @@ public class Key {
|
|||||||
return mappedKeyChars.getKeyChar(modifier).isDead();
|
return mappedKeyChars.getKeyChar(modifier).isDead();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove this map when KeyEvent.getExtendedKeyCodeForChar(ch) is fixed for latin keys in jbruntime
|
|
||||||
// Map storing latin chars and corresponding VK_ codes
|
|
||||||
private static final HashMap<Character, Integer> latinKeyCodesMap = new HashMap<Character, Integer>() {
|
|
||||||
{
|
|
||||||
// Please see:
|
|
||||||
// jbruntime/src/java.desktop/share/classes/sun/awt/event/KeyEvent.java
|
|
||||||
|
|
||||||
put((char) 0x00DF, VK_ESZETT);
|
|
||||||
put((char) 0x00E0, VK_A_WITH_GRAVE);
|
|
||||||
put((char) 0x00E1, VK_A_WITH_ACUTE);
|
|
||||||
put((char) 0x00E2, VK_A_WITH_CIRCUMFLEX);
|
|
||||||
put((char) 0x00E3, VK_A_WITH_TILDE);
|
|
||||||
put((char) 0x00E4, VK_A_WITH_DIAERESIS);
|
|
||||||
put((char) 0x00E5, VK_A_WITH_RING_ABOVE);
|
|
||||||
put((char) 0x00E6, VK_AE);
|
|
||||||
put((char) 0x00E7, VK_C_WITH_CEDILLA);
|
|
||||||
put((char) 0x00E8, VK_E_WITH_GRAVE);
|
|
||||||
put((char) 0x00E9, VK_E_WITH_ACUTE);
|
|
||||||
put((char) 0x00EA, VK_E_WITH_CIRCUMFLEX);
|
|
||||||
put((char) 0x00EB, VK_E_WITH_DIAERESIS);
|
|
||||||
put((char) 0x00EC, VK_I_WITH_GRAVE);
|
|
||||||
put((char) 0x00ED, VK_I_WITH_GRAVE);
|
|
||||||
put((char) 0x00EE, VK_I_WITH_CIRCUMFLEX);
|
|
||||||
put((char) 0x00EF, VK_I_WITH_DIAERESIS);
|
|
||||||
put((char) 0x00F0, VK_ETH);
|
|
||||||
put((char) 0x00F1, VK_N_WITH_TILDE);
|
|
||||||
put((char) 0x00F2, VK_O_WITH_GRAVE);
|
|
||||||
put((char) 0x00F3, VK_O_WITH_ACUTE);
|
|
||||||
put((char) 0x00F4, VK_O_WITH_CIRCUMFLEX);
|
|
||||||
put((char) 0x00F5, VK_O_WITH_TILDE);
|
|
||||||
put((char) 0x00F6, VK_O_WITH_DIAERESIS);
|
|
||||||
put((char) 0x00F7, VK_DIVISION_SIGN);
|
|
||||||
put((char) 0x00F8, VK_O_WITH_SLASH);
|
|
||||||
put((char) 0x00F9, VK_U_WITH_GRAVE);
|
|
||||||
put((char) 0x00FA, VK_U_WITH_ACUTE);
|
|
||||||
put((char) 0x00FB, VK_U_WITH_CIRCUMFLEX);
|
|
||||||
put((char) 0x00FC, VK_U_WITH_DIAERESIS);
|
|
||||||
put((char) 0x00FD, VK_Y_WITH_ACUTE);
|
|
||||||
put((char) 0x00FE, VK_THORN);
|
|
||||||
put((char) 0x00FF, VK_Y_WITH_DIAERESIS);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map storing possible dead key chars and corresponding VK_ codes
|
// Map storing possible dead key chars and corresponding VK_ codes
|
||||||
private static final HashMap<Character, Integer> deadKeyCodesMap = new HashMap<Character, Integer>() {
|
private static final HashMap<Character, Integer> deadKeyCodesMap = new HashMap<Character, Integer>() {
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ java/awt/Frame/UnfocusableMaximizedFrameResizablity/UnfocusableMaximizedFrameRes
|
|||||||
java/awt/Frame/WindowDragTest/WindowDragTest.java 8169470 generic-all
|
java/awt/Frame/WindowDragTest/WindowDragTest.java 8169470 generic-all
|
||||||
java/awt/FullScreen/8013581/bug8013581.java 8169471 macosx-all,windows-all,linux-all
|
java/awt/FullScreen/8013581/bug8013581.java 8169471 macosx-all,windows-all,linux-all
|
||||||
java/awt/FullScreen/AltTabCrashTest/AltTabCrashTest.java 8047218 generic-all
|
java/awt/FullScreen/AltTabCrashTest/AltTabCrashTest.java 8047218 generic-all
|
||||||
|
java/awt/FullScreen/BufferStrategyExceptionTest/BufferStrategyExceptionTest.java 8246558 windows-all
|
||||||
java/awt/FullScreen/DisplayChangeVITest/DisplayChangeVITest.java 8169469,JBR-1897 windows-all,macosx-all,linux-all (linux: NPE commit testing)
|
java/awt/FullScreen/DisplayChangeVITest/DisplayChangeVITest.java 8169469,JBR-1897 windows-all,macosx-all,linux-all (linux: NPE commit testing)
|
||||||
java/awt/FullScreen/FullScreenInsets/FullScreenInsets.java 7019055 windows-all,linux-all,macosx-all
|
java/awt/FullScreen/FullScreenInsets/FullScreenInsets.java 7019055 windows-all,linux-all,macosx-all
|
||||||
java/awt/FullScreen/NoResizeEventOnDMChangeTest/NoResizeEventOnDMChangeTest.java 8169468 windows-all,macosx-all
|
java/awt/FullScreen/NoResizeEventOnDMChangeTest/NoResizeEventOnDMChangeTest.java 8169468 windows-all,macosx-all
|
||||||
@@ -595,6 +596,7 @@ java/awt/TrayIcon/PopupMenuLeakTest/PopupMenuLeakTest.java
|
|||||||
java/awt/Window/8159168/SetShapeTest.java 8208507 generic-all
|
java/awt/Window/8159168/SetShapeTest.java 8208507 generic-all
|
||||||
java/awt/Window/BackgroundIsNotUpdated/BackgroundIsNotUpdated.java 8142536 generic-all
|
java/awt/Window/BackgroundIsNotUpdated/BackgroundIsNotUpdated.java 8142536 generic-all
|
||||||
java/awt/Window/Grab/GrabTest.java 8196019 macosx-all,windows-all,linux-all
|
java/awt/Window/Grab/GrabTest.java 8196019 macosx-all,windows-all,linux-all
|
||||||
|
java/awt/Window/GetScreenLocation/GetScreenLocationTest.java 8225787 linux-all
|
||||||
java/awt/Window/MultiWindowApp/ChildAlwaysOnTopTest.java 8215132,8194941 macosx-all,windows-all,linux-all
|
java/awt/Window/MultiWindowApp/ChildAlwaysOnTopTest.java 8215132,8194941 macosx-all,windows-all,linux-all
|
||||||
java/awt/Window/MultiWindowApp/MultiWindowAppTest.java 8159904 macosx-all,windows-all,linux-all
|
java/awt/Window/MultiWindowApp/MultiWindowAppTest.java 8159904 macosx-all,windows-all,linux-all
|
||||||
java/awt/Window/OwnedWindowsLeak/OwnedWindowsLeak.java 8225116 windows-all
|
java/awt/Window/OwnedWindowsLeak/OwnedWindowsLeak.java 8225116 windows-all
|
||||||
@@ -635,6 +637,7 @@ java/awt/dnd/NoFormatsCrashTest/NoFormatsCrashTest.html
|
|||||||
java/awt/dnd/URIListBetweenJVMsTest/URIListBetweenJVMsTest.html 8171510,7124379 macosx-all,linux-all
|
java/awt/dnd/URIListBetweenJVMsTest/URIListBetweenJVMsTest.html 8171510,7124379 macosx-all,linux-all
|
||||||
java/awt/dnd/URIListToFileListBetweenJVMsTest/URIListToFileListBetweenJVMsTest.html 8194947 generic-all
|
java/awt/dnd/URIListToFileListBetweenJVMsTest/URIListToFileListBetweenJVMsTest.html 8194947 generic-all
|
||||||
java/awt/event/ComponentEvent/MovedResizedTardyEventTest/MovedResizedTardyEventTest.html 6511207 generic-all
|
java/awt/event/ComponentEvent/MovedResizedTardyEventTest/MovedResizedTardyEventTest.html 6511207 generic-all
|
||||||
|
java/awt/event/ComponentEvent/MovedResizedTwiceTest/MovedResizedTwiceTest.java 8225787 linux-all
|
||||||
java/awt/event/HierarchyEvent/AncestorResized/AncestorResized.java 6618538 generic-all
|
java/awt/event/HierarchyEvent/AncestorResized/AncestorResized.java 6618538 generic-all
|
||||||
java/awt/event/InputEvent/EventWhenTest/EventWhenTest.java 8168646 generic-all
|
java/awt/event/InputEvent/EventWhenTest/EventWhenTest.java 8168646 generic-all
|
||||||
java/awt/event/KeyEvent/CorrectTime/CorrectTime.java 6626492 generic-all
|
java/awt/event/KeyEvent/CorrectTime/CorrectTime.java 6626492 generic-all
|
||||||
@@ -1262,5 +1265,6 @@ javax/swing/LookAndFeel/8146276/NimbusGlueTest.java
|
|||||||
sanity/client/SwingSet/src/GridBagLayoutDemoTest.java JBR-1977 linux-aarch64
|
sanity/client/SwingSet/src/GridBagLayoutDemoTest.java JBR-1977 linux-aarch64
|
||||||
|
|
||||||
jb/java/jcef/JCEFStartupTest.java JBR-1996 linux-i386,windows-x86
|
jb/java/jcef/JCEFStartupTest.java JBR-1996 linux-i386,windows-x86
|
||||||
jb/java/awt/event/TouchScreenEvent/TouchScreenEventsTest.java nobug windows-6.1 not supported on Windows 7
|
jb/java/awt/event/TouchScreenEvent/TouchScreenEventsTest.java JBR-2585 linux-all,windows-all nobug windows-6.1 not supported on Windows 7
|
||||||
jb/java/awt/event/TouchScreenEvent/TouchScreenEventsTestLinux.sh JBR-2585 linux-all
|
jb/java/awt/event/TouchScreenEvent/TouchScreenEventsTestLinux.sh JBR-2585 linux-all
|
||||||
|
jb/java/awt/Focus/ChainOfPopupsFocusTest.java JBR-2657 windows-all,linux-all
|
||||||
@@ -41,7 +41,6 @@ java/awt/Frame/MiscUndecorated/FrameCloseTest.java
|
|||||||
java/awt/Frame/NormalToIconified/NormalToIconifiedTest.java nobug linux-all,windows-all
|
java/awt/Frame/NormalToIconified/NormalToIconifiedTest.java nobug linux-all,windows-all
|
||||||
java/awt/Frame/ObscuredFrame/ObscuredFrameTest.java nobug macosx-all,linux-all
|
java/awt/Frame/ObscuredFrame/ObscuredFrameTest.java nobug macosx-all,linux-all
|
||||||
java/awt/Frame/UnfocusableMaximizedFrameResizablity/UnfocusableMaximizedFrameResizablity.java nobug linux-all,windows-all,macosx-all
|
java/awt/Frame/UnfocusableMaximizedFrameResizablity/UnfocusableMaximizedFrameResizablity.java nobug linux-all,windows-all,macosx-all
|
||||||
java/awt/FullScreen/BufferStrategyExceptionTest/BufferStrategyExceptionTest.java nobug macosx-all,linux-all,windows-all
|
|
||||||
java/awt/FullScreen/SetFSWindow/FSFrame.java nobug windows-all
|
java/awt/FullScreen/SetFSWindow/FSFrame.java nobug windows-all
|
||||||
java/awt/FullScreen/TranslucentWindow/TranslucentWindow.java nobug windows-all
|
java/awt/FullScreen/TranslucentWindow/TranslucentWindow.java nobug windows-all
|
||||||
java/awt/Graphics/LineClipTest.java nobug macosx-all,windows-all
|
java/awt/Graphics/LineClipTest.java nobug macosx-all,windows-all
|
||||||
|
|||||||
Reference in New Issue
Block a user