fbpx

Downloading resources in HTML5 a[download] may not work as expected

HTML anchor tag can’t download resources from a different origin, learn how I fixed this in my GIF app

<a href="http://this-iamgecom/file.png" download="fiename">
  download me
</a>

HTML anchor tag

HTML5 allows us to add a download attribute with an optional value to indicate that the resource should be downloaded onto the user’s device.

However, as of late 2018, clicking the link won’t trigger a download if the resource to be downloaded wasn’t served from the same origin or same server so to say. Apparently, this is restriction is a security measure.

Attackers may exploit your users by planting malicious download links into your website, so this is a security measure by the browser guys to prevent against that.

Quick example

A hyperlink on https://www.facebook.com cannot download an image(or any resource) that was served from https://www.google.com. They are not on the same origin 🤷🏿‍♂️

A possible use case scenario

This was a big issue while I was building a simple GIF app that uses the Giphfy translate API to convert some text into a sweet GIF.

In turn, I wanted the user to be able to download the GIF once it loads but I couldn’t! because the GIF simply wasn’t severed from the same origin as my GIF app. You can check it out here.

In turn, I wanted the user to be able to download the GIF once it loads but I couldn’t! because the GIF simply wasn’t severed from the same origin as my GIF app. You can check it out here.

Solution1: Data URIs

Basically, a data URI is a base64 encoding of a resource, this means you don’t need to link to an external resource, the text itself is the resource 🙂.

Here is what it looks like

hyperlink download example

anchor tag with data URI

That gibberish text will be way more than that if the file is large. I recommend using the second solution though.

Back to the GIF app!

To incorporate this idea of data URIs, this means we have to convert the file — remote file in this case — into a data URI.

As usual, I ran to npm to search for a package that can help convert my remote GIFs into a data URI. Pweeeeh! After testing about four of them, image-data-uri worked well for me 😘 That’s not all though!

You may also like this

10 Ways to Earn Side Income for Programmers

You’ll have a create a link and programmatically click it to get the file downloaded.

I created a react component to do just that, it takes in URL, once clicked and a filename as prop.

Once clicked:

  • It fetches and converts the resource into a data URI
  • creates an anchor tag, sets the href and clicks the link
/**
 * Modern browsers can download files that aren't from same origin this is a workaround to download a remote file
 * @param `url` Remote URL for the file to be downloaded
 */
function Download({ url, filename }) {
  const [creatingURI, setCreatingURI] = useState(false);

  const download = () => {
    setCreatingURI(true);

    imageDataURI
      .encodeFromURL(url)
      .then(uri => {
        setCreatingURI(false);

        const link = document.createElement("a");
        link.href = uri;
        link.download = `${filename}.gif`;
        document.body.appendChild(link);
        link.click();
      })
      .catch(() => {
        setCreatingURI(false);
      });
  };

  return (
    <button
      disabled={creatingURI}
      onClick={download}
      aria-label="download gif"
    >
      DOWNLOAD
    </button>
  );
}

download file from different origin

Solution 2: Blob (A better solution)

Instead of converting the resource to base64 we can convert into a blob URL instead. This seems to be the perfect solution.

/**
 * Modern browsers can download files that aren't from same origin this is a workaround to download a remote file
 * @param `url` Remote URL for the file to be downloaded
 */
function Download({ url, filename }) {
   const [fetching, setFetching] = useState(false);
  const [error, setError] = useState(false);

  const download = (url, name) => {
    if (!url) {
      throw new Error("Resource URL not provided! You need to provide one");
    }
    setFetching(true);
    fetch(url)
      .then(response => response.blob())
      .then(blob => {
        setFetching(false);
        const blobURL = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = blobURL;
        a.style = "display: none";

        if (name && name.length) a.download = name;
        document.body.appendChild(a);
        a.click();
      })
      .catch(() => setError(true));
  };

  return (
    <button
      disabled={fetching}
      onClick={()=> download(url, filename)}
      aria-label="download gif"
    >
      DOWNLOAD
    </button>
  );
}

I totally recommend this using blob over data URI

Thanks for reading 🙂Don’t forget to share if this helped you in any way.

You can catch me on twitter @MarvinJudeHK

The Gif app is on Github

👋🏿👋🏿👋🏿👋🏿👋🏿👋🏿

WRITTEN BY

Marvin Jude

Software Engineer. React • JavaScript • Full-stack @PubNub Ambassador #Always bet on Javascript.

Adewale Adetona

I'm a FullStack Developer and Digital Marketer. I started this blog to give you the proven strategies and resources you need to accelerate your career as a software developer.

Leave a Reply