/** * Copyright 2007-2015, Kaazing Corporation. All rights reserved. * * 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. */ package org.kaazing.specification.ws.internal; import static java.lang.Character.toLowerCase; import static java.lang.Character.toUpperCase; import static java.nio.charset.StandardCharsets.UTF_8; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Random; import org.kaazing.k3po.lang.el.Function; import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; public final class Functions { // See RFC-6455, section 1.3 Opening Handshake private static final byte[] WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(UTF_8); private static final Random RANDOM = new Random(); private static final int MAX_ACCEPTABLE_HEADER_LENGTH = 200; @Function public static String base64Encode(String login) { byte[] bytes = login.getBytes(); return new String(Base64.encode(bytes)); } @Function public static String append(String... strings) { StringBuilder x = new StringBuilder(); for (String s:strings) { x.append(s); } return x.toString(); } @Function public static byte[] handshakeKey() { byte[] bytes = new byte[16]; RANDOM.nextBytes(bytes); return Base64.encode(bytes); } @Function public static byte[] handshakeHash(byte[] wsKeyBytes) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); sha1.update(wsKeyBytes); byte[] digest = sha1.digest(WEBSOCKET_GUID); return Base64.encode(digest); } @Function public static byte[] randomBytes(int length) { byte[] bytes = new byte[length]; for (int i = 0; i < length; i++) { bytes[i] = (byte) RANDOM.nextInt(0x100); } return bytes; } @Function public static byte[] randomBytesUTF8(int length) { byte[] bytes = new byte[length]; randomBytesUTF8(bytes, 0, length); return bytes; } @Function public static byte[] randomBytesInvalidUTF8(int length) { // TODO: make invalid UTF-8 bytes less like valid UTF-8 (!) byte[] bytes = new byte[length]; bytes[0] = (byte) 0x80; randomBytesUTF8(bytes, 1, length - 1); return bytes; } @Function public static byte[] randomBytesUnalignedUTF8(int length, int unalignAt) { assert unalignAt < length; byte[] bytes = new byte[length]; int straddleWidth = RANDOM.nextInt(3) + 2; int straddleAt = unalignAt - straddleWidth + 1; randomBytesUTF8(bytes, 0, straddleAt); int realignAt = randomCharBytesUTF8(bytes, straddleAt, straddleWidth); randomBytesUTF8(bytes, realignAt, length); return bytes; } @Function public static byte[] copyOfRange(byte[] original, int from, int to) { return Arrays.copyOfRange(original, from, to); } /** * Takes a string and randomizes which letters in the text are upper or * lower case * @param text * @return */ @Function public static String randomizeLetterCase(String text) { StringBuilder result = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (RANDOM.nextBoolean()) { c = toUpperCase(c); } else { c = toLowerCase(c); } result.append(c); } return result.toString(); } @Function public static String randomHeaderNot(String header) { // random strings from bytes can generate random bad chars like \n \r \f \v etc which are not allowed // except under special conditions, and will crash the http pipeline String commonHeaderChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890!@#$%^&*()_+-=`~[]\\{}|;':\",./<>?"; StringBuilder result = new StringBuilder(); do { int randomHeaderLength = RANDOM.nextInt(MAX_ACCEPTABLE_HEADER_LENGTH) + 1; for (int i = 0; i < randomHeaderLength; i++) { result.append(commonHeaderChars.charAt(RANDOM.nextInt(commonHeaderChars.length()))); } } while (result.toString().equalsIgnoreCase(header)); return result.toString(); } @Function public static String randomCaseNot(String value) { String result; char[] resultChars = new char[value.length()]; do { for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); resultChars[i] = RANDOM.nextBoolean() ? toUpperCase(c) : toLowerCase(c); } result = new String(resultChars); } while(!result.equals(value)); return result; } @Function public static String randomMethodNot(String method) { String[] methods = new String[]{"GET", "OPTIONS", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}; String result; do { result = methods[RANDOM.nextInt(methods.length)]; } while (result.equalsIgnoreCase(method)); return result; } private static void randomBytesUTF8(byte[] bytes, int start, int end) { for (int offset = start; offset < end;) { int remaining = end - offset; int width = Math.min(RANDOM.nextInt(4) + 1, remaining); offset = randomCharBytesUTF8(bytes, offset, width); } } private static int randomCharBytesUTF8(byte[] bytes, int offset, int width) { switch (width) { case 1: bytes[offset++] = (byte) RANDOM.nextInt(0x80); break; case 2: bytes[offset++] = (byte) (0xc0 | RANDOM.nextInt(0x20) | 1 << (RANDOM.nextInt(4) + 1)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x40)); break; case 3: // UTF-8 not legal for 0xD800 through 0xDFFF (see RFC 3269) bytes[offset++] = (byte) (0xe0 | RANDOM.nextInt(0x08) | 1 << RANDOM.nextInt(3)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x40)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x40)); break; case 4: // UTF-8 ends at 0x10FFFF (see RFC 3269) bytes[offset++] = (byte) (0xf0 | RANDOM.nextInt(0x04) | 1 << RANDOM.nextInt(2)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x10)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x40)); bytes[offset++] = (byte) (0x80 | RANDOM.nextInt(0x40)); break; } return offset; } public static class Mapper extends FunctionMapperSpi.Reflective { public Mapper() { super(Functions.class); } @Override public String getPrefixName() { return "ws"; } } private Functions() { // utility } }