1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.http.internal.common;
  27 
  28 import jdk.internal.misc.InnocuousThread;
  29 import sun.net.NetProperties;
  30 import sun.net.util.IPAddressUtil;
  31 
  32 import javax.net.ssl.SSLParameters;
  33 import java.io.ByteArrayOutputStream;
  34 import java.io.Closeable;
  35 import java.io.IOException;
  36 import java.io.UncheckedIOException;
  37 import java.io.PrintStream;
  38 import java.io.UnsupportedEncodingException;
  39 import java.net.InetSocketAddress;
  40 import java.net.NetPermission;
  41 import java.net.URI;
  42 import java.net.URLPermission;
  43 import java.nio.ByteBuffer;
  44 import java.nio.charset.Charset;
  45 import java.nio.charset.StandardCharsets;
  46 import java.security.AccessController;
  47 import java.security.PrivilegedAction;
  48 import java.util.Arrays;
  49 import java.util.Collection;
  50 import java.util.List;
  51 import java.util.Map;
  52 import java.util.Optional;
  53 import java.util.Set;
  54 import java.util.concurrent.BlockingQueue;
  55 import java.util.concurrent.CompletableFuture;
  56 import java.util.concurrent.Executor;
  57 import java.util.concurrent.ExecutorService;
  58 import java.util.concurrent.Executors;
  59 import java.util.concurrent.LinkedBlockingQueue;
  60 import java.util.function.Predicate;
  61 import jdk.incubator.http.HttpHeaders;
  62 
  63 /**
  64  * Miscellaneous utilities
  65  */
  66 public final class Utils {
  67 
  68     /**
  69      * Allocated buffer size. Must never be higher than 16K. But can be lower
  70      * if smaller allocation units preferred. HTTP/2 mandates that all
  71      * implementations support frame payloads of at least 16K.
  72      */
  73     public static final int DEFAULT_BUFSIZE = 16 * 1024;
  74 
  75     public static final int BUFSIZE = getIntegerNetProperty(
  76             "jdk.httpclient.bufsize", DEFAULT_BUFSIZE
  77     );
  78 
  79     private static final Set<String> DISALLOWED_HEADERS_SET = Set.of(
  80             "authorization", "connection", "cookie", "content-length",
  81             "date", "expect", "from", "host", "origin", "proxy-authorization",
  82             "referer", "user-agent", "upgrade", "via", "warning");
  83 
  84     public static final Predicate<String>
  85         ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header);
  86 
  87     public static final Predicate<String>
  88         ALL_HEADERS = header -> true;
  89 
  90     public static ByteBuffer getBuffer() {
  91         return ByteBuffer.allocate(BUFSIZE);
  92     }
  93 
  94     public static IOException getIOException(Throwable t) {
  95         if (t instanceof IOException) {
  96             return (IOException) t;
  97         }
  98         Throwable cause = t.getCause();
  99         if (cause != null) {
 100             return getIOException(cause);
 101         }
 102         return new IOException(t);
 103     }
 104 
 105     /**
 106      * We use the same buffer for reading all headers and dummy bodies in an Exchange.
 107      */
 108     public static ByteBuffer getExchangeBuffer() {
 109         ByteBuffer buf = getBuffer();
 110         // Force a read the first time it is used
 111         buf.limit(0);
 112         return buf;
 113     }
 114 
 115     /**
 116      * Puts position to limit and limit to capacity so we can resume reading
 117      * into this buffer, but if required > 0 then limit may be reduced so that
 118      * no more than required bytes are read next time.
 119      */
 120     static void resumeChannelRead(ByteBuffer buf, int required) {
 121         int limit = buf.limit();
 122         buf.position(limit);
 123         int capacity = buf.capacity() - limit;
 124         if (required > 0 && required < capacity) {
 125             buf.limit(limit + required);
 126         } else {
 127             buf.limit(buf.capacity());
 128         }
 129     }
 130 
 131     private Utils() { }
 132 
 133     public static ExecutorService innocuousThreadPool() {
 134         return Executors.newCachedThreadPool(
 135                 (r) -> InnocuousThread.newThread("DefaultHttpClient", r));
 136     }
 137 
 138     // ABNF primitives defined in RFC 7230
 139     private static final boolean[] tchar      = new boolean[256];
 140     private static final boolean[] fieldvchar = new boolean[256];
 141 
 142     static {
 143         char[] allowedTokenChars =
 144                 ("!#$%&'*+-.^_`|~0123456789" +
 145                  "abcdefghijklmnopqrstuvwxyz" +
 146                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
 147         for (char c : allowedTokenChars) {
 148             tchar[c] = true;
 149         }
 150         for (char c = 0x21; c < 0xFF; c++) {
 151             fieldvchar[c] = true;
 152         }
 153         fieldvchar[0x7F] = false; // a little hole (DEL) in the range
 154     }
 155 
 156     /*
 157      * Validates a RFC 7230 field-name.
 158      */
 159     public static boolean isValidName(String token) {
 160         for (int i = 0; i < token.length(); i++) {
 161             char c = token.charAt(i);
 162             if (c > 255 || !tchar[c]) {
 163                 return false;
 164             }
 165         }
 166         return !token.isEmpty();
 167     }
 168 
 169     /**
 170      * If the address was created with a domain name, then return
 171      * the domain name string. If created with a literal IP address
 172      * then return null. We do this to avoid doing a reverse lookup
 173      * Used to populate the TLS SNI parameter. So, SNI is only set
 174      * when a domain name was supplied.
 175      */
 176     public static String getServerName(InetSocketAddress addr) {
 177         String host = addr.getHostString();
 178         if (IPAddressUtil.textToNumericFormatV4(host) != null)
 179             return null;
 180         if (IPAddressUtil.textToNumericFormatV6(host) != null)
 181             return null;
 182         return host;
 183     }
 184 
 185     /*
 186      * Validates a RFC 7230 field-value.
 187      *
 188      * "Obsolete line folding" rule
 189      *
 190      *     obs-fold = CRLF 1*( SP / HTAB )
 191      *
 192      * is not permitted!
 193      */
 194     public static boolean isValidValue(String token) {
 195         boolean accepted = true;
 196         for (int i = 0; i < token.length(); i++) {
 197             char c = token.charAt(i);
 198             if (c > 255) {
 199                 return false;
 200             }
 201             if (accepted) {
 202                 if (c == ' ' || c == '\t') {
 203                     accepted = false;
 204                 } else if (!fieldvchar[c]) {
 205                     return false; // forbidden byte
 206                 }
 207             } else {
 208                 if (c != ' ' && c != '\t') {
 209                     if (fieldvchar[c]) {
 210                         accepted = true;
 211                     } else {
 212                         return false; // forbidden byte
 213                     }
 214                 }
 215             }
 216         }
 217         return accepted;
 218     }
 219 
 220     /**
 221      * Returns the security permission required for the given details.
 222      * If method is CONNECT, then uri must be of form "scheme://host:port"
 223      */
 224     public static URLPermission getPermission(URI uri,
 225                                               String method,
 226                                               Map<String, List<String>> headers) {
 227         StringBuilder sb = new StringBuilder();
 228 
 229         String urlstring, actionstring;
 230 
 231         if (method.equals("CONNECT")) {
 232             urlstring = uri.toString();
 233             actionstring = "CONNECT";
 234         } else {
 235             sb.append(uri.getScheme())
 236                     .append("://")
 237                     .append(uri.getAuthority())
 238                     .append(uri.getPath());
 239             urlstring = sb.toString();
 240 
 241             sb = new StringBuilder();
 242             sb.append(method);
 243             if (headers != null && !headers.isEmpty()) {
 244                 sb.append(':');
 245                 Set<String> keys = headers.keySet();
 246                 boolean first = true;
 247                 for (String key : keys) {
 248                     if (!first) {
 249                         sb.append(',');
 250                     }
 251                     sb.append(key);
 252                     first = false;
 253                 }
 254             }
 255             actionstring = sb.toString();
 256         }
 257         return new URLPermission(urlstring, actionstring);
 258     }
 259 
 260     public static void checkNetPermission(String target) {
 261         SecurityManager sm = System.getSecurityManager();
 262         if (sm == null) {
 263             return;
 264         }
 265         NetPermission np = new NetPermission(target);
 266         sm.checkPermission(np);
 267     }
 268 
 269     public static int getIntegerNetProperty(String name, int defaultValue) {
 270         return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
 271                 NetProperties.getInteger(name, defaultValue));
 272     }
 273 
 274     static String getNetProperty(String name) {
 275         return AccessController.doPrivileged((PrivilegedAction<String>) () ->
 276                 NetProperties.get(name));
 277     }
 278 
 279     public static SSLParameters copySSLParameters(SSLParameters p) {
 280         SSLParameters p1 = new SSLParameters();
 281         p1.setAlgorithmConstraints(p.getAlgorithmConstraints());
 282         p1.setCipherSuites(p.getCipherSuites());
 283         // JDK 8 EXCL START
 284         p1.setEnableRetransmissions(p.getEnableRetransmissions());
 285         p1.setMaximumPacketSize(p.getMaximumPacketSize());
 286         // JDK 8 EXCL END
 287         p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
 288         p1.setNeedClientAuth(p.getNeedClientAuth());
 289         String[] protocols = p.getProtocols();
 290         if (protocols != null) {
 291             p1.setProtocols(protocols.clone());
 292         }
 293         p1.setSNIMatchers(p.getSNIMatchers());
 294         p1.setServerNames(p.getServerNames());
 295         p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
 296         p1.setWantClientAuth(p.getWantClientAuth());
 297         return p1;
 298     }
 299 
 300     /**
 301      * Set limit to position, and position to mark.
 302      */
 303     public static void flipToMark(ByteBuffer buffer, int mark) {
 304         buffer.limit(buffer.position());
 305         buffer.position(mark);
 306     }
 307 
 308     public static String stackTrace(Throwable t) {
 309         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 310         String s = null;
 311         try {
 312             PrintStream p = new PrintStream(bos, true, "US-ASCII");
 313             t.printStackTrace(p);
 314             s = bos.toString("US-ASCII");
 315         } catch (UnsupportedEncodingException ex) {
 316             // can't happen
 317         }
 318         return s;
 319     }
 320 
 321     /**
 322      * Copies as much of src to dst as possible.
 323      * Return number of bytes copied
 324      */
 325     public static int copy(ByteBuffer src, ByteBuffer dst) {
 326         int srcLen = src.remaining();
 327         int dstLen = dst.remaining();
 328         if (srcLen > dstLen) {
 329             int diff = srcLen - dstLen;
 330             int limit = src.limit();
 331             src.limit(limit - diff);
 332             dst.put(src);
 333             src.limit(limit);
 334         } else {
 335             dst.put(src);
 336         }
 337         return srcLen - src.remaining();
 338     }
 339 
 340     // copy up to amount from src to dst, but no more
 341     public static int copyUpTo(ByteBuffer src, ByteBuffer dst, int amount) {
 342         int toCopy = Math.min(src.remaining(), Math.min(dst.remaining(), amount));
 343         copy(src, dst, toCopy);
 344         return toCopy;
 345     }
 346 
 347     /**
 348      * Copy amount bytes from src to dst. at least amount must be
 349      * available in both dst and in src
 350      */
 351     public static void copy(ByteBuffer src, ByteBuffer dst, int amount) {
 352         int excess = src.remaining() - amount;
 353         assert excess >= 0;
 354         if (excess > 0) {
 355             int srclimit = src.limit();
 356             src.limit(srclimit - excess);
 357             dst.put(src);
 358             src.limit(srclimit);
 359         } else {
 360             dst.put(src);
 361         }
 362     }
 363 
 364     public static ByteBuffer copy(ByteBuffer src) {
 365         ByteBuffer dst = ByteBuffer.allocate(src.remaining());
 366         dst.put(src);
 367         dst.flip();
 368         return dst;
 369     }
 370 
 371     public static String dump(Object... objects) {
 372         return Arrays.toString(objects);
 373     }
 374 
 375     public static String stringOf(Collection<?> source) {
 376         // We don't know anything about toString implementation of this
 377         // collection, so let's create an array
 378         return Arrays.toString(source.toArray());
 379     }
 380 
 381     public static int remaining(ByteBuffer[] bufs) {
 382         int remain = 0;
 383         for (ByteBuffer buf : bufs) {
 384             remain += buf.remaining();
 385         }
 386         return remain;
 387     }
 388 
 389     public static int remaining(List<ByteBuffer> bufs) {
 390         int remain = 0;
 391         for (ByteBuffer buf : bufs) {
 392             remain += buf.remaining();
 393         }
 394         return remain;
 395     }
 396 
 397     public static int remaining(ByteBufferReference[] refs) {
 398         int remain = 0;
 399         for (ByteBufferReference ref : refs) {
 400             remain += ref.get().remaining();
 401         }
 402         return remain;
 403     }
 404 
 405     // assumes buffer was written into starting at position zero
 406     static void unflip(ByteBuffer buf) {
 407         buf.position(buf.limit());
 408         buf.limit(buf.capacity());
 409     }
 410 
 411     public static void close(Closeable... closeables) {
 412         for (Closeable c : closeables) {
 413             try {
 414                 c.close();
 415             } catch (IOException ignored) { }
 416         }
 417     }
 418 
 419     public static void close(Throwable t, Closeable... closeables) {
 420         for (Closeable c : closeables) {
 421             try {
 422                 ExceptionallyCloseable.close(t, c);
 423             } catch (IOException ignored) { }
 424         }
 425     }
 426 
 427     /**
 428      * Returns an array with the same buffers, but starting at position zero
 429      * in the array.
 430      */
 431     public static ByteBuffer[] reduce(ByteBuffer[] bufs, int start, int number) {
 432         if (start == 0 && number == bufs.length) {
 433             return bufs;
 434         }
 435         ByteBuffer[] nbufs = new ByteBuffer[number];
 436         int j = 0;
 437         for (int i=start; i<start+number; i++) {
 438             nbufs[j++] = bufs[i];
 439         }
 440         return nbufs;
 441     }
 442 
 443     static String asString(ByteBuffer buf) {
 444         byte[] b = new byte[buf.remaining()];
 445         buf.get(b);
 446         return new String(b, StandardCharsets.US_ASCII);
 447     }
 448 
 449     /**
 450      * Returns a single threaded executor which uses one invocation
 451      * of the parent executor to execute tasks (in sequence).
 452      *
 453      * Use a null valued Runnable to terminate.
 454      */
 455     // TODO: this is a blocking way of doing this;
 456     public static Executor singleThreadExecutor(Executor parent) {
 457         BlockingQueue<Optional<Runnable>> queue = new LinkedBlockingQueue<>();
 458         parent.execute(() -> {
 459             while (true) {
 460                 try {
 461                     Optional<Runnable> o = queue.take();
 462                     if (!o.isPresent()) {
 463                         return;
 464                     }
 465                     o.get().run();
 466                 } catch (InterruptedException ex) {
 467                     return;
 468                 }
 469             }
 470         });
 471         return new Executor() {
 472             @Override
 473             public void execute(Runnable command) {
 474                 queue.offer(Optional.ofNullable(command));
 475             }
 476         };
 477     }
 478 
 479     private static void executeInline(Runnable r) {
 480         r.run();
 481     }
 482 
 483     static Executor callingThreadExecutor() {
 484         return Utils::executeInline;
 485     }
 486 
 487     // Put all these static 'empty' singletons here
 488     @SuppressWarnings("rawtypes")
 489     public static final CompletableFuture[] EMPTY_CFARRAY = new CompletableFuture[0];
 490 
 491     public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
 492     public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
 493 
 494     public static ByteBuffer slice(ByteBuffer buffer, int amount) {
 495         ByteBuffer newb = buffer.slice();
 496         newb.limit(amount);
 497         buffer.position(buffer.position() + amount);
 498         return newb;
 499     }
 500 
 501     /**
 502      * Get the Charset from the Content-encoding header. Defaults to
 503      * UTF_8
 504      */
 505     public static Charset charsetFrom(HttpHeaders headers) {
 506         String encoding = headers.firstValue("Content-encoding")
 507                 .orElse("UTF_8");
 508         try {
 509             return Charset.forName(encoding);
 510         } catch (IllegalArgumentException e) {
 511             return StandardCharsets.UTF_8;
 512         }
 513     }
 514 
 515     public static UncheckedIOException unchecked(IOException e) {
 516         return new UncheckedIOException(e);
 517     }
 518 }