How FileReader.readAsText in HTML5 File API works?
Asked Answered
N

3

11

I wrote the following code to check whether the uploaded file exists or not using HTML5 file API.

<input type="file" id="myfile">
<button type="button" onclick="addDoc()">Add Document</button>
<p id="DisplayText"></p>

The following JavaScript code has been mapped to it is as follows:

function addDoc() {
  var file=document.getElementById("myFile").files[0]; //for input type=file
  var reader=new FileReader();
  reader.onload = function(e) {}
  reader.readAsText(file);
  var error = reader.error;
  var texte=reader.result;
  document.getElementById("DisplayText").innerText=reader.result; /*<p id="DisplayText>*/
}

After browsing a file from local system I tried to delete the "browsed" document form the folder before clicking on addDoc(). After clicking the button I could still see Filereader.result is not null and could display all the content.

Can someone explain on how the Filereader works? Is it that the FileReader gets bound as soon as the file is browsed?

Also can we check whether the system Readonly Attribute with FileReader similar to Java File.canread()?

Could someone suggest on this? I have IE11 to test the code.

Noli answered 20/10, 2016 at 6:15 Comment(0)
H
18

FileReader load event sets the .result value asynchronously. To access the .result use load or loadend event.

When a file has been selected at <input type="file"> Choose File or Browse... UI, deleting file at local filesystem should not effect the File object at FileList returned by .files call. See 2.9.2. Transferable objects, 6.7.3 The DataTransfer interface.

4. The Blob Interface and Binary Data

Each Blob must have an internal snapshot state, which must be initially set to the state of the underlying storage, if any such underlying storage exists, and must be preserved through structured clone. Further normative definition of snapshot state can be found for Files.

2.9.8 Monkey patch for Blob and FileList objects

This monkey patch will be removed in due course. See w3c/FileAPI issue 32.

Blob objects are cloneable objects.

  1. Each Blob object's [[Clone]] internal method, given targetRealm and ignoring memory, must run these steps:

  2. If this is closed, then throw a "DataCloneError" DOMException.

Return a new instance of this in targetRealm, corresponding to the same underlying data.

FileList objects are cloneable objects.

Each FileList object's [[Clone]] internal method, given targetRealm and memory, must run these steps:

  1. Let output be a new FileList object in targetRealm.

  2. For each file in this, add ? [StructuredClone][15](_file, targetRealm, memory_) to the end of the list of File objects of output.

Return output.


Selecting read-only files or folders at webkit and firefox browsers

At chrome, chromium if read-only permission is set for file at local filesystem and user selects file at <input type="file"> element, where FileReader is used to read file, an error is thrown at FileReader, generated from FileReader progress event.

If a Blob URL is set to the same file object, the blob: URL will not return the the read-only file at request to the Blob URL.

Selection of folder where folder permission is set to read-only

Chrome, chromium

At chrome, chromium where webkitdirectory attribute is set and folder is selected with read-only permission FileList .length of event.target.files returned 0; event.target.files.webkitGetAsEntry() is not called, "No file chosen" is rendered at <input type="file"> shadowDOM. When a folder is dropped at <input type="file"> or element where droppable attribute set, the directory .name and .path of the read-only folder is displayed at drop event.dataTransfer.

When user drops file or folder at <textarea> element, where no drop event is attached beforeunload event is called and a prompr is displayed at UI

Do you want to leave this site?
Changes you made may not be saved.
<Stay><Leave> // <buttons>

Firefox

At firefox version 47.0b9 with allowdirs attribute is set at <input type="file"> element, where user clicks "Choose folder.." <input>, the folder .name and .path of the parent folder are accessible at .then() chained to event.target.getFilesAndDirectories(). The files or folders contained within the selected folder are not returned when recursively iterating Directory entries; an an empty string is returned.

If user clicks "Choose file..." <input> and a folder is selected without read-only permission set, when the folder at file manager is clicked, the files in the folder are listed.

Where a folder is selected where read-only permission is set an alert() notification is rendered at UI displaying

  Could not read the contents of <directory name>
  Permission denied

Bug, security issue

*nix OS

When user drops folder at <textarea> element, where no drop event is attached, the full path to the folder at user filesystem file: protocol is exposed. The paths to the files contained within the folder are not also set as .value; e.g.,

"file:///home/user/Documents/Document/"

