mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-29 20:19:22 +03:00
Replaced added postal-mime for custom mime parser due to PGP verify requirements
This commit is contained in:
parent
9188a8bcd2
commit
292e21bae1
11 changed files with 177 additions and 2224 deletions
131
dev/Mime/Parser.js
Normal file
131
dev/Mime/Parser.js
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue