Skip to content

Commit

Permalink
fix: mime type detection
Browse files Browse the repository at this point in the history
  • Loading branch information
izatop committed Apr 6, 2023
1 parent a4f2990 commit 02f0706
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/fs/src/Driver/MinIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {assert, isString} from "@bunt/util";
import {Client, UploadedObjectInfo} from "minio";
import fetch from "node-fetch";
import {FsSource, FsStat, FsWritable} from "../interfaces";
import {getMimeType} from "../mime-db";
import {FsDriverAbstract} from "./FsDriverAbstract";
import {MinIOBucketPolicy} from "./MinIOBucketPolicy";

Expand Down Expand Up @@ -108,18 +109,19 @@ export class MinIO extends FsDriverAbstract {
if (protocols.includes(source.protocol)) {
const response = await fetch(source, {redirect: "follow", follow: 5});
const known = ["content-type"];
const headers = Object.fromEntries(
Object.assign(metadata, Object.fromEntries(
[...response.headers.entries()]
.filter(([key]) => known.includes(key)),
);
));

assert(response.body, `Response body is null for URL ${source.href}`);
metadata["content-type"] = this.#getMimeType(source.pathname, metadata);

return this.#client.putObject(
bucket,
name,
response.body as Readable,
{...headers, ...metadata},
metadata,
);
}

Expand All @@ -128,4 +130,8 @@ export class MinIO extends FsDriverAbstract {

return this.#client.putObject(bucket, name, source, metadata);
}

#getMimeType(source: string, metadata: Record<string, any>): string {
return metadata["content-type"] ?? getMimeType(source);
}
}
1 change: 1 addition & 0 deletions packages/fs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./Driver/FsDriverAbstract";
export * from "./Driver/MinIOBucketPolicy";
export * from "./Driver/MinIO";
export * from "./interfaces";
export * from "./mime-db";
267 changes: 267 additions & 0 deletions packages/fs/src/mime-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import {extname} from "path";

