mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: open read-only documents in ONLYOFFICE without the -103 warning #1187
This commit is contained in:
@@ -12,6 +12,14 @@ import { wopiContext } from '@/lib/wopi/request';
|
||||
* cookie on these requests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ONLYOFFICE treats a document the user may not edit as forced into view
|
||||
* mode and warns about it ("Error code: -103" on 9.1), even when the document
|
||||
* was opened through the view action. UserCanReview keeps it off that path,
|
||||
* and with SupportsReviewing false it grants nothing (#1187).
|
||||
*/
|
||||
const VIEW_ONLY_INFO = { UserCanReview: true, SupportsReviewing: false };
|
||||
|
||||
/** GET = CheckFileInfo */
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
@@ -39,6 +47,7 @@ export async function GET(
|
||||
UserCanNotWriteRelative: true,
|
||||
SupportsUpdate: false,
|
||||
SupportsLocks: false,
|
||||
...VIEW_ONLY_INFO,
|
||||
Version: payload.fileId,
|
||||
PostMessageOrigin: payload.postMessageOrigin || payload.origin,
|
||||
});
|
||||
@@ -59,6 +68,7 @@ export async function GET(
|
||||
UserCanNotWriteRelative: true,
|
||||
SupportsUpdate: true,
|
||||
SupportsLocks: false,
|
||||
...(auth.payload.canWrite ? {} : VIEW_ONLY_INFO),
|
||||
LastModifiedTime: node.modified,
|
||||
Version: node.blobId,
|
||||
PostMessageOrigin: auth.payload.postMessageOrigin || auth.payload.origin,
|
||||
|
||||
@@ -158,6 +158,9 @@ describe('WOPI calls for an attachment token', () => {
|
||||
UserCanWrite: false,
|
||||
ReadOnly: true,
|
||||
UserCanNotWriteRelative: true,
|
||||
// Keeps ONLYOFFICE from warning that it was forced into view mode (#1187).
|
||||
UserCanReview: true,
|
||||
SupportsReviewing: false,
|
||||
Version: 'Gblob1',
|
||||
PostMessageOrigin: ORIGIN,
|
||||
});
|
||||
|
||||
@@ -140,7 +140,18 @@ describe('WOPI on a file shared with the user (#1094)', () => {
|
||||
|
||||
const info = await checkFileInfo(request(`/api/wopi/files/${documentId}?${token}`), params(documentId));
|
||||
expect(info.status).toBe(200);
|
||||
expect(await info.json()).toMatchObject({ BaseFileName: 'new-file.docx', UserCanWrite: true, Version: 'Gshared' });
|
||||
const body = await info.json();
|
||||
expect(body).toMatchObject({ BaseFileName: 'new-file.docx', UserCanWrite: true, Version: 'Gshared' });
|
||||
expect(body).not.toHaveProperty('UserCanReview');
|
||||
});
|
||||
|
||||
it('describes a file shared read-only so ONLYOFFICE opens it without a warning (#1187)', async () => {
|
||||
node.myRights.mayModifyContent = false;
|
||||
const { data, documentId, token } = await openShared();
|
||||
expect(data.readOnly).toBe(true);
|
||||
|
||||
const info = await checkFileInfo(request(`/api/wopi/files/${documentId}?${token}`), params(documentId));
|
||||
expect(await info.json()).toMatchObject({ UserCanWrite: false, UserCanReview: true, SupportsReviewing: false });
|
||||
});
|
||||
|
||||
it('GetFile reads the blob through the owner account', async () => {
|
||||
|
||||
@@ -38,6 +38,23 @@ describe('parseWopiDiscovery', () => {
|
||||
expect(actions.edit.docx).toContain('/cool.html');
|
||||
});
|
||||
|
||||
it('unescapes the urlsrc, so ONLYOFFICE placeholders are filled in or dropped (#1187)', () => {
|
||||
const xml =
|
||||
'<wopi-discovery><net-zone name="external-http"><app name="Excel">' +
|
||||
'<action name="view" ext="xlsx" urlsrc="https://oo.example/hosting/wopi/cell/view?' +
|
||||
'<rs=DC_LLCC&><dchat=DISABLE_CHAT&><ui=UI_LLCC&><wopisrc=WOPI_SOURCE&>&"/>' +
|
||||
'<action name="edit" ext="xlsx" default="true" requires="locks,update" urlsrc="https://oo.example/hosting/wopi/cell/edit?' +
|
||||
'<ui=UI_LLCC&>&"/>' +
|
||||
'</app></net-zone></wopi-discovery>';
|
||||
const actions = parseWopiDiscovery(xml);
|
||||
expect(actions.view.xlsx).toBe(
|
||||
'https://oo.example/hosting/wopi/cell/view?<rs=DC_LLCC&><dchat=DISABLE_CHAT&><ui=UI_LLCC&><wopisrc=WOPI_SOURCE&>&',
|
||||
);
|
||||
expect(buildWopiActionUrl(actions.view.xlsx, 'https://mail.example/api/wopi/files/f1', 'de')).toBe(
|
||||
'https://oo.example/hosting/wopi/cell/view?rs=de&ui=de&&WOPISrc=https%3A%2F%2Fmail.example%2Fapi%2Fwopi%2Ffiles%2Ff1',
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores actions without an extension and tolerates junk', () => {
|
||||
expect(parseWopiDiscovery('<notxml>')).toEqual({ edit: {}, view: {} });
|
||||
expect(parseWopiDiscovery('')).toEqual({ edit: {}, view: {} });
|
||||
|
||||
+12
-1
@@ -68,13 +68,24 @@ function discoveryUrlFor(clientUrl: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
const XML_ENTITIES: Record<string, string> = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'" };
|
||||
|
||||
/**
|
||||
* Attribute values are XML-escaped: ONLYOFFICE writes its urlsrc placeholders
|
||||
* as `<ui=UI_LLCC&>`, which buildWopiActionUrl only recognises once
|
||||
* they read `<ui=UI_LLCC&>` again (#1187).
|
||||
*/
|
||||
function decodeXmlEntities(value: string): string {
|
||||
return value.replace(/&(lt|gt|amp|quot|apos);/g, (_match, name: string) => XML_ENTITIES[name]);
|
||||
}
|
||||
|
||||
export function parseWopiDiscovery(xml: string): WopiActions {
|
||||
const actions: WopiActions = { edit: {}, view: {} };
|
||||
const tags = xml.match(/<action\b[^>]*\/?>/gi) || [];
|
||||
for (const tag of tags) {
|
||||
const attr = (name: string): string => {
|
||||
const m = tag.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, 'i'));
|
||||
return m?.[1] ?? '';
|
||||
return decodeXmlEntities(m?.[1] ?? '');
|
||||
};
|
||||
const name = attr('name').toLowerCase();
|
||||
const ext = attr('ext').toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user