Protectator

Can we make an infinitely downloading file using only JavaScript?

Oct 30, 2015 · 4 min read
This post is unlisted: only those with this direct link can access it.
Be careful who you share it with.
Can we make an infinitely downloading file using only JavaScript?

Question is in the title. I don't know if the answer feels logical to most, but it wasn't to me when I first thought about it. Because:

  • You can download a finite file generated by a server.
  • You can try to download an infinite file generated by a server.
  • You can download a finite file generated by JavaScript.

Why couldn't you also try to download an infinite file generated by only JavaScript? (I mean, without using any fancy web API like websockets or webRTC)

So if you're here for a short answer to the title: You can't.

I initially got the idea to try to answer this question when I first saw OVH's promotion on a few domain names. ".download" was only 0.50€ for a year, and thought it was worth the time to try to make a joke page with an infinitely downloading file (actually streaming content, not just a frozen download).

OVH's '.download' TLD promotion

So I bought "cantstopthe.download". The goal was to set up a very simple HTML page, with a link to start the download of that file. But I don't want to generate useless network traffic ; the download should be entirely happening on the client's computer.

Infinite download from a server

Let's start by remembering how easy it is to make this to work with a server. The trick of course is not to store an infinitely large file and serve it, but to generate it as the user is downloading.

This isn't even remotely a challenge : Just provide a link to download a file that executes an infinite loop when queried. Here's a trivial example of this in PHP :

index.html

<a href="infinite.php" download>Link</a>

index.php

<?php while(1) echo "Hi."; ?>

If you try to get the infinite.php file, you'll receive endless "Hi." until you manually stop the download.

Now, things get more interesting when you want to delete the server part.


Of course, the interest of this question comes from the fact that we are able to generate the download of a finite file entirely in JavaScript.

I see two main ways to achieve the download of a file from an user's own browser : Either build a File from a Blob and link to it, or use a Data URI to represent the file.

Take 1 : Link to a built file

We can use the Blob Web API to create a representation of a file. So first, we'll create a finite text File to download using a Blob, and we'll provide a link pointing to its created content.

index.html

<script src="script.js"></script>
<a id="link" download="file.txt">Download !</a>

script.js

window.onload = function() {
  var a = document.getElementById('link');
  a.href = window.URL.createObjectURL(
    new Blob(['Hi ! This is text.'],
      {type: 'text/plain'}
    )
  );
}

Link to the blob

Clicking the link effectively downloads the text file. So, we just generated a file using only JavaScript, directly in the client's browser. We're not that far from what we can do with a server as we saw it, right ? Well...

What represents the file here is the blob, which is not executable code. So for our file to be downloading infinitely, we would need an infinite Blob. We can of course create a very long string with a loop that we would then assign to the Blob. But we would be trapped in that loop that creates the string, not in the download of it. So it seems like we're executing code a bit too far from our target to be able to make the file infinitely download.

Take 2 : Data URI

Using a Data URI is not that much different from the final result we obtained using the blob. We want to generate an infinitely large Data URI that would represent our file. But this time, we don't need to assign it to an other variable (as we did with the Blob). So we can try to build the file directly with the string, without having to keep it in memory.

We could try to generate the infinitely large URI in a loop, but we would be stuck in that loop the same way we talked about before.

For a finite file, we don't even need JavaScript at all, this time plain HTML is sufficient to make the download :

index.html

<a href="data:text;charset=utf-8;base64,VGhpcyBpcyB0ZXh0IHRvbyAh"
   download="file.txt">Download !</a>

Link to the Data URI in Chrome

The next step is to build the URI at the time the user tries to access it. We'll need to evaluate the value as late as possible.

Lazy evaluation ?

An idea would be to evaluate the string lazily, at the moment it's asked for. We'll need to change the code a bit. We don't want to assign a value to the href property, because we'd have to build the infinite string that before the user clicks the link, which we saw is impossible. Instead, we'd like to build the data as the user requests it.

Here is an other example of a script that downloads a file.

index.html

<script src="script.js"></script>
<a href="javascript:download();">Download !</a>

script.js

function download() {
  document.location =
    'data:Application/octet-stream,'
    + 'infinite_string_should_be_here';
}

In this example, I'm using data:Application/octet-stream so that browsers won't know how to interpret the incoming data, and will try to download the incoming file as a result.See the infinite_string_should_be_here string ? We can now try to replace this with a lazy string, right ?

The library Stream.js gives us a way to manipulate objects that behave like streams and generators. Which is what we want, right ? We'll just have to make a stream that generates an infinite amount of characters.

Here's what our code could be resembling...

index.html

<script src="stream-min.js"></script>
<script src="script.js"></script>
<a href="javascript:download();">Download !</a>

script.js

function download() {
  // generator always generates "Hi."
  var generator = Stream.generate(function() {
    return "Hi.";
  });

  // takeWhile(n) retrieves elements while n holds true.
  // join() returns a concatenation of the stream.
  document.location =
    'data:Application/octet-stream,'
    + generator.takeWhile(true).join();
}

You won't see that red fade away

The tab freezes : All of this for an infinite loop, and not the one we wanted. Maybe you saw it coming when true is sitting just next to while, though...

The explanation is trivial : Even with tons of laziness, the right-side of the expression has to be fully evaluated before being assigned. The end will of course never happen here, leaving the interpreter collect as many Hi. as it can handle until the tab crashes. And so ends my attempts.


Conclusion

What would it take to actually be able to achieve this ? The problem here is that whatever we do, we'll need to evaluate the full content to be downloaded before being able to start. I can't see any way we could detach the process generating the data from the process trying to get it for now.

I tried to answer this question at first because I fount it amusing and I didn't really got any satisfying answer after googling a bit. After having found my answer, I'd say this was a fun small research for me. :)

Category: Development