Skip to content

Commit

Permalink
Implement RFC2047 Q-Encoding to fix #68 #64 #59
Browse files Browse the repository at this point in the history
  • Loading branch information
jobisoft committed Jul 2, 2023
1 parent 00511cd commit 85fcc5f
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 2 deletions.
29 changes: 27 additions & 2 deletions src/content/scripts/editemailsubject.mjs
Expand Up @@ -16,6 +16,8 @@
* Modifications for TB78, TB91, TB102, TB115 by John Bieling (2020-2023)
*/

import * as q from "/content/scripts/q.mjs";

// Reduces a MessageList to a single message, if it contains a single message.
export function getSingleMessageFromList(messageList) {
return messageList && messageList.messages && messageList.messages.length == 1
Expand Down Expand Up @@ -71,11 +73,34 @@ export async function updateMessage({ msgId, tabId, keepBackup, newSubject, curr
while (headerPart.match(/\r\nSubject: .*\r\n\s+/))
headerPart = headerPart.replace(/(\r\nSubject: .*)(\r\n\s+)/, "$1 ");

// RFC2047 Q-Encoding
const encodeHeader = (name, value) => {
const startLine = " =?utf-8?q?"
const nextLine = "=?="
const endLine = "?=";

let lines = [];
let currentLine = `${name}:${startLine}`;

let uint8Array = new TextEncoder().encode(value);
for (let v of uint8Array.values()) {
let next = q.encode(String.fromCharCode(v)); // or btoa() for b-encoding
if (currentLine.length + next.length + endLine.length > 78) {
lines.push(currentLine + nextLine);
currentLine = startLine;
}
currentLine += next;
}
lines.push(currentLine + endLine);

return lines.join("\r\n");
}

// Either replace the subject header or add one if missing.
if (headerPart.includes("\nSubject: ")) {
headerPart = headerPart.replace(/\nSubject: .*\r\n/, "\nSubject: " + unescape(encodeURIComponent(newSubject)) + "\r\n");
headerPart = headerPart.replace(/\nSubject: .*\r\n/, "\n" + encodeHeader("Subject", newSubject) + "\r\n");
} else {
headerPart += "Subject: " + unescape(encodeURIComponent(newSubject)) + "\r\n";
headerPart += encodeHeader("Subject", newSubject) + "\r\n";
}

// Without changing the message-id, the MessageHeader obj of the new message and the old message will
Expand Down
41 changes: 41 additions & 0 deletions src/content/scripts/q.mjs
@@ -0,0 +1,41 @@
/*! https://mths.be/q v1.0.0 by @mathias | MIT license */

// https://tools.ietf.org/html/rfc2047#section-4.2
var stringFromCharCode = String.fromCharCode;
export function decode (input) {
return input
// Decode `_` into a space. This is character-encoding-independent;
// see https://tools.ietf.org/html/rfc2047#section-4.2, item 2.
.replace(/_/g, ' ')
// Decode escape sequences of the form `=XX` where `XX` is any
// combination of two hexidecimal digits. For optimal compatibility,
// lowercase hexadecimal digits are supported as well. See
// https://tools.ietf.org/html/rfc2045#section-6.7, note 1.
.replace(/=([a-fA-F0-9]{2})/g, function ($0, $1) {
var codePoint = parseInt($1, 16);
return stringFromCharCode(codePoint);
});
};

var regexUnsafeSymbols = /[\0-\x1F"-\),\.:-@\[-\^`\{-\uFFFF]/g;
export function encode (string) {
// Note: this assumes the input is already encoded into octets (e.g. using
// UTF-8), and that the resulting octets are within the extended ASCII
// range.
return string
// Encode symbols that are definitely unsafe (i.e. unsafe in any context).
.replace(regexUnsafeSymbols, function (symbol) {
if (symbol > '\xFF') {
throw RangeError(
'`q.encode()` expects extended ASCII input only. Don\u2019t ' +
'forget to encode the input first using a character encoding ' +
'like UTF-8.'
);
}
var codePoint = symbol.charCodeAt(0);
var hexadecimal = codePoint.toString(16).toUpperCase();
return '=' + ('0' + hexadecimal).slice(-2);
})
// Encode spaces as `_`, as it’s shorter than `=20`.
.replace(/\x20/g, '_');
};

0 comments on commit 85fcc5f

Please sign in to comment.