When a file is dropped at <textarea> element, where not drop event is attached, the full path to the file at user filesystem is set as .value of <textarea>; that is,

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.txt"

If multiple files are selected and dropped at <textarea> element, all of the full file paths are set as .value of <textarea>, delineated by new line character \n

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue1.txt"
"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue2.txt"
..

Where an XMLHttpRequest() is made for the file path and error is logged at console

NS_ERROR_DOM_BAD_URI: Access to restricted URI denied

When set as .src of an <img> element with .crossOrigin set to "anonymous" the img error event handler is called

At call to window.open() with full path set at first parameter

Error: Access to '"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.png"' from script denied

Specification

4.10.5.1.18. File Upload state (type=file)

EXAMPLE 16

For historical reasons, the value IDL attribute prefixes the file name with the string "C:\fakepath\". Some legacy user agents actually included the full path (which was a security vulnerability). As a result of this, obtaining the file name from the value IDL attribute in a backwards-compatible way is non-trivial.

4.10.5.4. Common <input> element APIs

filename

On getting, it must return the string "C:\fakepath\" followed by the name of the first file in the list of selected files, if any, or the empty string if the list is empty. On setting, if the new value is the empty string, it must empty the list of selected files; otherwise, it must throw an "InvalidStateError" DOMException.

NOTE: This "fakepath" requirement is a sad accident of history. See the example in the File Upload state section for more information.

NOTE: Since path components are not permitted in file names in the list of selected files, the "\fakepath\" cannot be mistaken for a path component.

4.10.5.1.18. File Upload state (type=file)

Path components

When an <input> element’s type attribute is in the File Upload state, the rules in this section apply.

The <input> element represents a list of selected files, each file consisting of a file name, a file type, and a file body (the contents of the file).

File names must not contain path components, even in the case that a user has selected an entire directory hierarchy or multiple files with the same name from different directories. Path components, for the purposes of the File Upload state, are those parts of file names that are separated by U+005C REVERSE SOLIDUS character () characters.

Bug report https://bugzilla.mozilla.org/show_bug.cgi?id=1311823


Dropping file at <textarea> at data URI

Following comment by Neal Deakin at bug report

I think the steps referred to are:

  1. Open data:text/html,
  2. Drag a file from the desktop to the textarea

I can reproduce this on Linux, but not on Windows or Mac.

The hunch above is correct; Linux is including the data as a url and plaintext as well.

dropped files at data: prototcol data URI at firefox, and chrome, chromium

data:text/html,<textarea></textarea>

Firefox

The full path name of file or folder set as .value of <textarea>.

Chrome, chromium

Dropping file at data URI having only textarea element at chrome, chromium replaces the data URI with dropped file path at address bar, and loads the dropped file at the same tab, replacing the data URI with the content of the dropped file.

plnkr http://plnkr.co/edit/ZfAGEAiyLLq8rGXD2ShE?p=preview


html, javascript to reproduce issue described above

<!DOCTYPE html>
<html>

