Skip to content

Export file data in GET /whfs/object as a public resource - #196

Open
RichardHoekstra wants to merge 1 commit into
WebHare:mainfrom
RichardHoekstra:fix/whfs-api-file-data-export
Open

RichardHoekstra wants to merge 1 commit into
WebHare:mainfrom
RichardHoekstra:fix/whfs-api-file-data-export

Conversation

@RichardHoekstra

Copy link
Copy Markdown
Contributor

Problem

GET /whfs/object?instances=* returns the file contents in the platform:virtual.objectdata instance as a serialised internal ResourceDescriptor, with fields such as _resource and _dbLoc. An API client cannot use that to get the bytes. Every other instance in the same response, and buildExportMetadata in whfs/src/export.ts, go through the export path with exportOptions and produce ExportedResource values (file.base64 or file.fetch).

Cause

whtree/modules/platform/openapi/api/whfs.ts, in getInstances:

data: await getVirtualObjectData(obj, { includeData: true })

With includeData: true, getVirtualObjectData puts the live ResourceDescriptor in data, and exportOptions is not passed in.

Fix

  • Call getVirtualObjectData(obj, { includeData: false }, exportOptions), as buildExportMetadata already does.
  • For a file whose type has data, add data: await file.data?.export(exportOptions) ?? null.
  • Keep data: null for an empty file, so clients can still tell "empty file" apart from "object without data". Folders and richdocuments get no data key.

There is no new endpoint and no schema change. One side effect: exportOptions now reaches getVirtualObjectData, so link targets in this instance are exported in the same way as in the other instances.

Test

New system.whapi.test_whapi_whfs_data. It creates files and reads them through the real OpenAPI client with a first-party token:

  • a UTF-8 text file (café 🌍), a PNG and an empty file: the bytes match exactly, through base64 or by following fetch, and no _resource/_dbLoc fields remain;
  • a folder and a richdocument: no data key.

The test failed on the internal descriptor fields before the fix and passed after it, on a dev container built from main 601237c9. The patch applies cleanly to main f63eb69b. We found this while reading WHFS files through the API from an external tool.

The virtual.objectdata instance returned the live ResourceDescriptor,
including internal fields, instead of an exported resource. Use the
export path with the request's exportOptions, as the other instances
and buildExportMetadata already do.
@RichardHoekstra
RichardHoekstra requested a review from a team November 6, 2026 14:11
@unilynx

unilynx commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

GET /whfs/object?instances=* returns the file contents in the platform:virtual.objectdata instance as a serialised internal ResourceDescriptor

that's broken, and probably caused by WHFS Export leaking through

But WHFS Export supplies data in a separate file - I wonder if we shouldn't we have a GET /whfs/data instead that delivers the body immediately, without a subfetch. Or offer both routes.

Similarly there could be a PUT /whfs/data that would allow uploads - up to a certain size (very large uploads up to an administrator specified limit (5MB? 50MB?) should be chunked using @webhare/upload).

This would also help to move some current users off webdav and to the webhare api (they're now using PUT and GET on the Webdav endpoints using simple curl)

@webharerob

Copy link
Copy Markdown
Member

I think we should fix getVirtualObjectData not returning an ExportedResource. Keeping the data there would be good for atomic updates, so I don't think we should remove it.

I'm not sure if the the POST/GET /whfs/data would be necessary. It can only work for simple/small files (because of the POST size restrictions), so it would probably need chunking upload code as fallback anyway - negating the 'simple implementation' argument .

@unilynx

unilynx commented Nov 9, 2026

Copy link
Copy Markdown
Member

I think we should fix getVirtualObjectData not returning an ExportedResource. Keeping the data there would be good for atomic updates, so I don't think we should remove it.

I'm not sure if the the POST/GET /whfs/data would be necessary. It can only work for simple/small files (because of the POST size restrictions), so it would probably need chunking upload code as fallback anyway - negating the 'simple implementation' argument .

it might be hard to do @webhare/upload-protocol uploads from curl, and submitting it base64 inside a hand-crafted JSON request will hit limits even faster

But it looks like nginx should be able to do upload validation in a js_content handler, so we may be able to increase limits for specific URLs for specific authenticated users.
(stored as https://docs.google.com/document/d/1y1jdmfuyhbBUsGur2myCgs18h7Kof9PMwqDq_G2QBbs/edit?tab=t.0)

So /whfs/data endpoints may still be nice to have

OTOH I agree that atomic updates are useful - specifically so you can change file type together with the new data. So let's do both eventually but fix /whfs/object for now

const actor = test.getUser("sysop").wrdId;
const hsSchema = await loadlib("mod::wrd/lib/api.whlib").openWRDSchema("webhare_testsuite:testschema");
const rights = await loadlib("mod::wrd/lib/auth.whlib").GetWRDAuthUserAPI(hsSchema);
const owner = await rights.GetUser(actor) as HSVMObject;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you merge this test into test_whapi_whfs to reuse all the (user) setup already being done there ?

const rights = await loadlib("mod::wrd/lib/auth.whlib").GetWRDAuthUserAPI(hsSchema);
const owner = await rights.GetUser(actor) as HSVMObject;
const root = await test.getTestSiteJSTemp();
await runInSeparateWork(() => owner.UpdateGrant("grant", "system:fs_browse", root.id, owner, {}));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary? owner = sysop = has system:sysop granted above. is this working around another bug?

@unilynx

unilynx commented Nov 11, 2026

Copy link
Copy Markdown
Member

I think we should fix getVirtualObjectData not returning an ExportedResource. Keeping the data there would be good for atomic updates, so I don't think we should remove it.

@RichardHoekstra I agree with this - getVirtualObjectData has only 2 callsites, and only one sets includeData: true (and doesn't pass on exportOptions, which getVirtualObjectData seems to support) so why not invoke export on (obj as WHFSFile).data in getVirtualObjectData?

This branch was successfully deployed

1 active deployment
external-ci — 35ccd658 Deployed Nov 6, 2026 by RichardHoekstra
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants