|
|
@ -1,8 +1,16 @@ |
|
|
|
|
|
|
|
function makeComponent (funct) { |
|
|
|
function makeComponent (waitView) { |
|
|
|
let component = class { |
|
|
|
onStartRequest (request, ctxt) { |
|
|
|
constructor () { |
|
|
|
// a new component is initialised for every new request,
|
|
|
|
// when users click on a received message in the messenger
|
|
|
|
// view. Every component handles only one request, that
|
|
|
|
// means a sequence of one call to `onStartRequest`, one
|
|
|
|
// or more calls to `onDataAvailable` and a final call to
|
|
|
|
// `onStopRequest`
|
|
|
|
this.data = ""; |
|
|
|
} |
|
|
|
onStartRequest (request/*, ctxt*/) { |
|
|
|
this.decoder = request |
|
|
|
.QueryInterface(Components.interfaces.nsIPgpMimeProxy); |
|
|
|
this.boundary = getBoundary(request.contentType); |
|
|
@ -22,20 +30,44 @@ function makeComponent (funct) { |
|
|
|
return reader.read(count); |
|
|
|
} |
|
|
|
} |
|
|
|
onStopRequest (request, status) { |
|
|
|
let messenger = Components.classes["@mozilla.org/messenger;1"] |
|
|
|
.createInstance(Components.interfaces.nsIMessenger), |
|
|
|
uri = request.messageURI.spec, // alternatively decoder.messageURI
|
|
|
|
header = messenger.msgHdrFromURI(uri), |
|
|
|
fromAddress = "test@test.com"; |
|
|
|
// having an `async onStopRequest` would crash Thunderbird
|
|
|
|
// so we use `synchronise` on `funct` which is async
|
|
|
|
let promise = funct(this.data, this.boundary, fromAddress); |
|
|
|
onStopRequest (/*request, status*/) { |
|
|
|
let mime = addWrapper(this.data, this.boundary); |
|
|
|
/* |
|
|
|
|
|
|
|
Decryption view-component tunnel, component |
|
|
|
end. `waitView` gives us a promise to wait until |
|
|
|
`resolveComponent` receives a function that returns a |
|
|
|
MIME message. The message can be passed to the MIME |
|
|
|
proxy's outputDecryptedData in order to trigger updates |
|
|
|
in the user interface like showing attachments or |
|
|
|
updating the body |
|
|
|
|
|
|
|
*/ |
|
|
|
let promise = waitView(mime); |
|
|
|
/* |
|
|
|
|
|
|
|
Having an `async onStopRequest` would crash |
|
|
|
Thunderbird. Maybe if we allow `onStopRequest` to |
|
|
|
terminate, some objects are deleted and we cannot call |
|
|
|
`decoder` any longer. So we use `synchronise` to pause |
|
|
|
the execution of `onStopRequest` until the promise is |
|
|
|
finalised |
|
|
|
|
|
|
|
*/ |
|
|
|
let decoded = synchronise(promise); |
|
|
|
// TB >= 57, for other versions see the body of
|
|
|
|
// MimeDecryptHandler.returnData
|
|
|
|
// https://gitlab.com/enigmail/enigmail/blob/master/package/mimeDecrypt.jsm#L702-706
|
|
|
|
this.decoder.outputDecryptedData(decoded, decoded.length); |
|
|
|
if (decoded) { |
|
|
|
/* |
|
|
|
|
|
|
|
This way to interface with the proxy decoder works |
|
|
|
for thunderbird >= 57, for other versions see the |
|
|
|
body of MimeDecryptHandler.returnData |
|
|
|
https://gitlab.com/enigmail/enigmail/blob/master/package/mimeDecrypt.jsm#L702-706
|
|
|
|
|
|
|
|
*/ |
|
|
|
this.decoder.outputDecryptedData(decoded, decoded.length); |
|
|
|
} else { |
|
|
|
console.log("this decryption component received no data from the view"); |
|
|
|
} |
|
|
|
function synchronise (promise) { |
|
|
|
let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector), |
|
|
|
synchronous, |
|
|
@ -43,6 +75,7 @@ function makeComponent (funct) { |
|
|
|
update = asynchronous => synchronous = asynchronous; |
|
|
|
promise |
|
|
|
.then(update) |
|
|
|
.catch(console.log.bind(console)) |
|
|
|
.finally(unlock); |
|
|
|
// wait here for the promise to resolve
|
|
|
|
inspector.enterNestedEventLoop(0); |
|
|
@ -69,9 +102,117 @@ function addWrapper (decryptedData, boundary) { |
|
|
|
decryptedData; |
|
|
|
} |
|
|
|
|
|
|
|
function promiseWithHandlers () { |
|
|
|
let resolve, |
|
|
|
reject, |
|
|
|
promise = new Promise((resolve_, reject_) => { |
|
|
|
resolve = resolve_; |
|
|
|
reject = reject_; |
|
|
|
}); |
|
|
|
return [promise, resolve, reject]; |
|
|
|
} |
|
|
|
|
|
|
|
function init () { |
|
|
|
/* |
|
|
|
|
|
|
|
We need state to synchronise every component invocation |
|
|
|
with the eventual view rendering. |
|
|
|
|
|
|
|
As we assume that our view is rendered once after every |
|
|
|
component call, we could just store the last handler to be |
|
|
|
called. |
|
|
|
|
|
|
|
Having a queue helps us in detecting unexpected timing |
|
|
|
conditions. With current logic, this queue can either be empty |
|
|
|
or contain one promise. |
|
|
|
|
|
|
|
*/ |
|
|
|
let waiting = false, |
|
|
|
resolve, |
|
|
|
reject, |
|
|
|
lastArguments, |
|
|
|
component = makeComponent(waitView); |
|
|
|
return { component, resolveComponent }; |
|
|
|
function waitView (...someArguments) { |
|
|
|
let promise; |
|
|
|
if (waiting) { |
|
|
|
console.log("warning: a decryption component was waiting for a view and it's being discarded"); |
|
|
|
/* |
|
|
|
|
|
|
|
Several components have been waiting for |
|
|
|
rendering, each one expecting some data for |
|
|
|
passing it to their mime decoder proxy. |
|
|
|
|
|
|
|
We reject former component promises in order not |
|
|
|
to leave hanging threads. These rejections are |
|
|
|
handled in component.onStopRequest synchronise |
|
|
|
with a simple console.log |
|
|
|
|
|
|
|
*/ |
|
|
|
reject("another component was initialised after this one"); |
|
|
|
waiting = false; |
|
|
|
} |
|
|
|
waiting = true; |
|
|
|
lastArguments = someArguments; |
|
|
|
[promise, resolve, reject] = promiseWithHandlers(); |
|
|
|
return promise; |
|
|
|
} |
|
|
|
function resolveComponent (handler) { |
|
|
|
if (!handler) { |
|
|
|
/* |
|
|
|
|
|
|
|
This branch is useful for troubleshooting. By calling |
|
|
|
`pEpController.factories.resolveDecryptComponent();` |
|
|
|
without arguments in the view, you can expect the user |
|
|
|
interface to show an encrypted message's parts as body |
|
|
|
and attachments. |
|
|
|
|
|
|
|
The other code here in `init` handles the arguments in a |
|
|
|
generic way so it can adapt to cases where waitView's |
|
|
|
arguments will change. Here in this fallback handler we |
|
|
|
use the current actual arguments for simplicity. |
|
|
|
|
|
|
|
*/ |
|
|
|
handler = (encryptedMime) => { |
|
|
|
return Promise.resolve(encryptedMime); |
|
|
|
} |
|
|
|
} |
|
|
|
if (waiting) { |
|
|
|
resolve(handler(...lastArguments)); |
|
|
|
waiting = false; |
|
|
|
} else { |
|
|
|
/* |
|
|
|
|
|
|
|
There are no components waiting for the view, we |
|
|
|
cannot render this part of the message. Maybe the |
|
|
|
view that called `resolveComponent` is not the right |
|
|
|
one. |
|
|
|
|
|
|
|
When users click on a received message in a window with |
|
|
|
location.href == |
|
|
|
'chrome://messenger/content/messenger.xhtml', we can |
|
|
|
see that a decryption component is called before the |
|
|
|
view initialisation. In the other cases we get no |
|
|
|
guarantees about the timing. |
|
|
|
|
|
|
|
We pass through this branch also when visiting cached |
|
|
|
messages through the view. In that case the decryption |
|
|
|
component gets called only the first time users visit |
|
|
|
the messages |
|
|
|
|
|
|
|
*/ |
|
|
|
console.log("no decryption component waiting to be resolved"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var pEpMimeDecrypt = { |
|
|
|
makeComponent, |
|
|
|
addWrapper |
|
|
|
// `init` is the main interface, currently used in p4tb.js
|
|
|
|
init, |
|
|
|
// `makeComponent` is less complex than `init`. It can help
|
|
|
|
// testing and troubleshooting and it's used in the runtime
|
|
|
|
// experiments
|
|
|
|
makeComponent |
|
|
|
}; |
|
|
|
|
|
|
|
const EXPORTED_SYMBOLS = ["pEpMimeDecrypt"]; |