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 }