GCC Code Coverage Report


Directory: src/gate/
File: src/gate/net/smtpclients.c
Date: 2026-10-19 17:06:30
Exec Total Coverage
Lines: 114 123 92.7%
Functions: 6 6 100.0%
Branches: 25 50 50.0%

Line Branch Exec Source
1 /* GATE PROJECT LICENSE:
2 +----------------------------------------------------------------------------+
3 | Copyright (c) 2018-2026, Stefan Meislinger <sm@opengate.at> |
4 | All rights reserved. |
5 | |
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met:|
8 | |
9 | 1. Redistributions of source code must retain the above copyright notice, |
10 | this list of conditions and the following disclaimer. |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"|
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 | ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
25 | THE POSSIBILITY OF SUCH DAMAGE. |
26 +----------------------------------------------------------------------------+
27 */
28
29 #include "gate/net/smtpclients.h"
30 #include "gate/results.h"
31
32 #if defined(GATE_SYS_WIN)
33 # define GATE_NET_SMTPCLIENTS_WINCDO 1
34 #elif defined(GATE_SYS_POSIX)
35 # define GATE_NET_SMTPCLIENTS_CURL 1
36 #else
37 # define GATE_NET_SMTPCLIENTS_NOIMPL 1
38 #endif
39
40
41 #if defined(GATE_NET_SMTPCLIENTS_WINCDO)
42
43 #include "gate/net/platform/cdosys_api.h"
44 #include "gate/debugging.h"
45 #include "gate/uris.h"
46
47 gate_result_t gate_smtpclient_create(gate_smtpclient_t* client,
48 gate_string_t const* server, gate_uint16_t port,
49 gate_string_t const* user, gate_string_t const* password,
50 gate_enumint_t flags)
51 {
52 gate_size_t len;
53 gate_mem_clear(client, sizeof(gate_smtpclient_t));
54 gate_string_to_buffer(server, client->server, sizeof(client->server));
55 len = gate_string_to_buffer(user, &client->config[0], sizeof(client->config) - 2);
56 gate_string_to_buffer(password, &client->config[len + 1], sizeof(client->config) - len - 1);
57 client->port = port;
58 client->flags = flags;
59 return GATE_RESULT_OK;
60 }
61
62 gate_result_t gate_smtpclient_release(gate_smtpclient_t* client)
63 {
64 gate_mem_clear(client, sizeof(gate_smtpclient_t));
65 return GATE_RESULT_OK;
66 }
67
68 static gate_result_t stringlist_to_senderstring(gate_string_t* target, gate_arraylist_t lst)
69 {
70 const gate_size_t length = gate_arraylist_is_null(lst) ? 0 : gate_arraylist_length(lst);
71 if (length == 0)
72 {
73 gate_string_create_empty(target);
74 return GATE_RESULT_OK;
75 }
76 else
77 {
78 gate_result_t ret;
79 gate_strbuilder_t sb;
80
81 gate_strbuilder_create(&sb, 48);
82 gate_size_t ndx;
83 for (ndx = 0; ndx != length; ++ndx)
84 {
85 gate_string_t const* entry = (gate_string_t const*)gate_arraylist_get(lst, ndx);
86 if (gate_string_is_empty(entry))
87 {
88 continue;
89 }
90 if (gate_strbuilder_length(&sb) != 0)
91 {
92 gate_strbuilder_append_cstr(&sb, ", ");
93 }
94 gate_strbuilder_append_string(&sb, entry);
95 }
96 ret = (NULL == gate_strbuilder_to_string(&sb, target))
97 ? GATE_RESULT_OUTOFMEMORY
98 : GATE_RESULT_OK;
99 gate_strbuilder_release(&sb);
100 return ret;
101 }
102 }
103
104 gate_result_t gate_smtpclient_send(gate_smtpclient_t* client, gate_mail_message_t const* message)
105 {
106 gate_result_t result;
107 gate_bool_t deinit_com = false;
108 gate_string_t receiver_to = GATE_INIT_EMPTY;
109 gate_string_t receiver_cc = GATE_INIT_EMPTY;
110 gate_string_t receiver_bcc = GATE_INIT_EMPTY;
111
112 GATE_DEBUG_ASSERT(client != NULL);
113 GATE_DEBUG_ASSERT(message != NULL);
114
115 do
116 {
117 gate_string_t smtp_server;
118 gate_string_t smtp_user;
119 gate_string_t smtp_pass;
120
121 result = gate_win32_com_init();
122 GATE_BREAK_IF_FAILED(result);
123 deinit_com = result == GATE_RESULT_OK;
124
125 result = stringlist_to_senderstring(&receiver_to, message->recipients);
126 GATE_BREAK_IF_FAILED(result);
127 result = stringlist_to_senderstring(&receiver_cc, message->recipients_cc);
128 GATE_BREAK_IF_FAILED(result);
129 result = stringlist_to_senderstring(&receiver_bcc, message->recipients_bcc);
130 GATE_BREAK_IF_FAILED(result);
131
132 gate_string_create_static(&smtp_server, client->server);
133 gate_string_create_static(&smtp_user, &client->config[0]);
134 gate_string_create_static(&smtp_pass, &client->config[smtp_user.length + 1]);
135
136 result = cdosys_send_mail(
137 &smtp_server, client->port,
138 GATE_FLAG_ENABLED(client->flags, GATE_SMTPCLIENT_FLAG_SSL),
139 &smtp_user, &smtp_pass,
140 &message->sender,
141 &receiver_to, &receiver_cc, &receiver_bcc,
142 &message->subject,
143 &message->text_body,
144 &message->html_body);
145
146 } while (0);
147
148 gate_string_release(&receiver_bcc);
149 gate_string_release(&receiver_cc);
150 gate_string_release(&receiver_to);
151
152 gate_win32_com_pump_messages(0);
153
154 if (deinit_com)
155 {
156 gate_win32_com_uninit();
157 }
158
159 return result;
160 }
161
162 #endif /* GATE_NETSMTPCLIENTS_WINCDO */
163
164
165
166 #if defined(GATE_NET_SMTPCLIENTS_CURL)
167
168 #include "gate/net/platform/libcurl_api.h"
169 #include "gate/utilities.h"
170
171
172 1 gate_result_t gate_smtpclient_create(gate_smtpclient_t* client,
173 gate_string_t const* server, gate_uint16_t port,
174 gate_string_t const* user, gate_string_t const* password,
175 gate_enumint_t flags)
176 {
177 gate_result_t ret;
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_DEBUG_ASSERT(client != NULL);
179
180 do
181 {
182 1 CURL* handle = NULL;
183 gate_strbuilder_t sb;
184 1 gate_bool_t const use_ssl = GATE_FLAG_ENABLED(flags, GATE_SMTPCLIENT_FLAG_SSL);
185
186 1 gate_mem_clear(client, sizeof(gate_smtpclient_t));
187
188 1 ret = load_libcurl_functions();
189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
190
191 1 handle = curl.easy_init();
192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (NULL == handle)
193 {
194 ✗ ret = GATE_RESULT_FAILED;
195 ✗ break;
196 }
197
198 {
199 1 gate_strbuilder_create_static(&sb, client->server, sizeof(client->server), 0);
200
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 gate_strbuilder_append(&sb,
201 GATE_PRINT_CSTR, "smtp://",
202 GATE_PRINT_STRING, server,
203 GATE_PRINT_CSTR, ":",
204 ✗ GATE_PRINT_UI16, (port != 0 ? port : (gate_uint16_t)(use_ssl ? 587 : 225)),
205 GATE_PRINT_END);
206 1 curl.easy_setopt(handle, CURLOPT_URL, client->server);
207 }
208
209
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (use_ssl)
210 {
211 1 curl.easy_setopt(handle, CURLOPT_USE_SSL, CURLUSESSL_ALL);
212 }
213
214
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (!gate_string_is_empty(user))
215 {
216 char str_buffer[256];
217 1 gate_string_to_buffer(user, str_buffer, sizeof(str_buffer));
218 1 curl.easy_setopt(handle, CURLOPT_USERNAME, str_buffer);
219
220 1 gate_string_to_buffer(password, str_buffer, sizeof(str_buffer));
221 1 curl.easy_setopt(handle, CURLOPT_PASSWORD, str_buffer);
222 }
223
224 1 client->handles[0] = (void*)handle;
225
226 1 ret = GATE_RESULT_OK;
227 } while (0);
228
229 1 return ret;
230 }
231
232 1 gate_result_t gate_smtpclient_release(gate_smtpclient_t* client)
233 {
234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_DEBUG_ASSERT(client != NULL);
235
236
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (client->handles[0] != NULL)
237 {
238 1 CURL* handle = (CURL*)client->handles[0];
239 1 curl.easy_cleanup(handle);
240 }
241 1 gate_mem_clear(client, sizeof(gate_smtpclient_t));
242 1 return GATE_RESULT_OK;
243 }
244
245 /* gate_string_t instances are not required to be NUL-terminated
246 -> ensure a nul-terminated string is available in cache */
247 5 static char const* get_terminated_string_ptr(gate_string_t const* text, gate_arraylist_t cache)
248 {
249
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 if (gate_string_is_terminated(text))
250 {
251 5 return gate_string_ptr(text, 0);
252 }
253 else
254 {
255 ✗ gate_string_t const* const str = gate_util_stringarray_add(cache, text);
256 ✗ return (str == NULL) ? NULL : gate_string_ptr(str, 0);
257 }
258 }
259
260 2 static void add_recipient_to_header(
261 struct curl_slist** ptr_slist_headers,
262 gate_arraylist_t cache,
263 gate_string_t const* recipient, char const* recipient_header_prefix)
264 {
265 gate_strbuilder_t sb;
266 gate_string_t entry;
267 2 gate_strbuilder_create(&sb, 16);
268 2 gate_strbuilder_append_cstr(&sb, recipient_header_prefix);
269 2 gate_strbuilder_append_string(&sb, recipient);
270 2 gate_strbuilder_to_string(&sb, &entry);
271 2 gate_util_stringarray_add(cache, &entry);
272 2 *ptr_slist_headers = curl.slist_append(*ptr_slist_headers, gate_string_ptr(&entry, 0));
273 2 gate_string_release(&entry);
274 2 gate_strbuilder_release(&sb);
275 2 }
276
277 3 static void create_recipient_list(
278 struct curl_slist** slist_recipients, struct curl_slist** slist_headers,
279 gate_arraylist_t str_list_recipients, gate_arraylist_t cache,
280 char const* recipient_header_prefix)
281 {
282 3 struct curl_slist* recipients = *slist_recipients;
283 3 struct curl_slist* headers = *slist_headers;
284 gate_enumerator_t e;
285
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if (NULL != gate_arraylist_enumerate(str_list_recipients, &e))
286 {
287 gate_string_t const* entry;
288
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
4 GATE_FOR_ENUMERATOR(gate_string_t const, entry, &e)
289 {
290 1 recipients = curl.slist_append(recipients, get_terminated_string_ptr(entry, cache));
291 1 add_recipient_to_header(&headers, cache, entry, recipient_header_prefix);
292 }
293 }
294 3 *slist_recipients = recipients;
295 3 *slist_headers = headers;
296 3 }
297
298 1 gate_result_t gate_smtpclient_send(gate_smtpclient_t* client, gate_mail_message_t const* message)
299 {
300 gate_result_t ret;
301 1 struct curl_slist* slist_recipients = NULL;
302 1 struct curl_slist* slist_headers = NULL;
303 1 curl_mime* mime = NULL;
304 1 gate_arraylist_t cache = GATE_INIT_EMPTY;
305
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_DEBUG_ASSERT(client != NULL);
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_DEBUG_ASSERT(message != NULL);
308
309 do
310 {
311 1 char curl_errbuf[CURL_ERROR_SIZE] = GATE_INIT_EMPTY;
312 CURLcode result;
313 1 CURL* handle = (CURL*)client->handles[0];
314 1 curl_mimepart* mime_part = NULL;
315 gate_enumerator_t enumerator;
316
317 1 cache = gate_arraylist_create(sizeof(gate_string_t), NULL, 0, &gate_string_create_copy_constructor, &gate_string_destructor);
318
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (gate_arraylist_is_null(cache))
319 {
320 ✗ ret = GATE_RESULT_OUTOFMEMORY;
321 ✗ break;
322 }
323
324
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (handle == NULL)
325 {
326 ✗ ret = GATE_RESULT_INVALIDSTATE;
327 ✗ break;
328 }
329
330 1 add_recipient_to_header(&slist_headers, cache, &message->sender, "From: ");
331 1 curl.easy_setopt(handle, CURLOPT_MAIL_FROM, get_terminated_string_ptr(&message->sender, cache));
332
333 1 create_recipient_list(&slist_recipients, &slist_headers, message->recipients, cache, "To: ");
334 1 create_recipient_list(&slist_recipients, &slist_headers, message->recipients_cc, cache, "Cc: ");
335 1 create_recipient_list(&slist_recipients, &slist_headers, message->recipients_bcc, cache, "Bcc: ");
336 1 curl.easy_setopt(handle, CURLOPT_MAIL_RCPT, slist_recipients);
337 1 curl.easy_setopt(handle, CURLOPT_HEADER, slist_headers);
338
339 1 mime = curl.mime_init(handle);
340
341
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (!gate_string_is_empty(&message->text_body))
342 {
343 1 mime_part = curl.mime_addpart(mime);
344 1 curl.mime_data(mime_part,
345 get_terminated_string_ptr(&message->text_body, cache),
346 CURL_ZERO_TERMINATED);
347 1 curl.mime_type(mime_part, "text/plain");
348 }
349
350
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (!gate_string_is_empty(&message->html_body))
351 {
352 1 mime_part = curl.mime_addpart(mime);
353 1 curl.mime_data(mime_part,
354 get_terminated_string_ptr(&message->html_body, cache),
355 CURL_ZERO_TERMINATED);
356 1 curl.mime_type(mime_part, "text/html");
357 }
358
359
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (NULL != gate_map_enumerate(&message->attachments, &enumerator))
360 {
361
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 for (; gate_enumerator_valid(&enumerator); gate_enumerator_next(&enumerator))
362 {
363 1 gate_mapping_t const* ptr_mapping = gate_enumerator_get(&enumerator);
364 1 gate_string_t const* attachment_filename = (gate_string_t const*)ptr_mapping->key;
365 1 gate_stream_t* attachment_stream = *((gate_stream_t**)ptr_mapping->value);
366 1 gate_strbuilder_t sb = GATE_INIT_EMPTY;
367 1 gate_stringstream_t* ss = NULL;
368 1 gate_string_t attachment_data = GATE_STRING_INIT_EMPTY;
369 1 gate_strbuilder_create(&sb, 0);
370 1 ss = gate_stringstream_create_builder(&sb);
371 1 gate_stream_transfer(attachment_stream, (gate_stream_t*)ss);
372 1 gate_object_release(ss);
373 1 gate_strbuilder_to_string(&sb, &attachment_data);
374 1 gate_strbuilder_release(&sb);
375 1 gate_util_stringarray_add(cache, &attachment_data);
376
377 1 mime_part = curl.mime_addpart(mime);
378 2 curl.mime_data(mime_part,
379 1 gate_string_ptr(&attachment_data, 0),
380 gate_string_length(&attachment_data));
381 1 curl.mime_filename(mime_part, get_terminated_string_ptr(attachment_filename, cache));
382 1 curl.mime_type(mime_part, "application/octet-stream");
383 1 gate_string_release(&attachment_data);
384 }
385 }
386 1 curl.easy_setopt(handle, CURLOPT_MIMEPOST, mime);
387
388 1 curl.easy_setopt(handle, CURLOPT_ERRORBUFFER, curl_errbuf);
389
390 1 result = curl.easy_perform(handle);
391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 ret = result == CURLE_OK ? GATE_RESULT_OK : GATE_RESULT_FAILED;
392
393 } while (0);
394
395
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (mime != NULL)
396 {
397 1 curl.mime_free(mime);
398 }
399
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (slist_recipients != NULL)
400 {
401 1 curl.slist_free_all(slist_recipients);
402 }
403
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (slist_headers != NULL)
404 {
405 1 curl.slist_free_all(slist_headers);
406 }
407 1 gate_arraylist_release(&cache);
408
409 1 return ret;
410 }
411
412 #endif /* GATE_NET_SMTPCLIENTS_CURL */
413
414
415
416 #if defined(GATE_NET_SMTPCLIENTS_NOIMPL)
417
418 gate_result_t gate_smtpclient_create(gate_smtpclient_t* client,
419 gate_string_t const* server, gate_uint16_t port,
420 gate_string_t const* user, gate_string_t const* password,
421 gate_enumint_t flags)
422 {
423 return GATE_RESULT_NOTIMPLEMENTED;
424 }
425
426 gate_result_t gate_smtpclient_release(gate_smtpclient_t* client)
427 {
428 return GATE_RESULT_NOTIMPLEMENTED;
429 }
430
431 gate_result_t gate_smtpclient_send(gate_smtpclient_t* client, gate_mail_message_t const* message)
432 {
433 return GATE_RESULT_NOTIMPLEMENTED;
434 }
435
436 #endif /* GATE_NET_SMTPCLIENTS_NOIMPL */
437