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.file;
16  
17  import java.io.IOException;
18  import java.nio.charset.Charset;
19  import java.nio.charset.StandardCharsets;
20  import java.nio.charset.UnsupportedCharsetException;
21  import java.util.Base64;
22  import java.util.Locale;
23  
24  import org.apache.commons.io.Charsets;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.htmlunit.corejs.javascript.Function;
29  import org.htmlunit.corejs.javascript.ScriptableObject;
30  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
31  import org.htmlunit.javascript.JavaScriptEngine;
32  import org.htmlunit.javascript.configuration.JsxClass;
33  import org.htmlunit.javascript.configuration.JsxConstant;
34  import org.htmlunit.javascript.configuration.JsxConstructor;
35  import org.htmlunit.javascript.configuration.JsxFunction;
36  import org.htmlunit.javascript.configuration.JsxGetter;
37  import org.htmlunit.javascript.configuration.JsxSetter;
38  import org.htmlunit.javascript.host.event.Event;
39  import org.htmlunit.javascript.host.event.EventTarget;
40  import org.htmlunit.protocol.data.DataURLConnection;
41  import org.htmlunit.util.MimeType;
42  
43  /**
44   * A JavaScript object for {@code FileReader}.
45   *
46   * @author Ahmed Ashour
47   * @author Ronald Brill
48   */
49  @JsxClass
50  public class FileReader extends EventTarget {
51  
52      private static final Log LOG = LogFactory.getLog(FileReader.class);
53  
54      /** No data has been loaded yet. */
55      @JsxConstant
56      public static final int EMPTY = 0;
57  
58      /** Data is currently being loaded. */
59      @JsxConstant
60      public static final int LOADING = 1;
61  
62      /** The entire read request has been completed. */
63      @JsxConstant
64      public static final int DONE = 2;
65  
66      private int readyState_ = EMPTY;
67      private Object result_;
68  
69      /**
70       * JavaScript constructor.
71       */
72      @Override
73      @JsxConstructor
74      public void jsConstructor() {
75          super.jsConstructor();
76      }
77  
78      /**
79       * Returns the current state of the reading operation.
80       *
81       * @return {@value #EMPTY}, {@value #LOADING}, or {@value #DONE}
82       */
83      @JsxGetter
84      public int getReadyState() {
85          return readyState_;
86      }
87  
88      /**
89       * Returns the file's contents.
90       * @return the file's contents
91       */
92      @JsxGetter
93      public Object getResult() {
94          return result_;
95      }
96  
97      /**
98       * Reads the contents of the specified {@link Blob} or {@link File}.
99       * @param object the {@link Blob} or {@link File} from which to read
100      * @throws IOException if an error occurs
101      */
102     @JsxFunction
103     public void readAsDataURL(final Object object) throws IOException {
104         readyState_ = LOADING;
105 
106         result_ = DataURLConnection.DATA_PREFIX;
107 
108         final byte[] bytes = ((Blob) object).getBytes();
109         final String value = new String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII);
110 
111         String contentType = ((Blob) object).getType();
112         if (StringUtils.isEmpty(contentType)) {
113             contentType = MimeType.APPLICATION_OCTET_STREAM;
114         }
115 
116         result_ += contentType + ";base64," + value;
117         readyState_ = DONE;
118 
119         final Event event = new Event(this, Event.TYPE_LOAD);
120         fireEvent(event);
121     }
122 
123     /**
124      * Reads the contents of the specified {@link Blob} or {@link File}.
125      * @param object the {@link Blob} or {@link File} from which to read
126      */
127     @JsxFunction
128     public void readAsArrayBuffer(final Object object) {
129         readyState_ = LOADING;
130 
131         if (object instanceof Blob) {
132             final byte[] bytes = ((Blob) object).getBytes();
133 
134             final NativeArrayBuffer buffer = new NativeArrayBuffer(bytes.length);
135             System.arraycopy(bytes, 0, buffer.getBuffer(), 0, bytes.length);
136             buffer.setParentScope(getParentScope());
137             buffer.setPrototype(ScriptableObject.getClassPrototype(getWindow(), buffer.getClassName()));
138 
139             result_ = buffer;
140         }
141 
142         readyState_ = DONE;
143 
144         final Event event = new Event(this, Event.TYPE_LOAD);
145         fireEvent(event);
146     }
147 
148     /**
149      * Reads the contents of the specified {@link Blob} or {@link File}.
150      * When the read operation is complete, the readyState is changed to DONE,
151      * the loaded event is triggered, and the result attribute contains the
152      * contents of the file as a text string.
153      * @param object the {@link Blob} or {@link File} from which to read
154      * @param encoding the encoding
155      */
156     @JsxFunction
157     public void readAsText(final Object object, final Object encoding) {
158         readyState_ = LOADING;
159 
160         Charset charset = StandardCharsets.UTF_8;
161         if (encoding != null && !JavaScriptEngine.isUndefined(encoding)) {
162             final String encAsString = JavaScriptEngine.toString(encoding);
163             if (StringUtils.isNotBlank(encAsString)) {
164                 try {
165                     charset = Charsets.toCharset(encAsString.trim().toLowerCase(Locale.ROOT));
166                 }
167                 catch (final UnsupportedCharsetException e) {
168                     if (LOG.isWarnEnabled()) {
169                         LOG.warn("FileReader readAsText was called with an unsupported encoding '"
170                                     + encoding + "'. Using UTF-8 instead.");
171                     }
172                 }
173             }
174         }
175 
176         if (object instanceof Blob) {
177             result_ = new String(((Blob) object).getBytes(), charset);
178         }
179 
180         readyState_ = DONE;
181 
182         final Event event = new Event(this, Event.TYPE_LOAD);
183         fireEvent(event);
184     }
185 
186     /**
187      * Returns the {@code onload} event handler for this {@link FileReader}.
188      * @return the {@code onload} event handler for this {@link FileReader}
189      */
190     @JsxGetter
191     public Function getOnload() {
192         return getEventHandler(Event.TYPE_LOAD);
193     }
194 
195     /**
196      * Sets the {@code onload} event handler for this {@link FileReader}.
197      * @param onload the {@code onload} event handler for this {@link FileReader}
198      */
199     @JsxSetter
200     public void setOnload(final Object onload) {
201         setEventHandler(Event.TYPE_LOAD, onload);
202     }
203 
204     /**
205      * Returns the {@code onerror} event handler for this {@link FileReader}.
206      * @return the {@code onerror} event handler for this {@link FileReader}
207      */
208     @JsxGetter
209     public Function getOnerror() {
210         return getEventHandler(Event.TYPE_ERROR);
211     }
212 
213     /**
214      * Sets the {@code onerror} event handler for this {@link FileReader}.
215      * @param onerror the {@code onerror} event handler for this {@link FileReader}
216      */
217     @JsxSetter
218     public void setOnerror(final Object onerror) {
219         setEventHandler(Event.TYPE_ERROR, onerror);
220     }
221 }