question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] Firefox downloads ends up being corrupt most of the time

See original GitHub issue

Describe the bug We’ve been using client-zip on our platform , we decided to not go for the streaming alternative initially since we wanted to support Safari as well. That is now fixed in recent Safari version, so we decided to use streaming instead.

To Reproduce Unable to give reproduction steps unfortunately. As our website only support this for Chrome, Safari and Edge right now. But you can try out working version by using Chrome, Safari or Edge on bimobject.com (you have to register) and click download and making sure you select multiple files.

Expected behavior Same as Chrome, Safari and Edge.

Screenshots

Archive downloaded through Firefox gets corrupt (most of the time)

Desktop (please complete the following information):

  • Mac, Linux and Windows
  • Firefox
  • Version 104.0 (64bit)

Additional context

The user can select files from a list that they want to download. It always works in Chrome, Safari and Edge. But in Firefox, the ZIP sometimes ends up complete (~10% of cases).

I ran zipdump

python3 zipdump.py ~/Downloads/firefoxdownload.zip

and it lists only some of the files

00000000: PK.0304: 002d 0008 0000 54af3988 00000000 00000000 00000000 0038 0000 |  0000001e 00000056 00000056 00000056 - Artificial ZZ plant 1100mmArtificial ZZ plant 1100mm.rfa
002c8056: PK.0708: a106776f 002c8000 002c8000 |  002c8066
002c8066: PK.0304: 002d 0008 0000 54af3986 00000000 00000000 00000000 0038 0000 |  002c8084 002c80bc 002c80bc 002c80bc - Artificial ZZ plant 1100mmArtificial ZZ plant 1100mm.dwg
005b5c55: PK.0708: c28b6444 002edb99 002edb99 |  005b5c65
005b5c65: PK.0304: 002d 0008 0000 54af398b 00000000 00000000 00000000 0038 0000 |  005b5c83 005b5cbb 005b5cbb 005b5cbb - Artificial ZZ plant 1100mmArtificial ZZ plant 1100mm.ifc

Same set of files on a chrome download

00000000: PK.0304: 002d 0008 0000 54af3988 00000000 00000000 00000000 0039 0000 |  0000001e 00000057 00000057 00000057 - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.rfa
002c8057: PK.0708: a106776f 002c8000 002c8000 |  002c8067
002c8067: PK.0304: 002d 0008 0000 54af3986 00000000 00000000 00000000 0039 0000 |  002c8085 002c80be 002c80be 002c80be - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.dwg
005b5c57: PK.0708: c28b6444 002edb99 002edb99 |  005b5c67
005b5c67: PK.0304: 002d 0008 0000 54af398b 00000000 00000000 00000000 0039 0000 |  005b5c85 005b5cbe 005b5cbe 005b5cbe - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.ifc
01287ad4: PK.0708: e6ed0337 00cd1e16 00cd1e16 |  01287ae4
01287ae4: PK.0304: 002d 0008 0000 54af3987 00000000 00000000 00000000 0039 0000 |  01287b02 01287b3b 01287b3b 01287b3b - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.obj
02c4f6e1: PK.0708: bffce8ef 019c7ba6 019c7ba6 |  02c4f6f1
02c4f6f1: PK.0304: 002d 0008 0000 54af3989 00000000 00000000 00000000 0039 0000 |  02c4f70f 02c4f748 02c4f748 02c4f748 - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.skp
04f9f516: PK.0708: 7160b309 0234fdce 0234fdce |  04f9f526
04f9f526: PK.0102: 032d 002d 0008 0000 54af3988 a106776f 002c8000 002c8000 0039 0000 0000 0000 0000 81b40000 00000000 |  04f9f554 04f9f58d 04f9f58d 04f9f58d - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.rfa
04f9f58d: PK.0102: 032d 002d 0008 0000 54af3986 c28b6444 002edb99 002edb99 0039 0000 0000 0000 0000 81b40000 002c8067 |  04f9f5bb 04f9f5f4 04f9f5f4 04f9f5f4 - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.dwg
04f9f5f4: PK.0102: 032d 002d 0008 0000 54af398b e6ed0337 00cd1e16 00cd1e16 0039 0000 0000 0000 0000 81b40000 005b5c67 |  04f9f622 04f9f65b 04f9f65b 04f9f65b - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.ifc
04f9f65b: PK.0102: 032d 002d 0008 0000 54af3987 bffce8ef 019c7ba6 019c7ba6 0039 0000 0000 0000 0000 81b40000 01287ae4 |  04f9f689 04f9f6c2 04f9f6c2 04f9f6c2 - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.obj
04f9f6c2: PK.0102: 032d 002d 0008 0000 54af3989 7160b309 0234fdce 0234fdce 0039 0000 0000 0000 0000 81b40000 02c4f6f1 |  04f9f6f0 04f9f729 04f9f729 04f9f729 - Artificial ZZ plant 1100mm/Artificial ZZ plant 1100mm.skp
04f9f729: PK.0506: 0000 0000 0005 0005 00000203 04f9f526 0000 |  04f9f73f 04f9f73f 

When trying to open the file on Ubuntu, it simply says: An error occurred loading the archive.

My service worker looks like this:

