vietnamese
This commit is contained in:
@@ -88,6 +88,17 @@ var ChatOmemo = {
|
||||
const localDeviceId = await store.getLocalRegistrationId();
|
||||
devicesIds = Object.values(devicesIds);
|
||||
|
||||
// A key backup was restored (Account.omemoRestore): publish it with new
|
||||
// prekeys and drop the device it replaced from our list
|
||||
let restored = localStorage.getObject(OMEMO_RESTORED_KEY);
|
||||
if (restored && localDeviceId != undefined) {
|
||||
localStorage.removeItem(OMEMO_RESTORED_KEY);
|
||||
devicesIds = devicesIds.filter(id => id != restored.replaced);
|
||||
ChatOmemo.refreshBundle(devicesIds, false);
|
||||
ChatOmemo.refreshed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (localDeviceId == undefined) {
|
||||
ChatOmemo.initiateBundle(devicesIds);
|
||||
return;
|
||||
@@ -427,56 +438,99 @@ var ChatOmemo = {
|
||||
? await sessionCipher.decryptPreKeyWhisperMessage(ciphertext, 'binary')
|
||||
: await sessionCipher.decryptWhisperMessage(ciphertext, 'binary');
|
||||
},
|
||||
searchEncryptedFile: function (plaintext) {
|
||||
let lines = plaintext.split('\n');
|
||||
let matches = lines[0].match(AESGCM_REGEX);
|
||||
// Decrypted files of this page: aesgcm URL → blob URL
|
||||
decryptedFiles: new Map(),
|
||||
/**
|
||||
* Render an aesgcm:// message (XEP-0454) in p: pictures and audio inline,
|
||||
* other files as a download link. Returns false if it is not one.
|
||||
* Built with the DOM: the URL comes from the contact.
|
||||
*/
|
||||
renderEncryptedFile: function (p, plaintext) {
|
||||
let encryptedUrl = plaintext.trim();
|
||||
let matches = encryptedUrl.match(AESGCM_REGEX);
|
||||
if (!matches) return false;
|
||||
|
||||
if (!matches) {
|
||||
return plaintext;
|
||||
let [, , filename, extension] = matches;
|
||||
let type = AESGCM_TYPES[extension.toLowerCase()] ?? '';
|
||||
p.textContent = '';
|
||||
|
||||
if (type.startsWith('image/') || type.startsWith('audio/')) {
|
||||
let media = document.createElement(type.startsWith('image/') ? 'img' : 'audio');
|
||||
media.className = 'encrypted_media';
|
||||
media.title = filename;
|
||||
if (media.tagName == 'AUDIO') media.controls = true;
|
||||
p.appendChild(media);
|
||||
|
||||
ChatOmemo.decryptFile(encryptedUrl).then(url => {
|
||||
media.src = url;
|
||||
if (media.tagName == 'IMG') media.onclick = () => window.open(url, '_blank');
|
||||
}).catch(e => {
|
||||
console.log('Cannot decrypt ' + filename, e);
|
||||
p.textContent = '';
|
||||
p.appendChild(ChatOmemo.encryptedFileLink(encryptedUrl, filename));
|
||||
});
|
||||
} else {
|
||||
p.appendChild(ChatOmemo.encryptedFileLink(encryptedUrl, filename));
|
||||
}
|
||||
let [match, , filename, extension, hash] = matches;
|
||||
return '<i class="material-symbols">file_download</i> <a href="#" class="encrypted_file" onclick="ChatOmemo.getEncryptedFile(\''
|
||||
+ lines[0]
|
||||
+ '\')">'
|
||||
+ filename
|
||||
+ '</a>';
|
||||
|
||||
return true;
|
||||
},
|
||||
encryptedFileLink: function (encryptedUrl, filename) {
|
||||
let fragment = document.createDocumentFragment();
|
||||
let icon = document.createElement('i');
|
||||
icon.className = 'material-symbols';
|
||||
icon.textContent = 'file_download';
|
||||
let link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.className = 'encrypted_file';
|
||||
link.textContent = filename;
|
||||
link.onclick = e => {
|
||||
e.preventDefault();
|
||||
ChatOmemo.getEncryptedFile(encryptedUrl);
|
||||
};
|
||||
fragment.append(icon, ' ', link);
|
||||
return fragment;
|
||||
},
|
||||
// Fetch and decrypt an aesgcm:// file, returns a blob URL (cached)
|
||||
decryptFile: async function (encryptedUrl) {
|
||||
if (ChatOmemo.decryptedFiles.has(encryptedUrl)) {
|
||||
return ChatOmemo.decryptedFiles.get(encryptedUrl);
|
||||
}
|
||||
|
||||
let [, url, , extension, hash] = encryptedUrl.match(AESGCM_REGEX);
|
||||
let response = await fetch('https://' + url);
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
|
||||
let cipher = await response.arrayBuffer();
|
||||
const iv = hash.slice(0, hash.length - 64);
|
||||
const key = hash.slice(hash.length - 64);
|
||||
|
||||
const keyObj = await crypto.subtle.importKey('raw', MovimUtils.hexToArrayBuffer(key), 'AES-GCM', false, ['decrypt']);
|
||||
const algo = {
|
||||
'name': 'AES-GCM',
|
||||
'iv': MovimUtils.hexToArrayBuffer(iv),
|
||||
};
|
||||
let plainFile = await crypto.subtle.decrypt(algo, keyObj, cipher);
|
||||
|
||||
let blobUrl = URL.createObjectURL(new Blob([plainFile], { type: AESGCM_TYPES[extension.toLowerCase()] ?? 'application/octet-stream' }));
|
||||
ChatOmemo.decryptedFiles.set(encryptedUrl, blobUrl);
|
||||
return blobUrl;
|
||||
},
|
||||
getEncryptedFile: async function (encryptedUrl) {
|
||||
Chat.enableSending();
|
||||
|
||||
let [, url, filename, , hash] = encryptedUrl.match(AESGCM_REGEX);
|
||||
url = 'https://' + url;
|
||||
let response;
|
||||
let [, , filename] = encryptedUrl.match(AESGCM_REGEX);
|
||||
|
||||
try {
|
||||
response = await fetch(url)
|
||||
} catch (e) {
|
||||
console.log('Cannot get the following file: ' + url)
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.status >= 200 && response.status < 400) {
|
||||
let cipher = await response.arrayBuffer();
|
||||
const iv = hash.slice(0, 24);
|
||||
const key = hash.slice(24);
|
||||
|
||||
const keyObj = await crypto.subtle.importKey('raw', MovimUtils.hexToArrayBuffer(key), 'AES-GCM', false, ['decrypt']);
|
||||
const algo = {
|
||||
'name': 'AES-GCM',
|
||||
'iv': MovimUtils.hexToArrayBuffer(iv),
|
||||
};
|
||||
let plainFile = await crypto.subtle.decrypt(algo, keyObj, cipher);
|
||||
|
||||
const file = new File([plainFile], filename);
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(file);
|
||||
link.href = await ChatOmemo.decryptFile(encryptedUrl);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
|
||||
Chat.disableSending();
|
||||
|
||||
URL.revokeObjectURL(link.href)
|
||||
} catch (e) {
|
||||
console.log('Cannot get the following file: ' + filename, e);
|
||||
}
|
||||
|
||||
Chat.disableSending();
|
||||
},
|
||||
resolveContactFingerprints: function (jid) {
|
||||
let omemoStorage = new ChatOmemoStorage;
|
||||
@@ -512,7 +566,7 @@ var ChatOmemo = {
|
||||
MovimWebsocket.attach(async () => {
|
||||
var store = new ChatOmemoStorage();
|
||||
const localDeviceId = await store.getLocalRegistrationId();
|
||||
if (!localDeviceId) {
|
||||
if (!localDeviceId || localStorage.getItem(OMEMO_RESTORED_KEY)) {
|
||||
ChatOmemo_ajaxGetDevicesList(USER_JID);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user