mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-24 15:07:17 +00:00
fix(share): accept tilde-rooted paths like the FS routes do
The SDK resolves relative paths to ~/..., but the share routes never expanded the tilde — a ~-prefixed string was read as a uid and every relative-path call 404'd. Item parsing now treats ~ as path-shaped and expands it to the actor's home with the same helper the legacy FS routes use, on share, revoke, and the shares listing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b76ca7a13f
commit
68f34f0f0c
@@ -158,6 +158,39 @@ describe('share endpoints over HTTP', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts tilde-rooted paths the way the FS routes do', async () => {
|
||||
const owner = env.users.user;
|
||||
const recipient = env.users.other;
|
||||
const file = await makeFile(owner);
|
||||
const tildePath = `~/${file.path.split('/').pop()}`;
|
||||
|
||||
const shareRes = await post('/share', owner.token, {
|
||||
recipients: [recipient.username],
|
||||
items: [tildePath],
|
||||
mode: 'read',
|
||||
});
|
||||
expect(shareRes.status).toBe(200);
|
||||
expect(await shareRes.json()).toMatchObject({ status: 'success' });
|
||||
|
||||
const listRes = await get('/share/shares', owner.token, {
|
||||
path: tildePath,
|
||||
});
|
||||
expect(listRes.status).toBe(200);
|
||||
const listed = (await listRes.json()) as {
|
||||
items: Array<{ holder: string }>;
|
||||
};
|
||||
expect(
|
||||
listed.items.some((i) => i.holder === recipient.username),
|
||||
).toBe(true);
|
||||
|
||||
const revokeRes = await post('/share/revoke', owner.token, {
|
||||
recipients: [recipient.username],
|
||||
items: [tildePath],
|
||||
});
|
||||
expect(revokeRes.status).toBe(200);
|
||||
expect(await revokeRes.json()).toMatchObject({ revoked: 1 });
|
||||
});
|
||||
|
||||
it('reports per-pair outcomes when only some recipients resolve', async () => {
|
||||
const owner = env.users.user;
|
||||
const file = await makeFile(owner);
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
ShareRecipient,
|
||||
ShareTarget,
|
||||
} from '../../services/share/ShareService.js';
|
||||
import { expandTildePath } from '../../services/fs/resolveNode.js';
|
||||
import { runWithConcurrencyLimitSettled } from '../../util/concurrency.js';
|
||||
import { normalizeLimit } from '../../util/pagination.js';
|
||||
import { PuterController } from '../types.js';
|
||||
@@ -101,7 +102,7 @@ export class ShareController extends PuterController {
|
||||
const actor = this.#requireActor(req);
|
||||
const body = this.#body(req);
|
||||
const recipients = this.#recipients(body);
|
||||
const items = this.#items(body);
|
||||
const items = this.#items(body, actor);
|
||||
const mode = typeof body.mode === 'string' ? body.mode : 'read';
|
||||
|
||||
// Every (recipient, item) pair is a distinct (holder, entry) key, so
|
||||
@@ -175,7 +176,7 @@ export class ShareController extends PuterController {
|
||||
const actor = this.#requireActor(req);
|
||||
const body = this.#body(req);
|
||||
const recipients = this.#recipients(body);
|
||||
const items = this.#items(body);
|
||||
const items = this.#items(body, actor);
|
||||
|
||||
const pairs = recipients.flatMap((recipient) =>
|
||||
items.map((item) => ({ recipient, item })),
|
||||
@@ -262,7 +263,8 @@ export class ShareController extends PuterController {
|
||||
const query = this.#query(req);
|
||||
const target: ShareTarget = {};
|
||||
if (typeof query.uid === 'string') target.uid = query.uid;
|
||||
if (typeof query.path === 'string') target.path = query.path;
|
||||
if (typeof query.path === 'string')
|
||||
target.path = expandTildePath(query.path, actor.user?.username);
|
||||
if (!target.uid && !target.path) {
|
||||
throw new HttpError(400, 'one of `uid` or `path` is required', {
|
||||
legacyCode: 'bad_request',
|
||||
@@ -358,7 +360,8 @@ export class ShareController extends PuterController {
|
||||
return out;
|
||||
}
|
||||
|
||||
#items(body: Record<string, unknown>): ShareTarget[] {
|
||||
#items(body: Record<string, unknown>, actor: Actor): ShareTarget[] {
|
||||
const username = actor.user?.username;
|
||||
const raw = body.items ?? body.item ?? body.path ?? body.uid;
|
||||
const list = Array.isArray(raw) ? raw : [raw];
|
||||
const out: ShareTarget[] = [];
|
||||
@@ -366,14 +369,21 @@ export class ShareController extends PuterController {
|
||||
if (typeof entry === 'string') {
|
||||
const value = entry.trim();
|
||||
if (!value) continue;
|
||||
// Tilde-rooted strings are paths, as the legacy FS routes treat them.
|
||||
const isPath = value.startsWith('/') || value.startsWith('~');
|
||||
out.push(
|
||||
value.startsWith('/') ? { path: value } : { uid: value },
|
||||
isPath
|
||||
? { path: expandTildePath(value, username) }
|
||||
: { uid: value },
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (entry && typeof entry === 'object') {
|
||||
const item = entry as Record<string, unknown>;
|
||||
const path = typeof item.path === 'string' ? item.path : '';
|
||||
const path =
|
||||
typeof item.path === 'string'
|
||||
? expandTildePath(item.path, username)
|
||||
: '';
|
||||
const uid = typeof item.uid === 'string' ? item.uid : '';
|
||||
if (path || uid) out.push(path ? { path } : { uid });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user