importScripts('./client-zip-2.2.2.min.js')

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url)

  if (url.pathname.includes('downloadZip/')) {
    event.respondWith(
      event.request
        .formData()
        .then(formData => downloadZip(activate(formData)))
        .catch(err => new Response(err.message, { status: 500 })),
    )
  }
})

function replaceHeader(response, productName) {
  const header = response.headers.get('Content-Disposition')
  productName = productName.replace(
    /[^\x00-\x7f]|[#|%|&|{|}|\\|\/|<|>|\*|\?|\$|!|'|"|:|@|\+|`|=]/g,
    '',
  )
  const parts = header.split(';')
  let fileName =
    productName +
    '/' +
    parts[1]
      .split('=')[1]
      .replace(/[^\x00-\x7f]|[#|%|&|{|}|\\|\/|<|>|\*|\?|\$|!|'|"|:|@|\+|`|=]/g, '')
  try {
    decodeURIComponent(fileName)
  } catch (ex) {
    fileName = 'unknown_filename'
  }

  var newHeaders = new Headers(response.headers)
  newHeaders.set('Content-Disposition', 'attachment; filename=' + fileName)
  const newFile = new Response(response.body, {
    headers: newHeaders,
  })
  return newFile
}
async function* activate(formData) {
  for (const value of formData.values()) {
    try {
      const parsedValue = JSON.parse(value)
      const response = await fetch(parsedValue.url)
      if (!response.ok) {
        console.warn(`skipping ${response.status} response for ${url}`)
      } else if (
        response.status === 204 ||
        response.headers.get('Content-Length') === '0' ||
        !response.body
      ) {
        console.warn(`skipping empty response for ${url}`)
      } else {
        yield replaceHeader(response.clone(), parsedValue.productName)
      }
    } catch (err) {
      console.error(err)
    }
  }
}

// Has to come after otherwise the download fetch is never executed. Needs to have try catch because it's not available on local development
try {
  importScripts('ngsw-worker.js')
} catch (e) {
  console.log('ngsw-worker.js not available (on local dev environment)', e)
}

and it’s triggered with this Angular code

const form = document.createElement('form')
    form.method = 'post'
    form.action = `downloadZip/${downloadName}.zip`
    const button = document.createElement('button')
    button.type = 'submit'
    for (let file of fileDownloads) {
      const input = document.createElement('input')
      input.value = JSON.stringify({
        url: file.url,
        productName: file.productName,
      })
      input.name = 'url'
      input.type = 'hidden'

      form.appendChild(input)
    }

    form.appendChild(button)
    document.body.appendChild(form)
    form.submit()
    form.remove()
 

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:16 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
Touffycommented, Sep 2, 2022

I see. Yeah, your headers are a bit overcomplicated alright. Anyway, the point wasn’t to remove your filename logic.

yield doesn’t matter here (it’s just the syntactic bit that allows a generator function to produce multiple results lazily, and you would use it either way). My suggestion is about what you yield. Essentially, just changing the replaceHeader function.

Your implementation clones the original Response body to make a new Response with a different Content-Disposition header, just so you can change the eventual filename in the archive. It’s pretty inefficient. client-zip infers the filename from Content-Disposition when you give it a Response, but if you give it an object with a name property like I suggested, it will use that and ignore whatever is in the Response headers.

So the result would be something like this (I just changed the end of the function, but you may want to rename it since it doesn’t actually replace headers anymore) :

function replaceHeader(response, productName) {
  const header = response.headers.get('Content-Disposition')
  productName = productName.replace(
    /[^\x00-\x7f]|[#|%|&|{|}|\\|\/|<|>|\*|\?|\$|!|'|"|:|@|\+|`|=]/g,
    '',
  )
  const parts = header.split(';')
  let fileName =
    productName +
    '/' +
    parts[1]
      .split('=')[1]
      .replace(/[^\x00-\x7f]|[#|%|&|{|}|\\|\/|<|>|\*|\?|\$|!|'|"|:|@|\+|`|=]/g, '')
  try {
    decodeURIComponent(fileName)
  } catch (ex) {
    fileName = 'unknown_filename'
  }

  return { name: fileName, input: response }
}
0reactions
Touffycommented, Sep 28, 2022

I’m closing this since it’s a browser bug and we’ve got a workaround. I’m also going to add link in the README. Most developers need to know about this if they use the Service Worker to download. Some users are bound to use Firefox.

Read more comments on GitHub >

github_iconTop Results From Across the Web

All of my downloads over ~200MB either fail or end up corrupt
Whenever I try to download something that's sized ~200 Megabytes or higher, it will take multiple (5-10+) tries to download it with success....
Read more >
Files downloaded with Firefox are often corrupt - Super User
Each time, the corruption is different. Most of the file contents match, with segments always missing from the corrupt file. In one of...
Read more >
Firefox downloads always corrupt - guru3D Forums
Hi, Anytime I download a file using Mozilla firefox it appears to download, but when I try and open it, the file is...
Read more >
I am getting an error message in Firefox that it cannot ...
The most common cause is an inability for the browser to establish a secure end-to-end connection to our server, and it sounds like...
Read more >
Solved: All files are corrupt after individual download
Solved: I am on Dropbox web on Firefox, Windows 7. Absolutely all the files I have downloaded individually from Dropbox with the direct...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found