2021-07-22 14:44:59 -03:00
/*
* Copyright ( c ) 2021 , Luke Wilde < lukew @ serenityos . org >
2021-11-09 09:17:57 -03:00
* Copyright ( c ) 2021 , Undefine < cqundefine @ gmail . com >
2022-02-10 16:28:48 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-07-22 14:44:59 -03:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "MailWidget.h"
# include <AK/Base64.h>
2021-08-18 08:22:52 -03:00
# include <AK/GenericLexer.h>
2021-07-22 14:44:59 -03:00
# include <Applications/Mail/MailWindowGML.h>
2021-08-28 11:01:59 -03:00
# include <LibConfig/Client.h>
2021-07-22 14:44:59 -03:00
# include <LibDesktop/Launcher.h>
# include <LibGUI/Action.h>
# include <LibGUI/Clipboard.h>
# include <LibGUI/Menu.h>
# include <LibGUI/MessageBox.h>
2021-08-02 05:11:34 -03:00
# include <LibGUI/PasswordInputDialog.h>
2023-08-24 04:11:20 -03:00
# include <LibGUI/SortingProxyModel.h>
2021-07-22 14:44:59 -03:00
# include <LibGUI/Statusbar.h>
# include <LibGUI/TableView.h>
# include <LibGUI/TreeView.h>
2023-07-31 13:47:17 -03:00
# include <LibIMAP/MessageHeaderEncoding.h>
2021-07-22 14:44:59 -03:00
# include <LibIMAP/QuotedPrintable.h>
MailWidget : : MailWidget ( )
{
2023-01-07 09:38:23 -03:00
load_from_gml ( mail_window_gml ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-22 14:44:59 -03:00
m_mailbox_list = * find_descendant_of_type_named < GUI : : TreeView > ( " mailbox_list " ) ;
m_individual_mailbox_view = * find_descendant_of_type_named < GUI : : TableView > ( " individual_mailbox_view " ) ;
2022-04-30 05:46:33 -03:00
m_web_view = * find_descendant_of_type_named < WebView : : OutOfProcessWebView > ( " web_view " ) ;
2021-07-22 14:44:59 -03:00
m_statusbar = * find_descendant_of_type_named < GUI : : Statusbar > ( " statusbar " ) ;
2023-08-29 10:50:37 -03:00
m_mailbox_list - > set_activates_on_selection ( true ) ;
m_mailbox_list - > on_activation = [ this ] ( auto & index ) {
selected_mailbox ( index ) ;
2021-07-22 14:44:59 -03:00
} ;
2023-08-29 10:50:37 -03:00
m_individual_mailbox_view - > set_activates_on_selection ( true ) ;
m_individual_mailbox_view - > on_activation = [ this ] ( auto & index ) {
2023-08-24 04:11:20 -03:00
selected_email_to_load ( m_mailbox_sorting_model - > map_to_source ( index ) ) ;
2021-07-22 14:44:59 -03:00
} ;
m_web_view - > on_link_click = [ this ] ( auto & url , auto & , unsigned ) {
if ( ! Desktop : : Launcher : : open ( url ) ) {
GUI : : MessageBox : : show (
window ( ) ,
2022-12-04 15:02:33 -03:00
DeprecatedString : : formatted ( " The link to '{}' could not be opened. " , url ) ,
2022-07-11 14:32:29 -03:00
" Failed to open link " sv ,
2021-07-22 14:44:59 -03:00
GUI : : MessageBox : : Type : : Error ) ;
}
} ;
m_web_view - > on_link_middle_click = [ this ] ( auto & url , auto & target , unsigned modifiers ) {
m_web_view - > on_link_click ( url , target , modifiers ) ;
} ;
m_web_view - > on_link_hover = [ this ] ( auto & url ) {
if ( url . is_valid ( ) )
2023-06-04 05:24:38 -03:00
m_statusbar - > set_text ( String : : from_deprecated_string ( url . to_deprecated_string ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
else
2023-06-04 05:24:38 -03:00
m_statusbar - > set_text ( { } ) ;
2021-07-22 14:44:59 -03:00
} ;
m_link_context_menu = GUI : : Menu : : construct ( ) ;
auto link_default_action = GUI : : Action : : create ( " &Open in Browser " , [ this ] ( auto & ) {
m_web_view - > on_link_click ( m_link_context_menu_url , " " , 0 ) ;
} ) ;
m_link_context_menu - > add_action ( link_default_action ) ;
m_link_context_menu_default_action = link_default_action ;
m_link_context_menu - > add_separator ( ) ;
m_link_context_menu - > add_action ( GUI : : Action : : create ( " &Copy URL " , [ this ] ( auto & ) {
2022-12-05 22:12:49 -03:00
GUI : : Clipboard : : the ( ) . set_plain_text ( m_link_context_menu_url . to_deprecated_string ( ) ) ;
2021-07-22 14:44:59 -03:00
} ) ) ;
2022-12-06 17:27:44 -03:00
m_web_view - > on_link_context_menu_request = [ this ] ( auto & url , auto screen_position ) {
2021-07-22 14:44:59 -03:00
m_link_context_menu_url = url ;
m_link_context_menu - > popup ( screen_position , m_link_context_menu_default_action ) ;
} ;
m_image_context_menu = GUI : : Menu : : construct ( ) ;
m_image_context_menu - > add_action ( GUI : : Action : : create ( " &Copy Image " , [ this ] ( auto & ) {
if ( m_image_context_menu_bitmap . is_valid ( ) )
GUI : : Clipboard : : the ( ) . set_bitmap ( * m_image_context_menu_bitmap . bitmap ( ) ) ;
} ) ) ;
m_image_context_menu - > add_action ( GUI : : Action : : create ( " Copy Image &URL " , [ this ] ( auto & ) {
2022-12-05 22:12:49 -03:00
GUI : : Clipboard : : the ( ) . set_plain_text ( m_image_context_menu_url . to_deprecated_string ( ) ) ;
2021-07-22 14:44:59 -03:00
} ) ) ;
m_image_context_menu - > add_separator ( ) ;
m_image_context_menu - > add_action ( GUI : : Action : : create ( " &Open Image in Browser " , [ this ] ( auto & ) {
m_web_view - > on_link_click ( m_image_context_menu_url , " " , 0 ) ;
} ) ) ;
2022-12-06 17:27:44 -03:00
m_web_view - > on_image_context_menu_request = [ this ] ( auto & image_url , auto screen_position , Gfx : : ShareableBitmap const & shareable_bitmap ) {
2021-07-22 14:44:59 -03:00
m_image_context_menu_url = image_url ;
m_image_context_menu_bitmap = shareable_bitmap ;
m_image_context_menu - > popup ( screen_position ) ;
} ;
}
2023-07-26 16:40:05 -03:00
ErrorOr < bool > MailWidget : : connect_and_login ( )
2021-07-22 14:44:59 -03:00
{
2022-07-11 14:32:29 -03:00
auto server = Config : : read_string ( " Mail " sv , " Connection " sv , " Server " sv , { } ) ;
2021-07-22 14:44:59 -03:00
if ( server . is_empty ( ) ) {
2022-07-11 14:32:29 -03:00
auto result = GUI : : MessageBox : : show ( window ( ) , " Mail has no servers configured. Do you want configure them now? " sv , " Error " sv , GUI : : MessageBox : : Type : : Error , GUI : : MessageBox : : InputType : : YesNo ) ;
2022-05-13 09:10:27 -03:00
if ( result = = GUI : : MessageBox : : ExecResult : : Yes )
2022-09-28 20:30:58 -03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( " /bin/MailSettings " ) ) ;
2021-07-22 14:44:59 -03:00
return false ;
}
// Assume TLS by default, which is on port 993.
2022-07-11 14:32:29 -03:00
auto port = Config : : read_i32 ( " Mail " sv , " Connection " sv , " Port " sv , 993 ) ;
auto tls = Config : : read_bool ( " Mail " sv , " Connection " sv , " TLS " sv , true ) ;
2021-07-22 14:44:59 -03:00
2022-07-11 14:32:29 -03:00
auto username = Config : : read_string ( " Mail " sv , " User " sv , " Username " sv , { } ) ;
2021-07-22 14:44:59 -03:00
if ( username . is_empty ( ) ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " Mail has no username configured. Refer to the Mail(1) man page for more information. " sv ) ;
2021-07-22 14:44:59 -03:00
return false ;
}
2023-07-26 16:40:05 -03:00
// FIXME: Plaintext password storage, yikes!
2022-07-11 14:32:29 -03:00
auto password = Config : : read_string ( " Mail " sv , " User " sv , " Password " sv , { } ) ;
2021-07-25 02:00:24 -03:00
while ( password . is_empty ( ) ) {
2022-07-11 14:32:29 -03:00
if ( GUI : : PasswordInputDialog : : show ( window ( ) , password , " Login " sv , server , username ) ! = GUI : : Dialog : : ExecResult : : OK )
2021-07-25 02:00:24 -03:00
return false ;
2021-07-22 14:44:59 -03:00
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " Connecting to {}:{}... " , server , port ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-12-18 09:38:44 -03:00
auto maybe_imap_client = tls ? IMAP : : Client : : connect_tls ( server , port ) : IMAP : : Client : : connect_plaintext ( server , port ) ;
if ( maybe_imap_client . is_error ( ) ) {
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to connect to '{}:{}' over {}: {} " , server , port , tls ? " TLS " : " Plaintext " , maybe_imap_client . error ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return false ;
}
2021-12-18 09:38:44 -03:00
m_imap_client = maybe_imap_client . release_value ( ) ;
2023-07-26 16:40:05 -03:00
TRY ( m_imap_client - > connection_promise ( ) - > await ( ) ) ;
2021-07-22 14:44:59 -03:00
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " Connected. Logging in as {}... " , username ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
2023-08-29 10:15:41 -03:00
auto response = TRY ( m_imap_client - > login ( username , password ) - > await ( ) ) ;
2023-07-26 16:40:05 -03:00
2021-07-22 14:44:59 -03:00
if ( response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
dbgln ( " Failed to login. The server says: '{}' " , response . response_text ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to login. The server says: '{}' " , response . response_text ( ) ) ) ;
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( " Failed to log in " _string ) ;
2021-07-22 14:44:59 -03:00
return false ;
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( " Logged in. Loading mailboxes... " _string ) ;
2023-08-29 10:15:41 -03:00
response = TRY ( m_imap_client - > list ( " " sv , " * " sv ) - > await ( ) ) ;
2021-07-22 14:44:59 -03:00
if ( response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
dbgln ( " Failed to retrieve mailboxes. The server says: '{}' " , response . response_text ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to retrieve mailboxes. The server says: '{}' " , response . response_text ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return false ;
}
auto & list_items = response . data ( ) . list_items ( ) ;
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " Loaded {} mailboxes " , list_items . size ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
m_account_holder = AccountHolder : : create ( ) ;
m_account_holder - > add_account_with_name_and_mailboxes ( username , move ( list_items ) ) ;
m_mailbox_list - > set_model ( m_account_holder - > mailbox_tree_model ( ) ) ;
m_mailbox_list - > expand_tree ( ) ;
return true ;
}
void MailWidget : : on_window_close ( )
{
2023-07-25 15:04:23 -03:00
if ( ! m_imap_client ) {
// User closed main window before a connection was established
return ;
}
2023-08-29 10:15:41 -03:00
auto response = move ( MUST ( m_imap_client - > send_simple_command ( IMAP : : CommandType : : Logout ) - > await ( ) ) . get < IMAP : : SolidResponse > ( ) ) ;
2021-07-22 14:44:59 -03:00
VERIFY ( response . status ( ) = = IMAP : : ResponseStatus : : OK ) ;
m_imap_client - > close ( ) ;
}
IMAP : : MultiPartBodyStructureData const * MailWidget : : look_for_alternative_body_structure ( IMAP : : MultiPartBodyStructureData const & current_body_structure , Vector < u32 > & position_stack ) const
{
2023-09-02 17:29:07 -03:00
if ( current_body_structure . multipart_subtype . equals_ignoring_ascii_case ( " ALTERNATIVE " sv ) )
2021-07-22 14:44:59 -03:00
return & current_body_structure ;
u32 structure_index = 1 ;
for ( auto & structure : current_body_structure . bodies ) {
if ( structure - > data ( ) . has < IMAP : : BodyStructureData > ( ) ) {
+ + structure_index ;
continue ;
}
position_stack . append ( structure_index ) ;
auto * potential_alternative_structure = look_for_alternative_body_structure ( structure - > data ( ) . get < IMAP : : MultiPartBodyStructureData > ( ) , position_stack ) ;
if ( potential_alternative_structure )
return potential_alternative_structure ;
position_stack . take_last ( ) ;
+ + structure_index ;
}
return nullptr ;
}
Vector < MailWidget : : Alternative > MailWidget : : get_alternatives ( IMAP : : MultiPartBodyStructureData const & multi_part_body_structure_data ) const
{
Vector < u32 > position_stack ;
auto * alternative_body_structure = look_for_alternative_body_structure ( multi_part_body_structure_data , position_stack ) ;
if ( ! alternative_body_structure )
return { } ;
Vector < MailWidget : : Alternative > alternatives ;
alternatives . ensure_capacity ( alternative_body_structure - > bodies . size ( ) ) ;
int alternative_index = 1 ;
for ( auto & alternative_body : alternative_body_structure - > bodies ) {
VERIFY ( alternative_body - > data ( ) . has < IMAP : : BodyStructureData > ( ) ) ;
position_stack . append ( alternative_index ) ;
MailWidget : : Alternative alternative = {
. body_structure = alternative_body - > data ( ) . get < IMAP : : BodyStructureData > ( ) ,
. position = position_stack ,
} ;
alternatives . append ( alternative ) ;
position_stack . take_last ( ) ;
+ + alternative_index ;
}
return alternatives ;
}
bool MailWidget : : is_supported_alternative ( Alternative const & alternative ) const
{
2023-03-10 04:48:54 -03:00
return alternative . body_structure . type . equals_ignoring_ascii_case ( " text " sv ) & & ( alternative . body_structure . subtype . equals_ignoring_ascii_case ( " plain " sv ) | | alternative . body_structure . subtype . equals_ignoring_ascii_case ( " html " sv ) ) ;
2021-07-22 14:44:59 -03:00
}
2023-08-29 10:50:37 -03:00
void MailWidget : : selected_mailbox ( GUI : : ModelIndex const & index )
2021-07-22 14:44:59 -03:00
{
2023-08-24 04:11:20 -03:00
m_mailbox_model = InboxModel : : create ( { } ) ;
m_individual_mailbox_view - > set_model ( m_mailbox_model ) ;
2021-07-22 14:44:59 -03:00
if ( ! index . is_valid ( ) )
return ;
auto & base_node = * static_cast < BaseNode * > ( index . internal_data ( ) ) ;
if ( is < AccountNode > ( base_node ) ) {
// FIXME: Do something when clicking on an account node.
return ;
}
auto & mailbox_node = verify_cast < MailboxNode > ( base_node ) ;
auto & mailbox = mailbox_node . mailbox ( ) ;
// FIXME: It would be better if we didn't allow the user to click on this mailbox node at all.
if ( mailbox . flags & ( unsigned ) IMAP : : MailboxFlag : : NoSelect )
return ;
2023-08-29 10:15:41 -03:00
auto response = MUST ( m_imap_client - > select ( mailbox . name ) - > await ( ) ) ;
2021-07-22 14:44:59 -03:00
if ( response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
dbgln ( " Failed to select mailbox. The server says: '{}' " , response . response_text ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to select mailbox. The server says: '{}' " , response . response_text ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return ;
}
if ( response . data ( ) . exists ( ) = = 0 ) {
// No mail in this mailbox, return.
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " [{}]: 0 messages " , mailbox . name ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
return ;
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " [{}]: Fetching {} messages... " , mailbox . name , response . data ( ) . exists ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
auto fetch_command = IMAP : : FetchCommand {
// Mail will always be numbered from 1 up to the number of mail items that exist, which is specified in the select response with "EXISTS".
. sequence_set = { { 1 , ( int ) response . data ( ) . exists ( ) } } ,
. data_items = {
IMAP : : FetchCommand : : DataItem {
2023-08-24 04:26:56 -03:00
. type = IMAP : : FetchCommand : : DataItemType : : Envelope ,
2021-07-22 14:44:59 -03:00
} ,
2023-08-24 04:07:15 -03:00
IMAP : : FetchCommand : : DataItem {
. type = IMAP : : FetchCommand : : DataItemType : : InternalDate ,
} ,
2023-07-11 14:18:26 -03:00
IMAP : : FetchCommand : : DataItem {
. type = IMAP : : FetchCommand : : DataItemType : : Flags ,
} ,
2021-07-22 14:44:59 -03:00
} ,
} ;
2023-08-29 10:15:41 -03:00
auto fetch_response = MUST ( m_imap_client - > fetch ( fetch_command , false ) - > await ( ) ) ;
2023-08-29 09:35:23 -03:00
if ( fetch_response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
2021-07-22 14:44:59 -03:00
dbgln ( " Failed to retrieve subject/from for e-mails. The server says: '{}' " , response . response_text ( ) ) ;
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " [{}]: Failed to fetch messages :^( " , mailbox . name ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to retrieve e-mails. The server says: '{}' " , response . response_text ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return ;
}
Vector < InboxEntry > active_inbox_entries ;
2023-07-25 17:03:45 -03:00
int i = 0 ;
2021-07-22 14:44:59 -03:00
for ( auto & fetch_data : fetch_response . data ( ) . fetch_data ( ) ) {
2023-08-29 10:44:13 -03:00
auto sequence_number = fetch_data . get < unsigned > ( ) ;
2021-07-22 14:44:59 -03:00
auto & response_data = fetch_data . get < IMAP : : FetchResponseData > ( ) ;
2023-08-24 04:26:56 -03:00
auto const & envelope = response_data . envelope ( ) ;
auto const & internal_date = response_data . internal_date ( ) ;
2021-07-22 14:44:59 -03:00
2023-07-11 14:18:26 -03:00
auto seen = ! response_data . flags ( ) . find_if ( [ ] ( StringView value ) { return value . equals_ignoring_ascii_case ( " \\ Seen " sv ) ; } ) . is_end ( ) ;
2023-08-24 04:07:15 -03:00
DeprecatedString date = internal_date . to_deprecated_string ( ) ;
2023-08-24 04:26:56 -03:00
DeprecatedString subject = envelope . subject . value_or ( " (No subject) " ) ;
2023-07-31 13:47:17 -03:00
if ( subject . contains ( " =? " sv ) & & subject . contains ( " ?= " sv ) ) {
subject = MUST ( IMAP : : decode_rfc2047_encoded_words ( subject ) ) ;
}
2023-08-24 04:26:56 -03:00
StringBuilder sender_builder ;
if ( envelope . from . has_value ( ) ) {
bool first { true } ;
for ( auto const & address : * envelope . from ) {
if ( ! first )
sender_builder . append ( " , " sv ) ;
if ( address . name . has_value ( ) ) {
if ( address . name - > contains ( " =? " sv ) & & address . name - > contains ( " ?= " sv ) )
sender_builder . append ( MUST ( IMAP : : decode_rfc2047_encoded_words ( * address . name ) ) ) ;
else
sender_builder . append ( * address . name ) ;
sender_builder . append ( " < " sv ) ;
sender_builder . append ( address . mailbox . value_or ( { } ) ) ;
sender_builder . append ( ' @ ' ) ;
sender_builder . append ( address . host . value_or ( { } ) ) ;
sender_builder . append ( ' > ' ) ;
} else {
sender_builder . append ( address . mailbox . value_or ( { } ) ) ;
sender_builder . append ( ' @ ' ) ;
sender_builder . append ( address . host . value_or ( { } ) ) ;
}
}
2023-07-25 15:11:54 -03:00
}
2023-08-24 04:26:56 -03:00
DeprecatedString from = sender_builder . to_deprecated_string ( ) ;
2021-07-22 14:44:59 -03:00
2023-08-29 10:54:19 -03:00
InboxEntry inbox_entry { sequence_number , date , from , subject , seen } ;
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " [{}]: Loading entry {} " , mailbox . name , + + i ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-22 14:44:59 -03:00
active_inbox_entries . append ( inbox_entry ) ;
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( String : : formatted ( " [{}]: Loaded {} entries " , mailbox . name , i ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2023-08-24 04:11:20 -03:00
m_mailbox_model = InboxModel : : create ( move ( active_inbox_entries ) ) ;
m_mailbox_sorting_model = MUST ( GUI : : SortingProxyModel : : create ( * m_mailbox_model ) ) ;
m_mailbox_sorting_model - > set_sort_role ( GUI : : ModelRole : : Display ) ;
m_individual_mailbox_view - > set_model ( m_mailbox_sorting_model ) ;
m_individual_mailbox_view - > set_key_column_and_sort_order ( InboxModel : : Date , GUI : : SortOrder : : Descending ) ;
2021-07-22 14:44:59 -03:00
}
2023-08-29 10:50:37 -03:00
void MailWidget : : selected_email_to_load ( GUI : : ModelIndex const & index )
2021-07-22 14:44:59 -03:00
{
if ( ! index . is_valid ( ) )
return ;
2023-08-29 10:44:13 -03:00
int id_of_email_to_load = index . data ( static_cast < GUI : : ModelRole > ( InboxModelCustomRole : : Sequence ) ) . as_u32 ( ) ;
2021-07-22 14:44:59 -03:00
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( " Fetching message... " _string ) ;
2021-07-22 14:44:59 -03:00
auto fetch_command = IMAP : : FetchCommand {
. sequence_set = { { id_of_email_to_load , id_of_email_to_load } } ,
. data_items = {
IMAP : : FetchCommand : : DataItem {
. type = IMAP : : FetchCommand : : DataItemType : : BodyStructure ,
} ,
} ,
} ;
2023-08-29 10:15:41 -03:00
auto fetch_response = MUST ( m_imap_client - > fetch ( fetch_command , false ) - > await ( ) ) ;
2021-07-22 14:44:59 -03:00
if ( fetch_response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
dbgln ( " Failed to retrieve the body structure of the selected e-mail. The server says: '{}' " , fetch_response . response_text ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to retrieve the selected e-mail. The server says: '{}' " , fetch_response . response_text ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return ;
}
Vector < u32 > selected_alternative_position ;
2022-12-04 15:02:33 -03:00
DeprecatedString selected_alternative_encoding ;
2021-07-22 14:44:59 -03:00
auto & response_data = fetch_response . data ( ) . fetch_data ( ) . last ( ) . get < IMAP : : FetchResponseData > ( ) ;
response_data . body_structure ( ) . data ( ) . visit (
[ & ] ( IMAP : : BodyStructureData const & data ) {
// The message will be in the first position.
selected_alternative_position . append ( 1 ) ;
selected_alternative_encoding = data . encoding ;
} ,
[ & ] ( IMAP : : MultiPartBodyStructureData const & data ) {
auto alternatives = get_alternatives ( data ) ;
if ( alternatives . is_empty ( ) ) {
dbgln ( " No alternatives. The server said: '{}' " , fetch_response . response_text ( ) ) ;
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " The server sent no message to display. " sv ) ;
2021-07-22 14:44:59 -03:00
return ;
}
// We can choose whichever alternative we want. In general, we should choose the last alternative that know we can display.
// RFC 2046 Section 5.1.4 https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.4
auto chosen_alternative = alternatives . last_matching ( [ this ] ( auto & alternative ) {
return is_supported_alternative ( alternative ) ;
} ) ;
if ( ! chosen_alternative . has_value ( ) ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show ( window ( ) , " Displaying this type of e-mail is currently unsupported. " sv , " Unsupported " sv , GUI : : MessageBox : : Type : : Information ) ;
2021-07-22 14:44:59 -03:00
return ;
}
selected_alternative_position = chosen_alternative - > position ;
selected_alternative_encoding = chosen_alternative - > body_structure . encoding ;
} ) ;
if ( selected_alternative_position . is_empty ( ) ) {
// An error occurred above, return.
return ;
}
fetch_command = IMAP : : FetchCommand {
. sequence_set { { id_of_email_to_load , id_of_email_to_load } } ,
. data_items = {
IMAP : : FetchCommand : : DataItem {
. type = IMAP : : FetchCommand : : DataItemType : : BodySection ,
. section = IMAP : : FetchCommand : : DataItem : : Section {
. type = IMAP : : FetchCommand : : DataItem : : SectionType : : Parts ,
. parts = selected_alternative_position ,
} ,
. partial_fetch = false ,
} ,
2023-07-11 15:24:03 -03:00
IMAP : : FetchCommand : : DataItem {
. type = IMAP : : FetchCommand : : DataItemType : : Flags ,
} ,
2021-07-22 14:44:59 -03:00
} ,
} ;
2023-08-29 10:15:41 -03:00
fetch_response = MUST ( m_imap_client - > fetch ( fetch_command , false ) - > await ( ) ) ;
2021-07-22 14:44:59 -03:00
if ( fetch_response . status ( ) ! = IMAP : : ResponseStatus : : OK ) {
dbgln ( " Failed to retrieve the body of the selected e-mail. The server says: '{}' " , fetch_response . response_text ( ) ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show_error ( window ( ) , DeprecatedString : : formatted ( " Failed to retrieve the selected e-mail. The server says: '{}' " , fetch_response . response_text ( ) ) ) ;
2021-07-22 14:44:59 -03:00
return ;
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( " Parsing message... " _string ) ;
2021-07-22 14:44:59 -03:00
auto & fetch_data = fetch_response . data ( ) . fetch_data ( ) ;
if ( fetch_data . is_empty ( ) ) {
dbgln ( " The server sent no fetch data. " ) ;
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " The server sent no data. " sv ) ;
2021-07-22 14:44:59 -03:00
return ;
}
auto & fetch_response_data = fetch_data . last ( ) . get < IMAP : : FetchResponseData > ( ) ;
2023-07-11 15:24:03 -03:00
auto seen = ! fetch_response_data . flags ( ) . find_if ( [ ] ( StringView value ) { return value . equals_ignoring_ascii_case ( " \\ Seen " sv ) ; } ) . is_end ( ) ;
2023-08-24 04:11:20 -03:00
m_mailbox_model - > set_seen ( index . row ( ) , seen ) ;
2023-07-11 15:24:03 -03:00
2021-07-22 14:44:59 -03:00
if ( ! fetch_response_data . contains_response_type ( IMAP : : FetchResponseType : : Body ) ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " The server sent no body. " sv ) ;
2021-07-22 14:44:59 -03:00
return ;
}
auto & body_data = fetch_response_data . body_data ( ) ;
2022-12-04 15:02:33 -03:00
auto body_text_part_iterator = body_data . find_if ( [ ] ( Tuple < IMAP : : FetchCommand : : DataItem , Optional < DeprecatedString > > & data ) {
2021-07-22 14:44:59 -03:00
const auto data_item = data . get < 0 > ( ) ;
return data_item . section . has_value ( ) & & data_item . section - > type = = IMAP : : FetchCommand : : DataItem : : SectionType : : Parts ;
} ) ;
VERIFY ( body_text_part_iterator ! = body_data . end ( ) ) ;
auto & encoded_data = body_text_part_iterator - > get < 1 > ( ) . value ( ) ;
2022-12-04 15:02:33 -03:00
DeprecatedString decoded_data ;
2021-07-22 14:44:59 -03:00
// FIXME: String uses char internally, so 8bit shouldn't be stored in it.
// However, it works for now.
2023-03-10 04:48:54 -03:00
if ( selected_alternative_encoding . equals_ignoring_ascii_case ( " 7bit " sv ) | | selected_alternative_encoding . equals_ignoring_ascii_case ( " 8bit " sv ) ) {
2021-07-22 14:44:59 -03:00
decoded_data = encoded_data ;
2023-03-10 04:48:54 -03:00
} else if ( selected_alternative_encoding . equals_ignoring_ascii_case ( " base64 " sv ) ) {
2022-01-20 14:18:17 -03:00
auto decoded_base64 = decode_base64 ( encoded_data ) ;
if ( ! decoded_base64 . is_error ( ) )
decoded_data = decoded_base64 . release_value ( ) ;
2023-03-10 04:48:54 -03:00
} else if ( selected_alternative_encoding . equals_ignoring_ascii_case ( " quoted-printable " sv ) ) {
2023-03-09 11:47:45 -03:00
decoded_data = IMAP : : decode_quoted_printable ( encoded_data ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-22 14:44:59 -03:00
} else {
dbgln ( " Mail: Unimplemented decoder for encoding: {} " , selected_alternative_encoding ) ;
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( window ( ) , DeprecatedString : : formatted ( " The e-mail encoding '{}' is currently unsupported. " , selected_alternative_encoding ) , " Unsupported " sv , GUI : : MessageBox : : Type : : Information ) ;
2021-07-22 14:44:59 -03:00
return ;
}
2023-07-25 17:03:45 -03:00
m_statusbar - > set_text ( " Message loaded. " _string ) ;
2021-07-22 14:44:59 -03:00
// FIXME: I'm not sure what the URL should be. Just use the default URL "about:blank".
// FIXME: It would be nice if we could pass over the charset.
2023-07-25 17:03:45 -03:00
// FIXME: Add ability to cancel the load when we switch to another email. Feels very sluggish on heavy emails otherwise
2023-09-17 12:12:17 -03:00
m_web_view - > load_html ( decoded_data ) ;
2021-07-22 14:44:59 -03:00
}