<head>
  <style>
    body {
      height: 400px;
    }

    textarea {
      width: 95%;
      height: inherit;
    }
  </style>

  <script>
    window.onload = function() {
      var button = document.querySelector("#myfile + button");
      var input = document.getElementById("myfile");
      var display = document.getElementById("DisplayText");
      var text = null;

      function readFullPathToFileOnUserFileSystem(e) {
        var path = e.target.value;
        console.log(path);
        var w = window.open(path, "_blank");
        var img = new Image;
        img.crossOrigin = "anonymous";
        img.onload = function() {
          document.body.appendChild(this);
        }
        img.onerror = function(err) {
          console.log("img error", err.message)
        }
        img.src = path;
        var request = new XMLHttpRequest();
        request.open("GET", path.trim(), true);
        request.onload = function() {
          console.log(this.responseText)
        }
        request.error = function(err) {
          console.log(err.message)
        }
        request.send();

      }

      display.addEventListener("input", readFullPathToFileOnUserFileSystem);
      input.addEventListener("change", addDoc);
      input.addEventListener("progress", function(event) {
        console.log("progress", event)
      });
      button.addEventListener("click", handleText)

      function addDoc(event) {
        var mozResult = [];

        function mozReadDirectories(entries, path) {
          console.log("dir", entries, path);
          return [].reduce.call(entries, function(promise, entry) {
              return promise.then(function() {
                console.log("entry", entry);
                return Promise.resolve(entry.getFilesAndDirectories() || entry)
                  .then(function(dir) {
                    console.log("dir getFilesAndDirectories", dir)
                    return dir
                  })
              })
            }, Promise.resolve())
            .catch(function(err) {
              console.log(err, err.message)
            })
            .then(function(items) {
              console.log("items", items);
              var dir = items.filter(function(folder) {
                return folder instanceof Directory
              });
              var files = items.filter(function(file) {
                return file instanceof File
              });
              if (files.length) {
                console.log("files:", files, path);
                mozResult = mozResult.concat.apply(mozResult, files);

              }
              if (dir.length) {
                console.log(dir, dir[0] instanceof Directory, dir[0]);
                return mozReadDirectories(dir, dir[0].path || path);

              } else {
                if (!dir.length) {

                  return Promise.resolve(mozResult).then(function(complete) {
                    return complete
                  })

                }
              }

            })
            .catch(function(err) {
              console.log(err)
            })

        };

        console.log("files", event.target.files);
        if ("getFilesAndDirectories" in event.target) {
          return (event.type === "drop" ? event.dataTransfer : event.target)
          .getFilesAndDirectories()
            .then(function(dir) {
              if (dir[0] instanceof Directory) {
                console.log(dir)
                return mozReadDirectories(dir, dir[0].path || path)
                  .then(function(complete) {
                    console.log("complete:", complete);
                    event.target.value = null;
                  });
              } else {
                if (dir[0] instanceof File && dir[0].size > 0) {
                  return Promise.resolve(dir)
                    .then(function(complete) {
                      console.log("complete:", complete);
                    })
                } else {
                  if (dir[0].size == 0) {
                    throw new Error("could not process '" + dir[0].name + "' directory" + " at drop event at firefox, upload folders at 'Choose folder...' input");
                  }
                }
              }
            }).catch(function(err) {
              console.log(err)
            })
        }

        var reader = new FileReader();
        reader.onload = function(e) {
          text = reader.result;
          console.log("FileReader.result", text);
          button.removeAttribute("disabled");
        }

        reader.onerror = function(err) {
          console.log(err, err.loaded, err.loaded === 0, file);
          button.removeAttribute("disabled");
        }

        reader.onprogress = function(e) {
          console.log(e, e.lengthComputable, e.loaded, e.total);
        }

        reader.readAsArrayBuffer(file);

      }

      function handleText() {

        // do stuff with `text`: `reader.result` from `addDoc`
        display.textContent = text;
        button.setAttribute("disabled", "disabled");
        // set `text` to `null` if not needed or referenced again
        text = null;
      }
    }
  </script>
</head>

<body>
  <input type="file" id="myfile" webkitdirectory directory allowdirs>
  <button type="button" disabled>Add Document</button>
  <br>
  <br>
  <textarea id="DisplayText"></textarea>
</body>

</html>

plnkr http://plnkr.co/edit/8Ovw3IlYKI8BYsLhzV88?p=preview


You can use change event attached to #myfile element to handle file selection action by user.

Substitute <textarea> element for <p> element to display result of load event from .readAsText() call.

To display .result of FileReader at click at button element, set variable text to reader.result within load event of FileReader at click event at button set .textContent of #DisplayText element to variable referencing previously set reader.result.

<!DOCTYPE html>
<html>
  <style>
    body {
      height: 400px;
    }
    textarea {
      width:95%;
      height: inherit;
    }
  </style>
<head>
  <script>
    window.onload = function() {
        var button = document.querySelector("#myfile + button");
        var input = document.getElementById("myfile");
        var display = document.getElementById("DisplayText");
        var text = null;
        input.addEventListener("change", addDoc);
        button.addEventListener("click", handleText)

        function addDoc(event) {
          var file = this.files[0]
          var reader = new FileReader();      
          reader.onload = function(e) {
            text = reader.result;
            button.removeAttribute("disabled");
          }

          reader.onerror = function(err) {
            console.log(err, err.loaded
                        , err.loaded === 0
                        , file);
            button.removeAttribute("disabled");
          }

          reader.readAsText(event.target.files[0]);
        }

        function handleText() {
          
          // do stuff with `text`: `reader.result` from `addDoc`
          display.textContent = text;
          button.setAttribute("disabled", "disabled");
          // set `text` to `null` if not needed or referenced again
          text = null; 
        }
    }
  </script>
