diff -r 1ad58e0cbf16 -r 7133f144981a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java Fri Dec 09 11:35:02 2016 +0000 @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2015, 2016, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.incubator.http.internal.common; + +import jdk.internal.misc.InnocuousThread; +import sun.net.NetProperties; + +import javax.net.ssl.SSLParameters; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.NetPermission; +import java.net.URI; +import java.net.URLPermission; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Predicate; + +/** + * Miscellaneous utilities + */ +public final class Utils { + + /** + * Allocated buffer size. Must never be higher than 16K. But can be lower + * if smaller allocation units preferred. HTTP/2 mandates that all + * implementations support frame payloads of at least 16K. + */ + public static final int DEFAULT_BUFSIZE = 16 * 1024; + + public static final int BUFSIZE = getIntegerNetProperty( + "jdk.httpclient.bufsize", DEFAULT_BUFSIZE + ); + + private static final Set DISALLOWED_HEADERS_SET = Set.of( + "authorization", "connection", "cookie", "content-length", + "date", "expect", "from", "host", "origin", "proxy-authorization", + "referer", "user-agent", "upgrade", "via", "warning"); + + public static final Predicate + ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header); + + public static final Predicate + ALL_HEADERS = header -> true; + + public static ByteBuffer getBuffer() { + return ByteBuffer.allocate(BUFSIZE); + } + + public static IOException getIOException(Throwable t) { + if (t instanceof IOException) { + return (IOException) t; + } + Throwable cause = t.getCause(); + if (cause != null) { + return getIOException(cause); + } + return new IOException(t); + } + + /** + * We use the same buffer for reading all headers and dummy bodies in an Exchange. + */ + public static ByteBuffer getExchangeBuffer() { + ByteBuffer buf = getBuffer(); + // Force a read the first time it is used + buf.limit(0); + return buf; + } + + /** + * Puts position to limit and limit to capacity so we can resume reading + * into this buffer, but if required > 0 then limit may be reduced so that + * no more than required bytes are read next time. + */ + static void resumeChannelRead(ByteBuffer buf, int required) { + int limit = buf.limit(); + buf.position(limit); + int capacity = buf.capacity() - limit; + if (required > 0 && required < capacity) { + buf.limit(limit + required); + } else { + buf.limit(buf.capacity()); + } + } + + private Utils() { } + + public static ExecutorService innocuousThreadPool() { + return Executors.newCachedThreadPool( + (r) -> InnocuousThread.newThread("DefaultHttpClient", r)); + } + + // ABNF primitives defined in RFC 7230 + private static final boolean[] tchar = new boolean[256]; + private static final boolean[] fieldvchar = new boolean[256]; + + static { + char[] allowedTokenChars = + ("!#$%&'*+-.^_`|~0123456789" + + "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); + for (char c : allowedTokenChars) { + tchar[c] = true; + } + for (char c = 0x21; c < 0xFF; c++) { + fieldvchar[c] = true; + } + fieldvchar[0x7F] = false; // a little hole (DEL) in the range + } + + /* + * Validates a RFC 7230 field-name. + */ + public static boolean isValidName(String token) { + for (int i = 0; i < token.length(); i++) { + char c = token.charAt(i); + if (c > 255 || !tchar[c]) { + return false; + } + } + return !token.isEmpty(); + } + + /* + * Validates a RFC 7230 field-value. + * + * "Obsolete line folding" rule + * + * obs-fold = CRLF 1*( SP / HTAB ) + * + * is not permitted! + */ + public static boolean isValidValue(String token) { + boolean accepted = true; + for (int i = 0; i < token.length(); i++) { + char c = token.charAt(i); + if (c > 255) { + return false; + } + if (accepted) { + if (c == ' ' || c == '\t') { + accepted = false; + } else if (!fieldvchar[c]) { + return false; // forbidden byte + } + } else { + if (c != ' ' && c != '\t') { + if (fieldvchar[c]) { + accepted = true; + } else { + return false; // forbidden byte + } + } + } + } + return accepted; + } + + /** + * Returns the security permission required for the given details. + * If method is CONNECT, then uri must be of form "scheme://host:port" + */ + public static URLPermission getPermission(URI uri, + String method, + Map> headers) { + StringBuilder sb = new StringBuilder(); + + String urlstring, actionstring; + + if (method.equals("CONNECT")) { + urlstring = uri.toString(); + actionstring = "CONNECT"; + } else { + sb.append(uri.getScheme()) + .append("://") + .append(uri.getAuthority()) + .append(uri.getPath()); + urlstring = sb.toString(); + + sb = new StringBuilder(); + sb.append(method); + if (headers != null && !headers.isEmpty()) { + sb.append(':'); + Set keys = headers.keySet(); + boolean first = true; + for (String key : keys) { + if (!first) { + sb.append(','); + } + sb.append(key); + first = false; + } + } + actionstring = sb.toString(); + } + return new URLPermission(urlstring, actionstring); + } + + public static void checkNetPermission(String target) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return; + } + NetPermission np = new NetPermission(target); + sm.checkPermission(np); + } + + public static int getIntegerNetProperty(String name, int defaultValue) { + return AccessController.doPrivileged((PrivilegedAction) () -> + NetProperties.getInteger(name, defaultValue)); + } + + static String getNetProperty(String name) { + return AccessController.doPrivileged((PrivilegedAction) () -> + NetProperties.get(name)); + } + + public static SSLParameters copySSLParameters(SSLParameters p) { + SSLParameters p1 = new SSLParameters(); + p1.setAlgorithmConstraints(p.getAlgorithmConstraints()); + p1.setCipherSuites(p.getCipherSuites()); + p1.setEnableRetransmissions(p.getEnableRetransmissions()); + p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm()); + p1.setMaximumPacketSize(p.getMaximumPacketSize()); + p1.setNeedClientAuth(p.getNeedClientAuth()); + String[] protocols = p.getProtocols(); + if (protocols != null) { + p1.setProtocols(protocols.clone()); + } + p1.setSNIMatchers(p.getSNIMatchers()); + p1.setServerNames(p.getServerNames()); + p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder()); + p1.setWantClientAuth(p.getWantClientAuth()); + return p1; + } + + /** + * Set limit to position, and position to mark. + */ + public static void flipToMark(ByteBuffer buffer, int mark) { + buffer.limit(buffer.position()); + buffer.position(mark); + } + + public static String stackTrace(Throwable t) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + String s = null; + try { + PrintStream p = new PrintStream(bos, true, "US-ASCII"); + t.printStackTrace(p); + s = bos.toString("US-ASCII"); + } catch (UnsupportedEncodingException ex) { + // can't happen + } + return s; + } + + /** + * Copies as much of src to dst as possible. + * Return number of bytes copied + */ + public static int copy(ByteBuffer src, ByteBuffer dst) { + int srcLen = src.remaining(); + int dstLen = dst.remaining(); + if (srcLen > dstLen) { + int diff = srcLen - dstLen; + int limit = src.limit(); + src.limit(limit - diff); + dst.put(src); + src.limit(limit); + } else { + dst.put(src); + } + return srcLen - src.remaining(); + } + + // copy up to amount from src to dst, but no more + public static int copyUpTo(ByteBuffer src, ByteBuffer dst, int amount) { + int toCopy = Math.min(src.remaining(), Math.min(dst.remaining(), amount)); + copy(src, dst, toCopy); + return toCopy; + } + + /** + * Copy amount bytes from src to dst. at least amount must be + * available in both dst and in src + */ + public static void copy(ByteBuffer src, ByteBuffer dst, int amount) { + int excess = src.remaining() - amount; + assert excess >= 0; + if (excess > 0) { + int srclimit = src.limit(); + src.limit(srclimit - excess); + dst.put(src); + src.limit(srclimit); + } else { + dst.put(src); + } + } + + public static ByteBuffer copy(ByteBuffer src) { + ByteBuffer dst = ByteBuffer.allocate(src.remaining()); + dst.put(src); + dst.flip(); + return dst; + } + + public static String dump(Object... objects) { + return Arrays.toString(objects); + } + + public static String stringOf(Collection source) { + // We don't know anything about toString implementation of this + // collection, so let's create an array + return Arrays.toString(source.toArray()); + } + + public static int remaining(ByteBuffer[] bufs) { + int remain = 0; + for (ByteBuffer buf : bufs) { + remain += buf.remaining(); + } + return remain; + } + + public static int remaining(List bufs) { + int remain = 0; + for (ByteBuffer buf : bufs) { + remain += buf.remaining(); + } + return remain; + } + + public static int remaining(ByteBufferReference[] refs) { + int remain = 0; + for (ByteBufferReference ref : refs) { + remain += ref.get().remaining(); + } + return remain; + } + + // assumes buffer was written into starting at position zero + static void unflip(ByteBuffer buf) { + buf.position(buf.limit()); + buf.limit(buf.capacity()); + } + + public static void close(Closeable... closeables) { + for (Closeable c : closeables) { + try { + c.close(); + } catch (IOException ignored) { } + } + } + + public static void close(Throwable t, Closeable... closeables) { + for (Closeable c : closeables) { + try { + ExceptionallyCloseable.close(t, c); + } catch (IOException ignored) { } + } + } + + /** + * Returns an array with the same buffers, but starting at position zero + * in the array. + */ + public static ByteBuffer[] reduce(ByteBuffer[] bufs, int start, int number) { + if (start == 0 && number == bufs.length) { + return bufs; + } + ByteBuffer[] nbufs = new ByteBuffer[number]; + int j = 0; + for (int i=start; i> queue = new LinkedBlockingQueue<>(); + parent.execute(() -> { + while (true) { + try { + Optional o = queue.take(); + if (!o.isPresent()) { + return; + } + o.get().run(); + } catch (InterruptedException ex) { + return; + } + } + }); + return new Executor() { + @Override + public void execute(Runnable command) { + queue.offer(Optional.ofNullable(command)); + } + }; + } + + private static void executeInline(Runnable r) { + r.run(); + } + + static Executor callingThreadExecutor() { + return Utils::executeInline; + } + + // Put all these static 'empty' singletons here + @SuppressWarnings("rawtypes") + public static final CompletableFuture[] EMPTY_CFARRAY = new CompletableFuture[0]; + + public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0); + public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0]; + + public static ByteBuffer slice(ByteBuffer buffer, int amount) { + ByteBuffer newb = buffer.slice(); + newb.limit(amount); + buffer.position(buffer.position() + amount); + return newb; + } + +}