Skip to content

Commit

Permalink
Merge pull request #20 from deepkolos/develop
Browse files Browse the repository at this point in the history
Implement binary data support for `writeFile`.
Fix incorrect handling of international characters in paths.
  • Loading branch information
StarLederer authored Oct 28, 2023
2 parents 3032c76 + a56d524 commit 553a2f1
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 34 deletions.
Empty file.
24 changes: 17 additions & 7 deletions __tests__/src/abstraction/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,37 @@ describe('abstraction', () => {
await abstraction.readdir('', true);
expect(lastFetch).toEqual({ url: `http://localhost:${testPort}/?cmd=readdir&withFileTypes=true` });
});

it('should build correct readFile queries', async () => {
await abstraction.readFile('');
expect(lastFetch).toEqual({ url: `http://localhost:${testPort}/?cmd=readFile` });
});

it('should build correct stat queries', async () => {
await abstraction.stat('');
expect(lastFetch).toEqual({ url: `http://localhost:${testPort}/?cmd=stat` });
});

it('should build correct writeFile queries', async () => {
await abstraction.writeFile('file', '');
expect(lastFetch).toEqual({
const expected = {
url: `http://localhost:${testPort}/file?cmd=writeFile`,
init: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: '{"data":""}',
headers: { 'Content-Type': 'text/plain' },
body: '',
},
});
};

await abstraction.writeFile('file', '');
expect(lastFetch).toEqual(expected);

await abstraction.writeFile('file', new Uint8Array());
expect(lastFetch).toEqual(expected);

await abstraction.writeFile('file', new DataView(new ArrayBuffer(0)));
expect(lastFetch).toEqual(expected);
});

it('should build correct rm queries', async () => {
await abstraction.rm('');
expect(lastFetch).toEqual({
Expand Down
88 changes: 75 additions & 13 deletions __tests__/src/server/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fetch from 'node-fetch';
import * as fs from 'fs/promises';
import { resolve } from 'path';

import { UserOptions } from 'src/plugin/Options';
import { SimpleStats } from 'src/common/ApiResponses';
import FsServer from '../../../src/plugin/server';
Expand Down Expand Up @@ -84,6 +83,11 @@ describe('readdir request', () => {
const response = await fetch(`${url}/file?cmd=readdir`);
expect(response.status).toEqual(400);
});

it('should support various UTF-8 characters in path', async () => {
const response = await fetch(`${url}/directory 目录 каталог/file 文件 файл?cmd=readFile`);
expect(response.status).toEqual(200);
});
});

// readFile
Expand Down Expand Up @@ -119,6 +123,11 @@ describe('readFile request', () => {
const response = await fetch(`${url}/directory?cmd=readFile`);
expect(response.status).toEqual(400);
});

it('should support various UTF-8 characters in path', async () => {
const response = await fetch(`${url}/directory 目录 каталог/file 文件 файл?cmd=readFile`);
expect(response.status).toEqual(200);
});
});

// stat
Expand All @@ -142,23 +151,67 @@ describe('stat request', () => {
const response = await fetch(`${url}/notfile?cmd=stat`);
expect(response.status).toEqual(404);
});

it('should support various UTF-8 characters in path', async () => {
const response = await fetch(`${url}/directory 目录 каталог/file 文件 файл?cmd=stat`);
expect(response.status).toEqual(200);
});
});

// writeFile

describe('writeFile request', () => {
it('should write files correctly', async () => {
const response = await fetch(`${url}/newdirectory/newfile?cmd=writeFile`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'new data' }),
});
const newdata = await fs.readFile(resolveWithRoot('newdirectory/newfile'), 'utf-8');
expect(response.status).toEqual(200);
expect(newdata).toEqual('new data');
const textDecoder = new TextDecoder();

it('should write strings to files correctly', async () => {
const testData = 'new data';
const testFilename = 'newfile-string';
const response = await fetch(`${url}/newdirectory/${testFilename}?cmd=writeFile`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: testData,
});
const newdata = await fs.readFile(resolveWithRoot(`newdirectory/${testFilename}`), 'utf-8');
expect(response.status).toEqual(200);
expect(newdata).toEqual(testData);
});

it('should write TypedArrays to files correctly', async () => {
const testData = new Uint8Array([84, 121, 112, 101, 100, 65, 114, 114, 97, 121]);
const testFilename = 'newfile-typedarray';
const response = await fetch(`${url}/newdirectory/${testFilename}?cmd=writeFile`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: textDecoder.decode(testData),
});
const newdata = await fs.readFile(resolveWithRoot(`newdirectory/${testFilename}`));
expect(response.status).toEqual(200);
expect(newdata.buffer).toEqual(testData.buffer);
});

