Replaced added postal-mime for custom mime parser due to PGP verify requirements

This commit is contained in:
the-djmaze 2022-02-10 15:37:05 +01:00
parent 9188a8bcd2
commit 292e21bae1
11 changed files with 177 additions and 2224 deletions

131
dev/Mime/Parser.js Normal file
View file

@ -0,0 +1,131 @@
const
// RFC2045
QPDecodeIn = /=([0-9A-F]{2})/g,
QPDecodeOut = (...args) => String.fromCharCode(parseInt(args[1], 16));
export function ParseMime(text)
{
class MimePart
{
header(name) {
return this.headers && this.headers[name];
}
headerValue(name) {
return (this.header(name) || {value:null}).value;
}
get raw() {
return text.slice(this.start, this.end);
}
get bodyRaw() {
return text.slice(this.body_start, this.body_end);
}
get body() {
let body = this.bodyRaw,
encoding = this.headerValue('content-transfer-encoding');
if ('quoted-printable' == encoding) {
body = body.replace(/=\r?\n/g, '').replace(QPDecodeIn, QPDecodeOut);
} else if ('base64' == encoding) {
body = atob(body.replace(/\r?\n/g, ''));
}
return body;
}
get dataUrl() {
let body = this.bodyRaw,
encoding = this.headerValue('content-transfer-encoding');
if ('base64' == encoding) {
body = body.replace(/\r?\n/g, '');
} else {
if ('quoted-printable' == encoding) {
body = body.replace(/=\r?\n/g, '').replace(QPDecodeIn, QPDecodeOut);
}
body = btoa(body);
}
return 'data:' + this.headerValue('content-type') + ';base64,' + body;
}
forEach(fn) {
fn(this);
if (this.parts) {
this.parts.forEach(part => part.forEach(fn));
}
}
getByContentType(type) {
if (type == this.headerValue('content-type')) {
return this;
}
if (this.parts) {
let i = 0, p = this.parts, part;
for (i; i < p.length; ++i) {
if ((part = p[i].getByContentType(type))) {
return part;
}
}
}
}
}
const ParsePart = (mimePart, start_pos = 0, id = '') =>
{
let part = new MimePart;
if (id) {
part.id = id;
part.start = start_pos;
part.end = start_pos + mimePart.length;
}
// get headers
let head = mimePart.match(/^[\s\S]+?\r?\n\r?\n/);
if (head) {
head = head[0];
let headers = {};
head.replace(/\r?\n\s+/g, ' ').split(/\r?\n/).forEach(header => {
let match = header.match(/^([^:]+):\s*([^;]+)/),
params = {};
[...header.matchAll(/;\s*([^;=]+)=\s*"?([^;"]+)"?/g)].forEach(param =>
params[param[1].trim().toLowerCase()] = param[2].trim()
);
headers[match[1].trim().toLowerCase()] = {
value: match[2].trim(),
params: params
};
});
part.headers = headers;
// get body
part.body_start = start_pos + head.length;
part.body_end = start_pos + mimePart.length;
let boundary = headers['content-type'].params.boundary;
if (boundary) {
part.boundary = boundary;
part.parts = [];
let regex = new RegExp('(?:^|\r?\n)--' + boundary + '(?:--)?(?:\r?\n|$)', 'g'),
body = mimePart.slice(head.length),
bodies = body.split(regex),
pos = part.body_start;
[...body.matchAll(regex)].forEach(([boundary], index) => {
if (!index) {
part.body_text = bodies[0];
}
pos += bodies[index].length;
pos += boundary.length;
part.parts.push(ParsePart(bodies[1+index], pos, ((id ? id + '.' : '') + (1+index))));
if ('--' == boundary.trim().slice(-2)) {
// end
}
});
}
}
return part;
};
return ParsePart(text);
}

View file

@ -52,7 +52,7 @@ import { AbstractViewRight } from 'Knoin/AbstractViews';
import { PgpUserStore } from 'Stores/User/Pgp';
import PostalMime from '../../../../vendors/postal-mime/src/postal-mime.js';
import { ParseMime } from 'Mime/Parser';
import { AttachmentModel } from 'Model/Attachment';
const
@ -61,44 +61,57 @@ const
currentMessage = () => MessageUserStore.message(),
mimeToMessage = (data, message) => {
// TODO: Check multipart/signed application/pgp-signature application/pgp-keys
const headers = data.split(/\r?\n\r?\n/)[0];
if (/Content-Type:/i.test(headers)) {
// https://github.com/postalsys/postal-mime
(new PostalMime).parse(data).then(result => {
// TODO: multipart/signed
let html = result.html,
regex = /^<+|>+$/g;
result.attachments.forEach(data => {
const struct = ParseMime(data),
text = struct.getByContentType('text/plain');
let html = struct.getByContentType('text/html');
html = html ? html.body : '';
// TODO: Check multipart/signed application/pgp-signature application/pgp-keys
struct.forEach(part => {
let cd = part.header('content-disposition'),
cid = part.header('content-id'),
type = part.header('content-type');
if (cid || cd) {
// if (cd && 'attachment' === cd.value) {
let attachment = new AttachmentModel;
attachment.mimeType = data.mimeType;
attachment.fileName = data.filename;
attachment.content = data.content; // ArrayBuffer
attachment.cid = data.contentId || '';
// Parse inline attachments from result.attachments
if (attachment.cid) {
if (html) {
let cid = 'cid:' + attachment.cid.replace(regex, ''),
b64 = 'data:' + data.mimeType + ';base64,' + btoa(String.fromCharCode(...new Uint8Array(data.content)));
html = html
.replace('src="' + cid + '"', 'src="' + b64 + '"')
.replace("src='" + cid + "'", "src='" + b64 + "'");
}
attachment.mimeType = type.value;
attachment.fileName = (type.name || (cd && cd.params.filename));
attachment.url = part.dataUrl;
/*
attachment.fileNameExt = '';
attachment.fileType = FileType.Unknown;
attachment.friendlySize = '';
attachment.isLinked = false;
attachment.isThumbnail = false;
attachment.contentLocation = '';
attachment.download = '';
attachment.folder = '';
attachment.uid = '';
attachment.mimeIndex = part.id;
attachment.framed = false;
*/
attachment.cid = cid ? cid.value : '';
if (cid && html) {
let cid = 'cid:' + attachment.contentId();
attachment.isInline = html.includes(cid);
html = html
.replace('src="' + cid + '"', 'src="' + attachment.url + '"')
.replace("src='" + cid + "'", "src='" + attachment.url + "'");
} else {
message.attachments.push(attachment);
}
// data.disposition
// data.related = true;
message.attachments.push(attachment);
});
message.hasAttachments(message.attachments().hasVisible());
// result.headers;
message.plain(result.text || '');
if (html) {
message.html(html.replace(/<\/?script[\s\S]*?>/gi, '') || '');
message.viewHtml();
} else {
message.viewPlain();
}
});
message.plain(text ? text.body : '');
if (html) {
message.html(html.replace(/<\/?script[\s\S]*?>/gi, ''));
message.viewHtml();
} else {
message.viewPlain();
}
return;
}
message.plain(data);