Skip to content

Commit

Permalink
fix(base64-file-converter): fix downloading of index.html content wit…
Browse files Browse the repository at this point in the history
…hout data preambula (CorentinTh#750)

* fix(base64-file-converter): fix downloading of index.html content without data preambula

* feat(base64-file-converter): infer mime type from base64 signature

---------

Co-authored-by: akharlov <[email protected]>
  • Loading branch information
CorentinTh and halex2005 authored Nov 13, 2023
1 parent ca43a25 commit 043e4f0
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 12 deletions.
32 changes: 32 additions & 0 deletions src/composable/downloadBase64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { getMimeTypeFromBase64 } from './downloadBase64';

describe('downloadBase64', () => {
describe('getMimeTypeFromBase64', () => {
it('when the base64 string has a data URI, it returns the mime type', () => {
expect(getMimeTypeFromBase64({ base64String: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/png' });
expect(getMimeTypeFromBase64({ base64String: 'data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/jpg' });
});

it('when the base64 string has no data URI, it try to infer the mime type from the signature', () => {
// https://en.wikipedia.org/wiki/List_of_file_signatures

// PNG
expect(getMimeTypeFromBase64({ base64String: 'iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/png' });

// GIF
expect(getMimeTypeFromBase64({ base64String: 'R0lGODdh' })).to.deep.equal({ mimeType: 'image/gif' });
expect(getMimeTypeFromBase64({ base64String: 'R0lGODlh' })).to.deep.equal({ mimeType: 'image/gif' });

// JPG
expect(getMimeTypeFromBase64({ base64String: '/9j/' })).to.deep.equal({ mimeType: 'image/jpg' });

// PDF
expect(getMimeTypeFromBase64({ base64String: 'JVBERi0' })).to.deep.equal({ mimeType: 'application/pdf' });
});

it('when the base64 string has no data URI and no signature, it returns an undefined mimeType', () => {
expect(getMimeTypeFromBase64({ base64String: 'JVBERi' })).to.deep.equal({ mimeType: undefined });
});
});
});
52 changes: 40 additions & 12 deletions src/composable/downloadBase64.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,60 @@
import { extension as getExtensionFromMime } from 'mime-types';
import type { Ref } from 'vue';
import _ from 'lodash';

function getFileExtensionFromBase64({
base64String,
export { getMimeTypeFromBase64, useDownloadFileFromBase64 };

const commonMimeTypesSignatures = {
'JVBERi0': 'application/pdf',
'R0lGODdh': 'image/gif',
'R0lGODlh': 'image/gif',
'iVBORw0KGgo': 'image/png',
'/9j/': 'image/jpg',
};

function getMimeTypeFromBase64({ base64String }: { base64String: string }) {
const [,mimeTypeFromBase64] = base64String.match(/data:(.*?);base64/i) ?? [];

if (mimeTypeFromBase64) {
return { mimeType: mimeTypeFromBase64 };
}

const inferredMimeType = _.find(commonMimeTypesSignatures, (_mimeType, signature) => base64String.startsWith(signature));

if (inferredMimeType) {
return { mimeType: inferredMimeType };
}

return { mimeType: undefined };
}

function getFileExtensionFromMimeType({
mimeType,
defaultExtension = 'txt',
}: {
base64String: string
mimeType: string | undefined
defaultExtension?: string
}) {
const hasMimeType = base64String.match(/data:(.*?);base64/i);

if (hasMimeType) {
return getExtensionFromMime(hasMimeType[1]) || defaultExtension;
if (mimeType) {
return getExtensionFromMime(mimeType) ?? defaultExtension;
}

return defaultExtension;
}

export function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
return {
download() {
const base64String = source.value;

if (base64String === '') {
if (source.value === '') {
throw new Error('Base64 string is empty');
}

const cleanFileName = filename ?? `file.${getFileExtensionFromBase64({ base64String })}`;
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value });
const base64String = mimeType
? source.value
: `data:text/plain;base64,${source.value}`;

const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`;

const a = document.createElement('a');
a.href = base64String;
Expand Down

0 comments on commit 043e4f0

Please sign in to comment.