</head>

<body>
  <input type="file" id="myfile" accept="text/*">
  <button type="button" disabled>Add Document</button><br><br>
  <textarea id="DisplayText"></textarea>
</body>

</html>
Hydrosol answered 20/10, 2016 at 6:23 Comment(4)
@Noli See 2.9.8 Monkey patch for Blob and FileList objects , File APIHydrosol
,Thank you for the response.But in addition to that ,how deleting file at local filesystem after browsing and before adddoc() is not effecting reader.result.Is it creating any cache?Noli
@Noli At html <input type="file"> element id is "myfile", at javascript you reference #myFile, which does not appear at html at Question. Also, use change event attached to <input type="file"> element to handle file being selected from local filesystem.Hydrosol
@Noli Found a bug at firefox where files dropped at <textarea> element sets .value of <textarea> to fulll path to file at local filesystem.Hydrosol
I
1

The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read.

File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement.

The readAsText method is used to read the contents of the specified Blob or File. When the read operation is complete, the readyState is changed to DONE, the loadend is triggered, and the result attribute contains the contents of the file as a text string.

Syntax

instanceOfFileReader.readAsText(blob[, encoding]);

Parameters

Blob

The Blob or File from which to read.

encoding Optional

A string specifying the encoding to use for the returned data. By default, UTF-8 is assumed if this parameter is not specified.

For the metadata about a file we can check the File object F such that: F has a readability state of OPENED. F refers to the bytes byte sequence. F.size is set to the number of total bytes in bytes. F.name is set to n. F.type is set to t.

Note: The type t of a File is considered a parsable MIME type if the ASCII-encoded string representing the File object's type, when converted to a byte sequence, does not return undefined for the parse MIME type algorithm [MIMESNIFF].

F.lastModified is set to d.

See more about browser compatibility and detailed document for FileReader, File and readAsText at MDN, also this W3C draft for FileApi

Introrse answered 11/11, 2016 at 9:4 Comment(6)
How does you Answer address questions presented at OP "Is it that the FileReader gets binded as soon as the file is browsed? Also can we check whether the system Readonly Attribute with FileReader similar to Java File.canread()?"?Hydrosol
added all available file attributes as mentioned in w3c, but still readonly is not thereIntrorse
Have you tried reading a file or folder having read-only permission set at filesystem? "added all available file attributes as mentioned in w3c, but still readonly is not there" That is part of Question. Your Answer does not address that portion of Question, or these Questions "After browsing a file from local system I tried to delete the "browsed" document form the folder before clicking on addDoc(). After clicking the button I could still see Filereader.result is not null and could display all the content.".Hydrosol
@Hydrosol I tried files with read only permission, check a woking demo at html5rocks.com/en/tutorials/file/dndfiles. This demo shows all available options and attributes from file. I was able to not only read, but also split the "Read only" files. The files are actually replicated in the browser temp directory before upload as multi-part form data and you are allowed to read the "Read only" files, we cannot just overwrite or modify themIntrorse
"I tried files with read only permission, check a woking demo at html5rocks.com/en/tutorials/file/dndfiles. This demo shows all available options and attributes from file. I was able to not only read, but also split the "Read only" files. The files are actually replicated in the browser temp directory before upload as multi-part form data and you are allowed to read the "Read only" files, we cannot just overwrite or modify them" Interesting. Consider sharing what you found with description of how to reproduce at bugzilla.mozilla.org/show_bug.cgi?id=1311823Hydrosol
Can you create a plnkr to demonstrate?Hydrosol
B
1

Use this instead:-

function loadFileAsText()
{
    var fileToLoad = document.getElementById("fileToLoad").files[0];
 
    var fileReader = new FileReader();
    fileReader.onload = function(fileLoadedEvent) 
    {
        var textFromFileLoaded = fileLoadedEvent.target.result;
        document.getElementById("inputTextToSave").innerText = textFromFileLoaded;
    };
    fileReader.readAsText(fileToLoad, "UTF-8");
}
        <p>Select a File to Load:</p>
        <input type="file" id="fileToLoad"><button onclick="loadFileAsText()">Load Selected File</button>
            <br>
            <br>
            <br>
            <p>Text file loaded:</p>
         <p id="inputTextToSave"></p>
Burnell answered 27/8, 2020 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.