From dbc8b17dba47053b45c6889feba8e3852dba62c6 Mon Sep 17 00:00:00 2001 From: ExSlam <114887800+ExSlam@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:00:21 +0000 Subject: [PATCH 01/23] Add base64EncodeWithPaddingByLine function Store the position of newline characters for each line and add a padding before the newline if necessary. --- b64.cpp | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ b64.h | 25 +++++ 2 files changed, 338 insertions(+) create mode 100644 b64.cpp create mode 100644 b64.h diff --git a/b64.cpp b/b64.cpp new file mode 100644 index 0000000..0441138 --- /dev/null +++ b/b64.cpp @@ -0,0 +1,313 @@ +// This file is part of Notepad++ plugin MIME Tools project +// Copyright (C)2023 Don HO + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Enhance Base64 features, and rewrite Base64 encode/decode implementation +// Copyright 2019 by Paul Nankervis + +// Copyright 2024 by ExSlam +// Modified by ExSlam on March 16, 2024 to add a function to perserve newline spacing and +// to add padding at the end of each line before the newline character where required in Base64 encoded output. + +#include "PluginInterface.h" +#include "mimeTools.h" +#include "b64.h" +#include "qp.h" +#include "url.h" +#include "saml.h" +#include + +// Base64 encoding decoding - where 8 bit ascii is re-represented using just 64 ascii characters (plus optional padding '='). +// +// This code includes options to encode to base64 in multiple ways. For example the text lines:- +// +// If you can keep your head when all about you +// Are losing theirs and blaming it on you; +// +// Using "Encode with Unix EOL" would produce a single base64 string with line breaks after each 64 characters:- +// +// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3UNCkFy +// ZSBsb3NpbmcgdGhlaXJzIGFuZCBibGFtaW5nIGl0IG9uIHlvdTs= +// +// That would be decoded using a single base64 decode which ignored whitespace characters (the line breaks). +// +// Alternatively the same lines could be encoded using a "by line" option to encode each line of input as +// its own separate base64 string:- +// +// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3U +// QXJlIGxvc2luZyB0aGVpcnMgYW5kIGJsYW1pbmcgaXQgb24geW91Ow +// +// Each of these output lines could be decoded separately, or multiple lines decoded using "reset on whitespace" +// to cause base64 decoding to restart on each line + + +char base64CharSet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +int base64CharMap[] = { // base64 values or: -1 for illegal character, -2 to ignore character, and -3 for pad ('=') + -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, // & are ignored + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // is ignored + 52, 53, 54, 55 ,56, 57, 58, 59, 60, 61, -1, -1, -1, -3, -1, -1, // '=' is the pad character + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1 ,-1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; + +// base64Encode simply converts ascii to base64 with appropriate wrapping and padding. Encoding is done by loading +// three ascii characters at a time into a bitField, and then extracting them as four base64 values. +// returnString is assumed to be large enough to contain the result (which is typically 4 / 3 the input size +// plus line breaks), and the function return is the length of the result +// wrapLength sets the length at which to wrap the encoded test at (not valid with byLineFlag) +// padFlag controls whether the one or two '=' pad characters are included at the end of encoding +// byLineFlag causes each input line to be encoded as a separate base64 string + +int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag) +{ + size_t index; // input string index + size_t lineLength = 0; // current line length + int resultLength = 0, // result string length + bitField, // assembled bit field (up to 3 ascii characters at a time) + bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) + endOffset, // end offset index value + charValue; // character value + + for (index = 0; index < asciiStringLength; ) + { + bitField = 0; + for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) + { + charValue = (UCHAR)asciiString[index]; + if (byLineFlag && (charValue == '\n' || charValue == '\r')) + { + break; + } + index++; + bitField |= charValue << bitOffset; + } + endOffset = bitOffset + 3; // end indicator + for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) + { + if (wrapLength > 0 && lineLength++ >= wrapLength && !byLineFlag) + { + resultString[resultLength++] = '\n'; + lineLength = 1; + } + resultString[resultLength++] = base64CharSet[(bitField >> bitOffset) & 0x3f]; + } + if (byLineFlag) + { + while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) + { + resultString[resultLength++] = asciiString[index++]; + } + } + } + if (padFlag && !byLineFlag) + { + for (; bitOffset >= 0; bitOffset -= 6) + { + if (wrapLength > 0 && lineLength++ >= wrapLength) + { + resultString[resultLength++] = '\n'; + lineLength = 1; + } + resultString[resultLength++] = '='; + } + } + return resultLength; +} + +// base64Decode converts base64 to ascii. But there are choices about what to do with illegal characters or +// malformed strings. In this version there is a strict flag to indicate that the input must be a single +// valid base64 string with no illegal characters, no extra padding, and no short segments. Otherwise +// there is best effort to decode around illegal characters which ARE preserved in the output. +// So "TWFyeQ==.aGFk.YQ.bGl0dGxl.bGFtYg==" decodes to "Mary.had.a.little.lamb" with five seperate +// base64 strings decoded, each separated by the illegal character dot. In strict mode the first dot +// would trigger a fatal error. Some other implementations choose to ignore illegal characters which +// of course has it's own issues. +// The four whitespace characters and are silently ignored unless noWhitespaceFlag +// is set. In this case whitespace is treated similar to illegal characters and base64 decoding operates +// around the white space. So "TWFyeQ== aGFk YQ bGl0dGxl bGFtYg==" would decode as "Mary had a little lamb". +// Decoding is done by loading four base64 characters at a time into a bitField, and then extracting them as +// three ascii characters. +// returnString is assumed to be large enough to contain the result (which could be the same size as the input), +// and the function return is the length of the result, or a negative value in case of an error + +int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset) +{ + std::size_t index; // input string index + + int resultLength = 0, // result string length + bitField, // assembled bit field (up to 3 ascii characters at a time) + bitOffset, // offset into bit field (6 bit intput: 18, 12, 6, 0 -> 8 bit output: 16, 8, 0) + endOffset, // end offset index value + charValue = 0, // character value + charIndex = 0, // character index + padLength = 0; // pad characters seen + + for (index = 0; index < encodedStringLength; ) + { + bitField = 0; + for (bitOffset = 18; bitOffset >= 0 && index < encodedStringLength; ) + { + charValue = (UCHAR)encodedString[index++]; + charIndex = base64CharMap[charValue & 0x7f]; + if (charIndex >= 0) + { + if (padLength > 0 && strictFlag) + { + return -1; // **ERROR** Data after pad character + } + bitField |= charIndex << bitOffset; + bitOffset -= 6; + } + else + { + if (charIndex == -3) // -3 is Pad character '=' + { + padLength++; + if (strictFlag && bitOffset > 6) + { + return -2; // **ERROR** Pad character in wrong place + } + } + else // either -1 for illegal character or -2 for whitespace (ignored) + { + if (charIndex == -1 || whitespaceReset) + { + charIndex = -1; // Remember it as an illegal character for copy below + break; // exit loop to deal with illegal character + } + } + } + } + + if (strictFlag && bitOffset == 12) + { + return -3; // **ERROR** Single symbol block not valid + } + endOffset = bitOffset + 3; // end indicator + + for (bitOffset = 16; bitOffset > endOffset; bitOffset -= 8) + { + resultString[resultLength++] = (bitField >> bitOffset) & 0xff; + } + + if (charIndex == -1) // Was there an illegal character? + { + if (strictFlag) + { + return -4; // **ERROR** Bad character in input string + } + resultString[resultLength++] = (char)charValue; + } + } + return resultLength; +} + + +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength) +{ + std::size_t index; // input string index + //size_t lineLength = 0; // current line length + int resultLength = 0; // result string length + int bitField, // assembled bit field (up to 3 ascii characters at a time) + bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) + endOffset, // end offset index value + charValue; // character value + + for (index = 0; index < asciiStringLength; ) + { + bitField = 0; + for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) + { + charValue = (UCHAR)asciiString[index]; + if (charValue == '\n' || charValue == '\r') + { + break; + } + index++; + bitField |= charValue << bitOffset; + } + endOffset = bitOffset + 3; // end indicator + for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) + { + resultString.insert(resultString.end(), base64CharSet[(bitField >> bitOffset) & 0x3f]); + resultLength++; + } + + while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) + { + resultString.insert(resultString.end(), asciiString[index++]); + resultLength++; + } + } + std::vector asciiLineLengths; + std::vector resultLineLengths; + std::size_t numLines = 0; + std::size_t currLineLength = 0; + //first calculate the number of characters on each line, besides the newline character for the source string + for (std::size_t asciiIndex = 0; asciiIndex < asciiStringLength; asciiIndex++) + { + if (asciiString[asciiIndex] == '\n' || asciiString[asciiIndex] == '\r') + { + numLines++; + asciiLineLengths.push_back(currLineLength + 1); + currLineLength = 0; + } + else { + currLineLength++; + } + + } + numLines = 0; + currLineLength = 0; + //calculate the number of characters on each line, besides the newline character for the encoded base64 string + for (std::size_t resultIndex = 0; resultIndex < resultLength; resultIndex++) + { + if (resultString[resultIndex] == '\n' || resultString[resultIndex] == '\r') + { + numLines++; + resultLineLengths.push_back(currLineLength + 1); + currLineLength = 0; + } + else { + currLineLength++; + } + } + std::size_t currPos = 0uLL; + //basically the number of lines in the input and output strings; + //resultLineLengths and asciiLineLengths have the same size, so this check should be ok + for (std::size_t i = 0; i < numLines; i++) + { + //position of the character before the newline character + currPos += resultLineLengths[i] - 1; + if ((asciiLineLengths[i] - 1) % 3 != 0) + { + //length of the current line minus the line break character + std::size_t currentLineLength = resultLineLengths[i] - 1; + //use the remainder to calculate how much padding the base64 string requires. + //reduce (4 - ((currentLineLength) % 4)) to bitwise operation + int paddingLength = (4 - ((currentLineLength) & 3)); + resultString.insert(currPos, paddingLength, '='); + //Add the padding amount + currPos += paddingLength; + resultLength += paddingLength; + } + //need the +1 at the end to account for the '\n' or '\r' characters. + currPos++; + } + return resultLength; +} diff --git a/b64.h b/b64.h new file mode 100644 index 0000000..0bc5ed3 --- /dev/null +++ b/b64.h @@ -0,0 +1,25 @@ +// This file is part of Notepad++ plugin MIME Tools project +// Copyright (C)2023 Don HO + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#pragma once + +#include +#include + +int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag); +int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset); +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength); From d4b061e7d4c4d6480f979daa1762b8098b2dff66 Mon Sep 17 00:00:00 2001 From: ExSlam <114887800+ExSlam@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:01:33 +0000 Subject: [PATCH 02/23] Delete b64.h --- b64.h | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 b64.h diff --git a/b64.h b/b64.h deleted file mode 100644 index 0bc5ed3..0000000 --- a/b64.h +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of Notepad++ plugin MIME Tools project -// Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - - -#pragma once - -#include -#include - -int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag); -int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset); -int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength); From 78dfa69ecc464ea0357f006826275388b655593c Mon Sep 17 00:00:00 2001 From: ExSlam <114887800+ExSlam@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:01:45 +0000 Subject: [PATCH 03/23] Delete b64.cpp --- b64.cpp | 313 -------------------------------------------------------- 1 file changed, 313 deletions(-) delete mode 100644 b64.cpp diff --git a/b64.cpp b/b64.cpp deleted file mode 100644 index 0441138..0000000 --- a/b64.cpp +++ /dev/null @@ -1,313 +0,0 @@ -// This file is part of Notepad++ plugin MIME Tools project -// Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// Enhance Base64 features, and rewrite Base64 encode/decode implementation -// Copyright 2019 by Paul Nankervis - -// Copyright 2024 by ExSlam -// Modified by ExSlam on March 16, 2024 to add a function to perserve newline spacing and -// to add padding at the end of each line before the newline character where required in Base64 encoded output. - -#include "PluginInterface.h" -#include "mimeTools.h" -#include "b64.h" -#include "qp.h" -#include "url.h" -#include "saml.h" -#include - -// Base64 encoding decoding - where 8 bit ascii is re-represented using just 64 ascii characters (plus optional padding '='). -// -// This code includes options to encode to base64 in multiple ways. For example the text lines:- -// -// If you can keep your head when all about you -// Are losing theirs and blaming it on you; -// -// Using "Encode with Unix EOL" would produce a single base64 string with line breaks after each 64 characters:- -// -// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3UNCkFy -// ZSBsb3NpbmcgdGhlaXJzIGFuZCBibGFtaW5nIGl0IG9uIHlvdTs= -// -// That would be decoded using a single base64 decode which ignored whitespace characters (the line breaks). -// -// Alternatively the same lines could be encoded using a "by line" option to encode each line of input as -// its own separate base64 string:- -// -// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3U -// QXJlIGxvc2luZyB0aGVpcnMgYW5kIGJsYW1pbmcgaXQgb24geW91Ow -// -// Each of these output lines could be decoded separately, or multiple lines decoded using "reset on whitespace" -// to cause base64 decoding to restart on each line - - -char base64CharSet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -int base64CharMap[] = { // base64 values or: -1 for illegal character, -2 to ignore character, and -3 for pad ('=') - -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, // & are ignored - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // is ignored - 52, 53, 54, 55 ,56, 57, 58, 59, 60, 61, -1, -1, -1, -3, -1, -1, // '=' is the pad character - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1 ,-1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 -}; - -// base64Encode simply converts ascii to base64 with appropriate wrapping and padding. Encoding is done by loading -// three ascii characters at a time into a bitField, and then extracting them as four base64 values. -// returnString is assumed to be large enough to contain the result (which is typically 4 / 3 the input size -// plus line breaks), and the function return is the length of the result -// wrapLength sets the length at which to wrap the encoded test at (not valid with byLineFlag) -// padFlag controls whether the one or two '=' pad characters are included at the end of encoding -// byLineFlag causes each input line to be encoded as a separate base64 string - -int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag) -{ - size_t index; // input string index - size_t lineLength = 0; // current line length - int resultLength = 0, // result string length - bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) - endOffset, // end offset index value - charValue; // character value - - for (index = 0; index < asciiStringLength; ) - { - bitField = 0; - for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) - { - charValue = (UCHAR)asciiString[index]; - if (byLineFlag && (charValue == '\n' || charValue == '\r')) - { - break; - } - index++; - bitField |= charValue << bitOffset; - } - endOffset = bitOffset + 3; // end indicator - for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) - { - if (wrapLength > 0 && lineLength++ >= wrapLength && !byLineFlag) - { - resultString[resultLength++] = '\n'; - lineLength = 1; - } - resultString[resultLength++] = base64CharSet[(bitField >> bitOffset) & 0x3f]; - } - if (byLineFlag) - { - while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) - { - resultString[resultLength++] = asciiString[index++]; - } - } - } - if (padFlag && !byLineFlag) - { - for (; bitOffset >= 0; bitOffset -= 6) - { - if (wrapLength > 0 && lineLength++ >= wrapLength) - { - resultString[resultLength++] = '\n'; - lineLength = 1; - } - resultString[resultLength++] = '='; - } - } - return resultLength; -} - -// base64Decode converts base64 to ascii. But there are choices about what to do with illegal characters or -// malformed strings. In this version there is a strict flag to indicate that the input must be a single -// valid base64 string with no illegal characters, no extra padding, and no short segments. Otherwise -// there is best effort to decode around illegal characters which ARE preserved in the output. -// So "TWFyeQ==.aGFk.YQ.bGl0dGxl.bGFtYg==" decodes to "Mary.had.a.little.lamb" with five seperate -// base64 strings decoded, each separated by the illegal character dot. In strict mode the first dot -// would trigger a fatal error. Some other implementations choose to ignore illegal characters which -// of course has it's own issues. -// The four whitespace characters and are silently ignored unless noWhitespaceFlag -// is set. In this case whitespace is treated similar to illegal characters and base64 decoding operates -// around the white space. So "TWFyeQ== aGFk YQ bGl0dGxl bGFtYg==" would decode as "Mary had a little lamb". -// Decoding is done by loading four base64 characters at a time into a bitField, and then extracting them as -// three ascii characters. -// returnString is assumed to be large enough to contain the result (which could be the same size as the input), -// and the function return is the length of the result, or a negative value in case of an error - -int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset) -{ - std::size_t index; // input string index - - int resultLength = 0, // result string length - bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset, // offset into bit field (6 bit intput: 18, 12, 6, 0 -> 8 bit output: 16, 8, 0) - endOffset, // end offset index value - charValue = 0, // character value - charIndex = 0, // character index - padLength = 0; // pad characters seen - - for (index = 0; index < encodedStringLength; ) - { - bitField = 0; - for (bitOffset = 18; bitOffset >= 0 && index < encodedStringLength; ) - { - charValue = (UCHAR)encodedString[index++]; - charIndex = base64CharMap[charValue & 0x7f]; - if (charIndex >= 0) - { - if (padLength > 0 && strictFlag) - { - return -1; // **ERROR** Data after pad character - } - bitField |= charIndex << bitOffset; - bitOffset -= 6; - } - else - { - if (charIndex == -3) // -3 is Pad character '=' - { - padLength++; - if (strictFlag && bitOffset > 6) - { - return -2; // **ERROR** Pad character in wrong place - } - } - else // either -1 for illegal character or -2 for whitespace (ignored) - { - if (charIndex == -1 || whitespaceReset) - { - charIndex = -1; // Remember it as an illegal character for copy below - break; // exit loop to deal with illegal character - } - } - } - } - - if (strictFlag && bitOffset == 12) - { - return -3; // **ERROR** Single symbol block not valid - } - endOffset = bitOffset + 3; // end indicator - - for (bitOffset = 16; bitOffset > endOffset; bitOffset -= 8) - { - resultString[resultLength++] = (bitField >> bitOffset) & 0xff; - } - - if (charIndex == -1) // Was there an illegal character? - { - if (strictFlag) - { - return -4; // **ERROR** Bad character in input string - } - resultString[resultLength++] = (char)charValue; - } - } - return resultLength; -} - - -int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength) -{ - std::size_t index; // input string index - //size_t lineLength = 0; // current line length - int resultLength = 0; // result string length - int bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) - endOffset, // end offset index value - charValue; // character value - - for (index = 0; index < asciiStringLength; ) - { - bitField = 0; - for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) - { - charValue = (UCHAR)asciiString[index]; - if (charValue == '\n' || charValue == '\r') - { - break; - } - index++; - bitField |= charValue << bitOffset; - } - endOffset = bitOffset + 3; // end indicator - for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) - { - resultString.insert(resultString.end(), base64CharSet[(bitField >> bitOffset) & 0x3f]); - resultLength++; - } - - while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) - { - resultString.insert(resultString.end(), asciiString[index++]); - resultLength++; - } - } - std::vector asciiLineLengths; - std::vector resultLineLengths; - std::size_t numLines = 0; - std::size_t currLineLength = 0; - //first calculate the number of characters on each line, besides the newline character for the source string - for (std::size_t asciiIndex = 0; asciiIndex < asciiStringLength; asciiIndex++) - { - if (asciiString[asciiIndex] == '\n' || asciiString[asciiIndex] == '\r') - { - numLines++; - asciiLineLengths.push_back(currLineLength + 1); - currLineLength = 0; - } - else { - currLineLength++; - } - - } - numLines = 0; - currLineLength = 0; - //calculate the number of characters on each line, besides the newline character for the encoded base64 string - for (std::size_t resultIndex = 0; resultIndex < resultLength; resultIndex++) - { - if (resultString[resultIndex] == '\n' || resultString[resultIndex] == '\r') - { - numLines++; - resultLineLengths.push_back(currLineLength + 1); - currLineLength = 0; - } - else { - currLineLength++; - } - } - std::size_t currPos = 0uLL; - //basically the number of lines in the input and output strings; - //resultLineLengths and asciiLineLengths have the same size, so this check should be ok - for (std::size_t i = 0; i < numLines; i++) - { - //position of the character before the newline character - currPos += resultLineLengths[i] - 1; - if ((asciiLineLengths[i] - 1) % 3 != 0) - { - //length of the current line minus the line break character - std::size_t currentLineLength = resultLineLengths[i] - 1; - //use the remainder to calculate how much padding the base64 string requires. - //reduce (4 - ((currentLineLength) % 4)) to bitwise operation - int paddingLength = (4 - ((currentLineLength) & 3)); - resultString.insert(currPos, paddingLength, '='); - //Add the padding amount - currPos += paddingLength; - resultLength += paddingLength; - } - //need the +1 at the end to account for the '\n' or '\r' characters. - currPos++; - } - return resultLength; -} From 43de3b0aa569d628fb9ba68486eb0199d7d1a85c Mon Sep 17 00:00:00 2001 From: ExSlam <114887800+ExSlam@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:05:46 +0000 Subject: [PATCH 04/23] Add base64EncodeWithPaddingByLine function& button Add the Add base64EncodeWithPaddingByLine function to b64.h and b64.cpp. Add convertToBase64FromAscii_pad_byline() function to mimeTools.h and mimeTools.cpp. --- src/b64.cpp | 104 ++++++++++++++++++++++++++++++++++++++++- src/b64.h | 2 + src/mimeTools.cpp | 116 +++++++++++++++++++++++++++------------------- src/mimeTools.h | 1 + 4 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/b64.cpp b/src/b64.cpp index 9d9dbde..0441138 100644 --- a/src/b64.cpp +++ b/src/b64.cpp @@ -17,12 +17,17 @@ // Enhance Base64 features, and rewrite Base64 encode/decode implementation // Copyright 2019 by Paul Nankervis +// Copyright 2024 by ExSlam +// Modified by ExSlam on March 16, 2024 to add a function to perserve newline spacing and +// to add padding at the end of each line before the newline character where required in Base64 encoded output. + #include "PluginInterface.h" #include "mimeTools.h" #include "b64.h" #include "qp.h" #include "url.h" #include "saml.h" +#include // Base64 encoding decoding - where 8 bit ascii is re-represented using just 64 ascii characters (plus optional padding '='). // @@ -142,7 +147,7 @@ int base64Encode(char *resultString, const char *asciiString, size_t asciiString int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset) { - size_t index; // input string index + std::size_t index; // input string index int resultLength = 0, // result string length bitField, // assembled bit field (up to 3 ascii characters at a time) @@ -210,4 +215,99 @@ int base64Decode(char *resultString, const char *encodedString, size_t encodedSt } } return resultLength; -} \ No newline at end of file +} + + +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength) +{ + std::size_t index; // input string index + //size_t lineLength = 0; // current line length + int resultLength = 0; // result string length + int bitField, // assembled bit field (up to 3 ascii characters at a time) + bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) + endOffset, // end offset index value + charValue; // character value + + for (index = 0; index < asciiStringLength; ) + { + bitField = 0; + for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) + { + charValue = (UCHAR)asciiString[index]; + if (charValue == '\n' || charValue == '\r') + { + break; + } + index++; + bitField |= charValue << bitOffset; + } + endOffset = bitOffset + 3; // end indicator + for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) + { + resultString.insert(resultString.end(), base64CharSet[(bitField >> bitOffset) & 0x3f]); + resultLength++; + } + + while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) + { + resultString.insert(resultString.end(), asciiString[index++]); + resultLength++; + } + } + std::vector asciiLineLengths; + std::vector resultLineLengths; + std::size_t numLines = 0; + std::size_t currLineLength = 0; + //first calculate the number of characters on each line, besides the newline character for the source string + for (std::size_t asciiIndex = 0; asciiIndex < asciiStringLength; asciiIndex++) + { + if (asciiString[asciiIndex] == '\n' || asciiString[asciiIndex] == '\r') + { + numLines++; + asciiLineLengths.push_back(currLineLength + 1); + currLineLength = 0; + } + else { + currLineLength++; + } + + } + numLines = 0; + currLineLength = 0; + //calculate the number of characters on each line, besides the newline character for the encoded base64 string + for (std::size_t resultIndex = 0; resultIndex < resultLength; resultIndex++) + { + if (resultString[resultIndex] == '\n' || resultString[resultIndex] == '\r') + { + numLines++; + resultLineLengths.push_back(currLineLength + 1); + currLineLength = 0; + } + else { + currLineLength++; + } + } + std::size_t currPos = 0uLL; + //basically the number of lines in the input and output strings; + //resultLineLengths and asciiLineLengths have the same size, so this check should be ok + for (std::size_t i = 0; i < numLines; i++) + { + //position of the character before the newline character + currPos += resultLineLengths[i] - 1; + if ((asciiLineLengths[i] - 1) % 3 != 0) + { + //length of the current line minus the line break character + std::size_t currentLineLength = resultLineLengths[i] - 1; + //use the remainder to calculate how much padding the base64 string requires. + //reduce (4 - ((currentLineLength) % 4)) to bitwise operation + int paddingLength = (4 - ((currentLineLength) & 3)); + resultString.insert(currPos, paddingLength, '='); + //Add the padding amount + currPos += paddingLength; + resultLength += paddingLength; + } + //need the +1 at the end to account for the '\n' or '\r' characters. + currPos++; + } + return resultLength; +} diff --git a/src/b64.h b/src/b64.h index f6a45a9..0bc5ed3 100644 --- a/src/b64.h +++ b/src/b64.h @@ -18,6 +18,8 @@ #pragma once #include +#include int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag); int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset); +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength); diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index faf6dda..4143aa8 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -26,7 +26,7 @@ const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 22; +const int nbFunc = 23; HINSTANCE g_hInst = nullptr;; NppData nppData; @@ -42,61 +42,63 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ g_hInst = (HINSTANCE)hModule; funcItem[0]._pFunc = convertToBase64FromAscii; funcItem[1]._pFunc = convertToBase64FromAscii_pad; - funcItem[2]._pFunc = convertToBase64FromAscii_B64Format; - funcItem[3]._pFunc = convertToBase64FromAscii_byline; - funcItem[4]._pFunc = convertToAsciiFromBase64; - funcItem[5]._pFunc = convertToAsciiFromBase64_strict; - funcItem[6]._pFunc = convertToAsciiFromBase64_whitespaceReset; - - funcItem[7]._pFunc = NULL; - funcItem[8]._pFunc = convertToQuotedPrintable; - funcItem[9]._pFunc = convertToAsciiFromQuotedPrintable; - - funcItem[10]._pFunc = NULL; - funcItem[11]._pFunc = convertURLMinEncode; - funcItem[12]._pFunc = convertURLMinEncodeByLine; - funcItem[13]._pFunc = convertURLEncodeExtended; - funcItem[14]._pFunc = convertURLEncodeExtendedByLine; - funcItem[15]._pFunc = convertURLFullEncode; - funcItem[16]._pFunc = convertURLFullEncodeByLine; - funcItem[17]._pFunc = convertURLDecode; + funcItem[2]._pFunc = convertToBase64FromAscii_pad_byline; + funcItem[3]._pFunc = convertToBase64FromAscii_B64Format; + funcItem[4]._pFunc = convertToBase64FromAscii_byline; + funcItem[5]._pFunc = convertToAsciiFromBase64; + funcItem[6]._pFunc = convertToAsciiFromBase64_strict; + funcItem[7]._pFunc = convertToAsciiFromBase64_whitespaceReset; + + funcItem[8]._pFunc = NULL; + funcItem[9]._pFunc = convertToQuotedPrintable; + funcItem[10]._pFunc = convertToAsciiFromQuotedPrintable; + + funcItem[11]._pFunc = NULL; + funcItem[12]._pFunc = convertURLMinEncode; + funcItem[13]._pFunc = convertURLMinEncodeByLine; + funcItem[14]._pFunc = convertURLEncodeExtended; + funcItem[15]._pFunc = convertURLEncodeExtendedByLine; + funcItem[16]._pFunc = convertURLFullEncode; + funcItem[17]._pFunc = convertURLFullEncodeByLine; + funcItem[18]._pFunc = convertURLDecode; - funcItem[18]._pFunc = NULL; - funcItem[19]._pFunc = convertSamlDecode; + funcItem[19]._pFunc = NULL; + funcItem[20]._pFunc = convertSamlDecode; - funcItem[20]._pFunc = NULL; - funcItem[21]._pFunc = about; + funcItem[21]._pFunc = NULL; + funcItem[22]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); - lstrcpy(funcItem[2]._itemName, TEXT("Base64 Encode with Unix EOL")); - lstrcpy(funcItem[3]._itemName, TEXT("Base64 Encode by line")); - lstrcpy(funcItem[4]._itemName, TEXT("Base64 Decode")); - lstrcpy(funcItem[5]._itemName, TEXT("Base64 Decode strict")); - lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode by line")); + lstrcpy(funcItem[2]._itemName, TEXT("Base64 Encode with padding by line")); + lstrcpy(funcItem[3]._itemName, TEXT("Base64 Encode with Unix EOL")); + lstrcpy(funcItem[4]._itemName, TEXT("Base64 Encode by line")); + lstrcpy(funcItem[5]._itemName, TEXT("Base64 Decode")); + lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode strict")); + lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line")); - lstrcpy(funcItem[7]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[8]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[8]._itemName, TEXT("Quoted-printable Encode")); - lstrcpy(funcItem[9]._itemName, TEXT("Quoted-printable Decode")); + lstrcpy(funcItem[9]._itemName, TEXT("Quoted-printable Encode")); + lstrcpy(funcItem[10]._itemName, TEXT("Quoted-printable Decode")); - lstrcpy(funcItem[10]._itemName, TEXT("-SEPARATOR-")); - - lstrcpy(funcItem[11]._itemName, TEXT("URL Encode (RFC1738)")); - lstrcpy(funcItem[12]._itemName, TEXT("URL Encode (RFC1738) by line")); - lstrcpy(funcItem[13]._itemName, TEXT("URL Encode (Extended)")); - lstrcpy(funcItem[14]._itemName, TEXT("URL Encode (Extended) by line")); - lstrcpy(funcItem[15]._itemName, TEXT("URL Encode (Full)")); - lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Full) by line")); - lstrcpy(funcItem[17]._itemName, TEXT("URL Decode")); + lstrcpy(funcItem[11]._itemName, TEXT("-SEPARATOR-")); + + lstrcpy(funcItem[12]._itemName, TEXT("URL Encode (RFC1738)")); + lstrcpy(funcItem[13]._itemName, TEXT("URL Encode (RFC1738) by line")); + lstrcpy(funcItem[14]._itemName, TEXT("URL Encode (Extended)")); + lstrcpy(funcItem[15]._itemName, TEXT("URL Encode (Extended) by line")); + lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Full)")); + lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (Full) by line")); + lstrcpy(funcItem[18]._itemName, TEXT("URL Decode")); - lstrcpy(funcItem[18]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[19]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[20]._itemName, TEXT("SAML Decode")); - lstrcpy(funcItem[20]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[21]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[21]._itemName, TEXT("About")); + lstrcpy(funcItem[22]._itemName, TEXT("About")); funcItem[0]._init2Check = false; funcItem[1]._init2Check = false; @@ -116,6 +118,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[15]._init2Check = false; funcItem[16]._init2Check = false; funcItem[17]._init2Check = false; + funcItem[18]._init2Check = false; // If you don't need the shortcut, you have to make it NULL funcItem[0]._pShKey = NULL; @@ -136,6 +139,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[15]._pShKey = NULL; funcItem[16]._pShKey = NULL; funcItem[17]._pShKey = NULL; + funcItem[18]._pShKey = NULL; } break; @@ -227,11 +231,24 @@ void convertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) { bufferLength += bufferLength / wrapLength; } + char *encodedText = new char[bufferLength + 1]; + int len; - int len = base64Encode(encodedText, selectedText, selectedLength, wrapLength, padFlag, byLineFlag); - encodedText[len] = '\0'; - + if (padFlag && byLineFlag) + { + delete[] encodedText; + std::string encodedString; + encodedString.reserve((selectedLength / 3 + 1) * 4); + len = base64EncodeWithPaddingByLine(encodedString, selectedText, selectedLength); + //encodedText = encodedString.c_str(); + encodedText = new char[len]; + encodedString.copy(encodedText, len); + } + else { + len = base64Encode(encodedText, selectedText, selectedLength, wrapLength, padFlag, byLineFlag); + encodedText[len] = '\0'; + } ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)encodedText); @@ -251,6 +268,11 @@ void convertToBase64FromAscii_pad() convertAsciiToBase64(0, true, false); } +void convertToBase64FromAscii_pad_byline() +{ + convertAsciiToBase64(0, true, true); +} + void convertToBase64FromAscii_B64Format() { convertAsciiToBase64(64, true, false); diff --git a/src/mimeTools.h b/src/mimeTools.h index a98d386..992b343 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -34,6 +34,7 @@ void convertToBase64FromAscii(); void convertToBase64FromAscii_pad(); void convertToBase64FromAscii_B64Format(); +void convertToBase64FromAscii_pad_byline(); void convertToBase64FromAscii_byline(); void convertToAsciiFromBase64(); void convertToAsciiFromBase64_strict(); From 0880fb1079e80fd5c5aff1d1ef5367d6b4f78061 Mon Sep 17 00:00:00 2001 From: aylz10 Date: Tue, 27 Aug 2024 03:31:23 +0800 Subject: [PATCH 05/23] Add URL Base64 Encode and URL Base64 Decode --- src/mimeTools.cpp | 141 +++++++++++++++++++++++++++++++++++++++++++++- src/mimeTools.h | 2 + 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 4143aa8..bf33129 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -1,4 +1,4 @@ -// This file is part of Notepad++ plugin MIME Tools project +// This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO // This program is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 23; +const int nbFunc = 25; HINSTANCE g_hInst = nullptr;; NppData nppData; @@ -67,6 +67,9 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[21]._pFunc = NULL; funcItem[22]._pFunc = about; + funcItem[23]._pFunc = urlconvertToBase64FromAscii; + funcItem[24]._pFunc = urlconvertToAsciiFromBase64; + lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); @@ -100,6 +103,11 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[22]._itemName, TEXT("About")); + lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Encode")); + lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Decode")); + + + funcItem[0]._init2Check = false; funcItem[1]._init2Check = false; funcItem[2]._init2Check = false; @@ -119,6 +127,8 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[16]._init2Check = false; funcItem[17]._init2Check = false; funcItem[18]._init2Check = false; + funcItem[22]._init2Check = false; + funcItem[23]._init2Check = false; // If you don't need the shortcut, you have to make it NULL funcItem[0]._pShKey = NULL; @@ -140,6 +150,8 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[16]._pShKey = NULL; funcItem[17]._pShKey = NULL; funcItem[18]._pShKey = NULL; + funcItem[22]._pShKey = NULL; + funcItem[23]._pShKey = NULL; } break; @@ -257,12 +269,77 @@ void convertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) } +void urlconvertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) +{ + HWND hCurrScintilla = getCurrentScintillaHandle(); + size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); + if (nbSelections > 1) return; + + size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); + if (selectedLength == 0) return; + + char* selectedText = new char[selectedLength + 1]; + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); + + size_t bufferLength = (selectedLength + 2) / 3 * 4 + 1; + if (wrapLength > 0) + { + bufferLength += bufferLength / wrapLength; + } + char* encodedText = new char[bufferLength + 1]; + + int len = base64Encode(encodedText, selectedText, selectedLength, wrapLength, padFlag, byLineFlag); + + if (len > 0) + { + //2.在BASE64的基础上进行一下的编码 + //2.1 去除尾部的"=" + if ('=' == encodedText[len - 2]) + { + encodedText[len - 2] = '\0'; + len = len - 2; + } + else if ('=' == encodedText[len - 1]) + { + encodedText[len - 1] = '\0'; + len = len - 1; + } + // 2.2)把"+"替换成"-" + // 2.3)把"/"替换成"_" + for (int i = 0; i < len; i++) + { + if ('+' == encodedText[i]) + encodedText[i] = '-'; + else if ('/' == encodedText[i]) + encodedText[i] = '_'; + } + } + + + + + encodedText[len] = '\0'; + + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)encodedText); + + delete[] selectedText; + delete[] encodedText; + +} + void convertToBase64FromAscii() { convertAsciiToBase64(0, false, false); } +void urlconvertToBase64FromAscii() +{ + urlconvertAsciiToBase64(0, false, false); +} + void convertToBase64FromAscii_pad() { convertAsciiToBase64(0, true, false); @@ -316,11 +393,71 @@ void convertBase64ToAscii(bool strictFlag, bool whitespaceReset) } + +void urlconvertBase64ToAscii(bool strictFlag, bool whitespaceReset) +{ + HWND hCurrScintilla = getCurrentScintillaHandle(); + size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); + if (nbSelections > 1) return; + size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); + if (selectedLength == 0) return; + + char* selectedText = new char[selectedLength + 1]; + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); + + char* decodedText = new char[selectedLength]; + + + + char* pTmpBuffer = (char*)malloc((selectedLength + 10) * sizeof(char)); + memcpy(pTmpBuffer, selectedText, selectedLength); + //1、把BASE64URL的编码做如下解码 + // 1)把"-"替换成"+". + // 2)把"_"替换成"/" . + for (int i = 0; unsigned(i) < selectedLength; i++) + { + if ('-' == pTmpBuffer[i]) + pTmpBuffer[i] = '+'; + else if ('_' == pTmpBuffer[i]) + pTmpBuffer[i] = '/'; + } + + + int len = base64Decode(decodedText, pTmpBuffer, selectedLength, strictFlag, whitespaceReset); + + + + + + //int len = base64Decode(decodedText, selectedText, selectedLength, strictFlag, whitespaceReset); + + if (len < 0) + { + ::MessageBox(nppData._nppHandle, TEXT("Problem!"), TEXT("Base64"), MB_OK); + } + else + { + decodedText[len] = '\0'; + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)decodedText); + } + + delete[] selectedText; + delete[] decodedText; + +} + void convertToAsciiFromBase64() { convertBase64ToAscii(false, false); } +void urlconvertToAsciiFromBase64() +{ + urlconvertBase64ToAscii(false, false); +} + void convertToAsciiFromBase64_strict() { convertBase64ToAscii(true, false); diff --git a/src/mimeTools.h b/src/mimeTools.h index 992b343..ba8225b 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -32,11 +32,13 @@ #include "url.h" void convertToBase64FromAscii(); +void urlconvertToBase64FromAscii(); void convertToBase64FromAscii_pad(); void convertToBase64FromAscii_B64Format(); void convertToBase64FromAscii_pad_byline(); void convertToBase64FromAscii_byline(); void convertToAsciiFromBase64(); +void urlconvertToAsciiFromBase64(); void convertToAsciiFromBase64_strict(); void convertToAsciiFromBase64_whitespaceReset(); void convertToQuotedPrintable(); From 2ce1c5170eb54bf81d8986e578d8cdc32a71bc52 Mon Sep 17 00:00:00 2001 From: Lichtenshtein Date: Sun, 1 Feb 2026 20:21:35 +0300 Subject: [PATCH 06/23] Add manual build --- .github/workflows/CI_build.yml | 96 ++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/.github/workflows/CI_build.yml b/.github/workflows/CI_build.yml index 37753ad..8dfdbc2 100644 --- a/.github/workflows/CI_build.yml +++ b/.github/workflows/CI_build.yml @@ -1,44 +1,94 @@ -name: CI_build +name: Build mimeTools -on: [push, pull_request] +on: +# push: +# branches: [ main, master ] +# tags: [ 'v*' ] +# pull_request: + workflow_dispatch: jobs: build: - runs-on: windows-latest strategy: + fail-fast: false matrix: - build_configuration: [Release, Debug] + build_configuration: [Release] build_platform: [x64, Win32, ARM64] steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 - name: MSBuild of plugin dll working-directory: vs.proj\ run: msbuild mimeTools.vcxproj /m /p:configuration="${{ matrix.build_configuration }}" /p:platform="${{ matrix.build_platform }}" - - name: Archive artifacts for x64 - if: matrix.build_platform == 'x64' && matrix.build_configuration == 'Release' - uses: actions/upload-artifact@v3 - with: - name: plugin_dll_x64 - path: bin64\mimeTools.dll + - name: Prepare Artifacts + shell: pwsh + run: | + $dllSrc = switch ("${{ matrix.build_platform }}") { + "x64" { "bin64\mimeTools.dll" } + "Win32" { "bin\mimeTools.dll" } + "ARM64" { "arm64\mimeTools.dll" } + } - - name: Archive artifacts for Win32 - if: matrix.build_platform == 'Win32' && matrix.build_configuration == 'Release' - uses: actions/upload-artifact@v3 - with: - name: plugin_dll_x86 - path: bin\mimeTools.dll + $tagName = if ("${{ github.ref_type }}" -eq "tag") { "${{ github.ref_name }}" } else { "latest-build" } + $archSuffix = if ("${{ matrix.build_platform }}" -eq "Win32") { "x86" } else { "${{ matrix.build_platform }}" } + $archiveName = "mimeTools_$($tagName)_$($archSuffix).7z" + + $staging = "staging\mimeTools" + New-Item -ItemType Directory -Path $staging -Force | Out-Null + Copy-Item $dllSrc -Destination $staging\ + + 7z a -t7z -mx9 $archiveName "./staging/*" + + echo "ARCHIVE_NAME=$archiveName" >> $env:GITHUB_ENV + echo "RELEASE_TAG=$tagName" >> $env:GITHUB_ENV - - name: Archive artifacts for ARM64 - if: matrix.build_platform == 'ARM64' && matrix.build_configuration == 'Release' - uses: actions/upload-artifact@v3 + - name: Upload to Workflow + uses: actions/upload-artifact@v4 with: - name: plugin_dll_arm64 - path: arm64\mimeTools.dll + name: artifacts-${{ matrix.build_platform }} + path: ${{ env.ARCHIVE_NAME }} + retention-days: 1 + + publish: + needs: build + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref_type == 'tag') + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + merge-multiple: true + + - name: Set Release Tag + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + else + echo "RELEASE_TAG=latest-build" >> $GITHUB_ENV + fi + + - name: Delete Old Release (Auto-builds only) + if: github.ref_type != 'tag' + uses: dev-drprasad/delete-tag-and-release@v1.1 + with: + tag_name: ${{ env.RELEASE_TAG }} + delete_release: true + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_TAG }} + files: "*.7z" + generate_release_notes: true + prerelease: ${{ github.ref_type != 'tag' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 72d0c3fa3c3fff12f9120583944c1f4fc8cc4ca2 Mon Sep 17 00:00:00 2001 From: Lichtenshtein Date: Sun, 1 Feb 2026 20:32:12 +0300 Subject: [PATCH 07/23] fix compilation error for Win32 --- src/b64.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/b64.cpp b/src/b64.cpp index 0441138..8415b0f 100644 --- a/src/b64.cpp +++ b/src/b64.cpp @@ -275,7 +275,7 @@ int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiSt numLines = 0; currLineLength = 0; //calculate the number of characters on each line, besides the newline character for the encoded base64 string - for (std::size_t resultIndex = 0; resultIndex < resultLength; resultIndex++) + for (std::size_t resultIndex = 0; resultIndex < static_cast(resultLength); resultIndex++) { if (resultString[resultIndex] == '\n' || resultString[resultIndex] == '\r') { From eae27931d0b35f44c1d86229d3f1ad95977a2861 Mon Sep 17 00:00:00 2001 From: Lichtenshtein Date: Sat, 1 Aug 2026 16:30:07 +0300 Subject: [PATCH 08/23] update menu --- .github/workflows/CI_build.yml | 4 ++-- src/mimeTools.cpp | 10 +++++----- vs.proj/mimeTools.vcxproj | 7 +++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI_build.yml b/.github/workflows/CI_build.yml index 8dfdbc2..ab29e94 100644 --- a/.github/workflows/CI_build.yml +++ b/.github/workflows/CI_build.yml @@ -14,14 +14,14 @@ jobs: fail-fast: false matrix: build_configuration: [Release] - build_platform: [x64, Win32, ARM64] + build_platform: [x64] # , Win32, ARM64 steps: - name: Checkout repo uses: actions/checkout@v4 - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 - name: MSBuild of plugin dll working-directory: vs.proj\ diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index bf33129..7b8f868 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -97,17 +97,17 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Encode")); + lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Decode")); + + lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[20]._itemName, TEXT("SAML Decode")); lstrcpy(funcItem[21]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[22]._itemName, TEXT("About")); - lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Encode")); - lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Decode")); - - - funcItem[0]._init2Check = false; funcItem[1]._init2Check = false; funcItem[2]._init2Check = false; diff --git a/vs.proj/mimeTools.vcxproj b/vs.proj/mimeTools.vcxproj index aeb0817..6c0dc4c 100644 --- a/vs.proj/mimeTools.vcxproj +++ b/vs.proj/mimeTools.vcxproj @@ -236,9 +236,9 @@ copy ..\readme.txt ..\bin\readme.txt Level4 - Full + MaxSpeed true - false + true WIN32;NDEBUG;_WINDOWS;_USRDLL;MIMETOOLS_EXPORTS;%(PreprocessorDefinitions) true Speed @@ -246,11 +246,14 @@ copy ..\readme.txt ..\bin\readme.txt false MultiThreaded true + true + false true Windows false + UseLinkTimeCodeGeneration true true shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) From ea5b5519e96ee1919233345f26146b9f805e7680 Mon Sep 17 00:00:00 2001 From: Lichtenshtein Date: Sat, 1 Aug 2026 16:36:42 +0300 Subject: [PATCH 09/23] update menu --- src/mimeTools.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 7b8f868..6fc453f 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -63,13 +63,16 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[18]._pFunc = convertURLDecode; funcItem[19]._pFunc = NULL; + + funcItem[23]._pFunc = urlconvertToBase64FromAscii; + funcItem[24]._pFunc = urlconvertToAsciiFromBase64; + + funcItem[25]._pFunc = NULL; + funcItem[20]._pFunc = convertSamlDecode; funcItem[21]._pFunc = NULL; funcItem[22]._pFunc = about; - funcItem[23]._pFunc = urlconvertToBase64FromAscii; - funcItem[24]._pFunc = urlconvertToAsciiFromBase64; - lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); @@ -100,7 +103,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Encode")); lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Decode")); - lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[25]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[20]._itemName, TEXT("SAML Decode")); From 924df6183f4bf5b93548bd3dea9e9ca61037f97a Mon Sep 17 00:00:00 2001 From: Lichtenshtein Date: Sat, 1 Aug 2026 16:47:13 +0300 Subject: [PATCH 10/23] update menu --- src/mimeTools.cpp | 94 ++++++++++++----------------------------------- 1 file changed, 23 insertions(+), 71 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 6fc453f..4b4c885 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -24,11 +24,10 @@ #include "url.h" #include "saml.h" - const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 25; +const int nbFunc = 26; -HINSTANCE g_hInst = nullptr;; +HINSTANCE g_hInst = nullptr; NppData nppData; FuncItem funcItem[nbFunc]; HWND g_hAboutDlg = nullptr; @@ -40,6 +39,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ case DLL_PROCESS_ATTACH: { g_hInst = (HINSTANCE)hModule; + funcItem[0]._pFunc = convertToBase64FromAscii; funcItem[1]._pFunc = convertToBase64FromAscii_pad; funcItem[2]._pFunc = convertToBase64FromAscii_pad_byline; @@ -48,12 +48,12 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[5]._pFunc = convertToAsciiFromBase64; funcItem[6]._pFunc = convertToAsciiFromBase64_strict; funcItem[7]._pFunc = convertToAsciiFromBase64_whitespaceReset; - funcItem[8]._pFunc = NULL; + funcItem[9]._pFunc = convertToQuotedPrintable; funcItem[10]._pFunc = convertToAsciiFromQuotedPrintable; - funcItem[11]._pFunc = NULL; + funcItem[12]._pFunc = convertURLMinEncode; funcItem[13]._pFunc = convertURLMinEncodeByLine; funcItem[14]._pFunc = convertURLEncodeExtended; @@ -61,18 +61,15 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[16]._pFunc = convertURLFullEncode; funcItem[17]._pFunc = convertURLFullEncodeByLine; funcItem[18]._pFunc = convertURLDecode; - funcItem[19]._pFunc = NULL; - - funcItem[23]._pFunc = urlconvertToBase64FromAscii; - funcItem[24]._pFunc = urlconvertToAsciiFromBase64; - funcItem[25]._pFunc = NULL; + funcItem[20]._pFunc = urlconvertToBase64FromAscii; + funcItem[21]._pFunc = urlconvertToAsciiFromBase64; + funcItem[22]._pFunc = NULL; - funcItem[20]._pFunc = convertSamlDecode; - - funcItem[21]._pFunc = NULL; - funcItem[22]._pFunc = about; + funcItem[23]._pFunc = convertSamlDecode; + funcItem[24]._pFunc = NULL; + funcItem[25]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); @@ -82,12 +79,10 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[5]._itemName, TEXT("Base64 Decode")); lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode strict")); lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line")); - lstrcpy(funcItem[8]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[9]._itemName, TEXT("Quoted-printable Encode")); lstrcpy(funcItem[10]._itemName, TEXT("Quoted-printable Decode")); - lstrcpy(funcItem[11]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[12]._itemName, TEXT("URL Encode (RFC1738)")); @@ -97,64 +92,21 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Full)")); lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (Full) by line")); lstrcpy(funcItem[18]._itemName, TEXT("URL Decode")); - lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Encode")); - lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Decode")); - - lstrcpy(funcItem[25]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[20]._itemName, TEXT("URL Base64 Encode")); + lstrcpy(funcItem[21]._itemName, TEXT("URL Base64 Decode")); + lstrcpy(funcItem[22]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[20]._itemName, TEXT("SAML Decode")); - - lstrcpy(funcItem[21]._itemName, TEXT("-SEPARATOR-")); - - lstrcpy(funcItem[22]._itemName, TEXT("About")); - - funcItem[0]._init2Check = false; - funcItem[1]._init2Check = false; - funcItem[2]._init2Check = false; - funcItem[3]._init2Check = false; - funcItem[4]._init2Check = false; - funcItem[5]._init2Check = false; - funcItem[6]._init2Check = false; - funcItem[7]._init2Check = false; - funcItem[8]._init2Check = false; - funcItem[9]._init2Check = false; - funcItem[10]._init2Check = false; - funcItem[11]._init2Check = false; - funcItem[12]._init2Check = false; - funcItem[13]._init2Check = false; - funcItem[14]._init2Check = false; - funcItem[15]._init2Check = false; - funcItem[16]._init2Check = false; - funcItem[17]._init2Check = false; - funcItem[18]._init2Check = false; - funcItem[22]._init2Check = false; - funcItem[23]._init2Check = false; - - // If you don't need the shortcut, you have to make it NULL - funcItem[0]._pShKey = NULL; - funcItem[1]._pShKey = NULL; - funcItem[2]._pShKey = NULL; - funcItem[3]._pShKey = NULL; - funcItem[4]._pShKey = NULL; - funcItem[5]._pShKey = NULL; - funcItem[6]._pShKey = NULL; - funcItem[7]._pShKey = NULL; - funcItem[8]._pShKey = NULL; - funcItem[9]._pShKey = NULL; - funcItem[10]._pShKey = NULL; - funcItem[11]._pShKey = NULL; - funcItem[12]._pShKey = NULL; - funcItem[13]._pShKey = NULL; - funcItem[14]._pShKey = NULL; - funcItem[15]._pShKey = NULL; - funcItem[16]._pShKey = NULL; - funcItem[17]._pShKey = NULL; - funcItem[18]._pShKey = NULL; - funcItem[22]._pShKey = NULL; - funcItem[23]._pShKey = NULL; + lstrcpy(funcItem[23]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[24]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[25]._itemName, TEXT("About")); + + for (int i = 0; i < nbFunc; i++) + { + funcItem[i]._init2Check = false; + funcItem[i]._pShKey = NULL; + } } break; From e0047aa3a8752aa1bac5c1667827012fcd1b7526 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 13:44:01 -0400 Subject: [PATCH 11/23] Optimize MIME codecs and restore CI build behavior - Optimize Base64 and Base64URL encoding and decoding - Remove unnecessary Base64URL buffer copies and translation passes - Make padded-by-line Base64 encoding single-pass - Make Quoted-Printable decoding linear-time - Reduce Scintilla selection copying - Restore push/PR Debug and Release builds for x64, Win32, and ARM64 --- .github/workflows/CI_build.yml | 92 ++--- src/b64.cpp | 612 +++++++++++++++++---------------- src/b64.h | 34 +- src/qp.cpp | 332 +++++++----------- src/qp.h | 127 +++---- 5 files changed, 531 insertions(+), 666 deletions(-) diff --git a/.github/workflows/CI_build.yml b/.github/workflows/CI_build.yml index ab29e94..07d87d0 100644 --- a/.github/workflows/CI_build.yml +++ b/.github/workflows/CI_build.yml @@ -1,11 +1,6 @@ -name: Build mimeTools +name: CI_build -on: -# push: -# branches: [ main, master ] -# tags: [ 'v*' ] -# pull_request: - workflow_dispatch: +on: [push, pull_request] jobs: build: @@ -13,9 +8,9 @@ jobs: strategy: fail-fast: false matrix: - build_configuration: [Release] - build_platform: [x64] # , Win32, ARM64 - + build_configuration: [Release, Debug] + build_platform: [x64, Win32, ARM64] + steps: - name: Checkout repo uses: actions/checkout@v4 @@ -27,68 +22,23 @@ jobs: working-directory: vs.proj\ run: msbuild mimeTools.vcxproj /m /p:configuration="${{ matrix.build_configuration }}" /p:platform="${{ matrix.build_platform }}" - - name: Prepare Artifacts - shell: pwsh - run: | - $dllSrc = switch ("${{ matrix.build_platform }}") { - "x64" { "bin64\mimeTools.dll" } - "Win32" { "bin\mimeTools.dll" } - "ARM64" { "arm64\mimeTools.dll" } - } - - $tagName = if ("${{ github.ref_type }}" -eq "tag") { "${{ github.ref_name }}" } else { "latest-build" } - $archSuffix = if ("${{ matrix.build_platform }}" -eq "Win32") { "x86" } else { "${{ matrix.build_platform }}" } - $archiveName = "mimeTools_$($tagName)_$($archSuffix).7z" - - $staging = "staging\mimeTools" - New-Item -ItemType Directory -Path $staging -Force | Out-Null - Copy-Item $dllSrc -Destination $staging\ - - 7z a -t7z -mx9 $archiveName "./staging/*" - - echo "ARCHIVE_NAME=$archiveName" >> $env:GITHUB_ENV - echo "RELEASE_TAG=$tagName" >> $env:GITHUB_ENV - - - name: Upload to Workflow + - name: Archive artifacts for x64 + if: matrix.build_platform == 'x64' && matrix.build_configuration == 'Release' uses: actions/upload-artifact@v4 with: - name: artifacts-${{ matrix.build_platform }} - path: ${{ env.ARCHIVE_NAME }} - retention-days: 1 + name: plugin_dll_x64 + path: bin64\mimeTools.dll - publish: - needs: build - if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref_type == 'tag') - runs-on: ubuntu-latest - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - - - name: Set Release Tag - run: | - if [[ "${{ github.ref_type }}" == "tag" ]]; then - echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV - else - echo "RELEASE_TAG=latest-build" >> $GITHUB_ENV - fi - - - name: Delete Old Release (Auto-builds only) - if: github.ref_type != 'tag' - uses: dev-drprasad/delete-tag-and-release@v1.1 - with: - tag_name: ${{ env.RELEASE_TAG }} - delete_release: true - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Archive artifacts for Win32 + if: matrix.build_platform == 'Win32' && matrix.build_configuration == 'Release' + uses: actions/upload-artifact@v4 + with: + name: plugin_dll_x86 + path: bin\mimeTools.dll - - name: GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - files: "*.7z" - generate_release_notes: true - prerelease: ${{ github.ref_type != 'tag' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive artifacts for ARM64 + if: matrix.build_platform == 'ARM64' && matrix.build_configuration == 'Release' + uses: actions/upload-artifact@v4 + with: + name: plugin_dll_arm64 + path: arm64\mimeTools.dll diff --git a/src/b64.cpp b/src/b64.cpp index 8415b0f..c1e33b2 100644 --- a/src/b64.cpp +++ b/src/b64.cpp @@ -1,313 +1,331 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - // Enhance Base64 features, and rewrite Base64 encode/decode implementation // Copyright 2019 by Paul Nankervis - // Copyright 2024 by ExSlam -// Modified by ExSlam on March 16, 2024 to add a function to perserve newline spacing and -// to add padding at the end of each line before the newline character where required in Base64 encoded output. +// Optimization pass prepared against ExSlam/mimetools master (2e20af5), 2026. +// SPDX-License-Identifier: GPL-3.0-or-later -#include "PluginInterface.h" -#include "mimeTools.h" #include "b64.h" -#include "qp.h" -#include "url.h" -#include "saml.h" -#include - -// Base64 encoding decoding - where 8 bit ascii is re-represented using just 64 ascii characters (plus optional padding '='). -// -// This code includes options to encode to base64 in multiple ways. For example the text lines:- -// -// If you can keep your head when all about you -// Are losing theirs and blaming it on you; -// -// Using "Encode with Unix EOL" would produce a single base64 string with line breaks after each 64 characters:- -// -// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3UNCkFy -// ZSBsb3NpbmcgdGhlaXJzIGFuZCBibGFtaW5nIGl0IG9uIHlvdTs= -// -// That would be decoded using a single base64 decode which ignored whitespace characters (the line breaks). -// -// Alternatively the same lines could be encoded using a "by line" option to encode each line of input as -// its own separate base64 string:- -// -// SWYgeW91IGNhbiBrZWVwIHlvdXIgaGVhZCB3aGVuIGFsbCBhYm91dCB5b3U -// QXJlIGxvc2luZyB0aGVpcnMgYW5kIGJsYW1pbmcgaXQgb24geW91Ow -// -// Each of these output lines could be decoded separately, or multiple lines decoded using "reset on whitespace" -// to cause base64 decoding to restart on each line - - -char base64CharSet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -int base64CharMap[] = { // base64 values or: -1 for illegal character, -2 to ignore character, and -3 for pad ('=') - -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, // & are ignored - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // is ignored - 52, 53, 54, 55 ,56, 57, 58, 59, 60, 61, -1, -1, -1, -3, -1, -1, // '=' is the pad character - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1 ,-1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 -}; - -// base64Encode simply converts ascii to base64 with appropriate wrapping and padding. Encoding is done by loading -// three ascii characters at a time into a bitField, and then extracting them as four base64 values. -// returnString is assumed to be large enough to contain the result (which is typically 4 / 3 the input size -// plus line breaks), and the function return is the length of the result -// wrapLength sets the length at which to wrap the encoded test at (not valid with byLineFlag) -// padFlag controls whether the one or two '=' pad characters are included at the end of encoding -// byLineFlag causes each input line to be encoded as a separate base64 string - -int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag) + +#include +#include + +namespace { + +constexpr char kBase64Alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +constexpr char kBase64UrlAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +constexpr int kIllegal = -1; +constexpr int kWhitespace = -2; +constexpr int kPadding = -3; + +inline int checkedLength(std::size_t length) +{ + return length > static_cast(INT_MAX) ? -1 : static_cast(length); +} + +inline int decodeValue(unsigned char c, bool urlSafe) +{ + // Preserve the legacy decoder's 7-bit lookup semantics for non-ASCII bytes. + c &= 0x7f; + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+' || (urlSafe && c == '-')) return 62; + if (c == '/' || (urlSafe && c == '_')) return 63; + if (c == '=') return kPadding; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') return kWhitespace; + return kIllegal; +} + +inline std::size_t encodeBlock(char* out, const unsigned char* in, std::size_t length, + const char* alphabet, bool pad) +{ + std::size_t input = 0; + std::size_t output = 0; + + // Hot path: three input bytes become four output bytes with no branches. + while (input + 3 <= length) + { + const std::uint32_t bits = + (static_cast(in[input]) << 16) | + (static_cast(in[input + 1]) << 8) | + static_cast(in[input + 2]); + input += 3; + + out[output++] = alphabet[(bits >> 18) & 0x3f]; + out[output++] = alphabet[(bits >> 12) & 0x3f]; + out[output++] = alphabet[(bits >> 6) & 0x3f]; + out[output++] = alphabet[bits & 0x3f]; + } + + const std::size_t remaining = length - input; + if (remaining == 1) + { + const std::uint32_t bits = static_cast(in[input]) << 16; + out[output++] = alphabet[(bits >> 18) & 0x3f]; + out[output++] = alphabet[(bits >> 12) & 0x3f]; + if (pad) + { + out[output++] = '='; + out[output++] = '='; + } + } + else if (remaining == 2) + { + const std::uint32_t bits = + (static_cast(in[input]) << 16) | + (static_cast(in[input + 1]) << 8); + out[output++] = alphabet[(bits >> 18) & 0x3f]; + out[output++] = alphabet[(bits >> 12) & 0x3f]; + out[output++] = alphabet[(bits >> 6) & 0x3f]; + if (pad) + out[output++] = '='; + } + + return output; +} + +int encodeWrapped(char* resultString, const char* asciiString, std::size_t asciiStringLength, + std::size_t wrapLength, bool padFlag) { - size_t index; // input string index - size_t lineLength = 0; // current line length - int resultLength = 0, // result string length - bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) - endOffset, // end offset index value - charValue; // character value - - for (index = 0; index < asciiStringLength; ) - { - bitField = 0; - for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) - { - charValue = (UCHAR)asciiString[index]; - if (byLineFlag && (charValue == '\n' || charValue == '\r')) - { - break; - } - index++; - bitField |= charValue << bitOffset; - } - endOffset = bitOffset + 3; // end indicator - for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) - { - if (wrapLength > 0 && lineLength++ >= wrapLength && !byLineFlag) - { - resultString[resultLength++] = '\n'; - lineLength = 1; - } - resultString[resultLength++] = base64CharSet[(bitField >> bitOffset) & 0x3f]; - } - if (byLineFlag) - { - while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) - { - resultString[resultLength++] = asciiString[index++]; - } - } - } - if (padFlag && !byLineFlag) - { - for (; bitOffset >= 0; bitOffset -= 6) - { - if (wrapLength > 0 && lineLength++ >= wrapLength) - { - resultString[resultLength++] = '\n'; - lineLength = 1; - } - resultString[resultLength++] = '='; - } - } - return resultLength; + const auto* input = reinterpret_cast(asciiString); + std::size_t inputIndex = 0; + std::size_t resultLength = 0; + std::size_t lineLength = 0; + + auto put = [&](char c) { + if (wrapLength > 0 && lineLength >= wrapLength) + { + resultString[resultLength++] = '\n'; + lineLength = 0; + } + resultString[resultLength++] = c; + ++lineLength; + }; + + while (inputIndex + 3 <= asciiStringLength) + { + const std::uint32_t bits = + (static_cast(input[inputIndex]) << 16) | + (static_cast(input[inputIndex + 1]) << 8) | + static_cast(input[inputIndex + 2]); + inputIndex += 3; + + put(kBase64Alphabet[(bits >> 18) & 0x3f]); + put(kBase64Alphabet[(bits >> 12) & 0x3f]); + put(kBase64Alphabet[(bits >> 6) & 0x3f]); + put(kBase64Alphabet[bits & 0x3f]); + } + + const std::size_t remaining = asciiStringLength - inputIndex; + if (remaining == 1) + { + const std::uint32_t bits = static_cast(input[inputIndex]) << 16; + put(kBase64Alphabet[(bits >> 18) & 0x3f]); + put(kBase64Alphabet[(bits >> 12) & 0x3f]); + if (padFlag) + { + put('='); + put('='); + } + } + else if (remaining == 2) + { + const std::uint32_t bits = + (static_cast(input[inputIndex]) << 16) | + (static_cast(input[inputIndex + 1]) << 8); + put(kBase64Alphabet[(bits >> 18) & 0x3f]); + put(kBase64Alphabet[(bits >> 12) & 0x3f]); + put(kBase64Alphabet[(bits >> 6) & 0x3f]); + if (padFlag) + put('='); + } + + return checkedLength(resultLength); } -// base64Decode converts base64 to ascii. But there are choices about what to do with illegal characters or -// malformed strings. In this version there is a strict flag to indicate that the input must be a single -// valid base64 string with no illegal characters, no extra padding, and no short segments. Otherwise -// there is best effort to decode around illegal characters which ARE preserved in the output. -// So "TWFyeQ==.aGFk.YQ.bGl0dGxl.bGFtYg==" decodes to "Mary.had.a.little.lamb" with five seperate -// base64 strings decoded, each separated by the illegal character dot. In strict mode the first dot -// would trigger a fatal error. Some other implementations choose to ignore illegal characters which -// of course has it's own issues. -// The four whitespace characters and are silently ignored unless noWhitespaceFlag -// is set. In this case whitespace is treated similar to illegal characters and base64 decoding operates -// around the white space. So "TWFyeQ== aGFk YQ bGl0dGxl bGFtYg==" would decode as "Mary had a little lamb". -// Decoding is done by loading four base64 characters at a time into a bitField, and then extracting them as -// three ascii characters. -// returnString is assumed to be large enough to contain the result (which could be the same size as the input), -// and the function return is the length of the result, or a negative value in case of an error - -int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset) +int encodeByLine(char* resultString, const char* asciiString, std::size_t asciiStringLength, + bool padFlag) { - std::size_t index; // input string index - - int resultLength = 0, // result string length - bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset, // offset into bit field (6 bit intput: 18, 12, 6, 0 -> 8 bit output: 16, 8, 0) - endOffset, // end offset index value - charValue = 0, // character value - charIndex = 0, // character index - padLength = 0; // pad characters seen - - for (index = 0; index < encodedStringLength; ) - { - bitField = 0; - for (bitOffset = 18; bitOffset >= 0 && index < encodedStringLength; ) - { - charValue = (UCHAR)encodedString[index++]; - charIndex = base64CharMap[charValue & 0x7f]; - if (charIndex >= 0) - { - if (padLength > 0 && strictFlag) - { - return -1; // **ERROR** Data after pad character - } - bitField |= charIndex << bitOffset; - bitOffset -= 6; - } - else - { - if (charIndex == -3) // -3 is Pad character '=' - { - padLength++; - if (strictFlag && bitOffset > 6) - { - return -2; // **ERROR** Pad character in wrong place - } - } - else // either -1 for illegal character or -2 for whitespace (ignored) - { - if (charIndex == -1 || whitespaceReset) - { - charIndex = -1; // Remember it as an illegal character for copy below - break; // exit loop to deal with illegal character - } - } - } - } - - if (strictFlag && bitOffset == 12) - { - return -3; // **ERROR** Single symbol block not valid - } - endOffset = bitOffset + 3; // end indicator - - for (bitOffset = 16; bitOffset > endOffset; bitOffset -= 8) - { - resultString[resultLength++] = (bitField >> bitOffset) & 0xff; - } - - if (charIndex == -1) // Was there an illegal character? - { - if (strictFlag) - { - return -4; // **ERROR** Bad character in input string - } - resultString[resultLength++] = (char)charValue; - } - } - return resultLength; + std::size_t inputIndex = 0; + std::size_t outputIndex = 0; + + while (inputIndex < asciiStringLength) + { + const std::size_t lineStart = inputIndex; + while (inputIndex < asciiStringLength && + asciiString[inputIndex] != '\r' && asciiString[inputIndex] != '\n') + { + ++inputIndex; + } + + outputIndex += encodeBlock( + resultString + outputIndex, + reinterpret_cast(asciiString + lineStart), + inputIndex - lineStart, + kBase64Alphabet, + padFlag); + + // Preserve the exact EOL byte sequence, including CRLF and consecutive blank lines. + while (inputIndex < asciiStringLength && + (asciiString[inputIndex] == '\r' || asciiString[inputIndex] == '\n')) + { + resultString[outputIndex++] = asciiString[inputIndex++]; + } + } + + return checkedLength(outputIndex); } +int decodeImpl(char* resultString, const char* encodedString, std::size_t encodedStringLength, + bool strictFlag, bool whitespaceReset, bool urlSafe) +{ + std::size_t index = 0; + std::size_t resultLength = 0; + int padLength = 0; + + while (index < encodedStringLength) + { + std::uint32_t bitField = 0; + int bitOffset = 18; + int charValue = 0; + int charIndex = 0; + + while (bitOffset >= 0 && index < encodedStringLength) + { + charValue = static_cast(encodedString[index++]); + charIndex = decodeValue(static_cast(charValue), urlSafe); + + if (charIndex >= 0) + { + if (padLength > 0 && strictFlag) + return -1; // Data after pad character. + bitField |= static_cast(charIndex) << bitOffset; + bitOffset -= 6; + } + else if (charIndex == kPadding) + { + ++padLength; + if (strictFlag && bitOffset > 6) + return -2; // Pad character in wrong place. + } + else if (charIndex == kIllegal || whitespaceReset) + { + charIndex = kIllegal; + break; + } + // Whitespace is otherwise ignored. + } + + if (strictFlag && bitOffset == 12) + return -3; // Single-symbol block is invalid. + + const int endOffset = bitOffset + 3; + for (int outputOffset = 16; outputOffset > endOffset; outputOffset -= 8) + resultString[resultLength++] = static_cast((bitField >> outputOffset) & 0xff); + + if (charIndex == kIllegal) + { + if (strictFlag) + return -4; + resultString[resultLength++] = static_cast(charValue); + } + } + + return checkedLength(resultLength); +} + +} // namespace + +int base64Encode(char* resultString, const char* asciiString, std::size_t asciiStringLength, + std::size_t wrapLength, bool padFlag, bool byLineFlag) +{ + if (byLineFlag) + return encodeByLine(resultString, asciiString, asciiStringLength, false); + + if (wrapLength == 0) + { + const std::size_t resultLength = encodeBlock( + resultString, + reinterpret_cast(asciiString), + asciiStringLength, + kBase64Alphabet, + padFlag); + return checkedLength(resultLength); + } + + return encodeWrapped(resultString, asciiString, asciiStringLength, wrapLength, padFlag); +} + +int base64Decode(char* resultString, const char* encodedString, std::size_t encodedStringLength, + bool strictFlag, bool whitespaceReset) +{ + return decodeImpl(resultString, encodedString, encodedStringLength, + strictFlag, whitespaceReset, false); +} + +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, + std::size_t asciiStringLength) +{ + // Worst case is 4/3 expansion plus the original line endings. + resultString.clear(); + resultString.reserve(((asciiStringLength + 2) / 3) * 4 + asciiStringLength / 32 + 4); + + std::size_t inputIndex = 0; + char encoded[4]; + + while (inputIndex < asciiStringLength) + { + const std::size_t lineStart = inputIndex; + while (inputIndex < asciiStringLength && + asciiString[inputIndex] != '\r' && asciiString[inputIndex] != '\n') + { + ++inputIndex; + } + + std::size_t lineIndex = lineStart; + const std::size_t lineEnd = inputIndex; + while (lineIndex < lineEnd) + { + const std::size_t chunk = (lineEnd - lineIndex >= 3) ? 3 : (lineEnd - lineIndex); + const std::size_t produced = encodeBlock( + encoded, + reinterpret_cast(asciiString + lineIndex), + chunk, + kBase64Alphabet, + true); + resultString.append(encoded, produced); + lineIndex += chunk; + } + + while (inputIndex < asciiStringLength && + (asciiString[inputIndex] == '\r' || asciiString[inputIndex] == '\n')) + { + resultString.push_back(asciiString[inputIndex++]); + } + } + + return checkedLength(resultString.size()); +} + +int base64UrlEncode(char* resultString, const char* asciiString, std::size_t asciiStringLength) +{ + const std::size_t resultLength = encodeBlock( + resultString, + reinterpret_cast(asciiString), + asciiStringLength, + kBase64UrlAlphabet, + false); + return checkedLength(resultLength); +} -int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength) +int base64UrlDecode(char* resultString, const char* encodedString, std::size_t encodedStringLength, + bool strictFlag, bool whitespaceReset) { - std::size_t index; // input string index - //size_t lineLength = 0; // current line length - int resultLength = 0; // result string length - int bitField, // assembled bit field (up to 3 ascii characters at a time) - bitOffset = -1, // offset into bit field (8 bit input: 16, 8, 0 -> 6 bit output: 18, 12, 6, 0) - endOffset, // end offset index value - charValue; // character value - - for (index = 0; index < asciiStringLength; ) - { - bitField = 0; - for (bitOffset = 16; bitOffset >= 0 && index < asciiStringLength; bitOffset -= 8) - { - charValue = (UCHAR)asciiString[index]; - if (charValue == '\n' || charValue == '\r') - { - break; - } - index++; - bitField |= charValue << bitOffset; - } - endOffset = bitOffset + 3; // end indicator - for (bitOffset = 18; bitOffset > endOffset; bitOffset -= 6) - { - resultString.insert(resultString.end(), base64CharSet[(bitField >> bitOffset) & 0x3f]); - resultLength++; - } - - while (index < asciiStringLength && (asciiString[index] == '\n' || asciiString[index] == '\r')) - { - resultString.insert(resultString.end(), asciiString[index++]); - resultLength++; - } - } - std::vector asciiLineLengths; - std::vector resultLineLengths; - std::size_t numLines = 0; - std::size_t currLineLength = 0; - //first calculate the number of characters on each line, besides the newline character for the source string - for (std::size_t asciiIndex = 0; asciiIndex < asciiStringLength; asciiIndex++) - { - if (asciiString[asciiIndex] == '\n' || asciiString[asciiIndex] == '\r') - { - numLines++; - asciiLineLengths.push_back(currLineLength + 1); - currLineLength = 0; - } - else { - currLineLength++; - } - - } - numLines = 0; - currLineLength = 0; - //calculate the number of characters on each line, besides the newline character for the encoded base64 string - for (std::size_t resultIndex = 0; resultIndex < static_cast(resultLength); resultIndex++) - { - if (resultString[resultIndex] == '\n' || resultString[resultIndex] == '\r') - { - numLines++; - resultLineLengths.push_back(currLineLength + 1); - currLineLength = 0; - } - else { - currLineLength++; - } - } - std::size_t currPos = 0uLL; - //basically the number of lines in the input and output strings; - //resultLineLengths and asciiLineLengths have the same size, so this check should be ok - for (std::size_t i = 0; i < numLines; i++) - { - //position of the character before the newline character - currPos += resultLineLengths[i] - 1; - if ((asciiLineLengths[i] - 1) % 3 != 0) - { - //length of the current line minus the line break character - std::size_t currentLineLength = resultLineLengths[i] - 1; - //use the remainder to calculate how much padding the base64 string requires. - //reduce (4 - ((currentLineLength) % 4)) to bitwise operation - int paddingLength = (4 - ((currentLineLength) & 3)); - resultString.insert(currPos, paddingLength, '='); - //Add the padding amount - currPos += paddingLength; - resultLength += paddingLength; - } - //need the +1 at the end to account for the '\n' or '\r' characters. - currPos++; - } - return resultLength; + return decodeImpl(resultString, encodedString, encodedStringLength, + strictFlag, whitespaceReset, true); } diff --git a/src/b64.h b/src/b64.h index 0bc5ed3..1f91c00 100644 --- a/src/b64.h +++ b/src/b64.h @@ -1,25 +1,21 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - - +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once -#include +#include #include -int base64Encode(char *resultString, const char *asciiString, size_t asciiStringLength, size_t wrapLength, bool padFlag, bool byLineFlag); -int base64Decode(char *resultString, const char *encodedString, size_t encodedStringLength, bool strictFlag, bool whitespaceReset); -int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, size_t asciiStringLength); +int base64Encode(char *resultString, const char *asciiString, std::size_t asciiStringLength, + std::size_t wrapLength, bool padFlag, bool byLineFlag); +int base64Decode(char *resultString, const char *encodedString, std::size_t encodedStringLength, + bool strictFlag, bool whitespaceReset); +int base64EncodeWithPaddingByLine(std::string& resultString, const char* asciiString, + std::size_t asciiStringLength); + +// RFC 4648 base64url helpers. Encoding is unpadded and uses '-'/'_' directly. +// Decoding accepts both URL-safe and standard Base64 alphabet symbols, matching +// the previous MIME Tools Base64URL wrapper's permissive behavior. +int base64UrlEncode(char *resultString, const char *asciiString, std::size_t asciiStringLength); +int base64UrlDecode(char *resultString, const char *encodedString, std::size_t encodedStringLength, + bool strictFlag, bool whitespaceReset); diff --git a/src/qp.cpp b/src/qp.cpp index d71a067..a202c6d 100644 --- a/src/qp.cpp +++ b/src/qp.cpp @@ -1,221 +1,155 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - +// Optimization pass prepared against ExSlam/mimetools master (2e20af5), 2026. +// SPDX-License-Identifier: GPL-3.0-or-later #include "qp.h" -#include -char * QuotedPrintable::encode(const char *str) +#include + +char* QuotedPrintable::encode(const char* str) { - initVar(); - size_t len = strlen(str); - - _bufLen = len * 3; - size_t nbEOL = _bufLen / QP_ENCODED_LINE_LEN_MAX; - _bufLen += nbEOL * 3; - _bufLen += 1; - - _buffer = new char[_bufLen]; - memset(_buffer, 0, _bufLen); - - for (size_t i = 0 ; i < len ; i++) - { - getQPChar(str[i]); - putQPChar(); - } - _buffer[_i] = '\0'; - - return _buffer; + return encode(str, std::strlen(str)); } -void QuotedPrintable::getQPChar(char c) +char* QuotedPrintable::encode(const char* str, std::size_t len) { - bool crlf = false; - if ((c != '=' && c > 32 && c < 127) || c == ' ' || c == ' ' || (UCHAR)c == 0x0D) - { - _chars[0] = c; - _nbChar = 1; - } - else if ((int)c == 0x0A) - { - _chars[0] = c; - _nbChar = 1; - crlf = true; - } - else - { - UCHAR uc = (UCHAR)c; - _chars[0] = '='; - _chars[1] = toChar(uc >> 4); - _chars[2] = toChar(uc & 15); - _chars[3] = '\0'; - _nbChar = 3; - } - - if (crlf) - _nbCharInLine = _nbChar; - else - _nbCharInLine += _nbChar; - - // Lines of Quoted-Printable encoded data must not be longer than 76 characters. - // To satisfy this requirement without altering the encoded text, soft line breaks may be added as desired. - // A soft line break consists of an = at the end of an encoded line, and does not appear as a line break in the decoded text. - // These soft line breaks also allow encoding text without line breaks (or containing very long lines) for an environment where line size is limited, - // such as the 1000 characters per line limit of some SMTP software, as allowed by RFC 2821. - // ref: https://en.wikipedia.org/wiki/Quoted-printable - if (_nbCharInLine >= QP_ENCODED_LINE_LEN_MAX) - { - _buffer[_i++] = '='; - _buffer[_i++] = 0x0D; - _buffer[_i++] = 0x0A; - _nbCharInLine = _nbChar; - } + initVar(); + _bufLen = len * 3 + 1; + const std::size_t nbEOL = (len * 3) / QP_ENCODED_LINE_LEN_MAX; + _bufLen += nbEOL * 3; + + _buffer = new char[_bufLen]; + + for (std::size_t i = 0; i < len; ++i) + { + getQPChar(str[i]); + putQPChar(); + } + _buffer[_i] = '\0'; + return _buffer; } - -void QuotedPrintable::putQPChar() + +void QuotedPrintable::getQPChar(char c) { - // it happens rarely, but it happens - if (_i >= _bufLen) - { - size_t oldLen = _bufLen; - _bufLen *= 2; - char *newBuf = new char[_bufLen]; - - for (size_t i = 0 ; i < oldLen ; i++) - newBuf[i] = _buffer[i]; - - char *tmp = _buffer; - _buffer = newBuf; - delete [] tmp; - } - - for (int i = 0 ; i < _nbChar ; i++) - _buffer[_i++] = _chars[i]; + bool crlf = false; + const auto uc = static_cast(c); + + if ((c != '=' && c > 32 && c < 127) || c == ' ' || c == '\t' || uc == 0x0D) + { + _chars[0] = c; + _nbChar = 1; + } + else if (uc == 0x0A) + { + _chars[0] = c; + _nbChar = 1; + crlf = true; + } + else + { + _chars[0] = '='; + _chars[1] = toChar(uc >> 4); + _chars[2] = toChar(uc & 15); + _nbChar = 3; + } + + if (crlf) + _nbCharInLine = _nbChar; + else + _nbCharInLine += _nbChar; + + if (_nbCharInLine >= QP_ENCODED_LINE_LEN_MAX) + { + if (_i + 3 >= _bufLen) + { + const std::size_t oldLen = _bufLen; + _bufLen = _bufLen * 2 + 4; + char* newBuf = new char[_bufLen]; + std::memcpy(newBuf, _buffer, oldLen); + delete [] _buffer; + _buffer = newBuf; + } + _buffer[_i++] = '='; + _buffer[_i++] = '\r'; + _buffer[_i++] = '\n'; + _nbCharInLine = _nbChar; + } } -char * QuotedPrintable::decode(const char *str) +void QuotedPrintable::putQPChar() { - initVar(); - - char *p = (char *)str; - size_t len = strlen(str); - - _bufLen = len + 1; - _buffer = new char[_bufLen]; - char* line = new char[_bufLen]; - - while (*p) - { - if (readQPLine(&p, line) == -1) - { - delete [] line; - return NULL; - } - - if (!translate(line)) - { - delete[] line; - return NULL; - } - } - _buffer[_i] = '\0'; - delete[] line; - return _buffer; + if (_i + static_cast(_nbChar) >= _bufLen) + { + const std::size_t oldLen = _bufLen; + _bufLen = _bufLen * 2 + static_cast(_nbChar) + 1; + char* newBuf = new char[_bufLen]; + std::memcpy(newBuf, _buffer, oldLen); + delete [] _buffer; + _buffer = newBuf; + } + + for (int i = 0; i < _nbChar; ++i) + _buffer[_i++] = _chars[i]; } -int QuotedPrintable::readQPLine(char **pStr, char *lineBuf) +char* QuotedPrintable::decode(const char* str) { - size_t len = strlen(*pStr); - size_t i = 0; - for (; i < len ; i++) - { - // Make decoding more flexible and less strict (76 characters length of encoded text restriction for decoding is removed). - // - // Both following encoded format - // - // =D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=D0=B8=D1=81=D1=8C - // - // and - // - // =D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0= - // =BB=D0=B8=D1=81=D1=8C - // - // are allowed and the result of both are the same. - - /* - if (i >= (QP_ENCODED_LINE_LEN_MAX + 2 + 1)) return -1; - */ - - char c = (*pStr)[i]; - if (c == 0x0D) - { - lineBuf[i] = c; - i++; - if ((i >= len) || (i >= (QP_ENCODED_LINE_LEN_MAX + 2 + 1))) return -1; - if ((*pStr)[i] != (char)0x0A) return -1; - lineBuf[i] = (*pStr)[i]; - i++; - if (i >= (QP_ENCODED_LINE_LEN_MAX + 2 + 1)) return -1; - lineBuf[i] = '\0'; - *pStr += i; - - // Make sure there's no soft line break. - if (i >= 3 && lineBuf[i-3] == '=') - { - lineBuf[i-3] = '\0'; - return int(i - 3); - } - return int(i); - } - else if (c == 0x0A) - { - return -1; - } - else - lineBuf[i] = c; - } - *pStr += i; - lineBuf[i] = '\0'; - return int(i); + return decode(str, std::strlen(str)); } -bool QuotedPrintable::translate(char *line2Trans) +char* QuotedPrintable::decode(const char* str, std::size_t len) { - size_t len = strlen(line2Trans); - for (size_t i = 0 ; i < len ; i++) - { - if (line2Trans[i] == '=') - { - if (i == len || (i + 1) == len|| (i + 2) == len) - return false; - UCHAR restoredChar; - // - - restoredChar = makeChar(line2Trans[i+1], line2Trans[i+2]); - i += 2; - - if (!restoredChar) - return false; - _buffer[_i++] = restoredChar; - } - else - { - _buffer[_i++] = line2Trans[i]; - } - } - return true; + initVar(); + _bufLen = len + 1; + _buffer = new char[_bufLen]; + + // Single linear pass. The previous implementation repeatedly called strlen() + // on the remaining suffix and copied each line into a full-size temporary buffer. + for (std::size_t i = 0; i < len; ) + { + const char c = str[i]; + + if (c == '=') + { + // Soft line break: remove =CRLF entirely. + if (i + 2 < len && str[i + 1] == '\r' && str[i + 2] == '\n') + { + i += 3; + continue; + } + + if (i + 2 >= len) + return nullptr; + + unsigned char restored = 0; + if (!makeChar(str[i + 1], str[i + 2], restored) || restored == 0) + return nullptr; // Preserve the legacy decoder's rejection of =00. + + _buffer[_i++] = static_cast(restored); + i += 3; + continue; + } + + if (c == '\r') + { + if (i + 1 >= len || str[i + 1] != '\n') + return nullptr; + _buffer[_i++] = '\r'; + _buffer[_i++] = '\n'; + i += 2; + continue; + } + + // Preserve the existing decoder's requirement that physical newlines are CRLF. + if (c == '\n') + return nullptr; + + _buffer[_i++] = c; + ++i; + } + + _buffer[_i] = '\0'; + return _buffer; } diff --git a/src/qp.h b/src/qp.h index f415897..de75fe1 100644 --- a/src/qp.h +++ b/src/qp.h @@ -1,92 +1,59 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - - +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once -#include -#include -#include +#include +#include -// "QP works by using the equals sign = as an escape character.It also limits line length to 76, as some software has limits on line length." -// ref: https://en.wikipedia.org/wiki/Quoted-printable constexpr auto QP_ENCODED_LINE_LEN_MAX = 76; class QuotedPrintable { +public: + QuotedPrintable() : _buffer(nullptr) {} + ~QuotedPrintable() { delete [] _buffer; } -public: - QuotedPrintable() : _buffer(NULL) {}; - ~QuotedPrintable() { - if (_buffer) - delete [] _buffer; - }; - char * encode(const char *str); - char * decode(const char *str); + char* encode(const char* str); + char* encode(const char* str, std::size_t len); + char* decode(const char* str); + char* decode(const char* str, std::size_t len); + std::size_t length() const { return _i; } private: - char *_buffer = nullptr; - size_t _bufLen = 0; - size_t _i = 0; - int _nbCharInLine = 0; - - int _nbChar = 0; - char _chars[4] = {}; - - int readQPLine(char **pStr, char *lineBuf); - bool translate(char *line2Trans); - - void putQPChar(); - void getQPChar(char c); - - int32_t charToDigit(char c) const { - if (c >= '0' && c <= '9') - return (c - '0'); - if (c >= 'A' && c <= 'F') - return (10 + c - 'A'); - return -1; - }; - - unsigned char makeChar(char hiChar, char loChar) const { - auto hi = charToDigit(hiChar); - if (hi == -1) - return 0; - auto lo = charToDigit(loChar); - if (lo == -1) - return 0; - return static_cast(hi << 4 | lo); - }; - - - void initVar() { - if (_buffer) - { - delete [] _buffer; - _buffer = NULL; - } - _bufLen = 0; - _i = 0; - _nbChar = 0; - _nbCharInLine = 0; - }; - - char toChar(int i) { - if (i < 10) - return (char)((int)'0'+ i); - else - return (char)((int)'A'+ i-10); - }; - + char* _buffer = nullptr; + std::size_t _bufLen = 0; + std::size_t _i = 0; + int _nbCharInLine = 0; + int _nbChar = 0; + char _chars[4] = {}; + + void putQPChar(); + void getQPChar(char c); + + int32_t charToDigit(char c) const { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return 10 + c - 'A'; + return -1; + } + + bool makeChar(char hiChar, char loChar, unsigned char& value) const { + const auto hi = charToDigit(hiChar); + const auto lo = charToDigit(loChar); + if (hi < 0 || lo < 0) return false; + value = static_cast((hi << 4) | lo); + return true; + } + + void initVar() { + delete [] _buffer; + _buffer = nullptr; + _bufLen = 0; + _i = 0; + _nbChar = 0; + _nbCharInLine = 0; + } + + static char toChar(int i) { + return i < 10 ? static_cast('0' + i) : static_cast('A' + i - 10); + } }; From 1f64a817fd98d0988a5d4cc0013021fb7406aa61 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 14:24:30 -0400 Subject: [PATCH 12/23] Updated version value to 3.2 --- src/mimeTools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mimeTools.h b/src/mimeTools.h index ba8225b..4f88584 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -20,7 +20,7 @@ #pragma once -#define VERSION_VALUE "3.1\0" +#define VERSION_VALUE "3.2\0" #define VERSION_DIGITALVALUE 3, 1, 0, 0 #define IDD_ABOUTBOX 250 From f5698414cca4e74bdb2624a878e466e5c42ded7f Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 14:34:28 -0400 Subject: [PATCH 13/23] Translated comments to English --- src/mimeTools.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 4b4c885..489f096 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -248,8 +248,8 @@ void urlconvertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) if (len > 0) { - //2.在BASE64的基础上进行一下的编码 - //2.1 去除尾部的"=" + //2. Encode based on BASE64 as follows + //2.1 Remove the trailing "=" if ('=' == encodedText[len - 2]) { encodedText[len - 2] = '\0'; @@ -260,8 +260,8 @@ void urlconvertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) encodedText[len - 1] = '\0'; len = len - 1; } - // 2.2)把"+"替换成"-" - // 2.3)把"/"替换成"_" + // 2.2) Replace "+" with "-" + // 2.3) Replace "/" with "_" for (int i = 0; i < len; i++) { if ('+' == encodedText[i]) @@ -367,9 +367,9 @@ void urlconvertBase64ToAscii(bool strictFlag, bool whitespaceReset) char* pTmpBuffer = (char*)malloc((selectedLength + 10) * sizeof(char)); memcpy(pTmpBuffer, selectedText, selectedLength); - //1、把BASE64URL的编码做如下解码 - // 1)把"-"替换成"+". - // 2)把"_"替换成"/" . + //1. Decode the BASE64URL encoding as follows: + // 1) Replace "-" with "+". + // 2) Replace "_" with "/". for (int i = 0; unsigned(i) < selectedLength; i++) { if ('-' == pTmpBuffer[i]) From 0ea4d38a77b88a685002e41787f67926596d4fb4 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 14:34:51 -0400 Subject: [PATCH 14/23] Comment cleanup --- src/b64.cpp | 2 -- src/b64.h | 2 +- src/qp.cpp | 2 -- src/qp.h | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/b64.cpp b/src/b64.cpp index c1e33b2..de7b63d 100644 --- a/src/b64.cpp +++ b/src/b64.cpp @@ -3,8 +3,6 @@ // Enhance Base64 features, and rewrite Base64 encode/decode implementation // Copyright 2019 by Paul Nankervis // Copyright 2024 by ExSlam -// Optimization pass prepared against ExSlam/mimetools master (2e20af5), 2026. -// SPDX-License-Identifier: GPL-3.0-or-later #include "b64.h" diff --git a/src/b64.h b/src/b64.h index 1f91c00..fd1c180 100644 --- a/src/b64.h +++ b/src/b64.h @@ -1,6 +1,6 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO -// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include diff --git a/src/qp.cpp b/src/qp.cpp index a202c6d..95a792d 100644 --- a/src/qp.cpp +++ b/src/qp.cpp @@ -1,7 +1,5 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO -// Optimization pass prepared against ExSlam/mimetools master (2e20af5), 2026. -// SPDX-License-Identifier: GPL-3.0-or-later #include "qp.h" diff --git a/src/qp.h b/src/qp.h index de75fe1..1d53998 100644 --- a/src/qp.h +++ b/src/qp.h @@ -1,6 +1,6 @@ // This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO -// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include From a30bbd1c7d0967fcd1bf65dd8accc0a42ac291fb Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 14:42:22 -0400 Subject: [PATCH 15/23] Update VERSION_DIGITALVALUE to match VERSION_VALUE --- src/mimeTools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mimeTools.h b/src/mimeTools.h index 4f88584..c0fa2e5 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -21,7 +21,7 @@ #pragma once #define VERSION_VALUE "3.2\0" -#define VERSION_DIGITALVALUE 3, 1, 0, 0 +#define VERSION_DIGITALVALUE 3, 2, 0, 0 #define IDD_ABOUTBOX 250 From 31951ac32d1ad6ab1ca3e035a925952062a3ad66 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 16:44:38 -0400 Subject: [PATCH 16/23] Added Mime Header Decode (RFC 2047) as requested in issue 37 --- readme.txt | 5 +- src/mimeTools.cpp | 124 +++++++++---- src/mimeTools.h | 5 +- src/rfc2047.cpp | 371 ++++++++++++++++++++++++++++++++++++++ src/rfc2047.h | 15 ++ vs.proj/mimeTools.vcxproj | 4 +- 6 files changed, 482 insertions(+), 42 deletions(-) create mode 100644 src/rfc2047.cpp create mode 100644 src/rfc2047.h diff --git a/readme.txt b/readme.txt index 4bb5294..44e55cf 100644 --- a/readme.txt +++ b/readme.txt @@ -1,8 +1,9 @@ MIME plugin for Notepad++ implements several main functionalities defined in MIME (Multipurpose Internet Mail Extensions) : 1. Base64 Encoding/Decoding 2. Quoted-printable Encoding/Decoding -3. URL Encoding/Decoding -4. SAML Decoding (though it's not part of MIME) +3. RFC 2047 encoded-word MIME header decoding +4. URL Encoding/Decoding +5. SAML Decoding (though it's not part of MIME) This plugin is under GPL. Don Ho \ No newline at end of file diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 489f096..be97b75 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -1,4 +1,4 @@ -// This file is part of Notepad++ plugin MIME Tools project +// This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO // This program is free software: you can redistribute it and/or modify @@ -23,9 +23,10 @@ #include "qp.h" #include "url.h" #include "saml.h" +#include "rfc2047.h" const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 26; +const int nbFunc = 28; HINSTANCE g_hInst = nullptr; NppData nppData; @@ -49,28 +50,25 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[6]._pFunc = convertToAsciiFromBase64_strict; funcItem[7]._pFunc = convertToAsciiFromBase64_whitespaceReset; funcItem[8]._pFunc = NULL; - funcItem[9]._pFunc = convertToQuotedPrintable; funcItem[10]._pFunc = convertToAsciiFromQuotedPrintable; funcItem[11]._pFunc = NULL; - - funcItem[12]._pFunc = convertURLMinEncode; - funcItem[13]._pFunc = convertURLMinEncodeByLine; - funcItem[14]._pFunc = convertURLEncodeExtended; - funcItem[15]._pFunc = convertURLEncodeExtendedByLine; - funcItem[16]._pFunc = convertURLFullEncode; - funcItem[17]._pFunc = convertURLFullEncodeByLine; - funcItem[18]._pFunc = convertURLDecode; - funcItem[19]._pFunc = NULL; - - funcItem[20]._pFunc = urlconvertToBase64FromAscii; - funcItem[21]._pFunc = urlconvertToAsciiFromBase64; - funcItem[22]._pFunc = NULL; - - funcItem[23]._pFunc = convertSamlDecode; + funcItem[12]._pFunc = convertMimeHeaderDecode; + funcItem[13]._pFunc = NULL; + funcItem[14]._pFunc = convertURLMinEncode; + funcItem[15]._pFunc = convertURLMinEncodeByLine; + funcItem[16]._pFunc = convertURLEncodeExtended; + funcItem[17]._pFunc = convertURLEncodeExtendedByLine; + funcItem[18]._pFunc = convertURLFullEncode; + funcItem[19]._pFunc = convertURLFullEncodeByLine; + funcItem[20]._pFunc = convertURLDecode; + funcItem[21]._pFunc = NULL; + funcItem[22]._pFunc = urlconvertToBase64FromAscii; + funcItem[23]._pFunc = urlconvertToAsciiFromBase64; funcItem[24]._pFunc = NULL; - funcItem[25]._pFunc = about; - +\n funcItem[25]._pFunc = convertSamlDecode; + funcItem[26]._pFunc = NULL; + funcItem[27]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); lstrcpy(funcItem[2]._itemName, TEXT("Base64 Encode with padding by line")); @@ -80,27 +78,25 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode strict")); lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line")); lstrcpy(funcItem[8]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[9]._itemName, TEXT("Quoted-printable Encode")); lstrcpy(funcItem[10]._itemName, TEXT("Quoted-printable Decode")); lstrcpy(funcItem[11]._itemName, TEXT("-SEPARATOR-")); - - lstrcpy(funcItem[12]._itemName, TEXT("URL Encode (RFC1738)")); - lstrcpy(funcItem[13]._itemName, TEXT("URL Encode (RFC1738) by line")); - lstrcpy(funcItem[14]._itemName, TEXT("URL Encode (Extended)")); - lstrcpy(funcItem[15]._itemName, TEXT("URL Encode (Extended) by line")); - lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Full)")); - lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (Full) by line")); - lstrcpy(funcItem[18]._itemName, TEXT("URL Decode")); - lstrcpy(funcItem[19]._itemName, TEXT("-SEPARATOR-")); - - lstrcpy(funcItem[20]._itemName, TEXT("URL Base64 Encode")); - lstrcpy(funcItem[21]._itemName, TEXT("URL Base64 Decode")); - lstrcpy(funcItem[22]._itemName, TEXT("-SEPARATOR-")); - - lstrcpy(funcItem[23]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[12]._itemName, TEXT("MIME Header Decode (RFC 2047)")); + lstrcpy(funcItem[13]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[14]._itemName, TEXT("URL Encode (RFC1738)")); + lstrcpy(funcItem[15]._itemName, TEXT("URL Encode (RFC1738) by line")); + lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Extended)")); + lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (Extended) by line")); + lstrcpy(funcItem[18]._itemName, TEXT("URL Encode (Full)")); + lstrcpy(funcItem[19]._itemName, TEXT("URL Encode (Full) by line")); + lstrcpy(funcItem[20]._itemName, TEXT("URL Decode")); + lstrcpy(funcItem[21]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[22]._itemName, TEXT("URL Base64 Encode")); + lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Decode")); lstrcpy(funcItem[24]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[25]._itemName, TEXT("About")); +\n lstrcpy(funcItem[25]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[26]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[27]._itemName, TEXT("About")); for (int i = 0; i < nbFunc; i++) { @@ -423,6 +419,60 @@ void convertToAsciiFromBase64_whitespaceReset() convertBase64ToAscii(false, true); } + +void convertMimeHeaderDecode() +{ + HWND hCurrScintilla = getCurrentScintillaHandle(); + const size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); + if (nbSelections > 1) return; + + size_t start = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONSTART, 0, 0); + size_t end = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONEND, 0, 0); + if (end < start) + { + const size_t tmp = start; + start = end; + end = tmp; + } + const size_t selectedLength = end - start; + if (selectedLength == 0) return; + + char* selectedText = new char[selectedLength + 1]; + ::SendMessage(hCurrScintilla, SCI_SETTARGETSTART, start, 0); + ::SendMessage(hCurrScintilla, SCI_SETTARGETEND, end, 0); + ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, reinterpret_cast(selectedText)); + + std::string decodedText; + const unsigned int documentCodePage = static_cast( + ::SendMessage(hCurrScintilla, SCI_GETCODEPAGE, 0, 0)); + const Rfc2047DecodeResult result = decodeRfc2047Header( + selectedText, selectedLength, documentCodePage, decodedText); + + if (result.decodedWords == 0) + { + ::MessageBox(nppData._nppHandle, + TEXT("No valid RFC 2047 encoded-words were found in the selection."), + TEXT("MIME Header Decode"), MB_OK | MB_ICONINFORMATION); + } + else + { + ::SendMessage(hCurrScintilla, SCI_SETTARGETSTART, start, 0); + ::SendMessage(hCurrScintilla, SCI_SETTARGETEND, end, 0); + ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, decodedText.size(), + reinterpret_cast(decodedText.data())); + ::SendMessage(hCurrScintilla, SCI_SETSEL, start, start + decodedText.size()); + + if (result.skippedWords != 0) + { + ::MessageBox(nppData._nppHandle, + TEXT("Some malformed, unsupported, or unrepresentable encoded-words were left unchanged."), + TEXT("MIME Header Decode"), MB_OK | MB_ICONWARNING); + } + } + + delete[] selectedText; +} + void convertURLMinEncode() { convertURLEncode (UrlEncodeMethod::RFC1738); diff --git a/src/mimeTools.h b/src/mimeTools.h index c0fa2e5..c57c4be 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -20,8 +20,8 @@ #pragma once -#define VERSION_VALUE "3.2\0" -#define VERSION_DIGITALVALUE 3, 2, 0, 0 +#define VERSION_VALUE "3.3\0" +#define VERSION_DIGITALVALUE 3, 3, 0, 0 #define IDD_ABOUTBOX 250 @@ -43,6 +43,7 @@ void convertToAsciiFromBase64_strict(); void convertToAsciiFromBase64_whitespaceReset(); void convertToQuotedPrintable(); void convertToAsciiFromQuotedPrintable(); +void convertMimeHeaderDecode(); void convertURLMinEncode(); void convertURLEncodeExtended(); void convertURLFullEncode(); diff --git a/src/rfc2047.cpp b/src/rfc2047.cpp new file mode 100644 index 0000000..b2b3165 --- /dev/null +++ b/src/rfc2047.cpp @@ -0,0 +1,371 @@ +// RFC 2047 encoded-word decoder for MIME Tools. +// Designed for a single linear pass over the selected text and reuses the +// plugin's optimized Base64 implementation for "B" encoded-words. + +#include "rfc2047.h" +#include "b64.h" + +#include +#include +#include + +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "oleaut32.lib") +#pragma comment(lib, "uuid.lib") + +#include +#include +#include +#include + +namespace +{ + +inline char asciiLower(char c) +{ + return (c >= 'A' && c <= 'Z') ? static_cast(c + ('a' - 'A')) : c; +} + +bool asciiEquals(const char* text, std::size_t length, const char* literal) +{ + std::size_t i = 0; + for (; i < length && literal[i] != '\0'; ++i) + { + if (asciiLower(text[i]) != asciiLower(literal[i])) + return false; + } + return i == length && literal[i] == '\0'; +} + +bool isLinearWhitespace(const char* text, std::size_t length) +{ + for (std::size_t i = 0; i < length; ++i) + { + const char c = text[i]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') + return false; + } + return true; +} + +int hexValue(unsigned char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return -1; +} + +bool decodeQWord(const char* encoded, std::size_t length, std::string& decoded) +{ + decoded.clear(); + decoded.reserve(length); + + for (std::size_t i = 0; i < length; ++i) + { + const unsigned char c = static_cast(encoded[i]); + if (c == '_') + { + decoded.push_back(' '); + } + else if (c == '=') + { + if (i + 2 >= length) + return false; + const int hi = hexValue(static_cast(encoded[i + 1])); + const int lo = hexValue(static_cast(encoded[i + 2])); + if (hi < 0 || lo < 0) + return false; + decoded.push_back(static_cast((hi << 4) | lo)); + i += 2; + } + else + { + // RFC 2047 encoded-text cannot contain SPACE, TAB, CR, LF or '?'. + // Rejecting these here keeps malformed words from being rewritten. + if (c <= 0x20 || c == '?' || c == 0x7f) + return false; + decoded.push_back(static_cast(c)); + } + } + + return true; +} + +bool decodeBWord(const char* encoded, std::size_t length, std::string& decoded) +{ + if (length == 0 || length > static_cast(INT_MAX)) + return false; + + // RFC 2047 B encoded-text cannot contain whitespace or arbitrary bytes. + // Validate before entering the permissive legacy Base64 lookup table. + for (std::size_t i = 0; i < length; ++i) + { + const unsigned char c = static_cast(encoded[i]); + const bool valid = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '+' || c == '/' || c == '='; + if (!valid) + return false; + } + + decoded.assign(length, '\0'); + const int decodedLength = base64Decode(&decoded[0], encoded, length, true, false); + if (decodedLength < 0) + { + decoded.clear(); + return false; + } + + decoded.resize(static_cast(decodedLength)); + return true; +} + +class CharsetResolver +{ +public: + CharsetResolver() : _multiLanguage(nullptr), _comInitialized(false), _triedCom(false) {} + + ~CharsetResolver() + { + if (_multiLanguage != nullptr) + _multiLanguage->Release(); + if (_comInitialized) + ::CoUninitialize(); + } + + UINT resolve(const char* charset, std::size_t length) + { + // Fast paths for the overwhelmingly common MIME charsets. These avoid + // COM/MIME-database setup for the normal UTF-8 case. + if (asciiEquals(charset, length, "utf-8") || asciiEquals(charset, length, "utf8")) + return CP_UTF8; + if (asciiEquals(charset, length, "us-ascii") || asciiEquals(charset, length, "ascii")) + return 20127; + if (asciiEquals(charset, length, "iso-8859-1") || + asciiEquals(charset, length, "latin1") || asciiEquals(charset, length, "latin-1")) + return 28591; + if (asciiEquals(charset, length, "windows-1252") || asciiEquals(charset, length, "cp1252")) + return 1252; + + // Fall back to Windows' MIME charset database for less common aliases + // and legacy encodings rather than carrying a large hand-maintained map. + if (!ensureMultiLanguage() || length > static_cast(UINT_MAX)) + return 0; + + BSTR charsetName = ::SysAllocStringLen(nullptr, static_cast(length)); + if (charsetName == nullptr) + return 0; + + for (std::size_t i = 0; i < length; ++i) + charsetName[i] = static_cast(charset[i]); + + MIMECSETINFO info = {}; + const HRESULT hr = _multiLanguage->GetCharsetInfo(charsetName, &info); + ::SysFreeString(charsetName); + return SUCCEEDED(hr) ? (info.uiInternetEncoding != 0 ? info.uiInternetEncoding : info.uiCodePage) : 0; + } + +private: + bool ensureMultiLanguage() + { + if (_triedCom) + return _multiLanguage != nullptr; + + _triedCom = true; + const HRESULT init = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (init == S_OK || init == S_FALSE) + _comInitialized = true; + else if (init != RPC_E_CHANGED_MODE) + return false; + + const HRESULT hr = ::CoCreateInstance( + CLSID_CMultiLanguage, nullptr, CLSCTX_INPROC_SERVER, + IID_IMultiLanguage2, reinterpret_cast(&_multiLanguage)); + return SUCCEEDED(hr) && _multiLanguage != nullptr; + } + + IMultiLanguage2* _multiLanguage; + bool _comInitialized; + bool _triedCom; +}; + +bool appendConverted(std::string& output, const std::string& decodedBytes, + UINT sourceCodePage, UINT targetCodePage) +{ + if (decodedBytes.empty()) + return true; + + if (targetCodePage == 0) + targetCodePage = ::GetACP(); + + // No transcode needed. This is the hot path for UTF-8 mail in a UTF-8 tab. + if (sourceCodePage == targetCodePage) + { + output.append(decodedBytes); + return true; + } + + if (decodedBytes.size() > static_cast(INT_MAX)) + return false; + + const DWORD inFlags = sourceCodePage == CP_UTF8 ? MB_ERR_INVALID_CHARS : 0; + const int wideLength = ::MultiByteToWideChar( + sourceCodePage, inFlags, decodedBytes.data(), + static_cast(decodedBytes.size()), nullptr, 0); + if (wideLength <= 0) + return false; + + std::vector wide(static_cast(wideLength)); + if (::MultiByteToWideChar(sourceCodePage, inFlags, decodedBytes.data(), + static_cast(decodedBytes.size()), &wide[0], wideLength) != wideLength) + return false; + + if (targetCodePage == CP_UTF8 || targetCodePage == 54936) + { + const int outLength = ::WideCharToMultiByte( + targetCodePage, 0, &wide[0], wideLength, nullptr, 0, nullptr, nullptr); + if (outLength <= 0) + return false; + + const std::size_t oldSize = output.size(); + output.resize(oldSize + static_cast(outLength)); + if (::WideCharToMultiByte(targetCodePage, 0, &wide[0], wideLength, + &output[oldSize], outLength, nullptr, nullptr) != outLength) + { + output.resize(oldSize); + return false; + } + return true; + } + + BOOL usedDefault = FALSE; + const int outLength = ::WideCharToMultiByte( + targetCodePage, WC_NO_BEST_FIT_CHARS, &wide[0], wideLength, + nullptr, 0, nullptr, &usedDefault); + if (outLength <= 0 || usedDefault) + return false; + + const std::size_t oldSize = output.size(); + output.resize(oldSize + static_cast(outLength)); + usedDefault = FALSE; + if (::WideCharToMultiByte(targetCodePage, WC_NO_BEST_FIT_CHARS, &wide[0], wideLength, + &output[oldSize], outLength, nullptr, &usedDefault) != outLength || usedDefault) + { + output.resize(oldSize); + return false; + } + return true; +} + +std::size_t findEncodedWordStart(const char* input, std::size_t from, std::size_t length) +{ + while (from + 1 < length) + { + const void* found = std::memchr(input + from, '=', length - from - 1); + if (found == nullptr) + return length; + + const std::size_t pos = static_cast(found) - input; + if (input[pos + 1] == '?') + return pos; + from = pos + 1; + } + return length; +} + +std::size_t findQuestion(const char* input, std::size_t from, std::size_t length) +{ + const void* found = std::memchr(input + from, '?', length - from); + return found == nullptr ? length : static_cast(found) - input; +} + +bool tryDecodeWord(const char* input, std::size_t length, std::size_t start, + UINT targetCodePage, CharsetResolver& resolver, + std::string& converted, std::size_t& wordEnd) +{ + const std::size_t charsetEnd = findQuestion(input, start + 2, length); + if (charsetEnd == length || charsetEnd == start + 2) + return false; + + const std::size_t encodingEnd = findQuestion(input, charsetEnd + 1, length); + if (encodingEnd == length || encodingEnd != charsetEnd + 2) + return false; + + std::size_t textEnd = encodingEnd + 1; + while (textEnd + 1 < length && !(input[textEnd] == '?' && input[textEnd + 1] == '=')) + ++textEnd; + if (textEnd + 1 >= length || textEnd == encodingEnd + 1) + return false; + + const char encoding = asciiLower(input[charsetEnd + 1]); + if (encoding != 'b' && encoding != 'q') + return false; + + const UINT sourceCodePage = resolver.resolve(input + start + 2, charsetEnd - (start + 2)); + if (sourceCodePage == 0) + return false; + + const char* encodedText = input + encodingEnd + 1; + const std::size_t encodedLength = textEnd - (encodingEnd + 1); + + std::string decodedBytes; + const bool decoded = encoding == 'b' + ? decodeBWord(encodedText, encodedLength, decodedBytes) + : decodeQWord(encodedText, encodedLength, decodedBytes); + if (!decoded) + return false; + + converted.clear(); + converted.reserve(decodedBytes.size()); + if (!appendConverted(converted, decodedBytes, sourceCodePage, targetCodePage)) + return false; + + wordEnd = textEnd + 2; + return true; +} + +} // namespace + +Rfc2047DecodeResult decodeRfc2047Header(const char* input, std::size_t inputLength, + unsigned int targetCodePage, std::string& output) +{ + Rfc2047DecodeResult result = { 0, 0 }; + output.clear(); + output.reserve(inputLength); + + CharsetResolver resolver; + std::size_t cursor = 0; + bool previousWasEncodedWord = false; + + while (cursor < inputLength) + { + const std::size_t wordStart = findEncodedWordStart(input, cursor, inputLength); + if (wordStart == inputLength) + break; + + std::string converted; + std::size_t wordEnd = wordStart; + if (tryDecodeWord(input, inputLength, wordStart, targetCodePage, resolver, converted, wordEnd)) + { + const std::size_t betweenLength = wordStart - cursor; + if (!(previousWasEncodedWord && isLinearWhitespace(input + cursor, betweenLength))) + output.append(input + cursor, betweenLength); + + output.append(converted); + cursor = wordEnd; + previousWasEncodedWord = true; + ++result.decodedWords; + continue; + } + + // Preserve a malformed/unsupported candidate exactly and advance past + // the marker so the scan remains linear even on hostile input. + output.append(input + cursor, wordStart + 2 - cursor); + cursor = wordStart + 2; + previousWasEncodedWord = false; + ++result.skippedWords; + } + + output.append(input + cursor, inputLength - cursor); + return result; +} diff --git a/src/rfc2047.h b/src/rfc2047.h new file mode 100644 index 0000000..d21a5ac --- /dev/null +++ b/src/rfc2047.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +struct Rfc2047DecodeResult +{ + std::size_t decodedWords; + std::size_t skippedWords; +}; + +// Decode RFC 2047 encoded-words in a selected header fragment. +// targetCodePage is Scintilla's current document code page (0 means system ANSI). +Rfc2047DecodeResult decodeRfc2047Header(const char* input, std::size_t inputLength, + unsigned int targetCodePage, std::string& output); diff --git a/vs.proj/mimeTools.vcxproj b/vs.proj/mimeTools.vcxproj index 6c0dc4c..0f972df 100644 --- a/vs.proj/mimeTools.vcxproj +++ b/vs.proj/mimeTools.vcxproj @@ -1,4 +1,4 @@ - + @@ -29,6 +29,7 @@ + @@ -38,6 +39,7 @@ + From 4158f61b497fb66d7a8ef12b69ac876d5b499803 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 18:08:05 -0400 Subject: [PATCH 17/23] fixed buffer overflow issues --- src/mimeTools.cpp | 426 +++++++++++++++++++++++++++++++--------------- 1 file changed, 292 insertions(+), 134 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index be97b75..243553c 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -1,4 +1,4 @@ -// This file is part of Notepad++ plugin MIME Tools project +// This file is part of Notepad++ plugin MIME Tools project // Copyright (C)2023 Don HO // This program is free software: you can redistribute it and/or modify @@ -17,6 +17,9 @@ // Enhance Base64 features, and rewrite Base64 encode/decode implementation // Copyright 2019 by Paul Nankervis +#include +#include + #include "PluginInterface.h" #include "mimeTools.h" #include "b64.h" @@ -66,7 +69,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[22]._pFunc = urlconvertToBase64FromAscii; funcItem[23]._pFunc = urlconvertToAsciiFromBase64; funcItem[24]._pFunc = NULL; -\n funcItem[25]._pFunc = convertSamlDecode; + funcItem[25]._pFunc = convertSamlDecode; funcItem[26]._pFunc = NULL; funcItem[27]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); @@ -94,7 +97,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[22]._itemName, TEXT("URL Base64 Encode")); lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Decode")); lstrcpy(funcItem[24]._itemName, TEXT("-SEPARATOR-")); -\n lstrcpy(funcItem[25]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[25]._itemName, TEXT("SAML Decode")); lstrcpy(funcItem[26]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[27]._itemName, TEXT("About")); @@ -179,108 +182,225 @@ HWND getCurrentScintillaHandle() void convertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) { HWND hCurrScintilla = getCurrentScintillaHandle(); - size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); - if (nbSelections > 1) return; - size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); - if (selectedLength == 0) return; + size_t nbSelections = + ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); - char *selectedText = new char[selectedLength + 1]; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); + if (nbSelections > 1) + return; - size_t bufferLength = (selectedLength + 2) / 3 * 4 + 1; - if (wrapLength > 0) - { - bufferLength += bufferLength / wrapLength; - } + size_t selectedLength = + ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); + + if (selectedLength == 0) + return; - char *encodedText = new char[bufferLength + 1]; - int len; + std::vector selectedText(selectedLength + 1); - if (padFlag && byLineFlag) + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage( + hCurrScintilla, + SCI_GETTARGETTEXT, + 0, + reinterpret_cast(selectedText.data()) + ); + + /* + * Padded-per-line already uses the std::string implementation. + * Do not allocate/copy another temporary output buffer. + */ + if (padFlag && byLineFlag) { - delete[] encodedText; std::string encodedString; - encodedString.reserve((selectedLength / 3 + 1) * 4); - len = base64EncodeWithPaddingByLine(encodedString, selectedText, selectedLength); - //encodedText = encodedString.c_str(); - encodedText = new char[len]; - encodedString.copy(encodedText, len); + + int len = base64EncodeWithPaddingByLine( + encodedString, + selectedText.data(), + selectedLength + ); + + if (len < 0) + { + ::MessageBox( + nppData._nppHandle, + TEXT("Input is too large to encode."), + TEXT("Base64"), + MB_OK | MB_ICONERROR + ); + return; + } + + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage( + hCurrScintilla, + SCI_REPLACETARGET, + len, + reinterpret_cast(encodedString.data()) + ); + + return; } - else { - len = base64Encode(encodedText, selectedText, selectedLength, wrapLength, padFlag, byLineFlag); - encodedText[len] = '\0'; + + size_t bufferLength; + + if (byLineFlag) + { + /* + * Unpadded Base64 encoding of a one-byte line produces + * two output bytes. CR/LF bytes are copied unchanged. + * + * Therefore 2 * input length is a safe upper bound. + */ + bufferLength = selectedLength * 2 + 1; } - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)encodedText); + else + { + /* + * Standard padded Base64 upper bound. + */ + const size_t baseLength = + ((selectedLength + 2) / 3) * 4; - delete[] selectedText; - delete[] encodedText; + size_t lineBreaks = 0; -} + if (wrapLength > 0 && baseLength > 0) + lineBreaks = (baseLength - 1) / wrapLength; -void urlconvertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) -{ - HWND hCurrScintilla = getCurrentScintillaHandle(); - size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); - if (nbSelections > 1) return; + bufferLength = + baseLength + + lineBreaks + + 1; + } - size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); - if (selectedLength == 0) return; + std::vector encodedText(bufferLength); - char* selectedText = new char[selectedLength + 1]; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); + int len = base64Encode( + encodedText.data(), + selectedText.data(), + selectedLength, + wrapLength, + padFlag, + byLineFlag + ); - size_t bufferLength = (selectedLength + 2) / 3 * 4 + 1; - if (wrapLength > 0) + if (len < 0 || + static_cast(len) > encodedText.size()) { - bufferLength += bufferLength / wrapLength; + ::MessageBox( + nppData._nppHandle, + TEXT("Base64 output exceeded the allocated buffer."), + TEXT("Base64"), + MB_OK | MB_ICONERROR + ); + return; } - char* encodedText = new char[bufferLength + 1]; - int len = base64Encode(encodedText, selectedText, selectedLength, wrapLength, padFlag, byLineFlag); + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage( + hCurrScintilla, + SCI_REPLACETARGET, + len, + reinterpret_cast(encodedText.data()) + ); +} + +void urlconvertAsciiToBase64(size_t /*wrapLength*/, bool /*padFlag*/, bool /*byLineFlag*/) +{ + HWND hCurrScintilla = getCurrentScintillaHandle(); - if (len > 0) + const size_t nbSelections = + ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); + + if (nbSelections > 1) + return; + + const size_t selectedLength = + ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); + + if (selectedLength == 0) + return; + + // SCI_GETSELTEXT appends a terminating NUL, so allocate one extra byte. + std::vector selectedText(selectedLength + 1); + + ::SendMessage( + hCurrScintilla, + SCI_GETSELTEXT, + 0, + reinterpret_cast(selectedText.data()) + ); + + /* + * Unpadded Base64URL output length: + * + * 3 input bytes -> 4 output bytes + * 1 remaining byte -> 2 output bytes + * 2 remaining bytes -> 3 output bytes + */ + const size_t fullGroups = selectedLength / 3; + const size_t remainder = selectedLength % 3; + const size_t tailLength = + remainder == 0 ? 0 : remainder + 1; + + /* + * base64UrlEncode() returns int, so reject an input whose encoded + * result cannot be represented by that API. + * + * This check is performed before multiplying by 4, so the size_t + * calculation cannot overflow. + */ + if (fullGroups > + (static_cast(INT_MAX) - tailLength) / 4) { - //2. Encode based on BASE64 as follows - //2.1 Remove the trailing "=" - if ('=' == encodedText[len - 2]) - { - encodedText[len - 2] = '\0'; - len = len - 2; - } - else if ('=' == encodedText[len - 1]) - { - encodedText[len - 1] = '\0'; - len = len - 1; - } - // 2.2) Replace "+" with "-" - // 2.3) Replace "/" with "_" - for (int i = 0; i < len; i++) - { - if ('+' == encodedText[i]) - encodedText[i] = '-'; - else if ('/' == encodedText[i]) - encodedText[i] = '_'; - } + ::MessageBox( + nppData._nppHandle, + TEXT("The selected text is too large to Base64URL encode."), + TEXT("Base64URL"), + MB_OK | MB_ICONERROR + ); + + return; } + const size_t encodedLength = + fullGroups * 4 + tailLength; + std::vector encodedText(encodedLength); + const int len = base64UrlEncode( + encodedText.data(), + selectedText.data(), + selectedLength + ); - encodedText[len] = '\0'; - - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)encodedText); + if (len < 0 || + static_cast(len) != encodedLength) + { + ::MessageBox( + nppData._nppHandle, + TEXT("Base64URL encoding failed."), + TEXT("Base64URL"), + MB_OK | MB_ICONERROR + ); + + return; + } - delete[] selectedText; - delete[] encodedText; + ::SendMessage( + hCurrScintilla, + SCI_TARGETFROMSELECTION, + 0, + 0 + ); + ::SendMessage( + hCurrScintilla, + SCI_REPLACETARGET, + static_cast(len), + reinterpret_cast(encodedText.data()) + ); } - void convertToBase64FromAscii() { convertAsciiToBase64(0, false, false); @@ -315,33 +435,77 @@ void convertToBase64FromAscii_byline() void convertBase64ToAscii(bool strictFlag, bool whitespaceReset) { HWND hCurrScintilla = getCurrentScintillaHandle(); - size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); - if (nbSelections > 1) return; - size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); - if (selectedLength == 0) return; - char *selectedText = new char[selectedLength + 1]; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); - - char *decodedText = new char[selectedLength]; - - int len = base64Decode(decodedText, selectedText, selectedLength, strictFlag, whitespaceReset); + size_t nbSelections = ::SendMessage( + hCurrScintilla, + SCI_GETSELECTIONS, + 0, + 0 + ); + + if (nbSelections > 1) + return; + + size_t selectedLength = ::SendMessage( + hCurrScintilla, + SCI_GETSELTEXT, + 0, + 0 + ); + + if (selectedLength == 0) + return; + + std::vector selectedText(selectedLength + 1); + std::vector decodedText(selectedLength + 1); + + ::SendMessage( + hCurrScintilla, + SCI_TARGETFROMSELECTION, + 0, + 0 + ); + + ::SendMessage( + hCurrScintilla, + SCI_GETTARGETTEXT, + 0, + reinterpret_cast(selectedText.data()) + ); + + int len = base64Decode( + decodedText.data(), + selectedText.data(), + selectedLength, + strictFlag, + whitespaceReset + ); if (len < 0) { - ::MessageBox(nppData._nppHandle, TEXT("Problem!"), TEXT("Base64"), MB_OK); - } - else - { - decodedText[len] = '\0'; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)decodedText); + ::MessageBox( + nppData._nppHandle, + TEXT("Problem!"), + TEXT("Base64"), + MB_OK | MB_ICONERROR + ); + + return; } - delete[] selectedText; - delete[] decodedText; + ::SendMessage( + hCurrScintilla, + SCI_TARGETFROMSELECTION, + 0, + 0 + ); + ::SendMessage( + hCurrScintilla, + SCI_REPLACETARGET, + len, + reinterpret_cast(decodedText.data()) + ); } @@ -350,53 +514,47 @@ void urlconvertBase64ToAscii(bool strictFlag, bool whitespaceReset) HWND hCurrScintilla = getCurrentScintillaHandle(); size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); if (nbSelections > 1) return; + size_t selectedLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); if (selectedLength == 0) return; - char* selectedText = new char[selectedLength + 1]; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_GETTARGETTEXT, 0, (LPARAM)selectedText); - - char* decodedText = new char[selectedLength]; - - - - char* pTmpBuffer = (char*)malloc((selectedLength + 10) * sizeof(char)); - memcpy(pTmpBuffer, selectedText, selectedLength); - //1. Decode the BASE64URL encoding as follows: - // 1) Replace "-" with "+". - // 2) Replace "_" with "/". - for (int i = 0; unsigned(i) < selectedLength; i++) - { - if ('-' == pTmpBuffer[i]) - pTmpBuffer[i] = '+'; - else if ('_' == pTmpBuffer[i]) - pTmpBuffer[i] = '/'; - } - - - int len = base64Decode(decodedText, pTmpBuffer, selectedLength, strictFlag, whitespaceReset); - - + std::vector selectedText(selectedLength + 1); + std::vector decodedText(selectedLength + 1); - - - //int len = base64Decode(decodedText, selectedText, selectedLength, strictFlag, whitespaceReset); + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage( + hCurrScintilla, + SCI_GETTARGETTEXT, + 0, + reinterpret_cast(selectedText.data()) + ); + + int len = base64UrlDecode( + decodedText.data(), + selectedText.data(), + selectedLength, + strictFlag, + whitespaceReset + ); if (len < 0) { - ::MessageBox(nppData._nppHandle, TEXT("Problem!"), TEXT("Base64"), MB_OK); - } - else - { - decodedText[len] = '\0'; - ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); - ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)decodedText); + ::MessageBox( + nppData._nppHandle, + TEXT("Problem!"), + TEXT("Base64URL"), + MB_OK + ); + return; } - delete[] selectedText; - delete[] decodedText; - + ::SendMessage(hCurrScintilla, SCI_TARGETFROMSELECTION, 0, 0); + ::SendMessage( + hCurrScintilla, + SCI_REPLACETARGET, + len, + reinterpret_cast(decodedText.data()) + ); } void convertToAsciiFromBase64() From b8b56f6d63c26908f9edc821e59ccd4624a9d8f9 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 18:08:40 -0400 Subject: [PATCH 18/23] removed unused declarations --- src/Scintilla.h | 14 +++++++++----- src/tinf.h | 20 +++++++------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Scintilla.h b/src/Scintilla.h index 95fcf9b..178d93a 100644 --- a/src/Scintilla.h +++ b/src/Scintilla.h @@ -15,11 +15,15 @@ extern "C" { #endif -#if defined(_WIN32) -/* Return false on failure: */ -int Scintilla_RegisterClasses(void *hInstance); -int Scintilla_ReleaseResources(void); -#endif +/* +* MIME Tools uses the Scintilla control already owned by Notepad++. +* It does not statically link or initialize Scintilla itself. We don't need the following lines. +*#if defined(_WIN32) +* // Return false on failure: +* int Scintilla_RegisterClasses(void *hInstance); +* int Scintilla_ReleaseResources(void); +*#endif +*/ #ifdef __cplusplus } diff --git a/src/tinf.h b/src/tinf.h index 97bb952..dabe404 100644 --- a/src/tinf.h +++ b/src/tinf.h @@ -28,21 +28,15 @@ extern "C" { #define TINF_OK 0 #define TINF_DATA_ERROR (-3) -/* function prototypes */ + /* function prototypes used by MIME Tools */ -void TINFCC tinf_init(); + void TINFCC tinf_init(); -int TINFCC tinf_uncompress(void *dest, unsigned int *destLen, const void *source); - -int TINFCC tinf_gzip_uncompress(void *dest, unsigned int *destLen, - const void *source, unsigned int sourceLen); - -int TINFCC tinf_zlib_uncompress(void *dest, unsigned int *destLen, - const void *source, unsigned int sourceLen); - -unsigned int TINFCC tinf_adler32(const void *data, unsigned int length); - -unsigned int TINFCC tinf_crc32(const void *data, unsigned int length); + int TINFCC tinf_uncompress( + void* dest, + unsigned int* destLen, + const void* source + ); #ifdef __cplusplus } /* extern "C" */ From 96c50272c4a7a61245b522e8b3bf2351bc8c5f8d Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 18:12:04 -0400 Subject: [PATCH 19/23] Made compile settings consistent --- vs.proj/mimeTools.vcxproj | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/vs.proj/mimeTools.vcxproj b/vs.proj/mimeTools.vcxproj index 0f972df..f74249d 100644 --- a/vs.proj/mimeTools.vcxproj +++ b/vs.proj/mimeTools.vcxproj @@ -153,11 +153,13 @@ MultiThreadedDebug true false + true true Windows true + UseLinkTimeCodeGeneration shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -174,11 +176,13 @@ MultiThreadedDebug true false + true true Windows true + UseLinkTimeCodeGeneration shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -195,11 +199,13 @@ MultiThreadedDebug true false + true true Windows true + UseLinkTimeCodeGeneration shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -207,22 +213,24 @@ Level4 NotUsing - Full + MaxSpeed true - false + true WIN32;NDEBUG;_WINDOWS;_USRDLL;MIMETOOLS_EXPORTS;%(PreprocessorDefinitions) true Speed - false + true MultiThreaded true + true true Windows false + UseLinkTimeCodeGeneration true true shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -245,11 +253,11 @@ copy ..\readme.txt ..\bin\readme.txt true Speed false - false + true MultiThreaded true true - false + true true @@ -271,21 +279,23 @@ copy ..\readme.txt ..\bin64\readme.txt Level4 - Full + MaxSpeed true - false + true WIN32;NDEBUG;_WINDOWS;_USRDLL;MIMETOOLS_EXPORTS;%(PreprocessorDefinitions) true Speed false - false + true MultiThreaded true + true true Windows false + UseLinkTimeCodeGeneration true true shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) From 6f0bee052a30151301215b886ed11399fda24888 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 20:39:37 -0400 Subject: [PATCH 20/23] Added UTF-16LE support for Base64 encoding and decoding --- src/mimeTools.cpp | 338 ++++++++++++++++++++++++++++++++++++++++------ src/mimeTools.h | 6 +- 2 files changed, 304 insertions(+), 40 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 243553c..4bb5371 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -29,7 +29,7 @@ #include "rfc2047.h" const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 28; +const int nbFunc = 30; HINSTANCE g_hInst = nullptr; NppData nppData; @@ -52,26 +52,28 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[5]._pFunc = convertToAsciiFromBase64; funcItem[6]._pFunc = convertToAsciiFromBase64_strict; funcItem[7]._pFunc = convertToAsciiFromBase64_whitespaceReset; - funcItem[8]._pFunc = NULL; - funcItem[9]._pFunc = convertToQuotedPrintable; - funcItem[10]._pFunc = convertToAsciiFromQuotedPrintable; - funcItem[11]._pFunc = NULL; - funcItem[12]._pFunc = convertMimeHeaderDecode; + funcItem[8]._pFunc = convertToBase64FromUtf16Le; + funcItem[9]._pFunc = convertToUtf16LeFromBase64; + funcItem[10]._pFunc = NULL; + funcItem[11]._pFunc = convertToQuotedPrintable; + funcItem[12]._pFunc = convertToAsciiFromQuotedPrintable; funcItem[13]._pFunc = NULL; - funcItem[14]._pFunc = convertURLMinEncode; - funcItem[15]._pFunc = convertURLMinEncodeByLine; - funcItem[16]._pFunc = convertURLEncodeExtended; - funcItem[17]._pFunc = convertURLEncodeExtendedByLine; - funcItem[18]._pFunc = convertURLFullEncode; - funcItem[19]._pFunc = convertURLFullEncodeByLine; - funcItem[20]._pFunc = convertURLDecode; - funcItem[21]._pFunc = NULL; - funcItem[22]._pFunc = urlconvertToBase64FromAscii; - funcItem[23]._pFunc = urlconvertToAsciiFromBase64; - funcItem[24]._pFunc = NULL; - funcItem[25]._pFunc = convertSamlDecode; + funcItem[14]._pFunc = convertMimeHeaderDecode; + funcItem[15]._pFunc = NULL; + funcItem[16]._pFunc = convertURLMinEncode; + funcItem[17]._pFunc = convertURLMinEncodeByLine; + funcItem[18]._pFunc = convertURLEncodeExtended; + funcItem[19]._pFunc = convertURLEncodeExtendedByLine; + funcItem[20]._pFunc = convertURLFullEncode; + funcItem[21]._pFunc = convertURLFullEncodeByLine; + funcItem[22]._pFunc = convertURLDecode; + funcItem[23]._pFunc = NULL; + funcItem[24]._pFunc = urlconvertToBase64FromAscii; + funcItem[25]._pFunc = urlconvertToAsciiFromBase64; funcItem[26]._pFunc = NULL; - funcItem[27]._pFunc = about; + funcItem[27]._pFunc = convertSamlDecode; + funcItem[28]._pFunc = NULL; + funcItem[29]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); lstrcpy(funcItem[2]._itemName, TEXT("Base64 Encode with padding by line")); @@ -80,27 +82,28 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[5]._itemName, TEXT("Base64 Decode")); lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode strict")); lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line")); - lstrcpy(funcItem[8]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[9]._itemName, TEXT("Quoted-printable Encode")); - lstrcpy(funcItem[10]._itemName, TEXT("Quoted-printable Decode")); - lstrcpy(funcItem[11]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[12]._itemName, TEXT("MIME Header Decode (RFC 2047)")); + lstrcpy(funcItem[8]._itemName, TEXT("Base64 Encode UTF-16LE (PowerShell)")); + lstrcpy(funcItem[9]._itemName, TEXT("Base64 Decode UTF-16LE (PowerShell)")); + lstrcpy(funcItem[10]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[11]._itemName, TEXT("Quoted-printable Encode")); + lstrcpy(funcItem[12]._itemName, TEXT("Quoted-printable Decode")); lstrcpy(funcItem[13]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[14]._itemName, TEXT("URL Encode (RFC1738)")); - lstrcpy(funcItem[15]._itemName, TEXT("URL Encode (RFC1738) by line")); - lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (Extended)")); - lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (Extended) by line")); - lstrcpy(funcItem[18]._itemName, TEXT("URL Encode (Full)")); - lstrcpy(funcItem[19]._itemName, TEXT("URL Encode (Full) by line")); - lstrcpy(funcItem[20]._itemName, TEXT("URL Decode")); - lstrcpy(funcItem[21]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[22]._itemName, TEXT("URL Base64 Encode")); - lstrcpy(funcItem[23]._itemName, TEXT("URL Base64 Decode")); - lstrcpy(funcItem[24]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[25]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[14]._itemName, TEXT("MIME Header Decode (RFC 2047)")); + lstrcpy(funcItem[15]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[16]._itemName, TEXT("URL Encode (RFC1738)")); + lstrcpy(funcItem[17]._itemName, TEXT("URL Encode (RFC1738) by line")); + lstrcpy(funcItem[18]._itemName, TEXT("URL Encode (Extended)")); + lstrcpy(funcItem[19]._itemName, TEXT("URL Encode (Extended) by line")); + lstrcpy(funcItem[20]._itemName, TEXT("URL Encode (Full)")); + lstrcpy(funcItem[21]._itemName, TEXT("URL Encode (Full) by line")); + lstrcpy(funcItem[22]._itemName, TEXT("URL Decode")); + lstrcpy(funcItem[23]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Encode")); + lstrcpy(funcItem[25]._itemName, TEXT("URL Base64 Decode")); lstrcpy(funcItem[26]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[27]._itemName, TEXT("About")); - + lstrcpy(funcItem[27]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[28]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[29]._itemName, TEXT("About")); for (int i = 0; i < nbFunc; i++) { funcItem[i]._init2Check = false; @@ -177,7 +180,266 @@ HWND getCurrentScintillaHandle() return (currentEdit == 0)?nppData._scintillaMainHandle:nppData._scintillaSecondHandle; }; +namespace +{ + void showUtf16Base64Error(const TCHAR* message) + { + ::MessageBox(nppData._nppHandle, message, TEXT("Base64 UTF-16LE"), MB_OK | MB_ICONERROR); + } + + UINT getDocumentCodePage(HWND hScintilla) + { + const UINT codePage = static_cast( + ::SendMessage(hScintilla, SCI_GETCODEPAGE, 0, 0)); + return codePage == 0 ? CP_ACP : codePage; + } + + bool getSingleSelection(HWND hScintilla, size_t& start, size_t& end, std::vector& selectedText) + { + const size_t nbSelections = static_cast( + ::SendMessage(hScintilla, SCI_GETSELECTIONS, 0, 0)); + if (nbSelections > 1) + return false; + + start = static_cast(::SendMessage(hScintilla, SCI_GETSELECTIONSTART, 0, 0)); + end = static_cast(::SendMessage(hScintilla, SCI_GETSELECTIONEND, 0, 0)); + if (end < start) + { + const size_t tmp = start; + start = end; + end = tmp; + } + + const size_t selectedLength = end - start; + if (selectedLength == 0) + return false; + + selectedText.resize(selectedLength + 1); + ::SendMessage(hScintilla, SCI_SETTARGETSTART, start, 0); + ::SendMessage(hScintilla, SCI_SETTARGETEND, end, 0); + ::SendMessage(hScintilla, SCI_GETTARGETTEXT, 0, + reinterpret_cast(selectedText.data())); + return true; + } + + bool documentBytesToUtf16(HWND hScintilla, const char* text, size_t length, + std::vector& utf16) + { + if (length > static_cast(INT_MAX)) + return false; + if (length == 0) + { + utf16.clear(); + return true; + } + + const UINT codePage = getDocumentCodePage(hScintilla); + const DWORD flags = codePage == CP_UTF8 ? MB_ERR_INVALID_CHARS : 0; + const int inputLength = static_cast(length); + const int utf16Length = ::MultiByteToWideChar( + codePage, flags, text, inputLength, nullptr, 0); + if (utf16Length <= 0) + return false; + + utf16.resize(static_cast(utf16Length)); + return ::MultiByteToWideChar( + codePage, flags, text, inputLength, utf16.data(), utf16Length) == utf16Length; + } + + bool isValidUtf16(const wchar_t* text, size_t length) + { + for (size_t i = 0; i < length; ++i) + { + const unsigned int unit = static_cast(text[i]); + if (unit >= 0xD800 && unit <= 0xDBFF) + { + if (i + 1 >= length) + return false; + const unsigned int next = static_cast(text[++i]); + if (next < 0xDC00 || next > 0xDFFF) + return false; + } + else if (unit >= 0xDC00 && unit <= 0xDFFF) + { + return false; + } + } + return true; + } + + bool utf16ToDocumentBytes(HWND hScintilla, const wchar_t* text, size_t length, + std::vector& output) + { + if (length > static_cast(INT_MAX)) + return false; + if (length == 0) + { + output.clear(); + return true; + } + + const UINT codePage = getDocumentCodePage(hScintilla); + DWORD flags = 0; + BOOL usedDefaultChar = FALSE; + LPBOOL usedDefaultCharPtr = nullptr; + + if (codePage == CP_UTF8) + { + flags = WC_ERR_INVALID_CHARS; + } + else if (codePage != CP_UTF7 && codePage != 54936) + { + flags = WC_NO_BEST_FIT_CHARS; + usedDefaultCharPtr = &usedDefaultChar; + } + + const int inputLength = static_cast(length); + const int outputLength = ::WideCharToMultiByte( + codePage, flags, text, inputLength, nullptr, 0, nullptr, usedDefaultCharPtr); + if (outputLength <= 0 || usedDefaultChar) + return false; + + output.resize(static_cast(outputLength)); + usedDefaultChar = FALSE; + const int convertedLength = ::WideCharToMultiByte( + codePage, flags, text, inputLength, output.data(), outputLength, + nullptr, usedDefaultCharPtr); + return convertedLength == outputLength && !usedDefaultChar; + } + + void replaceSelection(HWND hScintilla, size_t start, size_t end, + const char* text, size_t length) + { + ::SendMessage(hScintilla, SCI_SETTARGETSTART, start, 0); + ::SendMessage(hScintilla, SCI_SETTARGETEND, end, 0); + ::SendMessage(hScintilla, SCI_REPLACETARGET, static_cast(length), + reinterpret_cast(text)); + ::SendMessage(hScintilla, SCI_SETSEL, start, start + length); + } +} + +void convertToBase64FromUtf16Le() +{ + static_assert(sizeof(wchar_t) == 2, "UTF-16LE Base64 requires 16-bit wchar_t on Windows."); + HWND hCurrScintilla = getCurrentScintillaHandle(); + size_t start = 0; + size_t end = 0; + std::vector selectedText; + if (!getSingleSelection(hCurrScintilla, start, end, selectedText)) + return; + + const size_t selectedLength = end - start; + std::vector utf16; + if (!documentBytesToUtf16(hCurrScintilla, selectedText.data(), selectedLength, utf16)) + { + showUtf16Base64Error(TEXT("The selected text cannot be converted to UTF-16 without decoding errors.")); + return; + } + + if (utf16.size() > static_cast(INT_MAX) / sizeof(wchar_t)) + { + showUtf16Base64Error(TEXT("The selected text is too large to Base64 encode as UTF-16LE.")); + return; + } + const size_t utf16ByteLength = utf16.size() * sizeof(wchar_t); + const size_t fullGroups = utf16ByteLength / 3; + const size_t tailLength = (utf16ByteLength % 3) == 0 ? 0 : 4; + if (fullGroups > (static_cast(INT_MAX) - tailLength) / 4) + { + showUtf16Base64Error(TEXT("The selected text is too large to Base64 encode as UTF-16LE.")); + return; + } + const size_t encodedLength = fullGroups * 4 + tailLength; + std::vector encodedText(encodedLength); + + const int len = base64Encode( + encodedText.data(), + reinterpret_cast(utf16.data()), + utf16ByteLength, + 0, + true, + false); + if (len < 0 || static_cast(len) != encodedLength) + { + showUtf16Base64Error(TEXT("Base64 UTF-16LE encoding failed.")); + return; + } + + replaceSelection(hCurrScintilla, start, end, encodedText.data(), encodedLength); +} + +void convertToUtf16LeFromBase64() +{ + static_assert(sizeof(wchar_t) == 2, "UTF-16LE Base64 requires 16-bit wchar_t on Windows."); + + HWND hCurrScintilla = getCurrentScintillaHandle(); + size_t start = 0; + size_t end = 0; + std::vector selectedText; + if (!getSingleSelection(hCurrScintilla, start, end, selectedText)) + return; + + const size_t selectedLength = end - start; + if (selectedLength > static_cast(INT_MAX)) + { + showUtf16Base64Error(TEXT("The selected Base64 text is too large to decode.")); + return; + } + + // Base64 decoding never produces more bytes than its input. Allocate aligned + // UTF-16 storage directly so no decoded-byte -> wchar_t copy is required. + std::vector utf16((selectedLength + sizeof(wchar_t) - 1) / sizeof(wchar_t)); + const int decodedByteLength = base64Decode( + reinterpret_cast(utf16.data()), + selectedText.data(), + selectedLength, + true, + false); + if (decodedByteLength < 0) + { + showUtf16Base64Error(TEXT("The selection is not valid Base64.")); + return; + } + if ((decodedByteLength % static_cast(sizeof(wchar_t))) != 0) + { + showUtf16Base64Error(TEXT("Decoded Base64 has an odd byte count and is not valid UTF-16LE.")); + return; + } + + size_t utf16Length = static_cast(decodedByteLength) / sizeof(wchar_t); + size_t offset = 0; + if (utf16Length > 0) + { + if (utf16[0] == static_cast(0xFEFF)) + { + offset = 1; // Optional UTF-16LE BOM. + } + else if (utf16[0] == static_cast(0xFFFE)) + { + showUtf16Base64Error(TEXT("Decoded data has a UTF-16BE BOM. This command expects UTF-16LE.")); + return; + } + } + + const wchar_t* utf16Text = utf16.data() + offset; + utf16Length -= offset; + if (!isValidUtf16(utf16Text, utf16Length)) + { + showUtf16Base64Error(TEXT("Decoded Base64 contains malformed UTF-16 surrogate pairs.")); + return; + } + + std::vector documentText; + if (!utf16ToDocumentBytes(hCurrScintilla, utf16Text, utf16Length, documentText)) + { + showUtf16Base64Error(TEXT("Decoded UTF-16LE text cannot be represented in the current document encoding.")); + return; + } + + replaceSelection(hCurrScintilla, start, end, + documentText.empty() ? "" : documentText.data(), documentText.size()); +} void convertAsciiToBase64(size_t wrapLength, bool padFlag, bool byLineFlag) { diff --git a/src/mimeTools.h b/src/mimeTools.h index c57c4be..455b8c6 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -20,8 +20,8 @@ #pragma once -#define VERSION_VALUE "3.3\0" -#define VERSION_DIGITALVALUE 3, 3, 0, 0 +#define VERSION_VALUE "3.4\0" +#define VERSION_DIGITALVALUE 3, 4, 0, 0 #define IDD_ABOUTBOX 250 @@ -37,10 +37,12 @@ void convertToBase64FromAscii_pad(); void convertToBase64FromAscii_B64Format(); void convertToBase64FromAscii_pad_byline(); void convertToBase64FromAscii_byline(); +void convertToBase64FromUtf16Le(); void convertToAsciiFromBase64(); void urlconvertToAsciiFromBase64(); void convertToAsciiFromBase64_strict(); void convertToAsciiFromBase64_whitespaceReset(); +void convertToUtf16LeFromBase64(); void convertToQuotedPrintable(); void convertToAsciiFromQuotedPrintable(); void convertMimeHeaderDecode(); From 10379433e7af7b49bbf1360bd0a3995702c98e02 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 22:07:33 -0400 Subject: [PATCH 21/23] Fixed SAML Decode corruption caused by DEFLATE table truncation --- src/mimeTools.h | 4 ++-- src/saml.cpp | 9 +++++++-- src/tinflate.c | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/mimeTools.h b/src/mimeTools.h index 455b8c6..58333fe 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -20,8 +20,8 @@ #pragma once -#define VERSION_VALUE "3.4\0" -#define VERSION_DIGITALVALUE 3, 4, 0, 0 +#define VERSION_VALUE "3.5\0" +#define VERSION_DIGITALVALUE 3, 5, 0, 0 #define IDD_ABOUTBOX 250 diff --git a/src/saml.cpp b/src/saml.cpp index c1e68af..c668d65 100644 --- a/src/saml.cpp +++ b/src/saml.cpp @@ -20,6 +20,8 @@ #include "url.h" #include "tinf.h" +#include + int samlDecode(char *dest, const char *encodedSamlStr, int bufLength) { @@ -30,7 +32,7 @@ int samlDecode(char *dest, const char *encodedSamlStr, int bufLength) // URL Decode - size_t urlDecodedLen = UrlToAscii(pUrlDecodedText, encodedSamlStr, bufLength); + int urlDecodedLen = UrlToAscii(pUrlDecodedText, encodedSamlStr, bufLength); if (urlDecodedLen < 0) { @@ -45,7 +47,10 @@ int samlDecode(char *dest, const char *encodedSamlStr, int bufLength) delete[] pUrlDecodedText; if (base64DecodedLen < 0) - return SAML_DECODE_ERROR_BASE64DECODE; + { + delete [] base64DecodedText; + return SAML_DECODE_ERROR_BASE64DECODE; + } base64DecodedText[base64DecodedLen] = '\0'; diff --git a/src/tinflate.c b/src/tinflate.c index 3eaaec4..a367326 100644 --- a/src/tinflate.c +++ b/src/tinflate.c @@ -89,12 +89,12 @@ static void tinf_build_bits_base(unsigned char *bits, unsigned short *base, int for (i = 0; i < delta; ++i) bits[i] = 0; for (i = 0; i < 30 - delta; ++i) - bits[i + delta] = (char)(i / delta); + bits[i + delta] = (unsigned char)(i / delta); /* build base table */ for (sum = first, i = 0; i < 30; ++i) { - base[i] = (char)sum; + base[i] = (unsigned short)sum; sum += 1 << bits[i]; } } From 520368a1649b110eea5a17f64e2032ffc9232e1b Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 22:31:09 -0400 Subject: [PATCH 22/23] added SAML encoding to compliment SAML decoding --- .gitignore | 7 +- readme.txt | 2 +- src/mimeTools.cpp | 75 +++++++- src/mimeTools.h | 1 + src/saml.cpp | 68 ++++++++ src/saml.h | 10 +- src/tdeflate.cpp | 358 ++++++++++++++++++++++++++++++++++++++ src/tdeflate.h | 19 ++ vs.proj/mimeTools.vcxproj | 2 + 9 files changed, 529 insertions(+), 13 deletions(-) create mode 100644 src/tdeflate.cpp create mode 100644 src/tdeflate.h diff --git a/.gitignore b/.gitignore index 77a807e..f3a86b9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,9 @@ vs.proj/.vs/ vs.proj/ARM64/ vs.proj/mimeTools.opensdf vs.proj/mimeTools.exp -vs.proj/mimeTools.lib \ No newline at end of file +vs.proj/mimeTools.lib +vs.proj/mimeTools/ +CppProperties.json +RCa25572 +.vscode +.vs \ No newline at end of file diff --git a/readme.txt b/readme.txt index 44e55cf..1ee4a14 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ MIME plugin for Notepad++ implements several main functionalities defined in MIM 2. Quoted-printable Encoding/Decoding 3. RFC 2047 encoded-word MIME header decoding 4. URL Encoding/Decoding -5. SAML Decoding (though it's not part of MIME) +5. SAML Encoding/Decoding (HTTP-Redirect binding; though it's not part of MIME) This plugin is under GPL. Don Ho \ No newline at end of file diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 4bb5371..6892a5e 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -29,7 +29,7 @@ #include "rfc2047.h" const TCHAR PLUGIN_NAME[] = TEXT("MIME Tools"); -const int nbFunc = 30; +const int nbFunc = 31; HINSTANCE g_hInst = nullptr; NppData nppData; @@ -71,9 +71,10 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ funcItem[24]._pFunc = urlconvertToBase64FromAscii; funcItem[25]._pFunc = urlconvertToAsciiFromBase64; funcItem[26]._pFunc = NULL; - funcItem[27]._pFunc = convertSamlDecode; - funcItem[28]._pFunc = NULL; - funcItem[29]._pFunc = about; + funcItem[27]._pFunc = convertSamlEncode; + funcItem[28]._pFunc = convertSamlDecode; + funcItem[29]._pFunc = NULL; + funcItem[30]._pFunc = about; lstrcpy(funcItem[0]._itemName, TEXT("Base64 Encode")); lstrcpy(funcItem[1]._itemName, TEXT("Base64 Encode with padding")); lstrcpy(funcItem[2]._itemName, TEXT("Base64 Encode with padding by line")); @@ -101,9 +102,10 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[24]._itemName, TEXT("URL Base64 Encode")); lstrcpy(funcItem[25]._itemName, TEXT("URL Base64 Decode")); lstrcpy(funcItem[26]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[27]._itemName, TEXT("SAML Decode")); - lstrcpy(funcItem[28]._itemName, TEXT("-SEPARATOR-")); - lstrcpy(funcItem[29]._itemName, TEXT("About")); + lstrcpy(funcItem[27]._itemName, TEXT("SAML Encode")); + lstrcpy(funcItem[28]._itemName, TEXT("SAML Decode")); + lstrcpy(funcItem[29]._itemName, TEXT("-SEPARATOR-")); + lstrcpy(funcItem[30]._itemName, TEXT("About")); for (int i = 0; i < nbFunc; i++) { funcItem[i]._init2Check = false; @@ -1096,6 +1098,65 @@ void about() ::SetWindowPos(g_hAboutDlg, HWND_TOP, x, y, (dlgRect.right - dlgRect.left), (dlgRect.bottom - dlgRect.top), SWP_SHOWWINDOW); } +void convertSamlEncode() +{ + HWND hCurrScintilla = getCurrentScintillaHandle(); + size_t nbSelections = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONS, 0, 0); + if (nbSelections > 1) return; + + size_t bufLength = ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, 0); + if (bufLength == 0) return; + + char *selectedText = new char[bufLength + 1]; + ::SendMessage(hCurrScintilla, SCI_GETSELTEXT, 0, (LPARAM)selectedText); + + // Scintilla 5.1.5+ reports selection length without the terminating NUL. + // strlen also keeps compatibility with older Scintilla length semantics. + const size_t selectedLength = strlen(selectedText); + if (selectedLength == 0) + { + delete [] selectedText; + return; + } + + std::string samlEncodedText; + const int len = samlEncode(samlEncodedText, selectedText, selectedLength); + + switch (len) + { + case 0: + ::MessageBox(nppData._nppHandle, TEXT("SAML Encode returned zero size."), TEXT("SAML Encode"), MB_OK); + break; + case SAML_ENCODE_ERROR_DEFLATE: + ::MessageBox(nppData._nppHandle, TEXT("Could not DEFLATE the selected SAML text."), TEXT("SAML Encode"), MB_OK); + break; + case SAML_ENCODE_ERROR_BASE64: + ::MessageBox(nppData._nppHandle, TEXT("Could not BASE64 Encode the compressed SAML text."), TEXT("SAML Encode"), MB_OK); + break; + case SAML_ENCODE_ERROR_URLENCODE: + ::MessageBox(nppData._nppHandle, TEXT("Could not URL Encode the BASE64 SAML text."), TEXT("SAML Encode"), MB_OK); + break; + default: + { + size_t start = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONSTART, 0, 0); + size_t end = ::SendMessage(hCurrScintilla, SCI_GETSELECTIONEND, 0, 0); + if (end < start) + { + size_t tmp = start; + start = end; + end = tmp; + } + ::SendMessage(hCurrScintilla, SCI_SETTARGETSTART, start, 0); + ::SendMessage(hCurrScintilla, SCI_SETTARGETEND, end, 0); + ::SendMessage(hCurrScintilla, SCI_REPLACETARGET, len, (LPARAM)samlEncodedText.c_str()); + ::SendMessage(hCurrScintilla, SCI_SETSEL, start, start + static_cast(len)); + break; + } + } + + delete [] selectedText; +} + void convertSamlDecode() { HWND hCurrScintilla = getCurrentScintillaHandle(); diff --git a/src/mimeTools.h b/src/mimeTools.h index 58333fe..b458f15 100644 --- a/src/mimeTools.h +++ b/src/mimeTools.h @@ -54,6 +54,7 @@ void convertURLEncodeExtendedByLine(); void convertURLFullEncodeByLine(); void convertURLEncode(UrlEncodeMethod method, bool isByLine = false); void convertURLDecode(); +void convertSamlEncode(); void convertSamlDecode(); void convertURLDecode(); void about(); diff --git a/src/saml.cpp b/src/saml.cpp index c668d65..6fe7607 100644 --- a/src/saml.cpp +++ b/src/saml.cpp @@ -18,9 +18,77 @@ #include "saml.h" #include "b64.h" #include "url.h" +#include "tdeflate.h" #include "tinf.h" +#include #include +#include +#include +#include + + +int samlEncode(std::string& dest, const char* samlStr, std::size_t samlLength) +{ + dest.clear(); + + if (samlStr == nullptr || samlLength == 0) + return 0; + + if (samlLength > static_cast(SAML_MESSAGE_MAX_SIZE)) + return SAML_ENCODE_ERROR_DEFLATE; + + std::vector compressed; + if (!tdefl_compress_raw(compressed, + reinterpret_cast(samlStr), + samlLength)) + { + return SAML_ENCODE_ERROR_DEFLATE; + } + + if (compressed.size() > std::numeric_limits::max() - 2) + return SAML_ENCODE_ERROR_BASE64; + + const std::size_t base64Groups = (compressed.size() + 2) / 3; + if (base64Groups > std::numeric_limits::max() / 4) + return SAML_ENCODE_ERROR_BASE64; + + const std::size_t base64Capacity = base64Groups * 4; + if (base64Capacity > static_cast(INT_MAX)) + return SAML_ENCODE_ERROR_BASE64; + + std::string base64Text(base64Capacity, '\0'); + const int base64Length = base64Encode( + &base64Text[0], + reinterpret_cast(compressed.data()), + compressed.size(), + 0, + true, + false); + + if (base64Length < 0) + return SAML_ENCODE_ERROR_BASE64; + + base64Text.resize(static_cast(base64Length)); + + // SAML HTTP-Redirect requires the Base64 value to be URL encoded. Use the + // extended encoder so '+' is escaped as %2B instead of being vulnerable to + // form/query parsers that interpret '+' as a space. + const std::size_t base64Size = base64Text.size(); + if (base64Size > (static_cast(INT_MAX) - 1) / 3) + return SAML_ENCODE_ERROR_URLENCODE; + + const int urlCapacity = static_cast(base64Size * 3 + 1); + std::string urlText(static_cast(urlCapacity), '\0'); + const int urlLength = AsciiToUrl(&urlText[0], base64Text.c_str(), + urlCapacity, UrlEncodeMethod::extended); + if (urlLength < 0 || urlLength >= urlCapacity) + return SAML_ENCODE_ERROR_URLENCODE; + + urlText.resize(static_cast(urlLength)); + dest = std::move(urlText); + return urlLength; +} int samlDecode(char *dest, const char *encodedSamlStr, int bufLength) diff --git a/src/saml.h b/src/saml.h index 2b76ec7..5aa4668 100644 --- a/src/saml.h +++ b/src/saml.h @@ -16,15 +16,17 @@ #pragma once -#include "b64.h" -#include "url.h" +#include +#include constexpr int SAML_DECODE_ERROR_URLDECODE = -1; constexpr int SAML_DECODE_ERROR_BASE64DECODE = -2; constexpr int SAML_DECODE_ERROR_INFLATE = -3; - +constexpr int SAML_ENCODE_ERROR_DEFLATE = -4; +constexpr int SAML_ENCODE_ERROR_BASE64 = -5; +constexpr int SAML_ENCODE_ERROR_URLENCODE = -6; constexpr int SAML_MESSAGE_MAX_SIZE = 200000; +int samlEncode(std::string& dest, const char* samlStr, std::size_t samlLength); int samlDecode(char *dest, const char *samlStr, int bufLength); - diff --git a/src/tdeflate.cpp b/src/tdeflate.cpp new file mode 100644 index 0000000..880ae27 --- /dev/null +++ b/src/tdeflate.cpp @@ -0,0 +1,358 @@ +// This file is part of Notepad++ plugin MIME Tools project +// Copyright (C)2026 ExSlam contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. + +#include "tdeflate.h" + +#include +#include +#include +#include + +namespace { + +constexpr std::size_t kWindowSize = 32768; +constexpr std::size_t kMaxMatch = 258; +constexpr std::size_t kMinMatch = 3; +constexpr std::size_t kHashBits = 15; +constexpr std::size_t kHashSize = static_cast(1) << kHashBits; +constexpr int kMaxChain = 64; + +constexpr unsigned short kLengthBase[] = { + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 258 +}; + +constexpr unsigned char kLengthExtra[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5, 0 +}; + +constexpr unsigned short kDistanceBase[] = { + 1, 2, 3, 4, 5, 7, 9, 13, + 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577 +}; + +constexpr unsigned char kDistanceExtra[] = { + 0, 0, 0, 0, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +unsigned reverseBits(unsigned value, unsigned bitCount) +{ + unsigned reversed = 0; + for (unsigned i = 0; i < bitCount; ++i) + { + reversed = (reversed << 1) | (value & 1U); + value >>= 1; + } + return reversed; +} + +class BitWriter +{ +public: + void reserve(std::size_t bytes) + { + _bytes.reserve(bytes); + } + + void writeBits(unsigned value, unsigned bitCount) + { + _bitBuffer |= static_cast(value) << _bitCount; + _bitCount += bitCount; + + while (_bitCount >= 8) + { + _bytes.push_back(static_cast(_bitBuffer & 0xffU)); + _bitBuffer >>= 8; + _bitCount -= 8; + } + } + + void finish() + { + if (_bitCount != 0) + { + _bytes.push_back(static_cast(_bitBuffer & 0xffU)); + _bitBuffer = 0; + _bitCount = 0; + } + } + + std::vector& bytes() + { + return _bytes; + } + +private: + std::vector _bytes; + std::uint64_t _bitBuffer = 0; + unsigned _bitCount = 0; +}; + +void writeFixedSymbol(BitWriter& writer, unsigned symbol) +{ + unsigned code = 0; + unsigned bitCount = 0; + + if (symbol <= 143) + { + code = 0x30U + symbol; + bitCount = 8; + } + else if (symbol <= 255) + { + code = 0x190U + (symbol - 144U); + bitCount = 9; + } + else if (symbol <= 279) + { + code = symbol - 256U; + bitCount = 7; + } + else + { + code = 0xC0U + (symbol - 280U); + bitCount = 8; + } + + writer.writeBits(reverseBits(code, bitCount), bitCount); +} + +void writeLength(BitWriter& writer, std::size_t length) +{ + for (unsigned i = 0; i < 29; ++i) + { + const unsigned extraBits = kLengthExtra[i]; + const std::size_t maxLength = static_cast(kLengthBase[i]) + + (extraBits == 0 ? 0 : ((static_cast(1) << extraBits) - 1)); + + if (length <= maxLength) + { + writeFixedSymbol(writer, 257U + i); + if (extraBits != 0) + { + const unsigned extraValue = static_cast( + length - static_cast(kLengthBase[i])); + writer.writeBits(extraValue, extraBits); + } + return; + } + } +} + +void writeDistance(BitWriter& writer, std::size_t distance) +{ + for (unsigned i = 0; i < 30; ++i) + { + const unsigned extraBits = kDistanceExtra[i]; + const std::size_t maxDistance = static_cast(kDistanceBase[i]) + + (extraBits == 0 ? 0 : ((static_cast(1) << extraBits) - 1)); + + if (distance <= maxDistance) + { + // Fixed distance codes are 5-bit canonical codes. Huffman codes are + // transmitted MSB-first, so reverse before feeding our LSB bit writer. + writer.writeBits(reverseBits(i, 5), 5); + if (extraBits != 0) + { + const unsigned extraValue = static_cast( + distance - static_cast(kDistanceBase[i])); + writer.writeBits(extraValue, extraBits); + } + return; + } + } +} + +std::size_t hash3(const unsigned char* input, std::size_t position) +{ + const std::uint32_t value = + (static_cast(input[position]) << 16) | + (static_cast(input[position + 1]) << 8) | + static_cast(input[position + 2]); + return static_cast((value * 2654435761U) >> (32U - kHashBits)); +} + +void insertPosition(const unsigned char* input, + std::size_t inputLength, + std::size_t position, + std::vector& head, + std::vector& previous) +{ + if (position + kMinMatch > inputLength) + return; + + const std::size_t hash = hash3(input, position); + previous[position] = head[hash]; + head[hash] = static_cast(position); +} + +void findMatch(const unsigned char* input, + std::size_t inputLength, + std::size_t position, + const std::vector& head, + const std::vector& previous, + std::size_t& bestLength, + std::size_t& bestDistance) +{ + bestLength = 0; + bestDistance = 0; + + if (position + kMinMatch > inputLength) + return; + + const std::size_t maxLength = std::min(kMaxMatch, inputLength - position); + int candidate = head[hash3(input, position)]; + int chain = 0; + + while (candidate >= 0 && chain < kMaxChain) + { + const std::size_t candidatePosition = static_cast(candidate); + const std::size_t distance = position - candidatePosition; + if (distance == 0 || distance > kWindowSize) + break; + + if (bestLength == 0 || + (bestLength < maxLength && + input[candidatePosition + bestLength] == input[position + bestLength])) + { + std::size_t length = 0; + while (length < maxLength && + input[candidatePosition + length] == input[position + length]) + { + ++length; + } + + if (length >= kMinMatch && length > bestLength) + { + bestLength = length; + bestDistance = distance; + if (length == maxLength) + break; + } + } + + candidate = previous[candidatePosition]; + ++chain; + } +} + +std::vector compressFixed(const unsigned char* input, std::size_t inputLength) +{ + BitWriter writer; + writer.reserve(inputLength + inputLength / 16 + 32); + + // BFINAL=1, BTYPE=01 (fixed Huffman codes). + writer.writeBits(1, 1); + writer.writeBits(1, 2); + + std::vector head(kHashSize, -1); + std::vector previous(inputLength, -1); + + std::size_t position = 0; + while (position < inputLength) + { + std::size_t matchLength = 0; + std::size_t matchDistance = 0; + findMatch(input, inputLength, position, head, previous, matchLength, matchDistance); + + // Three-byte matches with very large distances can cost more than literals. + // Requiring four bytes in that case gives better worst-case output while + // preserving useful short nearby matches. + const bool useMatch = matchLength >= kMinMatch && + !(matchLength == 3 && matchDistance > 1024); + + if (useMatch) + { + writeLength(writer, matchLength); + writeDistance(writer, matchDistance); + + for (std::size_t i = 0; i < matchLength; ++i) + insertPosition(input, inputLength, position + i, head, previous); + + position += matchLength; + } + else + { + writeFixedSymbol(writer, input[position]); + insertPosition(input, inputLength, position, head, previous); + ++position; + } + } + + writeFixedSymbol(writer, 256); // End-of-block. + writer.finish(); + return std::move(writer.bytes()); +} + +std::vector compressStored(const unsigned char* input, std::size_t inputLength) +{ + std::vector output; + const std::size_t blockCount = inputLength == 0 ? 1 : (inputLength + 65534) / 65535; + output.reserve(inputLength + blockCount * 5); + + std::size_t position = 0; + do + { + const std::size_t remaining = inputLength - position; + const unsigned short length = static_cast( + std::min(remaining, 65535)); + const bool finalBlock = position + length == inputLength; + + // Stored blocks start on a byte boundary. The low three bits are + // BFINAL followed by BTYPE=00; the remaining five padding bits are zero. + output.push_back(static_cast(finalBlock ? 0x01 : 0x00)); + output.push_back(static_cast(length & 0xffU)); + output.push_back(static_cast((length >> 8) & 0xffU)); + + const unsigned short inverseLength = static_cast(~length); + output.push_back(static_cast(inverseLength & 0xffU)); + output.push_back(static_cast((inverseLength >> 8) & 0xffU)); + + output.insert(output.end(), input + position, input + position + length); + position += length; + } + while (position < inputLength); + + return output; +} + +} // namespace + +bool tdefl_compress_raw(std::vector& output, + const unsigned char* input, + std::size_t inputLength) +{ + if (input == nullptr && inputLength != 0) + return false; + + try + { + const unsigned char emptyInput = 0; + const unsigned char* source = inputLength == 0 ? &emptyInput : input; + std::vector fixed = compressFixed(source, inputLength); + std::vector stored = compressStored(source, inputLength); + + output = fixed.size() <= stored.size() ? std::move(fixed) : std::move(stored); + return true; + } + catch (const std::bad_alloc&) + { + output.clear(); + return false; + } +} diff --git a/src/tdeflate.h b/src/tdeflate.h new file mode 100644 index 0000000..76e6cee --- /dev/null +++ b/src/tdeflate.h @@ -0,0 +1,19 @@ +// This file is part of Notepad++ plugin MIME Tools project +// Copyright (C)2026 ExSlam contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. + +#pragma once + +#include +#include + +// Compresses input as an RFC 1951 raw DEFLATE stream (no zlib/gzip wrapper). +// The encoder uses fixed Huffman blocks with LZ77 matching and falls back to +// stored blocks if that would be smaller. Returns false on allocation failure. +bool tdefl_compress_raw(std::vector& output, + const unsigned char* input, + std::size_t inputLength); diff --git a/vs.proj/mimeTools.vcxproj b/vs.proj/mimeTools.vcxproj index f74249d..a746876 100644 --- a/vs.proj/mimeTools.vcxproj +++ b/vs.proj/mimeTools.vcxproj @@ -32,6 +32,7 @@ + @@ -45,6 +46,7 @@ + From a345d5a5fc55d784aad39221a2e07e49c9c78062 Mon Sep 17 00:00:00 2001 From: ExSlam Date: Mon, 17 Aug 2026 22:39:26 -0400 Subject: [PATCH 23/23] Made some menu option labels more clear --- src/mimeTools.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mimeTools.cpp b/src/mimeTools.cpp index 6892a5e..f17875d 100644 --- a/src/mimeTools.cpp +++ b/src/mimeTools.cpp @@ -82,9 +82,9 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID /*lpReserved*/ lstrcpy(funcItem[4]._itemName, TEXT("Base64 Encode by line")); lstrcpy(funcItem[5]._itemName, TEXT("Base64 Decode")); lstrcpy(funcItem[6]._itemName, TEXT("Base64 Decode strict")); - lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line")); - lstrcpy(funcItem[8]._itemName, TEXT("Base64 Encode UTF-16LE (PowerShell)")); - lstrcpy(funcItem[9]._itemName, TEXT("Base64 Decode UTF-16LE (PowerShell)")); + lstrcpy(funcItem[7]._itemName, TEXT("Base64 Decode by line (padded/unpadded)")); + lstrcpy(funcItem[8]._itemName, TEXT("Base64 Encode UTF-16LE")); + lstrcpy(funcItem[9]._itemName, TEXT("Base64 Decode UTF-16LE")); lstrcpy(funcItem[10]._itemName, TEXT("-SEPARATOR-")); lstrcpy(funcItem[11]._itemName, TEXT("Quoted-printable Encode")); lstrcpy(funcItem[12]._itemName, TEXT("Quoted-printable Decode"));