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 }