2025-01-07 09:40:39 +01:00
// Copyright (C) 2025, The Duplicati Team
2024-02-28 15:45:30 +01:00
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using Duplicati.Library.Interface ;
2017-01-30 18:01:12 -05:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2024-04-24 13:16:21 +02:00
using System.Net.Http ;
using System.Net.Http.Headers ;
2017-01-30 18:01:12 -05:00
using System.Text ;
2024-04-24 13:16:21 +02:00
using System.Threading.Tasks ;
2024-12-04 13:25:55 -03:00
using Duplicati.Library.Utility ;
using Uri = System . Uri ;
2025-03-16 16:52:30 +01:00
using System.Threading ;
2017-01-30 18:01:12 -05:00
2024-04-24 13:16:21 +02:00
namespace Duplicati.Library.Modules.Builtin
{
/// <summary>
/// Helper module to send HTTP report messages
/// </summary>
2018-04-10 12:58:06 +02:00
public class SendHttpMessage : ReportHelper
2017-01-30 18:01:12 -05:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < SendHttpMessage >();
2024-04-24 13:16:21 +02:00
/// <summary>
/// Entry describing a request to be sent
/// </summary>
/// <param name="Url">The url to send to</param>
/// <param name="Verb">The verb to use</param>
/// <param name="Format">The format to send</param>
private record SendRequestType ( string Url , string Verb , ResultExportFormat Format );
2017-01-30 18:01:12 -05:00
#region Option names
/// <summary>
/// Option used to specify server URL
/// </summary>
private const string OPTION_URL = "send-http-url" ;
/// <summary>
2024-04-24 13:16:21 +02:00
/// Option used to specify server URLs for sending text reports
/// </summary>
private const string OPTION_URL_FORM = "send-http-form-urls" ;
/// <summary>
/// Option used to specify server URLs for sending json reports
/// </summary>
private const string OPTION_URL_JSON = "send-http-json-urls" ;
/// <summary>
2017-01-30 18:01:12 -05:00
/// Option used to specify report body
/// </summary>
private const string OPTION_MESSAGE = "send-http-message" ;
/// <summary>
2018-04-10 12:58:06 +02:00
/// Option used to specify report verb
/// </summary>
private const string OPTION_VERB = "send-http-verb" ;
/// <summary>
2017-01-30 18:01:12 -05:00
/// Option used to specify the parameter name for the message
/// </summary>
private const string OPTION_MESSAGE_PARAMETER_NAME = "send-http-message-parameter-name" ;
/// <summary>
/// Option used to specify extra parameters
/// </summary>
private const string OPTION_EXTRA_PARAMETERS = "send-http-extra-parameters" ;
/// <summary>
/// Option used to specify report level
/// </summary>
private const string OPTION_SENDLEVEL = "send-http-level" ;
/// <summary>
/// Option used to specify if reports are sent for other operations than backups
/// </summary>
private const string OPTION_SENDALL = "send-http-any-operation" ;
/// <summary>
2018-04-09 18:53:37 -02:30
/// Option used to specify what format the result is sent in.
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-09 18:53:37 -02:30
private const string OPTION_RESULT_FORMAT = "send-http-result-output-format" ;
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// Option used to set the log level
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-10 12:58:06 +02:00
private const string OPTION_LOG_LEVEL = "send-http-log-level" ;
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// Option used to set the log level
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-10 12:58:06 +02:00
private const string OPTION_LOG_FILTER = "send-http-log-filter" ;
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// Option used to set the maximum number of log lines
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-10 12:58:06 +02:00
private const string OPTION_MAX_LOG_LINES = "send-http-max-log-lines" ;
2025-01-14 14:03:48 +01:00
2024-12-04 13:25:55 -03:00
/// <summary>
/// The option used to accept a specific SSL certificate hash
/// </summary>
2025-01-14 14:03:48 +01:00
private const string OPTION_ACCEPT_SPECIFIED_CERTIFICATE = "send-http-accept-specified-ssl-hash" ;
2024-12-04 13:25:55 -03:00
/// <summary>
/// The option used to accept any SSL certificate
/// </summary>
2025-01-14 14:03:48 +01:00
private const string OPTION_ACCEPT_ANY_CERTIFICATE = "send-http-accept-any-ssl-certificate" ;
2025-03-16 16:52:30 +01:00
/// <summary>
/// The option used to specify the number of retries for sending the HTTP request
/// </summary>
private const string OPTION_SEND_HTTP_RETRIES = "send-http-retries" ;
/// <summary>
/// The option used to specify the delay between retries for sending the HTTP request
/// </summary>
private const string OPTION_SEND_HTTP_RETRY_DELAY = "send-http-retry-delay" ;
2018-04-10 12:58:06 +02:00
#endregion
#region Option defaults
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// The default message parameter name
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-10 12:58:06 +02:00
private const string DEFAULT_MESSAGE_PARAMETER_NAME = "message" ;
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// The default message body
2017-01-30 18:01:12 -05:00
/// </summary>
2024-10-16 16:22:47 +02:00
protected override string DEFAULT_BODY => string . Format ( "Duplicati %OPERATIONNAME% report for %backup-name% (%machine-id%, %backup-id%, %machine-name%){0}{0}%RESULT%" , Environment . NewLine );
2017-01-30 18:01:12 -05:00
/// <summary>
2018-04-10 12:58:06 +02:00
/// Don't use the subject for HTTP
2017-01-30 18:01:12 -05:00
/// </summary>
2018-04-10 12:58:06 +02:00
protected override string DEFAULT_SUBJECT => string . Empty ;
2025-03-16 16:52:30 +01:00
/// <summary>
/// The default number of retries
/// </summary>
private const int DEFAULT_RETRIES = 3 ;
/// <summary>
/// The default delay between retries
/// </summary>
private const string DEFAULT_RETRY_DELAY = "1s" ;
2018-04-10 12:58:06 +02:00
#endregion
#region Private variables
2017-01-30 18:01:12 -05:00
/// <summary>
2024-04-24 13:16:21 +02:00
/// The HTTP text report URLs
2017-01-30 18:01:12 -05:00
/// </summary>
2024-04-24 13:16:21 +02:00
private List < SendRequestType > m_report_targets ;
2017-01-30 18:01:12 -05:00
/// <summary>
/// The message parameter name
/// </summary>
private string m_messageParameterName ;
/// <summary>
/// The message parameter name
/// </summary>
private string m_extraParameters ;
2025-01-14 14:03:48 +01:00
2024-12-04 13:25:55 -03:00
/// <summary>
/// Option to accept any SSL certificate
/// </summary>
private bool m_acceptAnyCertificate ;
2025-01-14 14:03:48 +01:00
2024-12-04 13:25:55 -03:00
/// <summary>
/// Specific hashes to be accepted by the certificate validator
/// </summary>
private string [] m_acceptSpecificCertificates ;
2017-01-30 18:01:12 -05:00
2025-03-16 16:52:30 +01:00
/// <summary>
/// The number of retries to attempt
/// </summary>
private int m_retries ;
/// <summary>
/// The delay between retries
/// </summary>
private TimeSpan m_retryDelay ;
2017-01-30 18:01:12 -05:00
#endregion
#region Implementation of IGenericModule
/// <summary>
/// The module key, used to activate or deactivate the module on the commandline
/// </summary>
2018-04-10 12:58:06 +02:00
public override string Key { get { return "sendhttp" ; } }
2017-01-30 18:01:12 -05:00
/// <summary>
/// A localized string describing the module with a friendly name
/// </summary>
2024-04-24 13:16:21 +02:00
public override string DisplayName { get { return Strings . SendHttpMessage . DisplayName ; } }
2017-01-30 18:01:12 -05:00
/// <summary>
/// A localized description of the module
/// </summary>
2018-04-10 12:58:06 +02:00
public override string Description { get { return Strings . SendHttpMessage . Description ; } }
2017-01-30 18:01:12 -05:00
/// <summary>
/// A boolean value that indicates if the module should always be loaded.
/// If true, the user can choose to not load the module by entering the appropriate commandline option.
/// If false, the user can choose to load the module by entering the appropriate commandline option.
/// </summary>
2018-04-10 12:58:06 +02:00
public override bool LoadAsDefault { get { return true ; } }
2017-01-30 18:01:12 -05:00
/// <summary>
/// Gets a list of supported commandline arguments
/// </summary>
2025-03-24 14:35:16 +01:00
public override IList < ICommandLineArgument > SupportedCommands =>
[
new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlShort, Strings.SendHttpMessage.SendhttpurlLong),
new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageShort, Strings.SendHttpMessage.SendhttpmessageLong, DEFAULT_BODY),
new CommandLineArgument(OPTION_MESSAGE_PARAMETER_NAME, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageparameternameShort, Strings.SendHttpMessage.SendhttpmessageparameternameLong, DEFAULT_MESSAGE_PARAMETER_NAME),
new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpextraparametersShort, Strings.SendHttpMessage.SendhttpextraparametersLong),
new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttplevelShort, Strings.SendHttpMessage.SendhttplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ). ToArray ()),
new CommandLineArgument ( OPTION_SENDALL , CommandLineArgument . ArgumentType . Boolean , Strings . SendHttpMessage . SendhttpanyoperationShort , Strings . SendHttpMessage . SendhttpanyoperationLong ),
2018-04-10 12:58:06 +02:00
2025-03-24 14:35:16 +01:00
new CommandLineArgument ( OPTION_VERB , CommandLineArgument . ArgumentType . String , Strings . SendHttpMessage . HttpverbShort , Strings . SendHttpMessage . HttpverbLong , "POST" ),
new CommandLineArgument ( OPTION_LOG_LEVEL , CommandLineArgument . ArgumentType . Enumeration , Strings . ReportHelper . OptionLoglevelShort , Strings . ReportHelper . OptionLoglevelLong , DEFAULT_LOG_LEVEL . ToString (), null , Enum . GetNames ( typeof ( Logging . LogMessageType ))),
new CommandLineArgument ( OPTION_LOG_FILTER , CommandLineArgument . ArgumentType . String , Strings . ReportHelper . OptionLogfilterShort , Strings . ReportHelper . OptionLogfilterLong ),
new CommandLineArgument ( OPTION_MAX_LOG_LINES , CommandLineArgument . ArgumentType . Integer , Strings . ReportHelper . OptionmaxloglinesShort , Strings . ReportHelper . OptionmaxloglinesLong , DEFAULT_LOGLINES . ToString ()),
2018-04-12 11:16:57 +02:00
2025-03-24 14:35:16 +01:00
new CommandLineArgument ( OPTION_RESULT_FORMAT , CommandLineArgument . ArgumentType . Enumeration , Strings . ReportHelper . ResultFormatShort , Strings . ReportHelper . ResultFormatLong ( Enum . GetNames ( typeof ( ResultExportFormat ))), DEFAULT_EXPORT_FORMAT . ToString (), null , Enum . GetNames ( typeof ( ResultExportFormat ))),
2024-04-24 13:16:21 +02:00
2025-03-24 14:35:16 +01:00
new CommandLineArgument ( OPTION_URL_FORM , CommandLineArgument . ArgumentType . String , Strings . SendHttpMessage . SendhttpurlsformShort , Strings . SendHttpMessage . SendhttpurlsformLong ),
new CommandLineArgument ( OPTION_URL_JSON , CommandLineArgument . ArgumentType . String , Strings . SendHttpMessage . SendhttpurlsjsonShort , Strings . SendHttpMessage . SendhttpurlsjsonLong ),
2025-01-14 14:03:48 +01:00
2025-03-24 14:35:16 +01:00
new CommandLineArgument ( OPTION_ACCEPT_ANY_CERTIFICATE , CommandLineArgument . ArgumentType . Boolean , Strings . SendHttpMessage . AcceptAnyCertificateShort , Strings . SendHttpMessage . AcceptAnyCertificateLong ),
new CommandLineArgument ( OPTION_ACCEPT_SPECIFIED_CERTIFICATE , CommandLineArgument . ArgumentType . String , Strings . SendHttpMessage . AcceptSpecifiedCertificateShort , Strings . SendHttpMessage . AcceptSpecifiedCertificateLong ),
2025-03-16 16:52:30 +01:00
2025-03-24 14:35:16 +01:00
new CommandLineArgument ( OPTION_SEND_HTTP_RETRIES , CommandLineArgument . ArgumentType . Integer , Strings . SendHttpMessage . SendHttpRetriesShort , Strings . SendHttpMessage . SendHttpRetriesLong , DEFAULT_RETRIES . ToString ()),
new CommandLineArgument ( OPTION_SEND_HTTP_RETRY_DELAY , CommandLineArgument . ArgumentType . Integer , Strings . SendHttpMessage . SendHttpRetryDelayShort , Strings . SendHttpMessage . SendHttpRetryDelayLong , DEFAULT_RETRY_DELAY ),
];
2017-01-30 18:01:12 -05:00
2018-04-12 11:16:57 +02:00
protected override string SubjectOptionName => OPTION_MESSAGE ;
protected override string BodyOptionName => OPTION_MESSAGE ;
protected override string ActionLevelOptionName => OPTION_SENDLEVEL ;
protected override string ActionOnAnyOperationOptionName => OPTION_SENDALL ;
protected override string LogLevelOptionName => OPTION_LOG_LEVEL ;
2018-04-10 12:58:06 +02:00
protected override string LogFilterOptionName => OPTION_LOG_FILTER ;
protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES ;
2018-04-12 11:16:57 +02:00
protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT ;
2025-03-24 14:43:00 +01:00
protected override string ExtraDataOptionName => OPTION_EXTRA_PARAMETERS ;
2018-04-10 12:58:06 +02:00
2024-04-24 13:16:21 +02:00
/// <summary>
/// This method is the interception where the module can interact with the execution environment and modify the settings.
/// </summary>
/// <param name="commandlineOptions">A set of commandline options passed to Duplicati</param>
protected override bool ConfigureModule ( IDictionary < string , string > commandlineOptions )
2017-01-30 18:01:12 -05:00
{
2024-04-24 13:16:21 +02:00
var reportTargets = new List < SendRequestType >();
// Grab the legacy URL option if it exists, and add it to the appropriate list
commandlineOptions . TryGetValue ( OPTION_URL , out var legacy_urls );
if (! string . IsNullOrEmpty ( legacy_urls ))
{
if (! commandlineOptions . TryGetValue ( OPTION_RESULT_FORMAT , out var format ))
format = ResultExportFormat . Duplicati . ToString ();
2024-08-29 14:35:03 +02:00
if (! Enum . TryParse < ResultExportFormat >( format , true , out var exportFormat ))
2024-04-24 13:16:21 +02:00
exportFormat = ResultExportFormat . Duplicati ;
commandlineOptions . TryGetValue ( OPTION_VERB , out var verb );
if ( string . IsNullOrEmpty ( verb ))
verb = "POST" ;
reportTargets . AddRange ( legacy_urls . Split ( new char [] { ';' }, StringSplitOptions . RemoveEmptyEntries ). Select ( url => new SendRequestType ( url , verb , exportFormat )));
}
// Get the options as passed
commandlineOptions . TryGetValue ( OPTION_URL_FORM , out var formurls );
if (! string . IsNullOrWhiteSpace ( formurls ))
reportTargets . AddRange ( formurls . Split ( new char [] { ';' }, StringSplitOptions . RemoveEmptyEntries ). Select ( url => new SendRequestType ( url , "POST" , ResultExportFormat . Duplicati )));
commandlineOptions . TryGetValue ( OPTION_URL_JSON , out var jsonurls );
if (! string . IsNullOrWhiteSpace ( jsonurls ))
reportTargets . AddRange ( jsonurls . Split ( new char [] { ';' }, StringSplitOptions . RemoveEmptyEntries ). Select ( url => new SendRequestType ( url , "POST" , ResultExportFormat . Json )));
//We need at least one URL to report to
if ( reportTargets . Count == 0 )
2018-04-10 12:58:06 +02:00
return false ;
2017-01-30 18:01:12 -05:00
2025-05-30 08:16:20 +02:00
m_report_targets = reportTargets . DistinctBy ( x => ( x . Url , x . Verb , x . Format )). ToList ();
2024-04-24 13:16:21 +02:00
2017-01-30 18:01:12 -05:00
commandlineOptions . TryGetValue ( OPTION_MESSAGE_PARAMETER_NAME , out m_messageParameterName );
if ( string . IsNullOrEmpty ( m_messageParameterName ))
m_messageParameterName = DEFAULT_MESSAGE_PARAMETER_NAME ;
commandlineOptions . TryGetValue ( OPTION_EXTRA_PARAMETERS , out m_extraParameters );
2025-03-16 16:52:30 +01:00
m_acceptAnyCertificate = Utility . Utility . ParseBoolOption ( commandlineOptions . AsReadOnly (), OPTION_ACCEPT_ANY_CERTIFICATE );
2025-09-03 12:14:42 +02:00
m_acceptSpecificCertificates = commandlineOptions . ContainsKey ( OPTION_ACCEPT_SPECIFIED_CERTIFICATE ) ? commandlineOptions [ OPTION_ACCEPT_SPECIFIED_CERTIFICATE ]. Split ( new [] { ',' , ';' }, StringSplitOptions . RemoveEmptyEntries ) : null ;
2024-12-04 13:25:55 -03:00
2025-03-16 16:52:30 +01:00
m_retries = Utility . Utility . ParseIntOption ( commandlineOptions . AsReadOnly (), OPTION_SEND_HTTP_RETRIES , DEFAULT_RETRIES );
m_retryDelay = Utility . Utility . ParseTimespanOption ( commandlineOptions . AsReadOnly (), OPTION_SEND_HTTP_RETRY_DELAY , DEFAULT_RETRY_DELAY );
2018-04-10 12:58:06 +02:00
return true ;
2017-01-30 18:01:12 -05:00
}
2024-04-24 13:16:21 +02:00
#endregion
2017-01-30 18:01:12 -05:00
2025-01-14 14:03:48 +01:00
private async Task < Exception > SendMessage ( HttpClient client , SendRequestType target , string subject , string body )
2024-04-24 13:16:21 +02:00
{
2018-04-10 12:58:06 +02:00
byte [] data ;
2024-04-24 13:16:21 +02:00
MediaTypeHeaderValue contenttype ;
2017-01-30 18:01:12 -05:00
2024-04-24 13:16:21 +02:00
if ( target . Format == ResultExportFormat . Json )
2017-01-30 18:01:12 -05:00
{
2024-04-24 13:16:21 +02:00
contenttype = new MediaTypeHeaderValue ( "application/json" );
2018-04-12 11:16:57 +02:00
data = Encoding . UTF8 . GetBytes ( body );
2017-01-30 18:01:12 -05:00
}
2018-04-10 12:58:06 +02:00
else
2017-01-30 18:01:12 -05:00
{
2025-03-24 22:28:44 +01:00
const int chuncksize = 2048 ;
2024-04-24 13:16:21 +02:00
contenttype = new MediaTypeHeaderValue ( "application/x-www-form-urlencoded" );
2025-03-24 22:28:44 +01:00
var sb = new StringBuilder ();
sb . Append ( m_messageParameterName ). Append ( "=" );
for ( int i = 0 ; i < body . Length ; i += chuncksize )
sb . Append ( System . Uri . EscapeDataString ( body . Substring ( i , Math . Min ( chuncksize , body . Length - i ))));
2018-04-10 12:58:06 +02:00
if (! string . IsNullOrEmpty ( m_extraParameters ))
2025-03-24 22:28:44 +01:00
sb . Append ( "&" ). Append ( m_extraParameters );
data = Encoding . UTF8 . GetBytes ( sb . ToString ());
2017-01-30 18:01:12 -05:00
}
2024-04-24 13:16:21 +02:00
var request = new HttpRequestMessage
{
RequestUri = new Uri ( target . Url ),
Method = new HttpMethod ( target . Verb ),
Content = new ByteArrayContent ( data )
};
request . Content . Headers . ContentType = contenttype ;
2017-01-30 18:01:12 -05:00
2025-03-16 16:52:30 +01:00
Exception lastEx = null ;
await RetryHelper . Retry ( async () =>
2024-04-24 13:16:21 +02:00
{
var response = await client . SendAsync ( request );
var responseContent = await response . Content . ReadAsStringAsync ();
Logging . Log . WriteVerboseMessage ( LOGTAG , "HttpResponseMessage" ,
"HTTP Response to {0}: {1} - {2}: {3}" ,
target . Url ,
(( int ) response . StatusCode ). ToString (),
response . ReasonPhrase ,
responseContent
);
2024-07-10 09:22:07 +02:00
response . EnsureSuccessStatusCode ();
2025-03-16 16:52:30 +01:00
lastEx = null ;
}, ( ex , retry ) =>
2017-01-30 18:01:12 -05:00
{
2025-03-16 16:52:30 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "HttpResponseError" , ex , "HTTP Response request attempt {0} of {1} failed for: {2}" , retry , m_retries , target . Url );
lastEx = ex ;
}, m_retries , m_retryDelay , CancellationToken . None );
2024-04-24 13:16:21 +02:00
2025-03-16 16:52:30 +01:00
return lastEx ;
2024-04-24 13:16:21 +02:00
}
private Dictionary < ResultExportFormat , string > m_cachedBodyResults ;
protected override string ReplaceTemplate ( string input , object result , Exception exception , bool subjectline )
{
// No need to do the expansion as we throw away the result
if ( subjectline )
return string . Empty ;
if ( m_report_targets == null )
return string . Empty ;
m_cachedBodyResults = m_report_targets
. Select ( x => x . Format )
. Distinct ()
. ToDictionary (
x => x ,
x => base . ReplaceTemplate ( input , result , exception , false , x )
);
return string . Empty ;
}
protected override void SendMessage ( string subject , string body )
{
if ( m_report_targets == null || m_cachedBodyResults == null )
return ;
2024-12-04 13:25:55 -03:00
using HttpClientHandler httpHandler = new HttpClientHandler ();
HttpClientHelper . ConfigureHandlerCertificateValidator ( httpHandler , m_acceptAnyCertificate , m_acceptSpecificCertificates );
2025-06-12 10:33:13 -03:00
using var client = HttpClientHelper . CreateClient ( httpHandler );
2025-06-12 11:29:44 -03:00
// Explicitly keeping the default 100s timeout
2024-04-24 13:16:21 +02:00
Exception ex = null ;
foreach ( var target in m_report_targets )
2017-01-30 18:01:12 -05:00
{
2024-04-24 13:16:21 +02:00
if ( m_cachedBodyResults . TryGetValue ( target . Format , out var result ))
2025-03-16 16:52:30 +01:00
ex ??= SendMessage ( client , target , subject , result ). Await ();
2017-01-30 18:01:12 -05:00
}
if ( ex != null )
throw ex ;
}
}
}