jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
3.97k stars 413 forks source link

Downloading zip files triggers security content blocking #232

Open davideapvd opened 3 years ago

davideapvd commented 3 years ago

I'm using streamsaver to download a zip file and provide progress and download notification to the user, if using a browser with safe browsing functionality as chrome on mac/windows this triggers a security warning: "filname not commonly downloaded and could be dangerous";

I think it could be because the analyzer could not read the zip content and it blocks it, is it preventable? here is the code I've implemented:

fetch(url).then((response) => {
        const contentDisposition = response.headers.get('Content-Disposition')
        const fileName = contentDisposition.substring(contentDisposition.lastIndexOf('=') + 1).replaceAll('"', '').trim()
        const size = ~~response.headers.get('Content-Length')
        let read = 0

        const fileStream = this.$streamSaver.streamSaver.createWriteStream(fileName)
        window.writer = fileStream.getWriter()
        const writer = window.writer
        this.addWindowListener(writer)

        const reader = response.body.getReader()
        const pump = () => reader.read()
          .then(({ value, done }) => {
            // here you know how large the value (chunk) is and you can
            // figure out the download speed/progress when comparing it to the size
            if (!done) {
              read += value.length
              throttledUpdateProgress(read, size)
            } else {
              this.finish()
            }
            this.completed = done
            return done
              ? window.writer.close()
              : window.writer.write(value).then(pump)
          })

        pump()
      }).catch((error) => {
        this.error()
        console.error(error)
      })
jimmywarting commented 3 years ago

Hi, it looks like you are trying to save something that already provides a content-disposition attachment header and seems to already be downloadable?

What happens if you just navigate to url? dose it save then? My recommended approach is always use the server if possible instead of mimic it with service workers


edit: seems like the only reason why you are doing it is to provide a own way to display a progress bar + notification


edit2: if you want to provide a native progress bar (which i recommend) then you can set size (content-length) if it's known

  const fileStream = streamSaver.createWriteStream(filename, {
    size: 1234
  })
davideapvd commented 3 years ago

first, thank you for your answer.

Hi, it looks like you are trying to save something that already provides a content-disposition attachment header and seems to already be downloadable?

Yes, this method was supposed to be an UI 'enhancement' that provides a downloading file queue directly on the webapp, something like this

immagine

so the file has a fallback where it could be directly downloaded from the backend, but I can't provide any easy status on frontend (as I know) without modifying the backend in some manner.

What happens if you just navigate to url? dose it save then? My recommended approach is always use the server if possible instead of mimic it with service workers

I really can see why you are suggesting this and I'm incline to agree, but if there was a way to fix the main error I have that would be really cool.

Anyway I think streamsave could also be handy in cases where the frontend could not easily ad cookies and need to access a protected file download that needs some sort of header authorization, so my implementation could be useful also on those cases.

  const fileStream = streamSaver.createWriteStream(filename, {
    size: 1234
  })

thanks for this suggestion, I'll add it.