Skip to content

Commit

Permalink
Implement a permissions API
Browse files Browse the repository at this point in the history
  • Loading branch information
timvandermeij committed Sep 2, 2018
1 parent 7c22b22 commit b4401d9
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 4 deletions.
48 changes: 45 additions & 3 deletions src/core/obj.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
12 changes: 12 additions & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,14 @@ 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.
*/
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
Expand Down Expand Up @@ -2142,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) => {
Expand Down
13 changes: 13 additions & 0 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1014,6 +1026,7 @@ export {
NativeImageDecoding,
PasswordException,
PasswordResponses,
PermissionFlag,
StreamType,
TextRenderingMode,
UnexpectedResponseException,
Expand Down
3 changes: 3 additions & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,6 @@
!transparent.pdf
!xobject-image.pdf
!ccitt_EndOfBlock_false.pdf
!issue9972-1.pdf
!issue9972-2.pdf
!issue9972-3.pdf
Binary file added test/pdfs/issue9972-1.pdf
Binary file not shown.
Binary file added test/pdfs/issue9972-2.pdf
Binary file not shown.
Binary file added test/pdfs/issue9972-3.pdf
Binary file not shown.
60 changes: 59 additions & 1 deletion test/unit/api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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, }) {
Expand Down

0 comments on commit b4401d9

Please sign in to comment.