Skip to content

Commit

Permalink
Import X-Download-Options (ienoopen) middleware
Browse files Browse the repository at this point in the history
This imports the [ienoopen package][0] into this repo. You can find its
prior history in the old repo's source code, but now I'm moving Helmet
to be a monorepo.

[0]: https://github.com/helmetjs/ienoopen
  • Loading branch information
EvanHahn committed Jun 12, 2020
1 parent 53a0299 commit 13b496f
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 12 deletions.
5 changes: 5 additions & 0 deletions bin/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"es6": true
}
}
81 changes: 81 additions & 0 deletions bin/build-middleware-package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env node
const path = require("path");
const fs = require("fs").promises;
const os = require("os");
const crypto = require("crypto");

const PROJECT_ROOT_PATH = path.join(__dirname, "..");
const getRootFilePath = (filename) => path.join(PROJECT_ROOT_PATH, filename);

async function main(argv) {
if (argv.length !== 3) {
throw new Error("Incorrect number of arguments");
}

const stagingDirectoryPath = path.join(
os.tmpdir(),
`helmet-middleware-release-${argv[2]}-${crypto
.randomBytes(8)
.toString("hex")}`
);

const getSourceFilePath = (filename) =>
path.join(PROJECT_ROOT_PATH, "middlewares", argv[2], filename);
const getDistFilePath = (filename) =>
path.join(PROJECT_ROOT_PATH, "dist", "middlewares", argv[2], filename);
const getStagingFilePath = (filename) =>
path.join(stagingDirectoryPath, filename);

const packageFiles = require(getSourceFilePath("package-files.json"));

const packageJson = {
author: "Adam Baldwin <adam@npmjs.com> (https://evilpacket.net)",
contributors: ["Evan Hahn <me@evanhahn.com> (https://evanhahn.com)"],
license: "MIT",
homepage: "https://helmetjs.github.io/",
bugs: {
url: "https://github.com/helmetjs/helmet/issues",
email: "me@evanhahn.com",
},
repository: {
type: "git",
url: "git://github.com/helmetjs/helmet.git",
},
engines: {
node: ">=4.0.0",
},
files: ["CHANGELOG.md", "LICENSE", "README.md", ...packageFiles],
main: "index.js",
typings: "index.d.ts",
...require(getSourceFilePath("package-overrides.json")),
};

await fs.mkdir(stagingDirectoryPath, { recursive: true, mode: 0o700 });
await Promise.all([
fs.writeFile(
getStagingFilePath("package.json"),
JSON.stringify(packageJson, null, 2)
),
fs.copyFile(
getSourceFilePath("README.md"),
getStagingFilePath("README.md")
),
fs.copyFile(
getSourceFilePath("CHANGELOG.md"),
getStagingFilePath("CHANGELOG.md")
),
fs.copyFile(getRootFilePath("LICENSE"), getStagingFilePath("LICENSE")),
...packageFiles.map((filename) =>
fs.copyFile(getDistFilePath(filename), getStagingFilePath(filename))
),
]);

console.log(
`Staged ${packageJson.name}@${packageJson.version} in ${stagingDirectoryPath}`
);
}

main(process.argv).catch((err) => {
console.error(err);
process.exit(1);
});
3 changes: 2 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IncomingMessage, ServerResponse } from "http";
import xDownloadOptions from "./middlewares/x-download-options";
import depd = require("depd");