const types = {
"application/atom+xml": {
"extensions": ["atom"],
},
"application/java-archive": {
"extensions": ["jar", "war", "ear"],
},
"application/javascript": {
"extensions": ["js"],
},
"application/json": {
"extensions": ["json"],
},
"application/mac-binhex40": {
"extensions": ["hqx"],
},
"application/msword": {
"extensions": ["doc"],
},
"application/octet-stream": {
"extensions": ["bin", "exe", "dll", "deb", "dmg", "iso", "img", "msi", "msp", "msm"],
},
"application/pdf": {
"extensions": ["pdf"],
},
"application/postscript": {
"extensions": ["ps", "eps", "ai"],
},
"application/rss+xml": {
"extensions": ["rss"],
},
"application/rtf": {
"extensions": ["rtf"],
},
"application/vnd.apple.mpegurl": {
"extensions": ["m3u8"],
},
"application/vnd.google-earth.kml+xml": {
"extensions": ["kml"],
},
"application/vnd.google-earth.kmz": {
"extensions": ["kmz"],
},
"application/vnd.ms-excel": {
"extensions": ["xls"],
},
"application/vnd.ms-fontobject": {
"extensions": ["eot"],
},
"application/vnd.ms-powerpoint": {
"extensions": ["ppt"],
},
"application/vnd.oasis.opendocument.graphics": {
"extensions": ["odg"],
},
"application/vnd.oasis.opendocument.presentation": {
"extensions": ["odp"],
},
"application/vnd.oasis.opendocument.spreadsheet": {
"extensions": ["ods"],
},
"application/vnd.oasis.opendocument.text": {
"extensions": ["odt"],
},
"application/vnd.openxmlformats-officedocument.presentationml.presentation": {
"extensions": ["pptx"],
},
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
"extensions": ["xlsx"],
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"extensions": ["docx"],
},
"application/vnd.wap.wmlc": {
"extensions": ["wmlc"],
},
"application/wasm": {
"extensions": ["wasm"],
},
"application/x-7z-compressed": {
"extensions": ["7z"],
},
"application/x-cocoa": {
"extensions": ["cco"],
},
"application/x-java-archive-diff": {
"extensions": ["jardiff"],
},
"application/x-java-jnlp-file": {
"extensions": ["jnlp"],
},
"application/x-makeself": {
"extensions": ["run"],
},
"application/x-perl": {
"extensions": ["pl", "pm"],
},
"application/x-pilot": {
"extensions": ["prc", "pdb"],
},
"application/x-rar-compressed": {
"extensions": ["rar"],
},
"application/x-redhat-package-manager": {
"extensions": ["rpm"],
},
"application/x-sea": {
"extensions": ["sea"],
},
"application/x-shockwave-flash": {
"extensions": ["swf"],
},
"application/x-stuffit": {
"extensions": ["sit"],
},
"application/x-tcl": {
"extensions": ["tcl", "tk"],
},
"application/x-x509-ca-cert": {
"extensions": ["der", "pem", "crt"],
},
"application/x-xpinstall": {
"extensions": ["xpi"],
},
"application/xhtml+xml": {
"extensions": ["xhtml"],
},
"application/xspf+xml": {
"extensions": ["xspf"],
},
"application/zip": {
"extensions": ["zip"],
},
"audio/midi": {
"extensions": ["mid", "midi", "kar"],
},
"audio/mpeg": {
"extensions": ["mp3"],
},
"audio/ogg": {
"extensions": ["ogg"],
},
"audio/x-m4a": {
"extensions": ["m4a"],
},
"audio/x-realaudio": {
"extensions": ["ra"],
},
"font/woff": {
"extensions": ["woff"],
},
"font/woff2": {
"extensions": ["woff2"],
},
"image/avif": {
"extensions": ["avif"],
},
"image/gif": {
"extensions": ["gif"],
},
"image/jpeg": {
"extensions": ["jpeg", "jpg"],
},
"image/png": {
"extensions": ["png"],
},
"image/svg+xml": {
"extensions": ["svg", "svgz"],
},
"image/tiff": {
"extensions": ["tif", "tiff"],
},
"image/vnd.wap.wbmp": {
"extensions": ["wbmp"],
},
"image/webp": {
"extensions": ["webp"],
},
"image/x-icon": {
"extensions": ["ico"],
},
"image/x-jng": {
"extensions": ["jng"],
},
"image/x-ms-bmp": {
"extensions": ["bmp"],
},
"text/css": {
"extensions": ["css"],
},
"text/html": {
"extensions": ["html", "htm", "shtml"],
},
"text/mathml": {
"extensions": ["mml"],
},
"text/plain": {
"extensions": ["txt"],
},
"text/vnd.sun.j2me.app-descriptor": {
"extensions": ["jad"],
},
"text/vnd.wap.wml": {
"extensions": ["wml"],
},
"text/x-component": {
"extensions": ["htc"],
},
"text/xml": {
"extensions": ["xml"],
},
"video/3gpp": {
"extensions": ["3gpp", "3gp"],
},
"video/mp2t": {
"extensions": ["ts"],
},
"video/mp4": {
"extensions": ["mp4"],
},
"video/mpeg": {
"extensions": ["mpeg", "mpg"],
},
"video/quicktime": {
"extensions": ["mov"],
},
"video/webm": {
"extensions": ["webm"],
},
"video/x-flv": {
"extensions": ["flv"],
},
"video/x-m4v": {
"extensions": ["m4v"],
},
"video/x-mng": {
"extensions": ["mng"],
},
"video/x-ms-asf": {
"extensions": ["asx", "asf"],
},
"video/x-ms-wmv": {
"extensions": ["wmv"],
},
"video/x-msvideo": {
"extensions": ["avi"],
},
};

const extensions = Object.fromEntries(
Object.entries(types)
.map(([type, {extensions}]) => extensions.map((e) => [e, type]))
.flat(),
);

export const DEFAULT_MIME_TYPE = "application/octet-stream";

export function getMimeType(file: string): string;
export function getMimeType(file: string, defaultType: undefined): string | undefined;
export function getMimeType(file: string, defaultType: string): string;
export function getMimeType(file: string, defaultType = DEFAULT_MIME_TYPE): string {
const extension = extname(file).slice(1);

return extensions[extension] ?? defaultType;
}
10 changes: 10 additions & 0 deletions packages/fs/test/src/Main.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {dirname, resolve} from "node:path";
import {createReadStream} from "node:fs";
import {FileStorage, MinIO, MinIOBucketPolicy} from "../../src";
import {getMimeType} from "../../src/mime-db";

const fs = new FileStorage(new MinIO("http://minioadmin:minioadmin@localhost:9000"));

Expand All @@ -11,6 +12,15 @@ beforeAll(async () => {
});

describe("MinIO", () => {
test("type detect", async () => {
const bucket = fs.getBucket("test");
const type = getMimeType("https://hub.qubixinfinity.io/contracts/5.png");
expect(type).toBe("image/png");

const stat = await bucket.put("type-detect", new URL("https://hub.qubixinfinity.io/contracts/5.png"));
expect(stat.metadata["content-type"]).toBe(type);
});

test("put URL", async () => {
const bucket = fs.getBucket("test");
const stat = await bucket.put("url", new URL("https://www.google.com/favicon.ico"));
Expand Down

0 comments on commit 02f0706

Please sign in to comment.