mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-10 11:29:39 +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"?>
|
||||
<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">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</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
|
||||
* 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 it
|
||||
* isn't a directory and the DOS {@link DosFileAttributes#isHidden hidden}
|
||||
* attribute is set.
|
||||
* Finds and returns the position of the first mismatched byte in the content
|
||||
* of two files, or {@code -1L} if there is no mismatch. The position will be
|
||||
* in the inclusive range of {@code 0L} up to the size (in bytes) of the
|
||||
* smaller file.
|
||||
*
|
||||
* <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
|
||||
* 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.URISyntaxException;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
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
|
||||
@@ -49,37 +55,18 @@ class BasicAuthentication extends AuthenticationInfo {
|
||||
|
||||
/** The authentication string for this host, port, and realm. This is
|
||||
a simple BASE64 encoding of "login:password". */
|
||||
String auth;
|
||||
final String auth;
|
||||
|
||||
/**
|
||||
* Create a BasicAuthentication
|
||||
*/
|
||||
public BasicAuthentication(boolean isProxy, String host, int port,
|
||||
String realm, PasswordAuthentication pw,
|
||||
String authenticatorKey) {
|
||||
boolean isUTF8, String authenticatorKey) {
|
||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||
AuthScheme.BASIC, host, port, realm,
|
||||
Objects.requireNonNull(authenticatorKey));
|
||||
String plain = pw.getUserName() + ":";
|
||||
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.auth = authValueFrom(pw, isUTF8);
|
||||
this.pw = pw;
|
||||
}
|
||||
|
||||
@@ -99,34 +86,30 @@ class BasicAuthentication extends AuthenticationInfo {
|
||||
* Create a BasicAuthentication
|
||||
*/
|
||||
public BasicAuthentication(boolean isProxy, URL url, String realm,
|
||||
PasswordAuthentication pw,
|
||||
PasswordAuthentication pw, boolean isUTF8,
|
||||
String authenticatorKey) {
|
||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||
AuthScheme.BASIC, url, realm,
|
||||
Objects.requireNonNull(authenticatorKey));
|
||||
String plain = pw.getUserName() + ":";
|
||||
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.auth = authValueFrom(pw, isUTF8);
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -2274,6 +2274,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
if (host != null && authhdr.isPresent()) {
|
||||
HeaderParser p = authhdr.headerParser();
|
||||
String realm = p.findValue("realm");
|
||||
String charset = p.findValue("charset");
|
||||
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||
String scheme = authhdr.scheme();
|
||||
AuthScheme authScheme = UNKNOWN;
|
||||
if ("basic".equalsIgnoreCase(scheme)) {
|
||||
@@ -2319,7 +2321,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
realm, scheme, url, RequestorType.PROXY);
|
||||
if (a != null) {
|
||||
ret = new BasicAuthentication(true, host, port, realm, a,
|
||||
getAuthenticatorKey());
|
||||
isUTF8, getAuthenticatorKey());
|
||||
}
|
||||
break;
|
||||
case DIGEST:
|
||||
@@ -2437,6 +2439,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
HeaderParser p = authhdr.headerParser();
|
||||
String realm = p.findValue("realm");
|
||||
String scheme = authhdr.scheme();
|
||||
String charset = p.findValue("charset");
|
||||
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||
AuthScheme authScheme = UNKNOWN;
|
||||
if ("basic".equalsIgnoreCase(scheme)) {
|
||||
authScheme = BASIC;
|
||||
@@ -2488,7 +2492,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
realm, scheme, url, RequestorType.SERVER);
|
||||
if (a != null) {
|
||||
ret = new BasicAuthentication(false, url, realm, a,
|
||||
getAuthenticatorKey());
|
||||
isUTF8, getAuthenticatorKey());
|
||||
}
|
||||
break;
|
||||
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.
|
||||
*
|
||||
* 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.channels.*;
|
||||
import java.nio.channels.spi.*;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
throws IOException
|
||||
{
|
||||
if ((off < 0) || (off > bs.length) || (len < 0) ||
|
||||
((off + len) > bs.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0)
|
||||
Objects.checkFromIndexSize(off, len, bs.length);
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
ByteBuffer bb = ((this.bs == bs)
|
||||
@@ -119,6 +117,26 @@ public class ChannelInputStream
|
||||
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 {
|
||||
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.
|
||||
*
|
||||
* 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 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
|
||||
public static final int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1;
|
||||
public static final int SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2;
|
||||
|
||||
// volume flags
|
||||
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_DIRECTORY = 267;
|
||||
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_NOT_A_REPARSE_POINT = 4390;
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -470,9 +470,6 @@ class WindowsFileSystemProvider
|
||||
} catch (WindowsException x) {
|
||||
x.rethrowAsIOException(file);
|
||||
}
|
||||
// DOS hidden attribute not meaningful when set on directories
|
||||
if (attrs.isDirectory())
|
||||
return false;
|
||||
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.
|
||||
*
|
||||
* 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 jdk.internal.misc.Unsafe;
|
||||
|
||||
import static sun.nio.fs.WindowsConstants.*;
|
||||
|
||||
/**
|
||||
* Win32 and library calls.
|
||||
*/
|
||||
@@ -920,6 +922,12 @@ class WindowsNativeDispatcher {
|
||||
* LPCWSTR lpTargetFileName,
|
||||
* 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)
|
||||
throws WindowsException
|
||||
@@ -929,6 +937,19 @@ class WindowsNativeDispatcher {
|
||||
try {
|
||||
CreateSymbolicLink0(linkBuffer.address(), targetBuffer.address(),
|
||||
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 {
|
||||
targetBuffer.release();
|
||||
linkBuffer.release();
|
||||
|
||||
@@ -1056,17 +1056,7 @@ Java_sun_nio_fs_WindowsNativeDispatcher_CreateSymbolicLink0(JNIEnv* env,
|
||||
LPCWSTR link = jlong_to_ptr(linkAddress);
|
||||
LPCWSTR target = jlong_to_ptr(targetAddress);
|
||||
|
||||
// Allow creation of symbolic links when the process is not elevated.
|
||||
// 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)
|
||||
if (CreateSymbolicLinkW(link, target, (DWORD)flags) == 0)
|
||||
throwWindowsException(env, GetLastError());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,12 @@ import java.awt.Robot;
|
||||
import java.awt.peer.RobotPeer;
|
||||
|
||||
import sun.awt.CGraphicsDevice;
|
||||
import sun.security.action.GetIntegerAction;
|
||||
|
||||
final class CRobot implements RobotPeer {
|
||||
|
||||
private static final int MOUSE_LOCATION_UNKNOWN = -1;
|
||||
private static final int DEFAULT_SAFE_DELAY_MILLIS = 50;
|
||||
|
||||
private final CGraphicsDevice fDevice;
|
||||
private int mouseLastX = MOUSE_LOCATION_UNKNOWN;
|
||||
@@ -51,7 +53,12 @@ final class CRobot implements RobotPeer {
|
||||
*/
|
||||
public CRobot(Robot r, CGraphicsDevice 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
|
||||
@@ -193,7 +200,7 @@ final class CRobot implements RobotPeer {
|
||||
return c;
|
||||
}
|
||||
|
||||
private native void initRobot();
|
||||
private native void initRobot(int safeDelayMillis);
|
||||
private native void mouseEvent(int lastX, int lastY, int buttonsState,
|
||||
boolean isButtonsDownState,
|
||||
boolean isMouseMove);
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
|
||||
#import "java_awt_event_InputEvent.h"
|
||||
#import "java_awt_event_KeyEvent.h"
|
||||
#import "sun_awt_event_KeyEvent.h"
|
||||
#import "LWCToolkit.h"
|
||||
|
||||
#import "jni_util.h"
|
||||
@@ -446,53 +445,13 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSDictionary* getDiacriticUnicharToVkCodeDictionary() {
|
||||
static const int UNICODE_OFFSET = 0x01000000;
|
||||
|
||||
static NSDictionary* diacriticUnicharToVkCodeDictionary = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
diacriticUnicharToVkCodeDictionary =
|
||||
[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 BOOL isLatinUnicode(unichar ch) {
|
||||
// Latin-1 Supplement 0x0080 - 0x00FF
|
||||
// Latin Extended-A 0x0100 - 0x017F
|
||||
// Latin Extended-B 0x0180 - 0x024F
|
||||
return 0x0080 <= ch && ch <= 0x024F;
|
||||
}
|
||||
|
||||
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_SLASH], @"/",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PERIOD], @".",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_MULTIPLY], @"*",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_ADD], @"+",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_ASTERISK], @"*",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PLUS], @"+",
|
||||
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
|
||||
[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
|
||||
];
|
||||
// This is ok to retain a singleton object
|
||||
@@ -614,17 +582,21 @@ NsCharToJavaVirtualKeyCode(unichar ch, BOOL isDeadChar,
|
||||
if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:ch] ||
|
||||
[[NSCharacterSet symbolCharacterSet] characterIsMember:ch])
|
||||
{
|
||||
*keyCode = [[unicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]] intValue];
|
||||
// we cannot find key location from a char, so let's use key code
|
||||
*postsTyped = YES;
|
||||
*keyLocation = keyTable[key].javaKeyLocation;
|
||||
return;
|
||||
// punctuationCharacterSet and symbolCharacterSet are too big
|
||||
// to store them all in UnicharToVkCodeDictionary
|
||||
int tmpKeyCode = [[unicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]] intValue];
|
||||
if (tmpKeyCode != 0) {
|
||||
*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();
|
||||
NSNumber * jkc = [diacriticUnicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]];
|
||||
if (jkc != nil) {
|
||||
*keyCode = [jkc intValue];
|
||||
// Latin-1 suplement & Latin Extended A & B
|
||||
if (isLatinUnicode(ch)) {
|
||||
*keyCode = ((int) ch) + UNICODE_OFFSET;
|
||||
// we cannot find key location from a char, so let's use key code
|
||||
*postsTyped = YES;
|
||||
*keyLocation = keyTable[key].javaKeyLocation;
|
||||
|
||||
@@ -68,6 +68,7 @@ static NSTimeInterval gsLastClickTime;
|
||||
static int gsEventNumber;
|
||||
static int* gsButtonEventNumber;
|
||||
static NSTimeInterval gNextKeyEventTime;
|
||||
static NSTimeInterval safeDelay;
|
||||
|
||||
static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
|
||||
|
||||
@@ -104,17 +105,17 @@ static inline void autoDelay(BOOL isMove) {
|
||||
[NSThread sleepForTimeInterval:delay];
|
||||
}
|
||||
}
|
||||
gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + 0.050;
|
||||
gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + safeDelay;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_lwawt_macosx_CRobot
|
||||
* Method: initRobot
|
||||
* Signature: (V)V
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
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.
|
||||
// Always set all states, in case Apple ever changes default behaviors.
|
||||
@@ -143,6 +144,7 @@ Java_sun_lwawt_macosx_CRobot_initRobot
|
||||
gsClickCount = 0;
|
||||
gsLastClickTime = 0;
|
||||
gNextKeyEventTime = 0;
|
||||
safeDelay = (NSTimeInterval)safeDelayMillis/1000;
|
||||
gsEventNumber = ROBOT_EVENT_NUMBER_START;
|
||||
|
||||
gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons);
|
||||
|
||||
@@ -95,8 +95,6 @@ import sun.awt.AppContext;
|
||||
import sun.awt.ComponentFactory;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.awt.AppContext;
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.ConstrainableGraphics;
|
||||
import sun.awt.EmbeddedFrame;
|
||||
import sun.awt.RequestFocusController;
|
||||
@@ -112,7 +110,6 @@ import sun.java2d.SunGraphics2D;
|
||||
import sun.java2d.SunGraphicsEnvironment;
|
||||
import sun.java2d.pipe.Region;
|
||||
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.swing.SwingAccessor;
|
||||
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 focusLog = PlatformLogger.getLogger("java.awt.focus.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
|
||||
@@ -7958,6 +7956,12 @@ public abstract class Component implements ImageObserver, MenuContainer,
|
||||
boolean focusedWindowChangeAllowed,
|
||||
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.
|
||||
AWTEvent currentEvent = EventQueue.getCurrentEvent();
|
||||
if (currentEvent instanceof MouseEvent &&
|
||||
|
||||
@@ -382,6 +382,7 @@ public class Window extends Container implements Accessible {
|
||||
private static final long serialVersionUID = 4497834738069338734L;
|
||||
|
||||
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;
|
||||
|
||||
@@ -1308,6 +1309,9 @@ public class Window extends Container implements Accessible {
|
||||
// This functionality is implemented in a final package-private method
|
||||
// to insure that it cannot be overridden by client subclasses.
|
||||
final void toFront_NoClientCode() {
|
||||
if (focusRequestLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
focusRequestLog.fine("toFront() for" + this, new Throwable());
|
||||
}
|
||||
if (visible) {
|
||||
WindowPeer peer = (WindowPeer)this.peer;
|
||||
if (peer != null) {
|
||||
@@ -1351,6 +1355,9 @@ public class Window extends Container implements Accessible {
|
||||
// This functionality is implemented in a final package-private method
|
||||
// to insure that it cannot be overridden by client subclasses.
|
||||
final void toBack_NoClientCode() {
|
||||
if (focusRequestLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
focusRequestLog.fine("toBack() for " + this, new Throwable());
|
||||
}
|
||||
if(isAlwaysOnTop()) {
|
||||
try {
|
||||
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;
|
||||
|
||||
/**
|
||||
* Use this gamma 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
|
||||
* Use this hints if gray gamma shader is enabled
|
||||
*/
|
||||
#define GRAY_LIGHT_GAMMA 220
|
||||
#define GRAY_DARK_GAMMA 140
|
||||
#define GRAY_GAMMA_THRESHOLD 0.5f
|
||||
typedef struct {
|
||||
float light_gamma; // brightness of light text
|
||||
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
|
||||
@@ -325,29 +326,32 @@ static const char *lcdTextShaderSource =
|
||||
"}";
|
||||
|
||||
static const char *grayGammaTextShaderSource =
|
||||
"uniform vec3 src_adj;"
|
||||
"uniform vec4 src_adj;"
|
||||
"uniform sampler2D glyph_tex;"
|
||||
"uniform float inv_light_gamma;"
|
||||
"uniform float inv_dark_gamma;"
|
||||
"uniform float threshold;"
|
||||
"uniform float inv_light_exp;"
|
||||
"uniform float inv_dark_exp;"
|
||||
"void main(void)"
|
||||
"{"
|
||||
" float glyph_clr = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
||||
" if (dot(src_adj, vec3(1.0/3.0, 1.0/3.0, 1.0/3.0)) > threshold) {"
|
||||
" glyph_clr = pow(glyph_clr, inv_light_gamma);"
|
||||
" } else { "
|
||||
" glyph_clr = pow(glyph_clr, inv_dark_gamma);"
|
||||
" }"
|
||||
" gl_FragColor = vec4(src_adj, glyph_clr);"
|
||||
"}";
|
||||
"float a = src_adj.a;"
|
||||
"vec3 col = src_adj.rgb;"
|
||||
|
||||
static const char *grayTextShaderSource =
|
||||
"uniform vec3 src_adj;"
|
||||
"uniform sampler2D glyph_tex;"
|
||||
"void main(void)"
|
||||
"{"
|
||||
" float glyph_clr = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
||||
" gl_FragColor = vec4(src_adj, glyph_clr);"
|
||||
// calculate brightness of the fragment
|
||||
"float b = dot(col, vec3(1.0/3.0, 1.0/3.0, 1.0/3.0))*a;"
|
||||
|
||||
// adjust fragment coverage
|
||||
"float frag_cov = float(texture2D(glyph_tex, gl_TexCoord[0].st));"
|
||||
"float exp = mix(inv_dark_exp, inv_light_exp, b);"
|
||||
"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;
|
||||
}
|
||||
|
||||
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
|
||||
* function returns a handle to the newly created shader program; otherwise
|
||||
* returns 0.
|
||||
*/
|
||||
static GLhandleARB
|
||||
OGLTR_CreateGrayTextProgram(jint lightContrast, jint darkContrast, jfloat threshold)
|
||||
OGLTR_CreateGrayTextProgram(jboolean useFontSmoothing)
|
||||
{
|
||||
GLhandleARB grayTextProgram;
|
||||
GLint loc;
|
||||
|
||||
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_CreateGrayTextProgram");
|
||||
|
||||
grayTextProgram = OGLContext_CreateFragmentProgram(
|
||||
lightContrast > 0 ? grayGammaTextShaderSource : grayTextShaderSource);
|
||||
grayTextProgram = OGLContext_CreateFragmentProgram(grayGammaTextShaderSource);
|
||||
|
||||
if (grayTextProgram == 0) {
|
||||
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
|
||||
j2d_glUseProgramObjectARB(grayTextProgram);
|
||||
|
||||
if (lightContrast > 0) {
|
||||
double ilg = 1.0 / (((double) lightContrast) / 100.0);
|
||||
double idg = 1.0 / (((double) darkContrast) / 100.0);
|
||||
J2dTraceLn1(J2D_TRACE_INFO,
|
||||
"OGLTR_CreateGrayTextProgram: contrast=%d", lightContrast);
|
||||
GrayRenderHints *hints = &(getGrayRenderHints()[useFontSmoothing]);
|
||||
J2dTraceLn5(J2D_TRACE_INFO,
|
||||
"OGLTR_CreateGrayTextProgram: useFontSmoothing=%d "
|
||||
"light_gamma=%f dark_gamma=%f light_exp=%f dark_exp=%f",
|
||||
useFontSmoothing, hints->light_gamma, hints->dark_gamma,
|
||||
hints->light_exp, hints->dark_exp);
|
||||
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_gamma");
|
||||
j2d_glUniform1fARB(loc, ilg);
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_dark_gamma");
|
||||
j2d_glUniform1fARB(loc, idg);
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "threshold");
|
||||
j2d_glUniform1fARB(loc, threshold);
|
||||
|
||||
}
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_gamma");
|
||||
j2d_glUniform1fARB(loc, hints->light_gamma);
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_dark_gamma");
|
||||
j2d_glUniform1fARB(loc, hints->dark_gamma);
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "inv_light_exp");
|
||||
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
|
||||
j2d_glUseProgramObjectARB(0);
|
||||
@@ -506,7 +603,7 @@ OGLTR_UpdateLCDTextColor(jint contrast)
|
||||
static jboolean
|
||||
OGLTR_UpdateGrayTextColor()
|
||||
{
|
||||
GLfloat radj, gadj, badj;
|
||||
GLfloat radj, gadj, badj, aadj;
|
||||
GLfloat clr[4];
|
||||
GLint loc;
|
||||
|
||||
@@ -525,10 +622,11 @@ OGLTR_UpdateGrayTextColor()
|
||||
radj = (GLfloat)clr[0];
|
||||
gadj = (GLfloat)clr[1];
|
||||
badj = (GLfloat)clr[2];
|
||||
aadj = (GLfloat)clr[3];
|
||||
|
||||
// update the "src_adj" parameter of the shader program with this value
|
||||
loc = j2d_glGetUniformLocationARB(grayTextProgram, "src_adj");
|
||||
j2d_glUniform3fARB(loc, radj, gadj, badj);
|
||||
j2d_glUniform4fARB(loc, radj, gadj, badj, aadj);
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
@@ -598,9 +696,7 @@ OGLTR_EnableLCDGlyphModeState(GLuint glyphTextureID,
|
||||
* Enables the GrayScale text shader and updates any related states
|
||||
*/
|
||||
static jboolean
|
||||
OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID,
|
||||
jint lightContrast, jint darkContrast,
|
||||
jfloat threshold)
|
||||
OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID, jboolean useFontSmoothing)
|
||||
{
|
||||
// bind the texture containing glyph data to texture unit 0
|
||||
j2d_glActiveTextureARB(GL_TEXTURE0_ARB);
|
||||
@@ -614,8 +710,7 @@ OGLTR_EnableGrayGlyphModeState(GLuint glyphTextureID,
|
||||
|
||||
// create the Gray text shader, if necessary
|
||||
if (grayTextProgram == 0) {
|
||||
grayTextProgram = OGLTR_CreateGrayTextProgram(
|
||||
lightContrast, darkContrast, threshold);
|
||||
grayTextProgram = OGLTR_CreateGrayTextProgram(useFontSmoothing);
|
||||
if (grayTextProgram == 0) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
@@ -716,8 +811,7 @@ OGLTR_DisableGlyphModeState()
|
||||
}
|
||||
|
||||
static jboolean
|
||||
OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo,
|
||||
jint x, jint y, jint lightContrast, jint darkContrast, jfloat threshold)
|
||||
OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo, jint x, jint y, jboolean useFontSmoothing)
|
||||
{
|
||||
CacheCellInfo *cell;
|
||||
jfloat x1, y1, x2, y2;
|
||||
@@ -732,8 +826,7 @@ OGLTR_DrawGrayscaleGlyphViaCache(OGLContext *oglc, GlyphInfo *ginfo,
|
||||
}
|
||||
}
|
||||
|
||||
if (!OGLTR_EnableGrayGlyphModeState(
|
||||
glyphCacheAA->cacheID, lightContrast, darkContrast, threshold))
|
||||
if (!OGLTR_EnableGrayGlyphModeState(glyphCacheAA->cacheID, useFontSmoothing))
|
||||
{
|
||||
return JNI_FALSE;
|
||||
}
|
||||
@@ -1234,9 +1327,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
|
||||
int glyphCounter;
|
||||
GLuint dstTextureID = 0;
|
||||
jlong time;
|
||||
jint lighGamma = 0;
|
||||
jint darkGamma = 0;
|
||||
jfloat threshold = 0;
|
||||
jboolean fontSmoothing = JNI_FALSE;
|
||||
|
||||
J2dTraceLn(J2D_TRACE_INFO, "OGLTR_DrawGlyphList");
|
||||
if (graphicsPrimitive_traceflags & J2D_PTRACE_TIME) {
|
||||
@@ -1276,11 +1367,7 @@ OGLTR_DrawGlyphList(JNIEnv *env, OGLContext *oglc, OGLSDOps *dstOps,
|
||||
}
|
||||
|
||||
subPixPos = lcdSubPixelPosSupported ? subPixPos : 0;
|
||||
if (useFontSmoothing) {
|
||||
lighGamma = GRAY_LIGHT_GAMMA;
|
||||
darkGamma = GRAY_DARK_GAMMA;
|
||||
threshold = GRAY_GAMMA_THRESHOLD;
|
||||
}
|
||||
fontSmoothing = useFontSmoothing;
|
||||
#endif
|
||||
|
||||
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 &&
|
||||
ginfo->height <= OGLTR_CACHE_CELL_HEIGHT)
|
||||
{
|
||||
ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y,
|
||||
lighGamma, darkGamma, threshold);
|
||||
ok = OGLTR_DrawGrayscaleGlyphViaCache(oglc, ginfo, x, y, fontSmoothing);
|
||||
} else {
|
||||
ok = OGLTR_DrawGrayscaleGlyphNoCache(oglc, ginfo, x, y);
|
||||
}
|
||||
|
||||
@@ -1726,7 +1726,7 @@ static jlong
|
||||
if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD && width > 0) {
|
||||
glyphInfo->width = width/3;
|
||||
glyphInfo->topLeftX -= 1;
|
||||
glyphInfo->width += 1;
|
||||
glyphInfo->width += 2;
|
||||
} else if (ftglyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) {
|
||||
glyphInfo->height = glyphInfo->height/3;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ public class XBaseWindow {
|
||||
|
||||
private static XAtom wm_client_leader;
|
||||
|
||||
private long userTime;
|
||||
private static long globalUserTime;
|
||||
|
||||
static enum InitialiseState {
|
||||
@@ -669,7 +668,7 @@ public class XBaseWindow {
|
||||
try {
|
||||
this.visible = visible;
|
||||
if (visible) {
|
||||
setUserTimeFromGlobal();
|
||||
setUserTimeBeforeShowing();
|
||||
XlibWrapper.XMapWindow(XToolkit.getDisplay(), getWindow());
|
||||
}
|
||||
else {
|
||||
@@ -1029,7 +1028,7 @@ public class XBaseWindow {
|
||||
public void handleVisibilityEvent(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) {
|
||||
}
|
||||
@@ -1060,7 +1059,7 @@ public class XBaseWindow {
|
||||
if (!isWheel) {
|
||||
switch (xev.get_type()) {
|
||||
case XConstants.ButtonPress:
|
||||
setUserTime(xbe.get_time());
|
||||
setUserTime(xbe.get_time(), true);
|
||||
if (buttonState == 0) {
|
||||
XWindowPeer parent = getToplevelXWindow();
|
||||
// See 6385277, 6981400.
|
||||
@@ -1299,15 +1298,12 @@ public class XBaseWindow {
|
||||
return x >= getAbsoluteX() && y >= getAbsoluteY() && x < (getAbsoluteX()+getWidth()) && y < (getAbsoluteY()+getHeight());
|
||||
}
|
||||
|
||||
void setUserTimeFromGlobal() {
|
||||
setUserTime(globalUserTime);
|
||||
void setUserTimeBeforeShowing() {
|
||||
if (globalUserTime != 0) setUserTime(globalUserTime, false);
|
||||
}
|
||||
|
||||
private void setUserTime(long time) {
|
||||
if (time == userTime) return;
|
||||
|
||||
userTime = time;
|
||||
if ((int)time - (int)globalUserTime > 0 /* accounting for wrap-around */) {
|
||||
protected void setUserTime(long time, boolean updateGlobalTime) {
|
||||
if (updateGlobalTime && (int)time - (int)globalUserTime > 0 /* accounting for wrap-around */) {
|
||||
globalUserTime = time;
|
||||
}
|
||||
XNETProtocol netProtocol = XWM.getWM().getNETProtocol();
|
||||
|
||||
@@ -1071,20 +1071,31 @@ abstract class XDecoratedPeer extends XWindowPeer {
|
||||
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) {
|
||||
// JDK-8159460
|
||||
Window focusedWindow = XKeyboardFocusManagerPeer.getInstance()
|
||||
.getCurrentFocusedWindow();
|
||||
Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow);
|
||||
if (activeWindow != target) {
|
||||
requestWindowFocus(cl.get_data(1), true);
|
||||
requestWindowFocus(requestTimeStamp, true);
|
||||
} else {
|
||||
WindowEvent we = new WindowEvent(focusedWindow,
|
||||
WindowEvent.WINDOW_GAINED_FOCUS);
|
||||
sendEvent(we);
|
||||
}
|
||||
} else {
|
||||
requestWindowFocus(cl.get_data(1), true);
|
||||
requestWindowFocus(requestTimeStamp, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ final class XWM
|
||||
MUTTER_WM = 15,
|
||||
UNITY_COMPIZ_WM = 16,
|
||||
XMONAD_WM = 17,
|
||||
AWESOME_WM = 18;
|
||||
AWESOME_WM = 18,
|
||||
I3_WM = 19;
|
||||
|
||||
public String toString() {
|
||||
switch (WMID) {
|
||||
@@ -621,6 +622,10 @@ final class XWM
|
||||
return isNetWMName("awesome");
|
||||
}
|
||||
|
||||
static boolean isI3() {
|
||||
return isNetWMName("i3");
|
||||
}
|
||||
|
||||
static int awtWMNonReparenting = -1;
|
||||
static boolean isNonReparentingWM() {
|
||||
if (awtWMNonReparenting == -1) {
|
||||
@@ -824,6 +829,8 @@ final class XWM
|
||||
awt_wmgr = XWM.XMONAD_WM;
|
||||
} else if (isAwesome()) {
|
||||
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
|
||||
|
||||
@@ -1216,6 +1216,12 @@ class XWindow extends XBaseWindow implements X11ComponentPeer {
|
||||
return (uni > 0? sun.awt.ExtendedKeyCodes.getExtendedKeyCodeForChar(uni) : 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) {
|
||||
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
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;
|
||||
if (KeyEventProcessing.useNationalLayouts) {
|
||||
// if jkeyToReturn is VK_UNDEFINED then look for keycode in extended key code
|
||||
jkeyToReturn = jkc.getJavaKeycode();
|
||||
jkeyToReturn = getNationalKeyCode(jkc, unicodeFromPrimaryKeysym);
|
||||
jkeyExtended = jkeyToReturn;
|
||||
} else {
|
||||
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,
|
||||
ev.get_time(),
|
||||
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.
|
||||
int unicodeFromPrimaryKeysym = keysymToUnicode( xkeycodeToPrimaryKeysym(ev) ,0);
|
||||
|
||||
int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ?
|
||||
primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) :
|
||||
jkc.getJavaKeycode();
|
||||
|
||||
int jkeyToReturn;
|
||||
if (KeyEventProcessing.useNationalLayouts) {
|
||||
// if jkeyToReturn is VK_UNDEFINED then look for keycode in extended key code
|
||||
jkeyToReturn = jkc.getJavaKeycode();
|
||||
jkeyToReturn = getNationalKeyCode(jkc, unicodeFromPrimaryKeysym);
|
||||
jkeyExtended = jkeyToReturn;
|
||||
} else {
|
||||
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,
|
||||
ev.get_time(),
|
||||
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){
|
||||
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.event.ComponentEvent;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.InvocationEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.peer.ComponentPeer;
|
||||
import java.awt.peer.WindowPeer;
|
||||
@@ -36,13 +35,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
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.ComponentAccessor;
|
||||
@@ -1112,7 +1105,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
||||
if (!isVisible() && vis) {
|
||||
isBeforeFirstMapNotify = true;
|
||||
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
|
||||
* 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
|
||||
* activation. Also, on Metacity, for some reason, we have _two_
|
||||
* 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);
|
||||
}
|
||||
@@ -1184,6 +1184,16 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
||||
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() {
|
||||
return !(target instanceof Frame || target instanceof Dialog);
|
||||
}
|
||||
@@ -1425,7 +1435,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
||||
isUnhiding |= isWMStateNetHidden();
|
||||
|
||||
super.handleMapNotifyEvent(xev);
|
||||
if (!winAttr.initialFocus) {
|
||||
if (!winAttr.initialFocus && XWM.getWMID() != XWM.I3_WM) {
|
||||
suppressWmTakeFocus(false); // restore the protocol.
|
||||
/*
|
||||
* For some reason, on Metacity, a frame/dialog being shown
|
||||
@@ -2053,7 +2063,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
|
||||
this.visible = visible;
|
||||
if (visible) {
|
||||
applyWindowType();
|
||||
setUserTimeFromGlobal();
|
||||
setUserTimeBeforeShowing();
|
||||
XlibWrapper.XMapRaised(XToolkit.getDisplay(), getWindow());
|
||||
} else {
|
||||
XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), getWindow());
|
||||
|
||||
@@ -700,6 +700,20 @@ void AwtDesktopProperties::GetOtherParameters() {
|
||||
}
|
||||
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&) {
|
||||
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.
|
||||
*
|
||||
* 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.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.WeakHashMap;
|
||||
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.SERVER;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Implementation of Http Basic authentication.
|
||||
@@ -154,8 +156,8 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (proxyURI != null) {
|
||||
CacheEntry ca = cache.get(proxyURI, true);
|
||||
if (ca != null) {
|
||||
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
|
||||
addBasicCredentials(r, true, ca.value);
|
||||
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||
addBasicCredentials(r, true, ca.value, ca.isUTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,8 +166,8 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (exchange.serverauth == null) {
|
||||
CacheEntry ca = cache.get(r.uri(), false);
|
||||
if (ca != null) {
|
||||
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
|
||||
addBasicCredentials(r, false, ca.value);
|
||||
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||
addBasicCredentials(r, false, ca.value, ca.isUTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,11 +175,13 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
// TODO: refactor into per auth scheme class
|
||||
private static void addBasicCredentials(HttpRequestImpl r,
|
||||
boolean proxy,
|
||||
PasswordAuthentication pw) {
|
||||
PasswordAuthentication pw,
|
||||
boolean isUTF8) {
|
||||
String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
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;
|
||||
if (proxy) {
|
||||
if (r.isConnect()) {
|
||||
@@ -202,35 +206,36 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
int retries;
|
||||
PasswordAuthentication credentials; // used in request
|
||||
CacheEntry cacheEntry; // if used
|
||||
final boolean isUTF8; //
|
||||
|
||||
AuthInfo(boolean fromcache,
|
||||
String scheme,
|
||||
PasswordAuthentication credentials) {
|
||||
PasswordAuthentication credentials, boolean isUTF8) {
|
||||
this.fromcache = fromcache;
|
||||
this.scheme = scheme;
|
||||
this.credentials = credentials;
|
||||
this.retries = 1;
|
||||
this.isUTF8 = isUTF8;
|
||||
}
|
||||
|
||||
AuthInfo(boolean fromcache,
|
||||
String scheme,
|
||||
PasswordAuthentication credentials,
|
||||
CacheEntry ca) {
|
||||
this(fromcache, scheme, credentials);
|
||||
CacheEntry ca, boolean isUTF8) {
|
||||
this(fromcache, scheme, credentials, isUTF8);
|
||||
assert credentials == null || (ca != null && ca.value == null);
|
||||
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
|
||||
// instance with fromCache==false so that it's put back in the
|
||||
// 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.retries = retries;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,41 +245,45 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
HttpHeaders hdrs = r.headers();
|
||||
HttpRequestImpl req = r.request();
|
||||
|
||||
if (status != UNAUTHORIZED && 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 (status != PROXY_UNAUTHORIZED) {
|
||||
if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
|
||||
AuthInfo au = exchange.proxyauth;
|
||||
URI proxyURI = getProxyURI(req);
|
||||
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;
|
||||
}
|
||||
|
||||
boolean proxy = status == PROXY_UNAUTHORIZED;
|
||||
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||
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.
|
||||
if (status != 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, au.isUTF8);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
HeaderParser parser = new HeaderParser(authval);
|
||||
String scheme = parser.findKey(0);
|
||||
|
||||
// TODO: Need to generalise from Basic only. Delegate to a provider class etc.
|
||||
|
||||
if (!scheme.equalsIgnoreCase("Basic")) {
|
||||
return null; // error gets returned to app
|
||||
boolean proxy = status == PROXY_UNAUTHORIZED;
|
||||
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||
List<String> authvals = hdrs.allValues(authname);
|
||||
if (authvals.isEmpty() && exchange.client().authenticator().isPresent()) {
|
||||
throw new IOException(authname + " header missing for response code " + status);
|
||||
}
|
||||
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) {
|
||||
@@ -303,14 +312,14 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
throw new IOException("No credentials provided");
|
||||
}
|
||||
// No authentication in request. Get credentials from user
|
||||
au = new AuthInfo(false, "Basic", pw);
|
||||
au = new AuthInfo(false, "Basic", pw, isUTF8);
|
||||
if (proxy) {
|
||||
exchange.proxyauth = au;
|
||||
} else {
|
||||
exchange.serverauth = au;
|
||||
}
|
||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||
addBasicCredentials(req, proxy, pw);
|
||||
addBasicCredentials(req, proxy, pw, isUTF8);
|
||||
return req;
|
||||
} else if (au.retries > retry_limit) {
|
||||
throw new IOException("too many authentication attempts. Limit: " +
|
||||
@@ -329,14 +338,14 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (pw == null) {
|
||||
throw new IOException("No credentials provided");
|
||||
}
|
||||
au = au.retryWithCredentials(pw);
|
||||
au = au.retryWithCredentials(pw, isUTF8);
|
||||
if (proxy) {
|
||||
exchange.proxyauth = au;
|
||||
} else {
|
||||
exchange.serverauth = au;
|
||||
}
|
||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||
addBasicCredentials(req, proxy, au.credentials);
|
||||
addBasicCredentials(req, proxy, au.credentials, isUTF8);
|
||||
au.retries++;
|
||||
return req;
|
||||
}
|
||||
@@ -373,10 +382,18 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
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) {
|
||||
for (CacheEntry entry : entries) {
|
||||
if (entry.equalsKey(domain, proxy)) {
|
||||
entries.remove(entry);
|
||||
var iterator = entries.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
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,
|
||||
URI domain,
|
||||
boolean proxy,
|
||||
PasswordAuthentication value) {
|
||||
PasswordAuthentication value, boolean isUTF8) {
|
||||
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 boolean proxy;
|
||||
final PasswordAuthentication value;
|
||||
final boolean isUTF8;
|
||||
|
||||
CacheEntry(String authscheme,
|
||||
URI uri,
|
||||
boolean proxy,
|
||||
PasswordAuthentication value) {
|
||||
PasswordAuthentication value, boolean isUTF8) {
|
||||
this.scheme = authscheme;
|
||||
this.root = normalize(uri, true).toString(); // remove extraneous components
|
||||
this.proxy = proxy;
|
||||
this.value = value;
|
||||
this.isUTF8 = isUTF8;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* 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)
|
||||
: findConnection(key, plainPool);
|
||||
//System.out.println ("getConnection returning: " + c);
|
||||
assert c == null || c.isSecure() == secure;
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -155,6 +156,10 @@ final class ConnectionPool {
|
||||
// Called also by whitebox tests
|
||||
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,
|
||||
// but register it before the connection is added to the pool,
|
||||
// since we don't want to trigger the cleanup if the connection
|
||||
@@ -450,7 +455,7 @@ final class ConnectionPool {
|
||||
if (c instanceof PlainHttpConnection) {
|
||||
removeFromPool(c, plainPool);
|
||||
} else {
|
||||
assert c.isSecure();
|
||||
assert c.isSecure() : "connection " + c + " is not secure!";
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -263,7 +263,7 @@ class Http1Response<T> {
|
||||
connection.close();
|
||||
return MinimalFuture.completedFuture(null); // not treating as error
|
||||
} 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,
|
||||
boolean return2Cache,
|
||||
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;
|
||||
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.
|
||||
*
|
||||
* 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 jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import jdk.internal.net.http.websocket.OpeningHandshake;
|
||||
import jdk.internal.net.http.websocket.WebSocketRequest;
|
||||
|
||||
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. */
|
||||
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.
|
||||
*
|
||||
* 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 {
|
||||
|
||||
final int responseCode;
|
||||
final Exchange<T> exchange;
|
||||
final HttpRequest initialRequest;
|
||||
final Optional<HttpResponse<T>> previousResponse;
|
||||
final HttpHeaders headers;
|
||||
final Optional<SSLSession> sslSession;
|
||||
final URI uri;
|
||||
final HttpClient.Version version;
|
||||
RawChannel rawchan;
|
||||
final HttpConnection connection;
|
||||
final Stream<T> stream;
|
||||
final RawChannelProvider rawChannelProvider;
|
||||
final T body;
|
||||
|
||||
public HttpResponseImpl(HttpRequest initialRequest,
|
||||
@@ -62,7 +59,6 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
||||
T body,
|
||||
Exchange<T> exch) {
|
||||
this.responseCode = response.statusCode();
|
||||
this.exchange = exch;
|
||||
this.initialRequest = initialRequest;
|
||||
this.previousResponse = Optional.ofNullable(previousResponse);
|
||||
this.headers = response.headers();
|
||||
@@ -70,23 +66,10 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
||||
this.sslSession = Optional.ofNullable(response.getSSLSession());
|
||||
this.uri = response.request().uri();
|
||||
this.version = response.version();
|
||||
this.connection = connection(exch);
|
||||
this.stream = null;
|
||||
this.rawChannelProvider = RawChannelProvider.create(response, exch);
|
||||
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
|
||||
public int statusCode() {
|
||||
return responseCode;
|
||||
@@ -141,23 +124,35 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
||||
*/
|
||||
@Override
|
||||
public synchronized RawChannel rawChannel() throws IOException {
|
||||
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);
|
||||
if (rawChannelProvider == null) {
|
||||
throw new UnsupportedOperationException(
|
||||
"RawChannel is only supported for WebSocket creation");
|
||||
}
|
||||
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
|
||||
@@ -174,4 +169,68 @@ class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {
|
||||
.append(statusCode());
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -362,6 +362,10 @@ class MultiExchange<T> {
|
||||
this.response =
|
||||
new HttpResponseImpl<>(currentreq, response, this.response, null, 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) -> {
|
||||
previousreq = currentreq;
|
||||
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.
|
||||
*
|
||||
* 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.writePublisher = new WritePublisher();
|
||||
this.readSubscriber = new ReadSubscriber();
|
||||
dbgTag = "[WebSocket] RawChannelTube(" + tube.toString() +")";
|
||||
dbgTag = "[WebSocket] RawChannelTube(" + tube +")";
|
||||
debug = Utils.getWebSocketLogger(dbgTag::toString, Utils.DEBUG_WS);
|
||||
connection.client().webSocketOpen();
|
||||
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.
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* -Djava.net.HttpClient.log=
|
||||
* -Djdk.httpclient.HttpClient.log=
|
||||
* errors,requests,headers,
|
||||
* 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.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.net.http.HttpRequestImpl;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -240,6 +241,15 @@ public final class Utils {
|
||||
: ! 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) {
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
request = requestBuilder.buildForWebSocket();
|
||||
request.isWebSocket(true);
|
||||
request.setSystemHeader(HEADER_UPGRADE, "websocket");
|
||||
request.setSystemHeader(HEADER_CONNECTION, "Upgrade");
|
||||
Utils.setWebSocketUpgradeHeaders(request);
|
||||
request.setProxy(proxy);
|
||||
}
|
||||
|
||||
@@ -189,7 +188,7 @@ public class OpeningHandshake {
|
||||
|
||||
public CompletableFuture<Result> send() {
|
||||
PrivilegedAction<CompletableFuture<Result>> pa = () ->
|
||||
client.sendAsync(this.request, BodyHandlers.discarding())
|
||||
client.sendAsync(this.request, BodyHandlers.ofString())
|
||||
.thenCompose(this::resultFrom);
|
||||
return AccessController.doPrivileged(pa);
|
||||
}
|
||||
@@ -216,19 +215,26 @@ public class OpeningHandshake {
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6455#section-7.4.1
|
||||
Result result = null;
|
||||
Exception exception = null;
|
||||
Throwable exception = null;
|
||||
try {
|
||||
result = handleResponse(response);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
} catch (Exception 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) {
|
||||
return MinimalFuture.completedFuture(result);
|
||||
}
|
||||
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) {
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -40,6 +40,7 @@ public interface RawChannel extends Closeable {
|
||||
interface Provider {
|
||||
|
||||
RawChannel rawChannel() throws IOException;
|
||||
void closeRawChannel() throws IOException;
|
||||
}
|
||||
|
||||
interface RawEvent {
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
|
||||
package com.sun.net.httpserver;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* BasicAuthenticator provides an implementation of HTTP Basic
|
||||
@@ -35,15 +39,44 @@ import java.util.Base64;
|
||||
*/
|
||||
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
|
||||
* @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) {
|
||||
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.charset = charset;
|
||||
this.isUTF8 = charset.equals(UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +96,9 @@ public abstract class BasicAuthenticator extends Authenticator {
|
||||
String auth = rmap.getFirst ("Authorization");
|
||||
if (auth == null) {
|
||||
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);
|
||||
}
|
||||
int sp = auth.indexOf (' ');
|
||||
@@ -71,7 +106,7 @@ public abstract class BasicAuthenticator extends Authenticator {
|
||||
return new Authenticator.Failure (401);
|
||||
}
|
||||
byte[] b = Base64.getDecoder().decode(auth.substring(sp+1));
|
||||
String userpass = new String (b);
|
||||
String userpass = new String (b, charset);
|
||||
int colon = userpass.indexOf (':');
|
||||
String uname = userpass.substring (0, colon);
|
||||
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.
|
||||
*
|
||||
* 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.util.*;
|
||||
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
|
||||
@@ -37,6 +40,18 @@ public class ProxyServer extends Thread implements Closeable {
|
||||
ServerSocket listener;
|
||||
int port;
|
||||
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()
|
||||
@@ -46,19 +61,42 @@ public class ProxyServer extends Thread implements Closeable {
|
||||
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;
|
||||
listener = new ServerSocket();
|
||||
listener.setReuseAddress(false);
|
||||
listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
|
||||
this.port = listener.getLocalPort();
|
||||
this.credentials = credentials;
|
||||
setName("ProxyListener");
|
||||
setDaemon(true);
|
||||
connections = new LinkedList<>();
|
||||
start();
|
||||
}
|
||||
|
||||
public ProxyServer(String s) { }
|
||||
public ProxyServer(String s) {
|
||||
credentials = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port number this proxy is listening on
|
||||
@@ -194,16 +232,69 @@ public class ProxyServer extends Thread implements Closeable {
|
||||
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() {
|
||||
try {
|
||||
byte[] buf = readHeaders(clientIn);
|
||||
int p = findCRLF(buf);
|
||||
if (p == -1) {
|
||||
close();
|
||||
return;
|
||||
byte[] buf;
|
||||
while (true) {
|
||||
buf = readHeaders(clientIn);
|
||||
if (findCRLF(buf) == -1) {
|
||||
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[] params = cmd.split(" ");
|
||||
|
||||
if (params[0].equals("CONNECT")) {
|
||||
doTunnel(params[1]);
|
||||
} 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.
|
||||
*
|
||||
* 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.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
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;
|
||||
|
||||
@@ -92,12 +93,32 @@ public class DummyWebSocketServer implements Closeable {
|
||||
private ByteBuffer read = ByteBuffer.allocate(16384);
|
||||
private final CountDownLatch readReady = new CountDownLatch(1);
|
||||
|
||||
public DummyWebSocketServer() {
|
||||
this(defaultMapping());
|
||||
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 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);
|
||||
Credentials credentials = username != null ?
|
||||
new Credentials(username, password) : null;
|
||||
|
||||
thread = new Thread(() -> {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
@@ -107,14 +128,23 @@ public class DummyWebSocketServer implements Closeable {
|
||||
try {
|
||||
channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
channel.configureBlocking(true);
|
||||
StringBuilder request = new StringBuilder();
|
||||
if (!readRequest(channel, request)) {
|
||||
throw new IOException("Bad request:" + request);
|
||||
while (true) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
List<String> strings = asList(request.toString().split("\r\n"));
|
||||
List<String> response = mapping.apply(strings);
|
||||
writeResponse(channel, response);
|
||||
serve(channel);
|
||||
} catch (IOException e) {
|
||||
err.println("Error in connection: " + channel + ", " + e);
|
||||
} finally {
|
||||
@@ -125,7 +155,7 @@ public class DummyWebSocketServer implements Closeable {
|
||||
}
|
||||
} catch (ClosedByInterruptException ignored) {
|
||||
} catch (Exception e) {
|
||||
err.println(e);
|
||||
e.printStackTrace(err);
|
||||
} finally {
|
||||
close(ssc);
|
||||
err.println("Stopped at: " + getURI());
|
||||
@@ -256,8 +286,8 @@ public class DummyWebSocketServer implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private static Function<List<String>, List<String>> defaultMapping() {
|
||||
return request -> {
|
||||
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()) {
|
||||
@@ -309,14 +339,57 @@ public class DummyWebSocketServer implements Closeable {
|
||||
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'",
|
||||
|
||||
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.
|
||||
*
|
||||
* 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) {
|
||||
return serverWithCannedDataAndAuthentication(null, null, data);
|
||||
}
|
||||
|
||||
public static DummyWebSocketServer 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 serverWithCannedData(copy);
|
||||
return serverWithCannedDataAndAuthentication(username, password, copy);
|
||||
}
|
||||
|
||||
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);
|
||||
return new DummyWebSocketServer() {
|
||||
return new DummyWebSocketServer(username, password) {
|
||||
@Override
|
||||
protected void write(SocketChannel ch) throws IOException {
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8240666
|
||||
* @summary Basic test for WebSocketHandshakeException
|
||||
* @library /lib/testlibrary
|
||||
* @build jdk.testlibrary.SimpleSSLContext
|
||||
@@ -55,7 +56,9 @@ import java.util.concurrent.Executors;
|
||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
import static java.lang.System.out;
|
||||
|
||||
public class WSHandshakeExceptionTest {
|
||||
|
||||
@@ -107,6 +110,9 @@ public class WSHandshakeExceptionTest {
|
||||
}
|
||||
WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8217429
|
||||
* @build DummyWebSocketServer
|
||||
* @run testng/othervm
|
||||
* WebSocketTest
|
||||
@@ -33,23 +34,32 @@ import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
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.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.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||
import static java.net.http.HttpClient.newBuilder;
|
||||
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.assertThrows;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
public class WebSocketTest {
|
||||
|
||||
@@ -68,8 +78,11 @@ public class WebSocketTest {
|
||||
|
||||
@AfterTest
|
||||
public void cleanup() {
|
||||
server.close();
|
||||
webSocket.abort();
|
||||
System.out.println("AFTER TEST");
|
||||
if (server != null)
|
||||
server.close();
|
||||
if (webSocket != null)
|
||||
webSocket.abort();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,6 +147,8 @@ public class WebSocketTest {
|
||||
assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
|
||||
assertThrows(IAE, () -> webSocket.request(-1));
|
||||
assertThrows(IAE, () -> webSocket.request(0));
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -149,6 +164,7 @@ public class WebSocketTest {
|
||||
// Pings & Pongs are fine
|
||||
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
||||
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -165,6 +181,7 @@ public class WebSocketTest {
|
||||
// Pings & Pongs are fine
|
||||
webSocket.sendPing(ByteBuffer.allocate(125)).join();
|
||||
webSocket.sendPong(ByteBuffer.allocate(125)).join();
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -198,6 +215,8 @@ public class WebSocketTest {
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@DataProvider(name = "sequence")
|
||||
@@ -318,6 +337,8 @@ public class WebSocketTest {
|
||||
listener.invocations();
|
||||
violation.complete(null); // won't affect if completed exceptionally
|
||||
violation.join();
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -372,10 +393,48 @@ public class WebSocketTest {
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
|
||||
assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleAggregatingBinaryMessages() throws IOException {
|
||||
// Used to verify a server requiring Authentication
|
||||
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")
|
||||
.stream()
|
||||
.map(s -> s.getBytes(StandardCharsets.US_ASCII))
|
||||
@@ -399,7 +458,7 @@ public class WebSocketTest {
|
||||
};
|
||||
CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
|
||||
|
||||
server = Support.serverWithCannedData(binary);
|
||||
server = serverSupplier.apply(binary);
|
||||
server.open();
|
||||
|
||||
WebSocket.Listener listener = new WebSocket.Listener() {
|
||||
@@ -437,7 +496,7 @@ public class WebSocketTest {
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
.join();
|
||||
|
||||
List<byte[]> a = actual.join();
|
||||
assertEquals(a, expected);
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleAggregatingTextMessages() throws IOException {
|
||||
|
||||
@Test(dataProvider = "servers")
|
||||
public void simpleAggregatingTextMessages
|
||||
(Function<int[],DummyWebSocketServer> serverSupplier)
|
||||
throws IOException
|
||||
{
|
||||
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
||||
|
||||
int[] binary = new int[]{
|
||||
@@ -488,7 +554,7 @@ public class WebSocketTest {
|
||||
};
|
||||
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
||||
|
||||
server = Support.serverWithCannedData(binary);
|
||||
server = serverSupplier.apply(binary);
|
||||
server.open();
|
||||
|
||||
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)
|
||||
.join();
|
||||
|
||||
List<String> a = actual.join();
|
||||
assertEquals(a, expected);
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Exercises the scenario where requests for more messages are made prior to
|
||||
* completing the returned CompletionStage instances.
|
||||
*/
|
||||
@Test
|
||||
public void aggregatingTextMessages() throws IOException {
|
||||
|
||||
@Test(dataProvider = "servers")
|
||||
public void aggregatingTextMessages
|
||||
(Function<int[],DummyWebSocketServer> serverSupplier)
|
||||
throws IOException
|
||||
{
|
||||
List<String> expected = List.of("alpha", "beta", "gamma", "delta");
|
||||
|
||||
int[] binary = new int[]{
|
||||
@@ -566,8 +639,7 @@ public class WebSocketTest {
|
||||
};
|
||||
CompletableFuture<List<String>> actual = new CompletableFuture<>();
|
||||
|
||||
|
||||
server = Support.serverWithCannedData(binary);
|
||||
server = serverSupplier.apply(binary);
|
||||
server.open();
|
||||
|
||||
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)
|
||||
.join();
|
||||
|
||||
List<String> a = actual.join();
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -22,11 +22,14 @@
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @bug 4313887 6838333 8005566 8032220
|
||||
* @bug 4313887 6838333 8005566 8032220 8215467 8227080
|
||||
* @summary Unit test for miscellenous methods in java.nio.file.Files
|
||||
* @library ..
|
||||
*/
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.file.*;
|
||||
import static java.nio.file.Files.*;
|
||||
import static java.nio.file.LinkOption.*;
|
||||
@@ -44,6 +47,7 @@ public class Misc {
|
||||
testIsSameFile(dir);
|
||||
testFileTypeMethods(dir);
|
||||
testAccessMethods(dir);
|
||||
testSkip(dir);
|
||||
} finally {
|
||||
TestUtil.removeAll(dir);
|
||||
}
|
||||
@@ -102,6 +106,18 @@ public class Misc {
|
||||
} finally {
|
||||
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 {
|
||||
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) {
|
||||
if (!okay)
|
||||
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() {
|
||||
// myCefBrowser.close(false);
|
||||
myCefBrowser.close(true);
|
||||
myCefClient.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import java.awt.event.KeyEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static java.awt.event.KeyEvent.*;
|
||||
import static sun.awt.event.KeyEvent.*; /* comment this line to compile with jbrsdk8 */
|
||||
|
||||
/*
|
||||
* Class containing common key functionality
|
||||
@@ -54,18 +53,23 @@ public class Key {
|
||||
int getKeyCode() {
|
||||
KeyChar keyChar = mappedKeyChars.getKeyChar();
|
||||
char ch = keyChar.getChar();
|
||||
if (latinKeyCodesMap.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)) {
|
||||
if (keyChar.isDead() && deadKeyCodesMap.containsKey(ch)) {
|
||||
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for dead keys
|
||||
return deadKeyCodesMap.get(ch);
|
||||
} else if (isLatinUnicode(ch)) {
|
||||
// Please see JBR-2672
|
||||
final int UNICODE_OFFSET = 0x01000000;
|
||||
return UNICODE_OFFSET + (int) ch;
|
||||
} else {
|
||||
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
|
||||
public char getChar(Modifier modifier) {
|
||||
return mappedKeyChars.getKeyChar(modifier).getChar();
|
||||
@@ -91,49 +95,6 @@ public class Key {
|
||||
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
|
||||
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/FullScreen/8013581/bug8013581.java 8169471 macosx-all,windows-all,linux-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/FullScreenInsets/FullScreenInsets.java 7019055 windows-all,linux-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/BackgroundIsNotUpdated/BackgroundIsNotUpdated.java 8142536 generic-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/MultiWindowAppTest.java 8159904 macosx-all,windows-all,linux-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/URIListToFileListBetweenJVMsTest/URIListToFileListBetweenJVMsTest.html 8194947 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/InputEvent/EventWhenTest/EventWhenTest.java 8168646 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
|
||||
|
||||
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/TouchScreenEventsTestLinux.sh JBR-2585 linux-all
|
||||
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/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/ObscuredFrame/ObscuredFrameTest.java nobug macosx-all,linux-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/TranslucentWindow/TranslucentWindow.java nobug windows-all
|
||||
java/awt/Graphics/LineClipTest.java nobug macosx-all,windows-all
|
||||
|
||||
Reference in New Issue
Block a user