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 java.net.http; 27 28 import java.nio.ByteBuffer; 29 import java.nio.CharBuffer; 30 import java.nio.charset.CharacterCodingException; 31 import java.nio.charset.CharsetDecoder; 32 import java.nio.charset.CharsetEncoder; 33 import java.nio.charset.CoderResult; 34 35 import static java.nio.charset.StandardCharsets.UTF_8; 36 37 // The purpose of this class is to separate charset-related tasks from the main 38 // WebSocket logic, simplifying where possible. 39 // 40 // * Coders hide the differences between coding and flushing stages on the 41 // API level 42 // * Verifier abstracts the way the verification is performed 43 // (spoiler: it's a decoding into a throw-away buffer) 44 // 45 // Coding methods throw exceptions instead of returning coding result denoting 46 // errors, since any kind of handling and recovery is not expected. 47 final class CharsetToolkit { 48 49 private CharsetToolkit() { } 50 51 static final class Verifier { 52 53 private final CharsetDecoder decoder = UTF_8.newDecoder(); 54 // A buffer used to check validity of UTF-8 byte stream by decoding it. 55 // The contents of this buffer are never used. 56 // The size is arbitrary, though it should probably be chosen from the 57 // performance perspective since it affects the total number of calls to 58 // decoder.decode() and amount of work in each of these calls 59 private final CharBuffer blackHole = CharBuffer.allocate(1024); 60 61 void verify(ByteBuffer in, boolean endOfInput) 62 throws CharacterCodingException { 63 while (true) { 64 // Since decoder.flush() cannot produce an error, it's not 65 // helpful for verification. Therefore this step is skipped. 66 CoderResult r = decoder.decode(in, blackHole, endOfInput); 67 if (r.isOverflow()) { 68 blackHole.clear(); 69 } else if (r.isUnderflow()) { 70 break; 71 } else if (r.isError()) { 72 r.throwException(); 73 } else { 74 // Should not happen 75 throw new InternalError(); 76 } 77 } 78 } 79 80 Verifier reset() { 81 decoder.reset(); 82 return this; 83 } 84 } 85 86 static final class Encoder { 87 88 private final CharsetEncoder encoder = UTF_8.newEncoder(); 89 private boolean coding = true; 90 91 CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput) 92 throws CharacterCodingException { 93 94 if (coding) { 95 CoderResult r = encoder.encode(in, out, endOfInput); 96 if (r.isOverflow()) { 97 return r; 98 } else if (r.isUnderflow()) { 99 if (endOfInput) { 100 coding = false; 101 } else { 102 return r; 103 } 104 } else if (r.isError()) { 105 r.throwException(); 106 } else { 107 // Should not happen 108 throw new InternalError(); 109 } 110 } 111 assert !coding; 112 return encoder.flush(out); 113 } 114 115 Encoder reset() { 116 coding = true; 117 encoder.reset(); 118 return this; 119 } 120 } 121 122 static CharBuffer decode(ByteBuffer in) throws CharacterCodingException { 123 return UTF_8.newDecoder().decode(in); 124 } 125 126 static final class Decoder { 127 128 private final CharsetDecoder decoder = UTF_8.newDecoder(); 129 private boolean coding = true; // Either coding or flushing 130 131 CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput) 132 throws CharacterCodingException { 133 134 if (coding) { 135 CoderResult r = decoder.decode(in, out, endOfInput); 136 if (r.isOverflow()) { 137 return r; 138 } else if (r.isUnderflow()) { 139 if (endOfInput) { 140 coding = false; 141 } else { 142 return r; 143 } 144 } else if (r.isError()) { 145 r.throwException(); 146 } else { 147 // Should not happen 148 throw new InternalError(); 149 } 150 } 151 assert !coding; 152 return decoder.flush(out); 153 } 154 155 Decoder reset() { 156 coding = true; 157 decoder.reset(); 158 return this; 159 } 160 } 161 }