interface HelmetOptions {
Expand Down Expand Up @@ -130,7 +131,7 @@ helmet.expectCt = require("expect-ct");
helmet.frameguard = require("frameguard");
helmet.hidePoweredBy = require("hide-powered-by");
helmet.hsts = require("hsts");
helmet.ieNoOpen = require("ienoopen");
helmet.ieNoOpen = xDownloadOptions;
helmet.noSniff = require("dont-sniff-mimetype");
helmet.permittedCrossDomainPolicies = require("helmet-crossdomain");
helmet.referrerPolicy = require("referrer-policy");
Expand Down
21 changes: 21 additions & 0 deletions middlewares/x-download-options/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Changelog

## Unreleased

### Changed

- Excluded more files from npm package

## 1.1.0 - 2019-03-10

### Added

- Added TypeScript type definitions. See [#1](https://github.com/helmetjs/ienoopen/pull/1) and [helmetjs/helmet#188](https://github.com/helmetjs/helmet/issues/188)
- Created a changelog

### Changed

- Updated documentation
- Excluded some files from npm package

Changes in versions 1.0.0 and below can be found in [Helmet's changelog](https://github.com/helmetjs/helmet/blob/master/CHANGELOG.md).
12 changes: 12 additions & 0 deletions middlewares/x-download-options/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# X-Download-Options middleware

This middleware sets the `X-Download-Options` header to `noopen` to prevent Internet Explorer users from executing downloads in your site's context.

```javascript
const ienoopen = require("ienoopen");
app.use(ienoopen());
```

Some web applications will serve untrusted HTML for download. By default, some versions of IE will allow you to open those HTML files _in the context of your site_, which means that an untrusted HTML page could start doing bad things in the context of your pages. For more, see [this MSDN blog post](http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx).

This is pretty obscure, fixing a small bug on IE only. No real drawbacks other than performance/bandwidth of setting the headers, though.
17 changes: 17 additions & 0 deletions middlewares/x-download-options/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IncomingMessage, ServerResponse } from "http";

function xDownloadOptionsMiddleware(
_req: IncomingMessage,
res: ServerResponse,
next: () => void
): void {
res.setHeader("X-Download-Options", "noopen");
next();
}

function xDownloadOptions() {
return xDownloadOptionsMiddleware;
}

module.exports = xDownloadOptions;
export default xDownloadOptions;
1 change: 1 addition & 0 deletions middlewares/x-download-options/package-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["index.js", "index.d.ts"]
11 changes: 11 additions & 0 deletions middlewares/x-download-options/package-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "ienoopen",
"contributors": [
"Evan Hahn <me@evanhahn.com> (https://evanhahn.com)",
"Nathan Shively-Sanders <nathansa@microsoft.com> (https://github.com/sandersn)"
],
"description": "Middleware to set `X-Download-Options` header for IE8 security",
"version": "1.1.0",
"keywords": ["express", "security", "x-download-options"],
"homepage": "https://helmetjs.github.io/docs/ienoopen/"
}
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"LICENSE",
"README.md",
"SECURITY.md",
"dist/index.js"
"dist/index.js",
"dist/middlewares/x-download-options/index.js"
],
"dependencies": {
"depd": "2.0.0",
Expand All @@ -50,7 +51,6 @@
"hide-powered-by": "1.1.0",
"hpkp": "2.0.0",
"hsts": "2.2.0",
"ienoopen": "1.1.0",
"nocache": "2.1.0",
"referrer-policy": "1.2.0",
"x-xss-protection": "1.3.0"
Expand Down Expand Up @@ -79,6 +79,7 @@
"format": "prettier --write '**/*{md,js,json,ts}'",
"clean": "rm -rf dist",
"build": "npm run clean && tsc",
"build-middleware-package": "npm run build && ./bin/build-middleware-package.js",
"test": "jest"
},
"license": "MIT",
Expand Down
28 changes: 28 additions & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IncomingMessage, ServerResponse } from "http";
import connect = require("connect");
import supertest = require("supertest");

interface MiddlewareFunction {
(req: IncomingMessage, res: ServerResponse, next: () => void): void;
}

export async function check(
middleware: MiddlewareFunction,
expectedHeaders: Readonly<{ [headerName: string]: string | null }>
): Promise<void> {
const app = connect()
.use(middleware)
.use((_req: IncomingMessage, res: ServerResponse) => {
res.end("Hello world!");
});

const { header } = await supertest(app).get("/").expect(200, "Hello world!");

for (const [headerName, headerValue] of Object.entries(expectedHeaders)) {
if (headerValue === null) {
expect(header).not.toHaveProperty(headerName);
} else {
expect(header).toHaveProperty(headerName, headerValue);
}
}
}
6 changes: 3 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import helmet = require("..");
import { IncomingMessage, ServerResponse } from "http";
import connect = require("connect");
import request = require("supertest");
import xDowloadOptions from "../middlewares/x-download-options";

describe("helmet", function () {
describe("module aliases", function () {
Expand Down Expand Up @@ -107,9 +108,8 @@ describe("helmet", function () {
expect(helmet.hsts).toBe(pkg);
});

it('aliases "ienoopen"', function () {
const pkg = require("ienoopen");
expect(helmet.ieNoOpen).toBe(pkg);
it("aliases the X-Download-Options middleware to helmet.ieNoOpen", () => {
expect(helmet.ieNoOpen.name).toBe(xDowloadOptions.name);
});

// This test will be removed in helmet@4.
Expand Down
10 changes: 10 additions & 0 deletions test/x-download-options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { check } from "./helpers";
import xDownloadOptions from "../middlewares/x-download-options";

describe("X-Download-Options middleware", () => {
it('sets "X-Download-Options: noopen"', async () => {
await check(xDownloadOptions(), {
"x-download-options": "noopen",
});
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"declaration": false,
"declaration": true,
"esModuleInterop": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
Expand Down

0 comments on commit 13b496f

Please sign in to comment.