diff --git a/GViewCore/include/GView.hpp b/GViewCore/include/GView.hpp index f473177a..b53af0b0 100644 --- a/GViewCore/include/GView.hpp +++ b/GViewCore/include/GView.hpp @@ -1437,11 +1437,19 @@ namespace App }; // namespace App -namespace Unpack::Base64 +namespace Unpack { - CORE_EXPORT void Encode(BufferView view, Buffer& output); - CORE_EXPORT bool Decode(BufferView view, Buffer& output); -} + namespace Base64 + { + CORE_EXPORT void Encode(BufferView view, Buffer& output); + CORE_EXPORT bool Decode(BufferView view, Buffer& output); + } // namespace Base64 + namespace QuotedPrintable + { + CORE_EXPORT void Encode(BufferView view, Buffer& output); + CORE_EXPORT bool Decode(BufferView view, Buffer& output); + } // namespace QuotedPrintable +} // namespace Unpack }; // namespace GView ADD_FLAG_OPERATORS(GView::View::LexicalViewer::StringFormat, AppCUI::uint32); diff --git a/GViewCore/src/Unpack/CMakeLists.txt b/GViewCore/src/Unpack/CMakeLists.txt index 7c583317..c087a3b9 100644 --- a/GViewCore/src/Unpack/CMakeLists.txt +++ b/GViewCore/src/Unpack/CMakeLists.txt @@ -1,3 +1,4 @@ target_sources(GViewCore PRIVATE Base64.cpp + QuotedPrintable.cpp ) diff --git a/GViewCore/src/Unpack/QuotedPrintable.cpp b/GViewCore/src/Unpack/QuotedPrintable.cpp new file mode 100644 index 00000000..c72dc888 --- /dev/null +++ b/GViewCore/src/Unpack/QuotedPrintable.cpp @@ -0,0 +1,96 @@ +#include "Internal.hpp" + +//TODO: THIS WAS NOT TESTED! +void GView::Unpack::QuotedPrintable::Encode(BufferView view, Buffer& output) +{ + // Iterate over each character in the input buffer + for (size_t i = 0; i < view.GetLength(); i++) { + // Get the current character + const char character = view[i]; + + // Check if the character is printable + if (character >= 33 && character <= 126) { + // Write the character to the output buffer + output.Add(string_view(&character, 1)); + } else { + // Write the character to the output buffer + output.Add(string_view("=", 1)); + + // Convert the character to its hexadecimal representation + char hex1 = (character >> 4) & 0xF; + char hex2 = character & 0xF; + + // Convert the hexadecimal digits to their ASCII representation + hex1 = (hex1 < 10) ? hex1 + '0' : hex1 - 10 + 'A'; + hex2 = (hex2 < 10) ? hex2 + '0' : hex2 - 10 + 'A'; + + // Write the hexadecimal digits to the output buffer + output.Add(string_view(&hex1, 1)); + output.Add(string_view(&hex2, 1)); + } + } +} + +//TODO: Consider more testing! +bool GView::Unpack::QuotedPrintable::Decode(BufferView view, Buffer& output) +{ + char temp_buffer[2] = {}; + // Iterate over each character in the input buffer + for (size_t i = 0; i < view.GetLength(); i++) { + // Check if the character is an encoded character + if (view[i] == '=') { + // Check if there are enough characters remaining for an encoded sequence + if (i + 2 < view.GetLength()) { + // Get the two hexadecimal digits following the '=' character + const char hex1 = view[i + 1]; + const char hex2 = view[i + 2]; + + if (hex1 == '2' && hex2 == 'E') { + temp_buffer[0] = '.'; + output.Add(string_view(temp_buffer, 1)); + i += 2; + continue; + } + if (hex1 == '\r' && hex2 == '\n') { + i += 2; + continue; + } + + // Convert the hexadecimal digits to their decimal values + int value = 0; + if (hex1 >= '0' && hex1 <= '9') { + value += (hex1 - '0') * 16; + } else if (hex1 >= 'A' && hex1 <= 'F') { + value += (hex1 - 'A' + 10) * 16; + } else if (hex1 >= 'a' && hex1 <= 'f') { + value += (hex1 - 'a' + 10) * 16; + } + + if (hex2 >= '0' && hex2 <= '9') { + value += hex2 - '0'; + } else if (hex2 >= 'A' && hex2 <= 'F') { + value += hex2 - 'A' + 10; + } else if (hex2 >= 'a' && hex2 <= 'f') { + value += hex2 - 'a' + 10; + } + + // Write the decoded character to the output buffer + temp_buffer[0] = static_cast(value); + output.Add(string_view(temp_buffer, 1)); + + // Skip the next two characters in the input buffer + i += 2; + } else { + // If '=' is at the end of the line, it should be treated as a literal '=' + temp_buffer[0] = '='; + output.Add(string_view(temp_buffer, 1)); + } + } else { + // Write the character to the output buffer + temp_buffer[0] = view[i]; + output.Add(string_view(temp_buffer, 1)); + } + } + + return true; +} diff --git a/Types/EML/include/eml.hpp b/Types/EML/include/eml.hpp index 9ec7826f..58db0f33 100644 --- a/Types/EML/include/eml.hpp +++ b/Types/EML/include/eml.hpp @@ -38,6 +38,7 @@ namespace Type std::optional TryGetNameQuotes(std::u16string& contentTypeToSearch, bool removeIfFound = false); std::u16string GetIdentifierFromContentType(std::u16string& contentTypeToChange); std::u16string GetBufferNameFromHeaderFields(); + std::u16string GetGViewFileName(const std::u16string& value, const std::u16string& prefix); std::vector> headerFields; void ParsePart(GView::View::LexicalViewer::TextParser text, uint32 start, uint32 end); diff --git a/Types/EML/src/EMLFile.cpp b/Types/EML/src/EMLFile.cpp index db63cfbe..2c679583 100644 --- a/Types/EML/src/EMLFile.cpp +++ b/Types/EML/src/EMLFile.cpp @@ -105,22 +105,34 @@ void EMLFile::OnOpenItem(std::u16string_view path, AppCUI::Controls::TreeViewIte BufferView itemBufferView(bufferView.GetData() + itemData->startIndex, itemData->dataLength); if (!itemData->leafNode) { - GView::App::OpenBuffer(itemBufferView, currentContentType, path, GView::App::OpenMethod::ForceType, "eml"); + auto bufferName = GetGViewFileName(currentContentType, itemData->identifier); + GView::App::OpenBuffer(itemBufferView, bufferName, path, GView::App::OpenMethod::ForceType, "eml"); } else { const auto& encodingHeader = std::find_if(headerFields.begin(), headerFields.end(), [](const auto& item) { return item.first == u"Content-Transfer-Encoding"; }); - if (encodingHeader != headerFields.end() && encodingHeader->second == u"base64") { + const auto headerValueName = GetBufferNameFromHeaderFields(); + const auto bufferName = GetGViewFileName(headerValueName, itemData->identifier); + + if (encodingHeader != headerFields.end()) { Buffer output; - if (GView::Unpack::Base64::Decode(itemBufferView, output)) { - auto bufferName = GetBufferNameFromHeaderFields(); - GView::App::OpenBuffer(output, bufferName, path, GView::App::OpenMethod::BestMatch); + if (encodingHeader->second == u"base64") { + if (GView::Unpack::Base64::Decode(itemBufferView, output)) { + GView::App::OpenBuffer(output, bufferName, path, GView::App::OpenMethod::BestMatch); + } else { + AppCUI::Dialogs::MessageBox::ShowError("Error!", "Malformed base64 buffer!"); + } + } else if (encodingHeader->second == u"quoted-printable") { + if (GView::Unpack::QuotedPrintable::Decode(itemBufferView, output)) { + GView::App::OpenBuffer(output, bufferName, path, GView::App::OpenMethod::BestMatch); + } else { + AppCUI::Dialogs::MessageBox::ShowError("Error!", "Malformed quoted-printable buffer!"); + } } else { - AppCUI::Dialogs::MessageBox::ShowError("Error!", "Malformed base64 buffer!"); + GView::App::OpenBuffer(itemBufferView, bufferName, path, GView::App::OpenMethod::BestMatch); } } else { - auto bufferName = GetBufferNameFromHeaderFields(); GView::App::OpenBuffer(itemBufferView, bufferName, path, GView::App::OpenMethod::BestMatch); } } @@ -223,6 +235,20 @@ std::u16string EMLFile::GetBufferNameFromHeaderFields() return std::u16string(obj->GetName()); } +std::u16string EMLFile::GetGViewFileName(const std::u16string& value, const std::u16string& prefix) +{ + LocalUnicodeStringBuilder<64> sb = {}; + if (!prefix.empty()) { + sb.Add(prefix); + sb.AddChar(':'); + sb.AddChar(' '); + } + sb.Add(value); + std::u16string output; + sb.ToString(output); + return output; +} + void EMLFile::ParsePart(GView::View::LexicalViewer::TextParser text, uint32 start, uint32 end) { ParseHeaders(text, start);