From eabcedf862202a078c8918c55ca74270ca15d13a Mon Sep 17 00:00:00 2001 From: Josh Hejna Date: Tue, 12 May 2020 19:52:10 -0700 Subject: [PATCH] Typescript "complete" version Can't work due to issue uploading binary files: https://github.com/Azure/azure-functions-host/issues/4475 Without control over headers we only get the UTF-8 parsed version of the body which ruins our data. --- .funcignore | 11 ++ .gitignore | 99 ++++++++++++++++ .nvmrc | 1 + .prettierrc | 21 ++++ .vscode/extensions.json | 5 + .vscode/launch.json | 12 ++ .vscode/settings.json | 9 ++ .vscode/tasks.json | 31 +++++ FlatDav/function.json | 19 +++ FlatDav/index.ts | 251 ++++++++++++++++++++++++++++++++++++++++ FlatDav/sample.dat | 3 + Makefile | 30 +++++ docker-compose.yml | 15 +++ host.json | 12 ++ package-lock.json | 125 ++++++++++++++++++++ package.json | 21 ++++ proxies.json | 4 + test/.gitignore | 1 + test/e2etest.sh | 24 ++++ test/tcp-proxy.sh | 17 +++ test/testfile.bin | 2 + test/testfile.txt | 4 + tsconfig.json | 11 ++ 23 files changed, 728 insertions(+) create mode 100644 .funcignore create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 .prettierrc create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 FlatDav/function.json create mode 100644 FlatDav/index.ts create mode 100644 FlatDav/sample.dat create mode 100644 Makefile create mode 100644 docker-compose.yml create mode 100644 host.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 proxies.json create mode 100644 test/.gitignore create mode 100755 test/e2etest.sh create mode 100755 test/tcp-proxy.sh create mode 100644 test/testfile.bin create mode 100644 test/testfile.txt create mode 100644 tsconfig.json diff --git a/.funcignore b/.funcignore new file mode 100644 index 0000000..82212b3 --- /dev/null +++ b/.funcignore @@ -0,0 +1,11 @@ +*.js.map +*.ts +.git* +.vscode +local.settings.json +test +tsconfig.json +Makefile +docker-compose.yml +.localstack +test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8d91c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,99 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TypeScript output +dist +out + +# Azure Functions artifacts +bin +obj +appsettings.json +local.settings.json + + + +localstack-env +.localstack diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..493319d --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +12.16.3 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e89e7f9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,21 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "trailingComma": "es5", + "useTabs": false, + "vueIndentScriptAndStyle": false, + "filepath": "/home/josh/projects/azure-functions-webdav/HttpTriggerEcho/index.ts", + "parser": "typescript" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..26786f9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e7be044 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Node Functions", + "type": "node", + "request": "attach", + "port": 9229, + "preLaunchTask": "func: host start" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9df9fb0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "azureFunctions.deploySubpath": ".", + "azureFunctions.postDeployTask": "npm install", + "azureFunctions.projectLanguage": "TypeScript", + "azureFunctions.projectRuntime": "~3", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "npm prune", + "python.pythonPath": "/usr/bin/python3" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..60554de --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "func", + "command": "host start", + "problemMatcher": "$func-watch", + "isBackground": true, + "dependsOn": "npm build" + }, + { + "type": "shell", + "label": "npm build", + "command": "npm run build", + "dependsOn": "npm install", + "problemMatcher": "$tsc" + }, + { + "type": "shell", + "label": "npm install", + "command": "npm install" + }, + { + "type": "shell", + "label": "npm prune", + "command": "npm prune --production", + "dependsOn": "npm build", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/FlatDav/function.json b/FlatDav/function.json new file mode 100644 index 0000000..204a524 --- /dev/null +++ b/FlatDav/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "route": "dav/{filename}", + "methods": ["delete", "put", "move", "get"], + "dataType": "binary" + }, + { + "type": "http", + "direction": "out", + "name": "res" + } + ], + "scriptFile": "../dist/FlatDav/index.js" +} diff --git a/FlatDav/index.ts b/FlatDav/index.ts new file mode 100644 index 0000000..f6b5f94 --- /dev/null +++ b/FlatDav/index.ts @@ -0,0 +1,251 @@ +import { AzureFunction, Context, HttpRequest } from "@azure/functions"; +import S3 from "aws-sdk/clients/s3"; +import { ok, err, Result } from "neverthrow"; +import * as AWS from "aws-sdk/global"; +import fs from "fs"; + +const S3_CLIENT = new S3({ + region: "us-west-2", + params: { Bucket: process.env.S3_BUCKET }, + endpoint: process.env.AWS_S3_ENDPOINT || undefined, + sslEnabled: !process.env.AWS_S3_ENDPOINT, + s3ForcePathStyle: true, +}); +const BASIC_AUTH_HEADER = + "Basic " + + Buffer.from( + process.env.HTTP_BASIC_AUTH_USER + + ":" + + process.env.HTTP_BASIC_AUTH_PASS + ).toString("base64"); + +const httpTrigger: AzureFunction = async function ( + context: Context, + req: HttpRequest +): Promise { + context.log("App is processing request:"); + context.log(req); + if ( + !req.headers.authorization || + req.headers.authorization !== BASIC_AUTH_HEADER + ) { + context.res = { + status: 401, + body: "Incorrect HTTP Auth\n", + }; + return; + } + let parseres = parseOp(req); + if (parseres.isErr()) { + switch (parseres.error) { + case ParseFailure.UnsupportedMethod: + context.res = { + status: 405, + body: "Method not allowed." + req.method + "\n", + }; + return; + case ParseFailure.MalformedDestHeader: + context.res = { + status: 400, + body: "Destination header malformed or missing.\n", + }; + return; + } + } + let op = parseres.value; + try { + let s3resp = await performS3Operation(op); + let tfres = AwsToFunctionHttpResp(s3resp); + console.log(tfres); + context.res = tfres; + return; + } catch (e) { + context.log("Error from S3:"); + context.log(e); + context.res = { + status: 500, + body: "Internal error with S3.\n", + }; + return; + } +}; + +enum ParseFailure { + UnsupportedMethod, + MalformedDestHeader, +} + +function parseOp(req: HttpRequest): Result { + // As any is necessary because the ts type is a union, not a string + // Meaning custom verbs aren't type-safe (no MOVE) + switch (req.method as any) { + case "GET": + return ok({ verb: "GET", key: req.params.filename }); + case "DELETE": + return ok({ verb: "DELETE", key: req.params.filename }); + case "PUT": + fs.writeFileSync("PUTFILEBODY", req.body); + fs.writeFileSync("PUTFILERAWBODY", req.rawBody); + return ok({ + verb: "PUT", + key: req.params.filename, + body: req.body, + }); + case "MOVE": + let moveDest = computeDestS3Key( + req.headers.destination, + // any for undocumented attribute + (req as any).originalUrl + ); + if (!moveDest) { + return err(ParseFailure.MalformedDestHeader); + } + return ok({ + verb: "MOVE", + from: req.params.filename, + to: moveDest, + }); + default: + return err(ParseFailure.UnsupportedMethod); + } +} + +function getSuccessCode(op: Operation): number { + switch (op.verb) { + case "GET": + case "DELETE": + case "PUT": + return 200; + case "MOVE": + return 201; + default: + let never: never = op; + return never; + } +} + +interface GetOp { + verb: "GET"; + key: string; +} +interface DeleteOp { + verb: "DELETE"; + key: string; +} +interface PutOp { + verb: "PUT"; + key: string; + body: any; +} +interface MoveOp { + verb: "MOVE"; + from: string; + to: string; +} +type Operation = GetOp | DeleteOp | PutOp | MoveOp; + +const computeDestS3Key = function ( + destHeader: string, + originalUrl: string +): string | null { + if (!destHeader) { + return null; + } + // this method works because the namespace is flat, there are no folder terms to worry about + let split = destHeader.split("/"); + let key: string = split[split.length - 1]; + + // check that our method is sane via the original url: + let origSplit = originalUrl.split("/"); + let origBase = origSplit.slice(0, origSplit.length - 1).join("/"); + if (origBase + "/" + key !== destHeader) { + console.log({ + message: "Error when verifying destination header parsing.", + expected: destHeader, + actual: origBase + key, + }); + return null; + } + return key; +}; + +async function rawS3ResponsePromise( + req: AWS.Request +): Promise { + return new Promise((resolve, _reject) => { + req.on("complete", (response) => { + resolve(response.httpResponse); + }); + req.send(); + }); +} + +interface HttpResponseLike { + statusCode: number; + body: string | Buffer | Uint8Array; + headers?: { [key: string]: string }; +} +interface AzureFuncHttpResponse { + status: number; + headers?: { [key: string]: string }; + body: any; + isRaw?: boolean; +} + +function AwsToFunctionHttpResp(resp: HttpResponseLike): AzureFuncHttpResponse { + return { + status: resp.statusCode, + headers: resp.headers, + body: resp.body, + isRaw: true, + }; +} + +const performS3Operation = async function ( + op: Operation +): Promise { + switch (op.verb) { + case "GET": + return await rawS3ResponsePromise( + S3_CLIENT.getObject({ Key: op.key } as any) + ); + case "DELETE": + return await rawS3ResponsePromise( + S3_CLIENT.deleteObject({ + Key: op.key, + } as any) + ); + case "PUT": + return await rawS3ResponsePromise( + S3_CLIENT.putObject({ + Key: op.key, + Body: op.body, + } as any) + ); + case "MOVE": + // cop-out here: + // instead of passing along errors like with other calls + // (eg a 404 for missing key), we just let the S3 client catch them + // and pass every error off as a 500 in the outer function. + let _copyResult = await S3_CLIENT.copyObject({ + Key: op.to, + CopySource: encodeURIComponent( + process.env.S3_BUCKET + "/" + op.from + ), + } as any).promise(); + let _deleteResult = await S3_CLIENT.deleteObject({ + Key: op.from, + } as any).promise(); + return { + statusCode: 201, + body: "", + }; + default: + let never: never = op; + return never; + } +}; + +export default httpTrigger; + +// TODO: debug move issue diff --git a/FlatDav/sample.dat b/FlatDav/sample.dat new file mode 100644 index 0000000..2e60943 --- /dev/null +++ b/FlatDav/sample.dat @@ -0,0 +1,3 @@ +{ + "name": "Azure" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4a0d04 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +SHELL := bash +.ONESHELL: +.SHELLFLAGS := -eu -o pipefail -c +.DELETE_ON_ERROR: +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules + +build: + npm run build +.PHONY: build + +serve: + watchexec --watch FlatDav --restart $(MAKE) serve_restart +.PHONY: serve + +serve_restart: build + func host start +.PHONY: serve_restart + +localstack_up: + docker-compose up -d +.PHONY: localstack_up + +localstack_down: + docker compose down +.PHONY: localstack_down + +create_bucket: localstack_up + aws --endpoint-url=http://localhost:4572 s3 mb s3://flatdav-test-bucket +.PHONY: create_bucket diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2797164 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.2' +services: + localstack: + image: localstack/localstack:latest + container_name: localstack-flatdav + ports: + - '4563-4599:4563-4599' + - '8055:8080' + environment: + - SERVICES=s3 + - DEBUG=1 + - DATA_DIR=/tmp/localstack/data + volumes: + - './.localstack:/tmp/localstack' + - '/var/run/docker.sock:/var/run/docker.sock' diff --git a/host.json b/host.json new file mode 100644 index 0000000..555e14f --- /dev/null +++ b/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "extensions": { + "http": { + "routePrefix": "" + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[1.*, 2.0.0)" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..be76955 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,125 @@ +{ + "name": "azure-functions-webdav", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@azure/functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-1.2.0.tgz", + "integrity": "sha512-qkaQqTnr56xUnYNkKBM/2wsnf6imAJ3NF6Nbpk691Y6JYliA1YdZngsZsrpHS9tQ9/71MqARl8m50+EmEfLG3g==", + "dev": true + }, + "@types/node": { + "version": "13.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", + "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==", + "dev": true + }, + "aws-sdk": { + "version": "2.673.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.673.0.tgz", + "integrity": "sha512-OoEPqTLmA5+4uSFf/k4ZLb8cEdx+CwlzovqGf6/gKvb8VrUxe5B5/d2RGlGM777Ke9TmuFhJtTIDugpgc2jo/Q==", + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "neverthrow": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-2.2.1.tgz", + "integrity": "sha512-uWMimigYcHbwZLKTZkp5Mo3ZF5O7TG+45YlJ6XievDYEvXF7TVKUjJHZJ0Qt4ClAF5DvmF0j1bUvR7FQDjkidw==" + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c1f9816 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "azure-functions-webdav", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "prestart": "npm run build", + "start": "func start", + "test": "echo \"No tests yet...\"" + }, + "dependencies": { + "aws-sdk": "^2.673.0", + "neverthrow": "^2.2.1" + }, + "devDependencies": { + "@azure/functions": "^1.0.2-beta2", + "@types/node": "^13.13.5", + "typescript": "^3.3.3" + } +} diff --git a/proxies.json b/proxies.json new file mode 100644 index 0000000..b385252 --- /dev/null +++ b/proxies.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/proxies", + "proxies": {} +} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..f0363f4 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +tcpflow diff --git a/test/e2etest.sh b/test/e2etest.sh new file mode 100755 index 0000000..c5bcd39 --- /dev/null +++ b/test/e2etest.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +URL="http://localhost:7071/dav" +API="http://user:pass@localhost:7071/dav" +FILE="testfile.bin" + +curl -D - --upload-file $FILE $API/file + +echo "Compare:" +sha256sum $FILE +echo $(curl $API/file | sha256sum) + +# curl -X DELETE $API/file +# curl $API/file + + +# curl -X MOVE --header "Destination: $URL/file2" $API/file +# echo $(curl $API/file2 | sha256sum) + +sha256sum ../PUTFILEBODY ../PUTFILERAWBODY + +# aws --endpoint-url=http://localhost:4572 s3api copy-object --bucket flatdav-test-bucket --copy-source file --key file2 diff --git a/test/tcp-proxy.sh b/test/tcp-proxy.sh new file mode 100755 index 0000000..4ebed26 --- /dev/null +++ b/test/tcp-proxy.sh @@ -0,0 +1,17 @@ +#!/bin/sh -e + +if [ $# != 3 ] +then + echo "usage: $0 " + exit 0 +fi + +TMP=`mktemp -d` +BACK=$TMP/pipe.back +SENT=$TMP/pipe.sent +RCVD=$TMP/pipe.rcvd +trap 'rm -rf "$TMP"' EXIT +mkfifo -m 0600 "$BACK" "$SENT" "$RCVD" +sed 's/^/ => /' <"$SENT" & +sed 's/^/<= /' <"$RCVD" & +nc -l -p "$1" <"$BACK" | tee "$SENT" | nc "$2" "$3" | tee "$RCVD" >"$BACK" diff --git a/test/testfile.bin b/test/testfile.bin new file mode 100644 index 0000000..c885d11 --- /dev/null +++ b/test/testfile.bin @@ -0,0 +1,2 @@ +�'�����h���R̈���@���|��� �ܝ����N�/����h��)�� t����^Z��:���X���'�G@h�e�Ǎ�q��K�,�0�iy�F��i�d�m�N� Խ��h@Ag}+�Y��pS,�5�����~���ق��*��ɫ��n} +�l��P�Fҧ_���c%�$S���r^CU�ϔ����q7���]�2�q��v���WS��G�1�A\iN>qٔu����P� \ No newline at end of file diff --git a/test/testfile.txt b/test/testfile.txt new file mode 100644 index 0000000..bd1a778 --- /dev/null +++ b/test/testfile.txt @@ -0,0 +1,4 @@ +wcbFbibtxxFeftm9IUVBW8ItqgugUhDPNpikdXQnXbctTrq1oiJkvOgYbS2IInqoFRyZYxX4VQtm +NC61CU4kJ+v7BelMYnyHK8UECI2uXQmuTIP0iH0/GUUymdN+7Ak9wc/Rlwsdp9vjkHO1XLNyjHxg +iBZ6tSG/xeCHL8Gmtx/yPSxEP8e2t54SgIYYMvZVJDdXud1h1oNiLWHuY0c12JRHBlICn+2UW+7p +USlUSCKwoAGSagwF5nU1ctdNB \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8da8faa --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "dist", + "rootDir": ".", + "esModuleInterop": true, + "sourceMap": true, + "strict": true + } +}