Compare commits

...

40 Commits
1016 ... 1098

Author SHA1 Message Date
Egor Ushakov
9b960dd02a navigation to issues from vcs log 2020-09-16 15:38:20 +03:00
Mikhail Grishchenko
6b7d5fd58c updated JTreg exclude list 2020-09-16 16:25:07 +07:00
Dmitry Batrak
ebcdeb7d80 JBR-2698 setAutoRequestFocus(false) breaks focus logic under i3 window manager on Linux 2020-09-11 11:11:17 +03:00
Vitaly Provodin
67870df19e updated JTreg exclude list 2020-09-11 10:36:59 +07:00
Nikita Gubarkov
afd19dbefd JBR-2614 Fixed LCD glyph width to include both left & right padding, so that rowBytes = width * 3 2020-09-10 15:45:25 +03:00
Nikita Gubarkov
a55097289b Revert "JBR-2614 Passed real glyph type from native code instead of guessing on rowBytes & width"
This reverts commit 8de39b80
2020-09-10 15:39:00 +03:00
Nikita Gubarkov
8de39b80cd JBR-2614 Passed real glyph type from native code instead of guessing on rowBytes & width 2020-09-10 01:50:34 +03:00
Dmitry Batrak
0f038754e5 JBR-2696 Log focus API invocations with stack traces 2020-09-07 14:19:43 +03:00
Vitaly Provodin
4aa278e4a0 Revert "JBR-2577 implemented precise CompilationMXBean#getTotalCompilationTime"
This reverts commit 89163e73
2020-09-07 16:44:40 +07:00
Vitaly Provodin
e3562ecc99 Revert "thread.cpp:4574:43: error: ‘nullptr’ quick fix"
This reverts commit 82a36017
2020-09-07 16:44:20 +07:00
Vitaly Provodin
b62d47da9c updated JTreg exclude list 2020-09-06 06:27:20 +07:00
Vitaly Provodin
e748f39e20 updated JTreg exclude list 2020-08-26 11:41:35 +07:00
Brian Burkhalter
428ade4fd8 8221852: SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE should be selected at runtime, not build time
Reviewed-by: alanb, shade
2020-08-24 11:36:15 +07:00
Alexey Ushakov
e28ff71e97 JBR-2617 Text with opacity renders black
Some more adjustments in font thickness for dark text. Added JVM properties for fine tuning
2020-08-21 23:52:21 +03:00
Elena Sayapina
f826eb992e IDEA-242347: [TESTUPDATE] Added a comment 2020-08-20 18:10:47 +07:00
Denis Konoplev
ba3f14c83a JBR-2669: set unicode for both keyCode and extendedKeyCode 2020-08-19 21:56:51 +03:00
Alexey Titov
82a3601748 thread.cpp:4574:43: error: ‘nullptr’ quick fix 2020-08-19 14:58:35 +03:00
Kirill Kirichenko
eeab5252e6 JBR-2667 Post review: rename win.darkTheme.on to win.lightTheme.on and reversed the logic 2020-08-19 11:32:58 +03:00
Alexey Ushakov
838bbedd1a Merge pull request #33 from JetBrains/alexey.titov/precise-compilation-time
JBR-2577 implemented precise CompilationMXBean#getTotalCompilationTime
2020-08-18 17:40:47 +03:00
Kirill Kirichenko
0e4ad056dd JBR-2667 Add new AWT desktop property for light/dark theme detection on Windows 10 2020-08-18 14:12:37 +03:00
Alexey Titov
89163e73d0 JBR-2577 implemented precise CompilationMXBean#getTotalCompilationTime 2020-08-13 15:37:22 +03:00
Dmitry Batrak
665ebc5d47 JBR-2652 Window is not focused in some cases on Linux 2020-08-11 13:44:58 +03:00
denis.konoplev
32b1c35305 IDEA-242347: Remove sun.awt.event.KeyEvent import 2020-08-10 12:25:10 +03:00
Alexey Ushakov
55c7be5fe9 JBR-2617 Text with opacity renders black
Minor adjustments of dark text according to UX-1320
2020-08-07 19:52:02 +03:00
denis.konoplev
5017e2e385 IDEA-242347: Remove redundant conversion for Latin unicode 2020-08-07 15:00:02 +03:00
Anton Tarasov
492c217125 JBR-2645 enable CefBrowser.close(true) in jcef reg tests 2020-08-06 16:19:03 +03:00
Alexey Ushakov
269c9580fb JBR-2617 Text with opacity renders black
Corrected bright text thickness (smooth on), bright and dark text thickness (smooth off)
2020-08-06 13:47:28 +03:00
Elena Sayapina
5f691bb788 JBR-2630 Typing speed in IDE editor was dropped after switching to 11.0.8
Introduced sun.awt.osx.RobotSafeDelayMillis property to control macOS specific safe delay for Robot methods.
50 ms safe delay was initially hardcoded in 3862142d (JDK-8242174: [macos] The NestedModelessDialogTest test make the macOS unstable) which affected performance tests execution.
2020-08-06 12:29:37 +07:00
Vitaly Provodin
bcdd2242ef JBR-2634 add facilities generating windows fastdebug builds 2020-08-06 09:53:42 +07:00
Rahul Yadav
faee138574 8240666: Websocket client’s OpeningHandshake discards the HTTP response body
The fix updates jdk.internal.net.http.websocket. OpeningHandshake.send() method to process the response body from server