it('should write DataViews to files correctly', async () => {
const testData = new Uint8Array([68, 97, 116, 97, 86, 105, 101, 119]).buffer;
const testFilename = 'newfile-dataview';
const response = await fetch(`${url}/newdirectory/${testFilename}?cmd=writeFile`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: new DataView(testData),
});
const newdata = await fs.readFile(resolveWithRoot(`newdirectory/${testFilename}`));
expect(response.status).toEqual(200);
expect(newdata.buffer).toEqual(testData);
});

it('should support various UTF-8 characters in path', async () => {
const testFilename = 'new file 文件 файл';
const response = await fetch(`${url}/newdirectory/${testFilename}?cmd=writeFile`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: '',
});
const statPromise = fs.stat(resolveWithRoot(`newdirectory/${testFilename}`));
expect(response.status).toEqual(200);
await expect(statPromise).resolves.toBeTruthy();
});
});

Expand Down Expand Up @@ -193,6 +246,15 @@ describe('rm request', () => {
expect(response.status).toEqual(200);
await expect(statPromise).rejects.toBeTruthy();
});

it('should support various UTF-8 characters in path', async () => {
const testFilename = 'auto file 文件 файл';
try { await fs.writeFile(resolve(resolveWithRoot(testFilename)), ''); } catch (err) { /**/ }
const response = await fetch(`${url}/${testFilename}?cmd=rm`, { method: 'DELETE' });
const statPromise = fs.stat(resolveWithRoot(testFilename));
expect(response.status).toEqual(200);
await expect(statPromise).rejects.toBeTruthy();
});
});

export default {};
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

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

10 changes: 5 additions & 5 deletions src/abstraction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { SimpleDirent, SimpleStats } from 'src/common/ApiResponses';

const url = `http://localhost:${activePort}`;

const textDecoder = new TextDecoder();

const fs = {
async readdir(path: string, withFileTypes?: boolean): Promise<SimpleDirent[]> {
const res = await fetch(`${url}/${path}?cmd=readdir${withFileTypes ? '&withFileTypes=true' : ''}`);
Expand Down Expand Up @@ -37,13 +39,11 @@ const fs = {
throw new Error(await res.text());
},

async writeFile(path: string, data: string): Promise<void> {
async writeFile(path: string, data: string | ArrayBufferView | DataView): Promise<void> {
await fetch(`${url}/${path}?cmd=writeFile`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data }),
headers: { 'Content-Type': 'text/plain' },
body: typeof data === 'string' ? data : textDecoder.decode(data),
});
},

Expand Down
4 changes: 3 additions & 1 deletion src/plugin/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class FsServer {
this.rootDir = resolve(this.options.rootDir);

const app = new Koa();
app.use(bodyParser());
app.use(bodyParser({
enableTypes: ['json', 'text'],
}));
app.use(cors());

app.use(get(this.resolvePath));
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/server/requests/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function createRoutes(resolvePath: (path: string) => string): Rou

let path;
try {
path = resolvePath(ctx.path);
path = resolvePath(decodeURIComponent(ctx.path));
} catch (err) {
if (isNodeError(err)) {
ctx.status = 403;
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/server/requests/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function createRoutes(resolvePath: (path: string) => string): Rou

let path;
try {
path = resolvePath(ctx.path);
path = resolvePath(decodeURIComponent(ctx.path));
} catch (err) {
if (isNodeError(err)) {
ctx.status = 403;
Expand Down
5 changes: 2 additions & 3 deletions src/plugin/server/requests/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function createRoutes(resolvePath: (path: string) => string): Rou

let path;
try {
path = resolvePath(ctx.path);
path = resolvePath(decodeURIComponent(ctx.path));
} catch (err) {
if (isNodeError(err)) {
ctx.status = 403;
Expand All @@ -31,8 +31,7 @@ export default function createRoutes(resolvePath: (path: string) => string): Rou
if (ctx.query.cmd) {
if (ctx.query.cmd === 'writeFile') {
const dir = dirname(path);
const bdy = ctx.request.body as { data: '' };
const { data } = bdy;
const data = ctx.request.body as string;

try {
try {
Expand Down

0 comments on commit 553a2f1

Please sign in to comment.