1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-21 23:58:07 +02:00

Merge branch 'master' of github.com:mozilla/pdf.js into fallback-ui

Conflicts:
	extensions/firefox/components/PdfStreamConverter.js
This commit is contained in:
Brendan Dahl 2012-05-24 14:01:10 -07:00
commit 2da12930a6
43 changed files with 1052 additions and 179 deletions

View file

@ -7,20 +7,46 @@
* is used, which means it must follow the same origin rules that any XHR does
* e.g. No cross domain requests without CORS.
*
* @param {string|TypedAray} source Either a url to a PDF is located or a
* typed array (Uint8Array) already populated with data.
* @param {Object} headers An object containing the http headers like this:
* { Authorization: "BASIC XXX" }.
* @param {string|TypedAray|object} source Can be an url to where a PDF is
* located, a typed array (Uint8Array) already populated with data or
* and parameter object with the following possible fields:
* - url - The URL of the PDF.
* - data - A typed array with PDF data.
* - httpHeaders - Basic authentication headers.
* - password - For decrypting password-protected PDFs.
*
* @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
*/
PDFJS.getDocument = function getDocument(source, headers) {
PDFJS.getDocument = function getDocument(source) {
var url, data, headers, password, parameters = {};
if (typeof source === 'string') {
url = source;
} else if (isArrayBuffer(source)) {
data = source;
} else if (typeof source === 'object') {
url = source.url;
data = source.data;
headers = source.httpHeaders;
password = source.password;
parameters.password = password || null;
if (!url && !data)
error('Invalid parameter array, need either .data or .url');
} else {
error('Invalid parameter in getDocument, need either Uint8Array, ' +
'string or a parameter object');
}
var promise = new PDFJS.Promise();
var transport = new WorkerTransport(promise);
if (typeof source === 'string') {
if (data) {
// assuming the data is array, instantiating directly from it
transport.sendData(data, parameters);
} else if (url) {
// fetch url
PDFJS.getPdf(
{
url: source,
url: url,
progress: function getPDFProgress(evt) {
if (evt.lengthComputable)
promise.progress({
@ -35,12 +61,10 @@ PDFJS.getDocument = function getDocument(source, headers) {
headers: headers
},
function getPDFLoad(data) {
transport.sendData(data);
transport.sendData(data, parameters);
});
} else {
// assuming the source is array, instantiating directly from it
transport.sendData(source);
}
return promise;
};
@ -122,6 +146,11 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
});
return promise;
},
isEncrypted: function PDFDocumentProxy_isEncrypted() {
var promise = new PDFJS.Promise();
promise.resolve(this.pdfInfo.encrypted);
return promise;
},
destroy: function PDFDocumentProxy_destroy() {
this.transport.destroy();
}
@ -467,6 +496,14 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.workerReadyPromise.resolve(pdfDocument);
}, this);
messageHandler.on('NeedPassword', function transportPassword(data) {
this.workerReadyPromise.reject(data.exception.message, data.exception);
}, this);
messageHandler.on('IncorrectPassword', function transportBadPass(data) {
this.workerReadyPromise.reject(data.exception.message, data.exception);
}, this);
messageHandler.on('GetPage', function transportPage(data) {
var pageInfo = data.pageInfo;
var page = new PDFPageProxy(pageInfo, this);
@ -569,8 +606,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
});
},
sendData: function WorkerTransport_sendData(data) {
this.messageHandler.send('GetDocRequest', data);
sendData: function WorkerTransport_sendData(data, params) {
this.messageHandler.send('GetDocRequest', {data: data, params: params});
},
getPage: function WorkerTransport_getPage(pageNumber, promise) {

View file

@ -320,19 +320,19 @@ var Page = (function PageClosure() {
* `PDFDocument` objects on the main thread created.
*/
var PDFDocument = (function PDFDocumentClosure() {
function PDFDocument(arg, callback) {
function PDFDocument(arg, password) {
if (isStream(arg))
init.call(this, arg);
init.call(this, arg, password);
else if (isArrayBuffer(arg))
init.call(this, new Stream(arg));
init.call(this, new Stream(arg), password);
else
error('PDFDocument: Unknown argument type');
}
function init(stream) {
function init(stream, password) {
assertWellFormed(stream.length > 0, 'stream must have data');
this.stream = stream;
this.setup();
this.setup(password);
this.acroForm = this.catalog.catDict.get('AcroForm');
}
@ -423,11 +423,12 @@ var PDFDocument = (function PDFDocumentClosure() {
}
// May not be a PDF file, continue anyway.
},
setup: function PDFDocument_setup(ownerPassword, userPassword) {
setup: function PDFDocument_setup(password) {
this.checkHeader();
var xref = new XRef(this.stream,
this.startXRef,
this.mainXRefEntriesOffset);
this.mainXRefEntriesOffset,
password);
this.xref = xref;
this.catalog = new Catalog(xref);
},

View file

@ -419,13 +419,14 @@ var CipherTransform = (function CipherTransformClosure() {
})();
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) {
var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
var hashData = new Uint8Array(100), i = 0, j, n;
if (password) {
n = Math.min(32, password.length);
@ -462,9 +463,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var cipher, checkData;
if (revision >= 3) {
// padded password in hashData, we can use this array for user
// password check
i = 32;
for (i = 0; i < 32; ++i)
hashData[i] = defaultPasswordBytes[i];
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
@ -477,16 +477,53 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
}
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
return null;
}
} else {
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(hashData.subarray(0, 32));
}
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
error('incorrect password');
checkData = cipher.encryptBlock(defaultPasswordBytes);
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
return null;
}
}
return encryptionKey;
}
function decodeUserPassword(password, ownerPassword, revision, keyLength) {
var hashData = new Uint8Array(32), i = 0, j, n;
n = Math.min(32, password.length);
for (; i < n; ++i)
hashData[i] = password[i];
j = 0;
while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++];
}
var hash = calculateMD5(hashData, 0, i);
var keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, hash.length);
}
}
var cipher, userPassword;
if (revision >= 3) {
userPassword = ownerPassword;
var derivedKey = new Uint8Array(keyLengthInBytes), k;
for (j = 19; j >= 0; j--) {
for (k = 0; k < keyLengthInBytes; ++k)
derivedKey[k] = hash[k] ^ j;
cipher = new ARCFourCipher(derivedKey);
userPassword = cipher.encryptBlock(userPassword);
}
} else {
cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
userPassword = cipher.encryptBlock(ownerPassword);
}
return userPassword;
}
var identityName = new Name('Identity');
@ -516,10 +553,25 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
if (password)
passwordBytes = stringToBytes(password);
this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
ownerPassword, userPassword,
flags, revision,
keyLength, encryptMetadata);
var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
ownerPassword, userPassword, flags,
revision, keyLength, encryptMetadata);
if (!encryptionKey && !password) {
throw new PasswordException('No password given', 'needpassword');
} else if (!encryptionKey && password) {
// Attempting use the password as an owner password
var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
revision, keyLength);
encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
ownerPassword, userPassword, flags,
revision, keyLength, encryptMetadata);
}
if (!encryptionKey)
throw new PasswordException('Incorrect Password', 'incorrectpassword');
this.encryptionKey = encryptionKey;
if (algorithm == 4) {
this.cf = dict.get('CF');
this.stmf = dict.get('StmF') || identityName;

View file

@ -108,39 +108,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// Compatibility
BX: 'beginCompat',
EX: 'endCompat'
EX: 'endCompat',
// (reserved partial commands for the lexer)
BM: null,
BD: null,
'true': null,
fa: null,
fal: null,
fals: null,
'false': null,
nu: null,
nul: null,
'null': null
};
function splitCombinedOperations(operations) {
// Two or more operations can be combined together, trying to find which
// operations were concatenated.
var result = [];
var opIndex = 0;
if (!operations) {
return null;
}
while (opIndex < operations.length) {
var currentOp = '';
for (var op in OP_MAP) {
if (op == operations.substr(opIndex, op.length) &&
op.length > currentOp.length) {
currentOp = op;
}
}
if (currentOp.length > 0) {
result.push(operations.substr(opIndex, currentOp.length));
opIndex += currentOp.length;
} else {
return null;
}
}
return result;
}
PartialEvaluator.prototype = {
getOperatorList: function PartialEvaluator_getOperatorList(stream,
resources,
@ -284,39 +266,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
resources = resources || new Dict();
var xobjs = resources.get('XObject') || new Dict();
var patterns = resources.get('Pattern') || new Dict();
var parser = new Parser(new Lexer(stream), false, xref);
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
var res = resources;
var hasNextObj = false, nextObjs;
var args = [], obj;
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
while (true) {
if (hasNextObj) {
obj = nextObjs.pop();
hasNextObj = (nextObjs.length > 0);
} else {
obj = parser.getObj();
if (isEOF(obj))
break;
}
obj = parser.getObj();
if (isEOF(obj))
break;
if (isCmd(obj)) {
var cmd = obj.cmd;
var fn = OP_MAP[cmd];
if (!fn) {
// invalid content command, trying to recover
var cmds = splitCombinedOperations(cmd);
if (cmds) {
cmd = cmds[0];
fn = OP_MAP[cmd];
// feeding other command on the next iteration
hasNextObj = true;
nextObjs = [];
for (var idx = 1; idx < cmds.length; idx++) {
nextObjs.push(Cmd.get(cmds[idx]));
}
}
}
assertWellFormed(fn, 'Unknown command "' + cmd + '"');
// TODO figure out how to type-check vararg functions
@ -659,8 +621,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
} else if (octet == 0x3E) {
if (token.length) {
// XXX guessing chars size by checking number size in the CMap
if (token.length <= 2 && properties.composite)
// Heuristic: guessing chars size by checking numbers sizes
// in the CMap entries.
if (token.length == 2 && properties.composite)
properties.wideChars = false;
if (token.length <= 4) {

View file

@ -1976,9 +1976,9 @@ var Font = (function FontClosure() {
this.isSymbolicFont = false;
}
// heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding
// does not set properly
if (glyphsRemoved > 2) {
// heuristics: if removed more than 10 glyphs encoding WinAnsiEncoding
// does not set properly (broken PDFs have about 100 removed glyphs)
if (glyphsRemoved > 10) {
warn('Switching TrueType encoding to MacRomanEncoding for ' +
this.name + ' font');
encoding = Encodings.MacRomanEncoding;
@ -3477,7 +3477,7 @@ var CFFFont = (function CFFFontClosure() {
this.properties = properties;
var parser = new CFFParser(file, properties);
var cff = parser.parse();
var cff = parser.parse(true);
var compiler = new CFFCompiler(cff);
this.readExtra(cff);
try {
@ -3568,7 +3568,7 @@ var CFFParser = (function CFFParserClosure() {
this.properties = properties;
}
CFFParser.prototype = {
parse: function CFFParser_parse() {
parse: function CFFParser_parse(normalizeCIDData) {
var properties = this.properties;
var cff = new CFF();
this.cff = cff;
@ -3623,6 +3623,21 @@ var CFFParser = (function CFFParserClosure() {
cff.charset = charset;
cff.encoding = encoding;
if (!cff.isCIDFont || !normalizeCIDData)
return cff;
// DirectWrite does not like CID fonts data. Trying to convert/flatten
// the font data and remove CID properties.
if (cff.fdArray.length !== 1)
error('Unable to normalize CID font in CFF data');
var fontDict = cff.fdArray[0];
fontDict.setByKey(17, topDict.getByName('CharStrings'));
cff.topDict = fontDict;
cff.isCIDFont = false;
delete cff.fdArray;
delete cff.fdSelect;
return cff;
},
parseHeader: function CFFParser_parseHeader() {
@ -4221,9 +4236,9 @@ var CFFPrivateDict = (function CFFPrivateDictClosure() {
[[12, 17], 'LanguageGroup', 'num', 0],
[[12, 18], 'ExpansionFactor', 'num', 0.06],
[[12, 19], 'initialRandomSeed', 'num', 0],
[19, 'Subrs', 'offset', null],
[20, 'defaultWidthX', 'num', 0],
[21, 'nominalWidthX', 'num', 0]
[21, 'nominalWidthX', 'num', 0],
[19, 'Subrs', 'offset', null]
];
var tables = null;
function CFFPrivateDict(strings) {

View file

@ -298,7 +298,7 @@ var Catalog = (function CatalogClosure() {
})();
var XRef = (function XRefClosure() {
function XRef(stream, startXRef, mainXRefEntriesOffset) {
function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
@ -311,8 +311,7 @@ var XRef = (function XRefClosure() {
var encrypt = trailerDict.get('Encrypt');
if (encrypt) {
var fileId = trailerDict.get('ID');
this.encrypt = new CipherTransformFactory(encrypt,
fileId[0] /*, password */);
this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password);
}
// get the root dictionary (catalog) object

View file

@ -264,8 +264,16 @@ var Parser = (function ParserClosure() {
})();
var Lexer = (function LexerClosure() {
function Lexer(stream) {
function Lexer(stream, knownCommands) {
this.stream = stream;
// The PDFs might have "glued" commands with other commands, operands or
// literals, e.g. "q1". The knownCommands is a dictionary of the valid
// commands and their prefixes. The prefixes are built the following way:
// if there a command that is a prefix of the other valid command or
// literal (e.g. 'f' and 'false') the following prefixes must be included,
// 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
// other commands or literals as a prefix. The knowCommands is optional.
this.knownCommands = knownCommands;
}
Lexer.isSpace = function Lexer_isSpace(ch) {
@ -529,12 +537,18 @@ var Lexer = (function LexerClosure() {
// command
var str = ch;
var knownCommands = this.knownCommands;
var knownCommandFound = knownCommands && (str in knownCommands);
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
// stop if known command is found and next character does not make
// the str a command
if (knownCommandFound && !((str + ch) in knownCommands))
break;
stream.skip();
if (str.length == 128)
error('Command token too long: ' + str.length);
str += ch;
knownCommandFound = knownCommands && (str in knownCommands);
}
if (str == 'true')
return true;

View file

@ -88,6 +88,19 @@ function shadow(obj, prop, value) {
return value;
}
var PasswordException = (function PasswordExceptionClosure() {
function PasswordException(msg, code) {
this.name = 'PasswordException';
this.message = msg;
this.code = code;
}
PasswordException.prototype = new Error();
PasswordException.constructor = PasswordException;
return PasswordException;
})();
function bytesToString(bytes) {
var str = '';
var length = bytes.length;
@ -486,7 +499,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
}
this.isResolved = true;
this.data = data || null;
this.data = (typeof data !== 'undefined') ? data : null;
var callbacks = this.callbacks;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
@ -501,7 +514,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
}
},
reject: function Promise_reject(reason) {
reject: function Promise_reject(reason, exception) {
if (this.isRejected) {
error('A Promise can be rejected only once ' + this.name);
}
@ -514,7 +527,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
var errbacks = this.errbacks;
for (var i = 0, ii = errbacks.length; i < ii; i++) {
errbacks[i].call(null, reason);
errbacks[i].call(null, reason, exception);
}
},

View file

@ -91,14 +91,35 @@ var WorkerMessageHandler = {
handler.on('GetDocRequest', function wphSetupDoc(data) {
// Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf.
pdfModel = new PDFDocument(new Stream(data));
var pdfData = data.data;
var pdfPassword = data.params.password;
try {
pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword);
} catch (e) {
if (e instanceof PasswordException) {
if (e.code === 'needpassword') {
handler.send('NeedPassword', {
exception: e
});
} else if (e.code === 'incorrectpassword') {
handler.send('IncorrectPassword', {
exception: e
});
}
return;
} else {
throw e;
}
}
var doc = {
numPages: pdfModel.numPages,
fingerprint: pdfModel.getFingerprint(),
destinations: pdfModel.catalog.destinations,
outline: pdfModel.catalog.documentOutline,
info: pdfModel.getDocumentInfo(),
metadata: pdfModel.catalog.metadata
metadata: pdfModel.catalog.metadata,
encrypted: !!pdfModel.xref.encrypt
};
handler.send('GetDoc', {pdfInfo: doc});
});