View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host;
16  
17  import java.nio.charset.Charset;
18  import java.util.Arrays;
19  import java.util.Locale;
20  
21  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
22  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBufferView;
23  import org.htmlunit.cyberneko.xerces.util.StandardEncodingTranslator;
24  import org.htmlunit.javascript.HtmlUnitScriptable;
25  import org.htmlunit.javascript.JavaScriptEngine;
26  import org.htmlunit.javascript.configuration.JsxClass;
27  import org.htmlunit.javascript.configuration.JsxConstructor;
28  import org.htmlunit.javascript.configuration.JsxFunction;
29  import org.htmlunit.javascript.configuration.JsxGetter;
30  import org.htmlunit.util.XUserDefinedCharset;
31  
32  /**
33   * A JavaScript object for {@code TextDecoder}.
34   *
35   * @author Ahmed Ashour
36   * @author Ronald Brill
37   */
38  @JsxClass
39  public class TextDecoder extends HtmlUnitScriptable {
40      private String whatwgEncoding_ = "utf-8";
41  
42      /**
43       * Creates an instance.
44       * @param encodingLabel the encoding
45       */
46      @JsxConstructor
47      public void jsConstructor(final Object encodingLabel) {
48          if (JavaScriptEngine.isUndefined(encodingLabel)) {
49              return;
50          }
51  
52          String enc = JavaScriptEngine.toString(encodingLabel);
53          enc = enc.trim().toLowerCase(Locale.ROOT);
54          final String whatwgEncoding = StandardEncodingTranslator.ENCODING_FROM_LABEL.get(enc);
55  
56          if (whatwgEncoding == null
57                  || StandardEncodingTranslator.REPLACEMENT.equalsIgnoreCase(whatwgEncoding)) {
58              throw JavaScriptEngine.rangeError("Failed to construct 'TextDecoder': The encoding label provided ('"
59                          + encodingLabel + "') is invalid.");
60          }
61  
62          whatwgEncoding_ = whatwgEncoding;
63      }
64  
65      /**
66       * @return the encoding - default is "utf-8"
67       */
68      @JsxGetter
69      public String getEncoding() {
70          return whatwgEncoding_;
71      }
72  
73      /**
74       * @param buffer to be decoded
75       * @return returns the decoded string
76       */
77      @JsxFunction
78      public String decode(final Object buffer) {
79          if (JavaScriptEngine.isUndefined(buffer)) {
80              return "";
81          }
82  
83          if (buffer instanceof NativeArrayBuffer) {
84              return new String(((NativeArrayBuffer) buffer).getBuffer(), getEncoding(whatwgEncoding_));
85          }
86  
87          if (buffer instanceof NativeArrayBufferView) {
88              final NativeArrayBufferView arrayBufferView = (NativeArrayBufferView) buffer;
89              final NativeArrayBuffer arrayBuffer = arrayBufferView.getBuffer();
90              if (arrayBuffer != null) {
91                  final int byteLength = arrayBufferView.getByteLength();
92                  final int byteOffset = arrayBufferView.getByteOffset();
93                  final byte[] backedBytes = arrayBuffer.getBuffer();
94                  final byte[] bytes = Arrays.copyOfRange(backedBytes, byteOffset, byteOffset + byteLength);
95                  return new String(bytes, getEncoding(whatwgEncoding_));
96              }
97          }
98  
99          throw JavaScriptEngine.typeError("Argument 1 of TextDecoder.decode could not be"
100                                 + " converted to any of: ArrayBufferView, ArrayBuffer.");
101     }
102 
103     private Charset getEncoding(final String encodingLabel) {
104         if (XUserDefinedCharset.NAME.equalsIgnoreCase(encodingLabel)) {
105             return XUserDefinedCharset.INSTANCE;
106         }
107 
108         final String ianaEncoding = StandardEncodingTranslator
109                 .ENCODING_TO_IANA_ENCODING.getOrDefault(encodingLabel, encodingLabel);
110         // Convert our IANA encoding names to Java charset names
111         final String javaEncoding = StandardEncodingTranslator
112                 .IANA_TO_JAVA_ENCODINGS.getOrDefault(ianaEncoding, ianaEncoding);
113 
114         return Charset.forName(javaEncoding);
115     }
116 }