jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java
changeset 42460 7133f144981a
child 43730 b6847c320451
--- /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<String> 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<String>
+        ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header);
+
+    public static final Predicate<String>
+        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<String, List<String>> 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<String> 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<Integer>) () ->
+                NetProperties.getInteger(name, defaultValue));
+    }
+
+    static String getNetProperty(String name) {
+        return AccessController.doPrivileged((PrivilegedAction<String>) () ->
+                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<ByteBuffer> 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<start+number; i++) {
+            nbufs[j++] = bufs[i];
+        }
+        return nbufs;
+    }
+
+    static String asString(ByteBuffer buf) {
+        byte[] b = new byte[buf.remaining()];
+        buf.get(b);
+        return new String(b, StandardCharsets.US_ASCII);
+    }
+
+    /**
+     * Returns a single threaded executor which uses one invocation
+     * of the parent executor to execute tasks (in sequence).
+     *
+     * Use a null valued Runnable to terminate.
+     */
+    // TODO: this is a blocking way of doing this;
+    public static Executor singleThreadExecutor(Executor parent) {
+        BlockingQueue<Optional<Runnable>> queue = new LinkedBlockingQueue<>();
+        parent.execute(() -> {
+            while (true) {
+                try {
+                    Optional<Runnable> 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;
+    }
+
+}