Skip to content

Commit ac144b8

Browse files
committed
initial commit
0 parents  commit ac144b8

18 files changed

+475
-0
lines changed

.eslintrc.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@chubbyts/chubbyts-eslint/dist/eslintrc').default;

.github/workflows/ci.yml

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
branches:
7+
- master
8+
schedule:
9+
- cron: '0 0 * * *'
10+
11+
jobs:
12+
node16:
13+
name: Node 16
14+
runs-on: ubuntu-22.04
15+
steps:
16+
- name: checkout
17+
uses: actions/checkout@v4
18+
- name: checkout node
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: '16'
22+
- run: npm install
23+
- run: npm test
24+
node18:
25+
name: Node 18
26+
runs-on: ubuntu-22.04
27+
steps:
28+
- name: checkout
29+
uses: actions/checkout@v4
30+
- name: checkout node
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: '18'
34+
- run: npm install
35+
- run: npm test
36+
node20:
37+
name: Node 20
38+
runs-on: ubuntu-22.04
39+
steps:
40+
- name: checkout
41+
uses: actions/checkout@v4
42+
- name: checkout node
43+
uses: actions/setup-node@v4
44+
with:
45+
node-version: '20'
46+
- run: npm install
47+
- run: npm run lint
48+
- run: npm run cs
49+
- run: npm test -- --coverage --no-cache
50+
- run: npm run infection
51+
env:
52+
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
53+
- name: coveralls.io
54+
uses: coverallsapp/github-action@master
55+
with:
56+
github-token: ${{ secrets.GITHUB_TOKEN }}
57+
- name: sonarcloud.io
58+
uses: sonarsource/sonarcloud-github-action@master
59+
env:
60+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.stryker-tmp
2+
coverage
3+
dist
4+
node_modules
5+
package-lock.json

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2024 Dominik Zogg
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# chubbyts-http-multipart
2+
3+
[![CI](https://github.com/chubbyts/chubbyts-http-multipart/workflows/CI/badge.svg?branch=master)](https://github.com/chubbyts/chubbyts-http-multipart/actions?query=workflow%3ACI)
4+
[![Coverage Status](https://coveralls.io/repos/github/chubbyts/chubbyts-http-multipart/badge.svg?branch=master)](https://coveralls.io/github/chubbyts/chubbyts-http-multipart?branch=master)
5+
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fchubbyts%2Fchubbyts-http-multipart%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/chubbyts/chubbyts-http-multipart/master)
6+
[![npm-version](https://img.shields.io/npm/v/@chubbyts/chubbyts-http-multipart.svg)](https://www.npmjs.com/package/@chubbyts/chubbyts-http-multipart)
7+
8+
[![bugs](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=bugs)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
9+
[![code_smells](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=code_smells)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
10+
[![coverage](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=coverage)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
11+
[![duplicated_lines_density](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
12+
[![ncloc](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=ncloc)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
13+
[![sqale_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
14+
[![alert_status](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=alert_status)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
15+
[![reliability_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
16+
[![security_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=security_rating)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
17+
[![sqale_index](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=sqale_index)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
18+
[![vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=chubbyts_chubbyts-http-multipart&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=chubbyts_chubbyts-http-multipart)
19+
20+
## Description
21+
22+
Chubbyts Http multipart request handling.
23+
24+
## Requirements
25+
26+
* node: 16
27+
* [@chubbyts/chubbyts-http-types][2]: ^1.2.3
28+
* [busboy][3]: ^1.6.0
29+
30+
## Installation
31+
32+
Through [NPM](https://www.npmjs.com) as [@chubbyts/chubbyts-http-multipart][1].
33+
34+
```sh
35+
npm i @chubbyts/chubbyts-http-multipart@^1.0.0
36+
```
37+
38+
## Usage
39+
40+
```ts
41+
import { createMultipartMiddleware } from '@chubbyts/chubbyts-http-multipart/dist/multipart-middleware';
42+
import type { Handler } from '@chubbyts/chubbyts-http-types/dist/handler';
43+
import type { Response, ServerRequest } from '@chubbyts/chubbyts-http-types/dist/message';
44+
45+
const request: ServerRequest = ...;
46+
const response: Response = ...;
47+
48+
// if request original content-type was multipart/form-data, the current content-type and body is now application/x-www-form-urlencoded
49+
const handler: Handler = async (request: ServerRequest): Promise<Response> ...;
50+
51+
const multipartMiddleware = createMultipartMiddleware();
52+
53+
const response = await multipartMiddleware(request, handler);
54+
```
55+
56+
## Copyright
57+
58+
2024 Dominik Zogg
59+
60+
[1]: https://www.npmjs.com/package/@chubbyts/chubbyts-http-multipart
61+
[2]: https://www.npmjs.com/package/@chubbyts/chubbyts-http-types
62+
[3]: https://www.npmjs.com/package/busboy

build.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/@chubbyts/chubbyts-packaging/build.js

package.json

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"name": "@chubbyts/chubbyts-http-multipart",
3+
"type": "module",
4+
"version": "1.0.0",
5+
"description": "Chubbyts Http multipart request handling",
6+
"keywords": [
7+
"chubbyts",
8+
"http",
9+
"multipart",
10+
"middleware"
11+
],
12+
"author": "Dominik Zogg",
13+
"license": "MIT",
14+
"repository": "chubbyts/chubbyts-http-multipart",
15+
"scripts": {
16+
"build": "node ./build.js",
17+
"cs-fix": "prettier --write src tests",
18+
"cs": "prettier --check src tests",
19+
"infection": "stryker run",
20+
"lint-fix": "eslint src tests --fix",
21+
"lint": "eslint src tests",
22+
"prepare": "npm run build",
23+
"test": "jest"
24+
},
25+
"jest": {
26+
"preset": "ts-jest",
27+
"testEnvironment": "node",
28+
"collectCoverageFrom": [
29+
"src/**/*.ts"
30+
],
31+
"coverageThreshold": {
32+
"global": {
33+
"lines": 100
34+
}
35+
},
36+
"coveragePathIgnorePatterns": [
37+
"src/index.ts"
38+
]
39+
},
40+
"prettier": {
41+
"printWidth": 120,
42+
"tabWidth": 2,
43+
"singleQuote": true,
44+
"trailingComma": "all"
45+
},
46+
"files": [
47+
"dist"
48+
],
49+
"exports": {
50+
"./*": {
51+
"types": "./*.d.ts",
52+
"require": "./*.cjs",
53+
"import": "./*.mjs",
54+
"default": "./*.mjs"
55+
}
56+
},
57+
"engines": {
58+
"node": ">=16"
59+
},
60+
"dependencies": {
61+
"@chubbyts/chubbyts-http-types": "^1.2.3",
62+
"busboy": "^1.6.0"
63+
},
64+
"devDependencies": {
65+
"@chubbyts/chubbyts-eslint": "^2.0.3",
66+
"@chubbyts/chubbyts-function-mock": "^1.4.1",
67+
"@chubbyts/chubbyts-packaging": "^2.0.5",
68+
"@stryker-mutator/core": "^8.0.0",
69+
"@stryker-mutator/jest-runner": "^8.0.0",
70+
"@types/busboy": "^1.5.3",
71+
"@types/jest": "^29.5.11",
72+
"@types/node": "^20.11.2",
73+
"@types/qs": "^6.9.11",
74+
"form-data": "^4.0.0",
75+
"jest": "^29.7.0",
76+
"prettier": "^3.2.5",
77+
"qs": "^6.11.2",
78+
"ts-jest": "^29.1.1",
79+
"typescript": "^5.3.3"
80+
},
81+
"publishConfig": {
82+
"access": "public"
83+
}
84+
}

sonar-project.properties

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
sonar.organization=chubbyts
2+
sonar.projectKey=chubbyts_chubbyts-http-multipart
3+
sonar.projectName=chubbyts-http-multipart
4+
5+
sonar.sources=src
6+
sonar.tests=tests
7+
sonar.language=typescript
8+
sonar.sourceEncoding=UTF-8
9+
sonar.javascript.lcov.reportPaths=coverage/lcov.info

src/multipart-middleware.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { randomBytes } from 'crypto';
2+
import { createWriteStream, mkdirSync } from 'fs';
3+
import type { IncomingHttpHeaders } from 'http';
4+
import { tmpdir } from 'os';
5+
import { PassThrough } from 'stream';
6+
import * as busboy from 'busboy';
7+
import type { Middleware } from '@chubbyts/chubbyts-http-types/dist/middleware';
8+
import type { ServerRequest, Response } from '@chubbyts/chubbyts-http-types/dist/message';
9+
import type { Handler } from '@chubbyts/chubbyts-http-types/dist/handler';
10+
11+
export const createMultipartMiddleware = (): Middleware => {
12+
return async (request: ServerRequest, handler: Handler): Promise<Response> => {
13+
const headers: IncomingHttpHeaders = Object.fromEntries(
14+
Object.entries(request.headers).map(([name, value]) => [name, value.join()]),
15+
);
16+
17+
if (!headers['content-type']?.match(/multipart\/form-data/)) {
18+
return handler(request);
19+
}
20+
21+
const body = await new Promise<PassThrough>((resolve) => {
22+
const temporaryPath = `${tmpdir()}/multipart/${randomBytes(64).toString('hex')}`;
23+
24+
mkdirSync(temporaryPath, { recursive: true });
25+
26+
const newBody = new PassThrough();
27+
28+
const multipartStream = busboy({ headers });
29+
30+
multipartStream.on('file', (name, file, info) => {
31+
const { filename, mimeType } = info;
32+
const filePath = `${temporaryPath}/${filename}`;
33+
34+
file.pipe(createWriteStream(filePath));
35+
36+
const value = `${filePath}; filename=${filename}; mimeType=${mimeType}`;
37+
38+
newBody.write(`${name}=${encodeURIComponent(value)}&`);
39+
});
40+
41+
multipartStream.on('field', (name, value) => {
42+
newBody.write(`${name}=${encodeURIComponent(value)}&`);
43+
});
44+
45+
multipartStream.on('close', () => {
46+
newBody.end();
47+
resolve(newBody);
48+
});
49+
50+
request.body.pipe(multipartStream);
51+
});
52+
53+
return handler({
54+
...request,
55+
headers: { ...request.headers, 'content-type': ['application/x-www-form-urlencoded'] },
56+
body,
57+
});
58+
};
59+
};

stryker.conf.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
3+
"testRunner": "jest",
4+
"coverageAnalysis": "off",
5+
"reporters": [
6+
"clear-text",
7+
"progress",
8+
"dashboard"
9+
]
10+
}

0 commit comments

Comments
 (0)