Implement a permissions API

This commit is contained in:
Tim van der Meij 2018-08-26 21:37:05 +02:00
parent 4874e9ace0
commit 959ed3705b
No known key found for this signature in database
GPG Key ID: 8C3FD2925A5F2762
9 changed files with 137 additions and 4 deletions

View File

@ -15,9 +15,9 @@
import {
bytesToString, createPromiseCapability, createValidAbsoluteUrl, FormatError,
info, InvalidPDFException, isBool, isString, MissingDataException, shadow,
stringToPDFString, stringToUTF8String, toRomanNumerals, unreachable, warn,
XRefParseException
info, InvalidPDFException, isBool, isNum, isString, MissingDataException,
PermissionFlag, shadow, stringToPDFString, stringToUTF8String,
toRomanNumerals, unreachable, warn, XRefParseException
} from '../shared/util';
import {
Dict, isCmd, isDict, isName, isRef, isRefsEqual, isStream, Ref, RefSet,
@ -177,6 +177,48 @@ class Catalog {
return (root.items.length > 0 ? root.items : null);
}
get permissions() {
let permissions = null;
try {
permissions = this._readPermissions();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn('Unable to read permissions.');
}
return shadow(this, 'permissions', permissions);
}
/**
* @private
*/
_readPermissions() {
const encrypt = this.xref.trailer.get('Encrypt');
if (!isDict(encrypt)) {
return null;
}
let flags = encrypt.get('P');
if (!isNum(flags)) {
return null;
}
// PDF integer objects are represented internally in signed 2's complement
// form. Therefore, convert the signed decimal integer to a signed 2's
// complement binary integer so we can use regular bitwise operations on it.
flags += 2 ** 32;
const permissions = [];
for (const key in PermissionFlag) {
const value = PermissionFlag[key];
if (flags & value) {
permissions.push(value);
}
}
return permissions;
}
get numPages() {
const obj = this.toplevelPagesDict.get('Count');
if (!Number.isInteger(obj)) {

View File

@ -703,6 +703,10 @@ var WorkerMessageHandler = {
}
);
handler.on('GetPermissions', function(data) {
return pdfManager.ensureCatalog('permissions');
});
handler.on('GetMetadata',
function wphSetupGetMetadata(data) {
return Promise.all([pdfManager.ensureDoc('documentInfo'),

View File

@ -691,6 +691,15 @@ class PDFDocumentProxy {
return this._transport.getOutline();
}
/**
* @return {Promise} A promise that is resolved with an {Array} that contains
* the permission flags for the PDF document, or `null` when
* no permissions are present in the PDF file.
*/
getPermissions() {
return this._transport.getPermissions();
}
/**
* @return {Promise} A promise that is resolved with an {Object} that has
* `info` and `metadata` properties. `info` is an {Object} filled with
@ -2141,6 +2150,10 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise('GetOutline', null);
}
getPermissions() {
return this.messageHandler.sendWithPromise('GetPermissions', null);
}
getMetadata() {
return this.messageHandler.sendWithPromise('GetMetadata', null).
then((results) => {

View File

@ -25,6 +25,18 @@ const NativeImageDecoding = {
DISPLAY: 'display',
};
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
const PermissionFlag = {
PRINT: 0x04,
MODIFY_CONTENTS: 0x08,
COPY: 0x10,
MODIFY_ANNOTATIONS: 0x20,
FILL_INTERACTIVE_FORMS: 0x100,
COPY_FOR_ACCESSIBILITY: 0x200,
ASSEMBLE: 0x400,
PRINT_HIGH_QUALITY: 0x800,
};
var TextRenderingMode = {
FILL: 0,
STROKE: 1,
@ -1014,6 +1026,7 @@ export {
NativeImageDecoding,
PasswordException,
PasswordResponses,
PermissionFlag,
StreamType,
TextRenderingMode,
UnexpectedResponseException,

View File

@ -325,3 +325,6 @@
!transparent.pdf
!xobject-image.pdf
!ccitt_EndOfBlock_false.pdf
!issue9972-1.pdf
!issue9972-2.pdf
!issue9972-3.pdf

BIN
test/pdfs/issue9972-1.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue9972-2.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue9972-3.pdf Normal file

Binary file not shown.

View File

@ -18,7 +18,8 @@ import {
} from './test_utils';
import {
createPromiseCapability, FontType, InvalidPDFException, MissingPDFException,
OPS, PasswordException, PasswordResponses, StreamType, stringToBytes
OPS, PasswordException, PasswordResponses, PermissionFlag, StreamType,
stringToBytes
} from '../../src/shared/util';
import {
DOMCanvasFactory, RenderingCancelledException, StatTimer
@ -822,6 +823,63 @@ describe('api', function() {
done.fail(reason);
});
});
it('gets non-existent permissions', function(done) {
doc.getPermissions().then(function(permissions) {
expect(permissions).toEqual(null);
done();
}).catch(function(reason) {
done.fail(reason);
});
});
it('gets permissions', function (done) {
// Editing not allowed.
const loadingTask0 =
getDocument(buildGetDocumentParams('issue9972-1.pdf'));
const promise0 = loadingTask0.promise.then(function(pdfDocument) {
return pdfDocument.getPermissions();
});
// Printing not allowed.
const loadingTask1 =
getDocument(buildGetDocumentParams('issue9972-2.pdf'));
const promise1 = loadingTask1.promise.then(function(pdfDocument) {
return pdfDocument.getPermissions();
});
// Copying not allowed.
const loadingTask2 =
getDocument(buildGetDocumentParams('issue9972-3.pdf'));
const promise2 = loadingTask2.promise.then(function(pdfDocument) {
return pdfDocument.getPermissions();
});
const totalPermissionCount = Object.keys(PermissionFlag).length;
Promise.all([promise0, promise1, promise2]).then(function(permissions) {
expect(permissions[0].length).toEqual(totalPermissionCount - 1);
expect(permissions[0].includes(PermissionFlag.MODIFY_CONTENTS))
.toBeFalsy();
expect(permissions[1].length).toEqual(totalPermissionCount - 2);
expect(permissions[1].includes(PermissionFlag.PRINT)).toBeFalsy();
expect(permissions[1].includes(PermissionFlag.PRINT_HIGH_QUALITY))
.toBeFalsy();
expect(permissions[2].length).toEqual(totalPermissionCount - 1);
expect(permissions[2].includes(PermissionFlag.COPY)).toBeFalsy();
Promise.all([
loadingTask0.destroy(),
loadingTask1.destroy(),
loadingTask2.destroy(),
]).then(done);
}).catch(function(reason) {
done.fail(reason);
});
});
it('gets metadata', function(done) {
var promise = doc.getMetadata();
promise.then(function({ info, metadata, contentDispositionFilename, }) {