Reviewed-by: chegar, dfuchs, prappo
(cherry picked from commit ed24927500)
2020-08-05 15:32:40 +07:00
Daniel Fuchs
fb69e212ba 8236859: WebSocket over authenticating proxy fails with NPE
This change fixes several issues with WebSocket and proxy authentication. The AuthenticationFilter is changed to support an authenticating server accessed through an authenticating proxy. MultiExchange is fixed to close the previous connection if a new connection is necessary to establish the websocket (websocket connections are not cached and must be closed in that case). WebSocket OpeningHandshake is fixed to close the connection (without creating the RawChannel) if the opening handshake doesn't result in 101 upgrade protocol.

Reviewed-by: prappo, chegar
(cherry picked from commit c6da6681d4)
2020-08-05 15:32:40 +07:00
Daniel Fuchs
d8cc648a18 8216478: Cleanup HttpResponseImpl back reference to HttpConnection
Retain a reference to Exchange and HttpConnection only when necessary, i.e. for WebSocket initial connection.

Reviewed-by: chegar
(cherry picked from commit ad67fe1baf)
2020-08-05 15:32:40 +07:00
Julia Boes
15d2b6217d 8232853: AuthenticationFilter.Cache::remove may throw ConcurrentModificationException
Change implementation to use iterator instead of plain LinkedList

Reviewed-by: dfuchs, vtewari
(cherry picked from commit d948bfd584)
2020-08-05 15:32:40 +07:00
Michael McMahon
b92f4a73b4 8199849: Add support for UTF-8 encoded credentials in HTTP Basic Authentication
Reviewed-by: chegar, dfuchs
(cherry picked from commit e3b6b7f842)
2020-08-05 15:32:40 +07:00
Michael McMahon
5f0e3bcd37 8217237: HttpClient does not deal well with multi-valued WWW-Authenticate challenge headers
Reviewed-by: chegar, dfuchs
(cherry picked from commit d089a4ae51)
2020-08-05 15:32:40 +07:00
Chris Hegarty
e6f336ae8b 8217429: WebSocket over authenticating proxy fails to send Upgrade headers
Reviewed-by: dfuchs, prappo
(cherry picked from commit 46f4ab603b)
2020-08-05 15:32:40 +07:00
Brian Burkhalter
7c0f78edf1 8227080: (fs) Files.newInputStream(...).skip(n) is slow
Reviewed-by: sbordet, rriggs, fweimer
(cherry picked from commit c6c82dd736)
2020-08-05 15:22:14 +07:00
Brian Burkhalter
a2ba1bd211 8215467: Files.isHidden should return true for hidden directories on Windows
Reviewed-by: alanb, bchristi, darcy
(cherry picked from commit 5c5d27962a)
2020-08-05 15:22:14 +07:00
Alexey Ushakov
c95adeb8f2 JBR-2617 Text with opacity renders black
Fix compilation on windows
2020-08-04 20:58:47 +03:00
Alexey Ushakov
c30306f779 JBR-2617 Text with opacity renders black
Implemented alpha blending in grayscale text rendering
2020-08-04 19:09:37 +03:00
56 changed files with 3574 additions and 521 deletions

14
.idea/vcs.xml generated
View File

@@ -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>

View 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

View 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

View File

@@ -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.

View File

@@ -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
*/

View File

@@ -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:

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 &&

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
}
/**

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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
*

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);
}
}

View 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");
}
}
}
}

View File

@@ -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);
}
}
}

View 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);
}
}

View 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");
}
}

View File

@@ -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 {

View File

@@ -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) { }
}
}
}

View File

@@ -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'",

View 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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View 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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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");

View 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);
}
}

View File

@@ -99,7 +99,7 @@ public class JBCefBrowser {
}
public void dispose() {
// myCefBrowser.close(false);
myCefBrowser.close(true);
myCefClient.dispose();
}
}

View File

@@ -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>() {
{

View File

@@ -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

View File

@@ -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