Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/fuse-daemon/internal/filesystem/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package filesystem

import (
"context"
"encoding/base64"
"strconv"
"log/slog"

Expand Down Expand Up @@ -64,7 +65,7 @@ func (f *InternxtFile) Read(dest []byte, off int64) (fuse.ReadResult, fuse.Statu
func (f *InternxtFile) Write(data []byte, off int64) (uint32, fuse.Status) {
f.logger.Debug("Received Write call", "path", f.path, "offset", off, "length", len(data))
headers := map[string]string{
"X-Path": f.path,
"X-Path-B64": base64.StdEncoding.EncodeToString([]byte(f.path)),
"X-Offset": strconv.FormatInt(off, 10),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('writeController', () => {
});

it('should return errno EINVAL when payload is invalid', async () => {
req.header.calledWith('X-Path').mockReturnValue('/some/file.txt');
req.header.calledWith('X-Path-B64').mockReturnValue(Buffer.from('/some/file.txt', 'utf8').toString('base64'));
req.header.calledWith('X-Offset').mockReturnValue('wrong');
req.body = Buffer.from('hello');

Expand All @@ -33,7 +33,7 @@ describe('writeController', () => {
});

it('should return errno 0 and written bytes when write succeeds', async () => {
req.header.calledWith('X-Path').mockReturnValue('/some/file.txt');
req.header.calledWith('X-Path-B64').mockReturnValue(Buffer.from('/some/file.txt', 'utf8').toString('base64'));
req.header.calledWith('X-Offset').mockReturnValue('0');
req.body = Buffer.from('hello');
writeMock.mockResolvedValue({ data: 5 });
Expand All @@ -46,7 +46,7 @@ describe('writeController', () => {
});

it('should return errno EIO when write fails', async () => {
req.header.calledWith('X-Path').mockReturnValue('/some/file.txt');
req.header.calledWith('X-Path-B64').mockReturnValue(Buffer.from('/some/file.txt', 'utf8').toString('base64'));
req.header.calledWith('X-Offset').mockReturnValue('0');
req.body = Buffer.from('hello');
writeMock.mockResolvedValue({ error: new FuseError(FuseCodes.EIO, 'io error') });
Expand All @@ -56,4 +56,39 @@ describe('writeController', () => {
expect(res.set).toHaveBeenCalledWith('X-Errno', String(FuseCodes.EIO));
expect(res.send).toHaveBeenCalledWith(Buffer.alloc(0));
});

it('should decode UTF-8 path from base64 header before write', async () => {
const encodedPath = Buffer.from('/тестовое изображение.jpeg', 'utf8').toString('base64');
req.header.calledWith('X-Path-B64').mockReturnValue(encodedPath);
req.header.calledWith('X-Offset').mockReturnValue('0');
req.body = Buffer.from('hello');
writeMock.mockResolvedValue({ data: 5 });

await writeController(req, res, container);

expect(writeMock).toHaveBeenCalledWith({
path: '/тестовое изображение.jpeg',
content: Buffer.from('hello'),
offset: 0,
container,
});
});

it('should decode base64 path from header when filename contains newline', async () => {
const newlinePath = '/nombre\narchivo.txt';
const encodedPath = Buffer.from(newlinePath, 'utf8').toString('base64');
req.header.calledWith('X-Path-B64').mockReturnValue(encodedPath);
req.header.calledWith('X-Offset').mockReturnValue('0');
req.body = Buffer.from('hello');
writeMock.mockResolvedValue({ data: 5 });

await writeController(req, res, container);

expect(writeMock).toHaveBeenCalledWith({
path: newlinePath,
content: Buffer.from('hello'),
offset: 0,
container,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { write } from '../../services/operations/write.service';
import { ensureLeadingSlash } from '../ensure-leading-slash';

export async function writeController(req: Request, res: Response, container: Container) {
const rawPath = req.header('X-Path');
const rawBase64Path = req.header('X-Path-B64');
const rawOffset = req.header('X-Offset');
const body = req.body;
const content = Buffer.isBuffer(body) ? body : undefined;
const offset = rawOffset ? Number.parseInt(rawOffset, 10) : Number.NaN;

if (typeof rawPath !== 'string' || Number.isNaN(offset) || !content) {
if (typeof rawBase64Path !== 'string' || Number.isNaN(offset) || !content) {
logger.error({
msg: '[FUSE DAEMON] Write: missing required fields',
headers: req.headers,
Expand All @@ -22,8 +22,7 @@ export async function writeController(req: Request, res: Response, container: Co
res.send(Buffer.alloc(0));
return;
}

const normalizedPath = ensureLeadingSlash(rawPath);
const normalizedPath = ensureLeadingSlash(Buffer.from(rawBase64Path, 'base64').toString('utf8'));

logger.debug({
msg: '[FUSE DAEMON] Write signal received',
Expand Down
Loading