1 /* GStreamer
2 * Copyright (C) 2011 Axis Communications <dev-gstreamer@axis.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-curlsink
22 * @title: curlsink
23 * @short_description: sink that uploads data to a server using libcurl
24 *
25 * This is a network sink that uses libcurl as a client to upload data to
26 * an SMTP server.
27 *
28 * ## Example launch line
29 *
30 * Upload a JPEG file to an SMTP server.
31 *
32 * |[
33 * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlsmtpsink \
34 * file-name=image.jpg \
35 * location=smtp://smtp.gmail.com:507 \
36 * user=test passwd=test \
37 * subject=my image \
38 * mail-from="me@gmail.com" \
39 * mail-rcpt="you@gmail.com,she@gmail.com" \
40 * use-ssl=TRUE \
41 * insecure=TRUE
42 * ]|
43 *
44 */
45
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
49
50 #include <curl/curl.h>
51 #include <string.h>
52 #include <stdio.h>
53
54 #if HAVE_SYS_SOCKET_H
55 #include <sys/socket.h>
56 #endif
57 #include <sys/types.h>
58 #include <sys/time.h>
59 #if HAVE_PWD_H
60 #include <pwd.h>
61 #endif
62 #if HAVE_NETINET_IN_H
63 #include <netinet/in.h>
64 #endif
65 #include <unistd.h>
66 #if HAVE_NETINET_IP_H
67 #include <netinet/ip.h>
68 #endif
69 #if HAVE_NETINET_TCP_H
70 #include <netinet/tcp.h>
71 #endif
72 #include <sys/stat.h>
73 #include <fcntl.h>
74
75 #include "gstcurlelements.h"
76 #include "gstcurltlssink.h"
77 #include "gstcurlsmtpsink.h"
78
79 /* Default values */
80 #define GST_CAT_DEFAULT gst_curl_smtp_sink_debug
81 #define DEFAULT_USE_SSL FALSE
82 #define DEFAULT_NBR_ATTACHMENTS 1
83
84 /* MIME definitions */
85 #define MIME_VERSION "MIME-version: 1.0"
86 #define BOUNDARY_STRING "curlsink-boundary"
87 #define BOUNDARY_STRING_END "--curlsink-boundary--"
88
89 #define MAIL_RCPT_DELIMITER ","
90
91 /* Plugin specific settings */
92
93 GST_DEBUG_CATEGORY_STATIC (gst_curl_smtp_sink_debug);
94
95 enum
96 {
97 PROP_0,
98 PROP_MAIL_RCPT,
99 PROP_MAIL_FROM,
100 PROP_SUBJECT,
101 PROP_MESSAGE_BODY,
102 PROP_POP_USER_NAME,
103 PROP_POP_USER_PASSWD,
104 PROP_POP_LOCATION,
105 PROP_NBR_ATTACHMENTS,
106 PROP_CONTENT_TYPE,
107 PROP_USE_SSL
108 };
109
110
111 /* Object class function declarations */
112 static void gst_curl_smtp_sink_finalize (GObject * gobject);
113 static void gst_curl_smtp_sink_set_property (GObject * object, guint prop_id,
114 const GValue * value, GParamSpec * pspec);
115 static void gst_curl_smtp_sink_get_property (GObject * object, guint prop_id,
116 GValue * value, GParamSpec * pspec);
117
118 static gboolean gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink
119 * sink);
120 static gboolean
121 gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * sink);
122 static void gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink,
123 GstCaps * caps);
124 static gboolean gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink);
125 static size_t gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * sink,
126 void *curl_ptr, size_t block_size, guint * last_chunk);
127 static size_t gst_curl_smtp_sink_flush_data_unlocked (GstCurlBaseSink * bcsink,
128 void *curl_ptr, size_t block_size, gboolean new_file,
129 gboolean close_transfer);
130
131 /* private functions */
132
133 static size_t transfer_payload_headers (GstCurlSmtpSink * sink, void *curl_ptr,
134 size_t block_size);
135
136 #define gst_curl_smtp_sink_parent_class parent_class
137 G_DEFINE_TYPE (GstCurlSmtpSink, gst_curl_smtp_sink, GST_TYPE_CURL_TLS_SINK);
138 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (curlsmtpsink, "curlsmtpsink",
139 GST_RANK_NONE, GST_TYPE_CURL_SMTP_SINK, curl_element_init (plugin));
140
141 static void
gst_curl_smtp_sink_notify_transfer_end_unlocked(GstCurlSmtpSink * sink)142 gst_curl_smtp_sink_notify_transfer_end_unlocked (GstCurlSmtpSink * sink)
143 {
144 GST_LOG ("transfer completed: %d", sink->transfer_end);
145 sink->transfer_end = TRUE;
146 g_cond_signal (&sink->cond_transfer_end);
147 }
148
149 static void
gst_curl_smtp_sink_wait_for_transfer_end_unlocked(GstCurlSmtpSink * sink)150 gst_curl_smtp_sink_wait_for_transfer_end_unlocked (GstCurlSmtpSink * sink)
151 {
152 GST_LOG ("waiting for final data do be sent: %d", sink->transfer_end);
153
154 while (!sink->transfer_end) {
155 g_cond_wait (&sink->cond_transfer_end, GST_OBJECT_GET_LOCK (sink));
156 }
157 GST_LOG ("final data sent");
158 }
159
160 static void
add_final_boundary_unlocked(GstCurlSmtpSink * sink)161 add_final_boundary_unlocked (GstCurlSmtpSink * sink)
162 {
163 GByteArray *array;
164 gchar *boundary_end;
165 gsize len;
166 gint save, state;
167 gchar *data_out;
168
169 GST_DEBUG ("adding final boundary");
170
171 array = sink->base64_chunk->chunk_array;
172 g_assert (array);
173
174 /* it will need up to 5 bytes if line-breaking is enabled
175 * additional byte is needed for <CR> as it is not automatically added by
176 * glib */
177 data_out = g_malloc (6);
178 save = sink->base64_chunk->save;
179 state = sink->base64_chunk->state;
180 len = g_base64_encode_close (TRUE, data_out, &state, &save);
181
182 /* workaround */
183 data_out[len - 1] = '\r';
184 data_out[len] = '\n';
185
186 /* +1 for CR */
187 g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1));
188 g_free (data_out);
189
190 boundary_end = g_strdup_printf ("\r\n%s\r\n", BOUNDARY_STRING_END);
191 g_byte_array_append (array, (guint8 *) boundary_end, strlen (boundary_end));
192 g_free (boundary_end);
193
194 sink->final_boundary_added = TRUE;
195 }
196
197 static gboolean
gst_curl_smtp_sink_event(GstBaseSink * bsink,GstEvent * event)198 gst_curl_smtp_sink_event (GstBaseSink * bsink, GstEvent * event)
199 {
200 GstCurlBaseSink *bcsink = GST_CURL_BASE_SINK (bsink);
201 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bsink);
202
203 switch (event->type) {
204 case GST_EVENT_EOS:
205 GST_DEBUG_OBJECT (sink, "received EOS");
206 gst_curl_base_sink_set_live (bcsink, FALSE);
207
208 GST_OBJECT_LOCK (sink);
209 sink->eos = TRUE;
210 if (bcsink->flow_ret == GST_FLOW_OK && sink->base64_chunk != NULL
211 && !sink->final_boundary_added) {
212 add_final_boundary_unlocked (sink);
213 gst_curl_base_sink_transfer_thread_notify_unlocked (bcsink);
214 GST_FIXME_OBJECT (sink, "if gstpoll errors in transfer thread, then "
215 "this wait will never timeout because the transfer thread does "
216 "not signal it upon errors");
217 gst_curl_smtp_sink_wait_for_transfer_end_unlocked (sink);
218 }
219 GST_OBJECT_UNLOCK (sink);
220 break;
221
222 default:
223 break;
224 }
225
226 return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
227 }
228
229 static gboolean
gst_curl_smtp_sink_has_buffered_data_unlocked(GstCurlBaseSink * bcsink)230 gst_curl_smtp_sink_has_buffered_data_unlocked (GstCurlBaseSink * bcsink)
231 {
232 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
233 Base64Chunk *chunk;
234 GByteArray *array = NULL;
235 gboolean ret = FALSE;
236
237 chunk = sink->base64_chunk;
238
239 if (chunk) {
240 array = chunk->chunk_array;
241 if (array)
242 ret = (array->len == 0 && sink->final_boundary_added) ? FALSE : TRUE;
243 }
244
245 return ret;
246 }
247
248 static void
gst_curl_smtp_sink_class_init(GstCurlSmtpSinkClass * klass)249 gst_curl_smtp_sink_class_init (GstCurlSmtpSinkClass * klass)
250 {
251 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
252 GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
253 GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass;
254 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
255
256 GST_DEBUG_CATEGORY_INIT (gst_curl_smtp_sink_debug, "curlsmtpsink", 0,
257 "curl smtp sink element");
258
259 gst_element_class_set_static_metadata (element_class,
260 "Curl smtp sink",
261 "Sink/Network",
262 "Upload data over SMTP protocol using libcurl",
263 "Patricia Muscalu <patricia@axis.com>");
264
265 gstcurlbasesink_class->set_protocol_dynamic_options_unlocked =
266 gst_curl_smtp_sink_set_payload_headers_unlocked;
267 gstcurlbasesink_class->set_options_unlocked =
268 gst_curl_smtp_sink_set_transfer_options_unlocked;
269 gstcurlbasesink_class->set_mime_type = gst_curl_smtp_sink_set_mime_type;
270 gstcurlbasesink_class->prepare_transfer = gst_curl_smtp_sink_prepare_transfer;
271 gstcurlbasesink_class->transfer_data_buffer =
272 gst_curl_smtp_sink_transfer_data_buffer;
273 gstcurlbasesink_class->flush_data_unlocked =
274 gst_curl_smtp_sink_flush_data_unlocked;
275 gstcurlbasesink_class->has_buffered_data_unlocked =
276 gst_curl_smtp_sink_has_buffered_data_unlocked;
277
278 gstbasesink_class->event = gst_curl_smtp_sink_event;
279 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_smtp_sink_finalize);
280 gobject_class->set_property = gst_curl_smtp_sink_set_property;
281 gobject_class->get_property = gst_curl_smtp_sink_get_property;
282
283 g_object_class_install_property (gobject_class, PROP_MAIL_RCPT,
284 g_param_spec_string ("mail-rcpt", "Mail recipient",
285 "Single address that the given mail should get sent to", NULL,
286 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
287 g_object_class_install_property (gobject_class, PROP_MAIL_FROM,
288 g_param_spec_string ("mail-from", "Mail sender",
289 "Single address that the given mail should get sent from", NULL,
290 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
291 g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
292 g_param_spec_string ("content-type", "Content type",
293 "The mime type of the body of the request", NULL,
294 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
295 g_object_class_install_property (gobject_class, PROP_SUBJECT,
296 g_param_spec_string ("subject", "UTF-8 encoded mail subject",
297 "Mail subject", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
298 g_object_class_install_property (gobject_class, PROP_MESSAGE_BODY,
299 g_param_spec_string ("message-body", "UTF-8 encoded message body",
300 "Message body", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
301 g_object_class_install_property (gobject_class, PROP_USE_SSL,
302 g_param_spec_boolean ("use-ssl", "Use SSL",
303 "Use SSL/TLS for the connection", DEFAULT_USE_SSL,
304 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
305 g_object_class_install_property (gobject_class, PROP_NBR_ATTACHMENTS,
306 g_param_spec_int ("nbr-attachments", "Number attachments",
307 "Number attachments to send", G_MININT, G_MAXINT,
308 DEFAULT_NBR_ATTACHMENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
309 g_object_class_install_property (gobject_class, PROP_POP_USER_NAME,
310 g_param_spec_string ("pop-user", "User name",
311 "User name to use for POP server authentication", NULL,
312 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
313 g_object_class_install_property (gobject_class, PROP_POP_USER_PASSWD,
314 g_param_spec_string ("pop-passwd", "User password",
315 "User password to use for POP server authentication", NULL,
316 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
317 g_object_class_install_property (gobject_class, PROP_POP_LOCATION,
318 g_param_spec_string ("pop-location", "POP location",
319 "URL POP used for authentication", NULL,
320 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
321
322 }
323
324 static void
gst_curl_smtp_sink_init(GstCurlSmtpSink * sink)325 gst_curl_smtp_sink_init (GstCurlSmtpSink * sink)
326 {
327 sink->curl_recipients = NULL;
328 sink->mail_rcpt = NULL;
329 sink->mail_from = NULL;
330 sink->subject = NULL;
331 sink->message_body = NULL;
332 sink->payload_headers = NULL;
333 sink->base64_chunk = NULL;
334
335 g_cond_init (&sink->cond_transfer_end);
336 sink->transfer_end = FALSE;
337 sink->eos = FALSE;
338 sink->final_boundary_added = FALSE;
339
340 sink->reset_transfer_options = FALSE;
341 sink->use_ssl = DEFAULT_USE_SSL;
342
343 sink->pop_user = NULL;
344 sink->pop_passwd = NULL;
345 sink->pop_location = NULL;
346 sink->pop_curl = NULL;
347 }
348
349 static void
gst_curl_smtp_sink_finalize(GObject * gobject)350 gst_curl_smtp_sink_finalize (GObject * gobject)
351 {
352 GstCurlSmtpSink *this = GST_CURL_SMTP_SINK (gobject);
353
354 GST_DEBUG ("finalizing curlsmtpsink");
355
356 if (this->curl_recipients != NULL) {
357 curl_slist_free_all (this->curl_recipients);
358 }
359 g_free (this->mail_rcpt);
360 g_free (this->mail_from);
361 g_free (this->subject);
362 g_free (this->message_body);
363 g_free (this->content_type);
364
365 g_cond_clear (&this->cond_transfer_end);
366
367 if (this->base64_chunk != NULL) {
368 if (this->base64_chunk->chunk_array != NULL) {
369 g_byte_array_free (this->base64_chunk->chunk_array, TRUE);
370 }
371 g_free (this->base64_chunk);
372 }
373
374 if (this->payload_headers != NULL) {
375 g_byte_array_free (this->payload_headers, TRUE);
376 }
377
378 g_free (this->pop_user);
379 g_free (this->pop_passwd);
380 if (this->pop_curl != NULL) {
381 curl_easy_cleanup (this->pop_curl);
382 this->pop_curl = NULL;
383 }
384 g_free (this->pop_location);
385
386 G_OBJECT_CLASS (parent_class)->finalize (gobject);
387 }
388
389 static void
gst_curl_smtp_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)390 gst_curl_smtp_sink_set_property (GObject * object, guint prop_id,
391 const GValue * value, GParamSpec * pspec)
392 {
393 GstCurlSmtpSink *sink;
394 GstState cur_state;
395
396 g_return_if_fail (GST_IS_CURL_SMTP_SINK (object));
397 sink = GST_CURL_SMTP_SINK (object);
398
399 gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
400 if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) {
401 GST_OBJECT_LOCK (sink);
402
403 switch (prop_id) {
404 case PROP_MAIL_RCPT:
405 g_free (sink->mail_rcpt);
406 sink->mail_rcpt = g_value_dup_string (value);
407 GST_DEBUG_OBJECT (sink, "mail-rcpt set to %s", sink->mail_rcpt);
408 break;
409 case PROP_MAIL_FROM:
410 g_free (sink->mail_from);
411 sink->mail_from = g_value_dup_string (value);
412 GST_DEBUG_OBJECT (sink, "mail-from set to %s", sink->mail_from);
413 break;
414 case PROP_SUBJECT:
415 g_free (sink->subject);
416 sink->subject = g_value_dup_string (value);
417 GST_DEBUG_OBJECT (sink, "subject set to %s", sink->subject);
418 break;
419 case PROP_MESSAGE_BODY:
420 g_free (sink->message_body);
421 sink->message_body = g_value_dup_string (value);
422 GST_DEBUG_OBJECT (sink, "message-body set to %s", sink->message_body);
423 break;
424 case PROP_CONTENT_TYPE:
425 g_free (sink->content_type);
426 sink->content_type = g_value_dup_string (value);
427 GST_DEBUG_OBJECT (sink, "content-type set to %s", sink->content_type);
428 break;
429 case PROP_USE_SSL:
430 sink->use_ssl = g_value_get_boolean (value);
431 GST_DEBUG_OBJECT (sink, "use-ssl set to %d", sink->use_ssl);
432 break;
433 case PROP_NBR_ATTACHMENTS:
434 sink->nbr_attachments = g_value_get_int (value);
435 sink->curr_attachment = 1;
436 GST_DEBUG_OBJECT (sink, "nbr-attachments set to %d",
437 sink->nbr_attachments);
438 break;
439 case PROP_POP_USER_NAME:
440 g_free (sink->pop_user);
441 sink->pop_user = g_value_dup_string (value);
442 GST_DEBUG_OBJECT (sink, "pop-user set to %s", sink->pop_user);
443 break;
444 case PROP_POP_USER_PASSWD:
445 g_free (sink->pop_passwd);
446 sink->pop_passwd = g_value_dup_string (value);
447 GST_DEBUG_OBJECT (sink, "pop-passwd set to %s", sink->pop_passwd);
448 break;
449 case PROP_POP_LOCATION:
450 g_free (sink->pop_location);
451 sink->pop_location = g_value_dup_string (value);
452 GST_DEBUG_OBJECT (sink, "pop-location set to %s", sink->pop_location);
453 break;
454
455 default:
456 GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
457 break;
458 }
459
460 GST_OBJECT_UNLOCK (sink);
461
462 return;
463 }
464
465 /* in PLAYING or PAUSED state */
466 GST_OBJECT_LOCK (sink);
467
468 switch (prop_id) {
469 case PROP_CONTENT_TYPE:
470 g_free (sink->content_type);
471 sink->content_type = g_value_dup_string (value);
472 GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type);
473 break;
474 default:
475 GST_WARNING_OBJECT (sink, "cannot set property when PLAYING");
476 break;
477 }
478
479 GST_OBJECT_UNLOCK (sink);
480 }
481
482 static void
gst_curl_smtp_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)483 gst_curl_smtp_sink_get_property (GObject * object, guint prop_id,
484 GValue * value, GParamSpec * pspec)
485 {
486 GstCurlSmtpSink *sink;
487
488 g_return_if_fail (GST_IS_CURL_SMTP_SINK (object));
489 sink = GST_CURL_SMTP_SINK (object);
490
491 switch (prop_id) {
492 case PROP_MAIL_RCPT:
493 g_value_set_string (value, sink->mail_rcpt);
494 break;
495 case PROP_MAIL_FROM:
496 g_value_set_string (value, sink->mail_from);
497 break;
498 case PROP_SUBJECT:
499 g_value_set_string (value, sink->subject);
500 break;
501 case PROP_MESSAGE_BODY:
502 g_value_set_string (value, sink->message_body);
503 break;
504 case PROP_CONTENT_TYPE:
505 g_value_set_string (value, sink->content_type);
506 break;
507 case PROP_USE_SSL:
508 g_value_set_boolean (value, sink->use_ssl);
509 break;
510 case PROP_NBR_ATTACHMENTS:
511 g_value_set_int (value, sink->nbr_attachments);
512 break;
513 case PROP_POP_USER_NAME:
514 g_value_set_string (value, sink->pop_user);
515 break;
516 case PROP_POP_USER_PASSWD:
517 g_value_set_string (value, sink->pop_passwd);
518 break;
519 case PROP_POP_LOCATION:
520 g_value_set_string (value, sink->pop_location);
521 break;
522
523 default:
524 GST_DEBUG_OBJECT (sink, "invalid property id");
525 break;
526 }
527 }
528
529 static gboolean
gst_curl_smtp_sink_set_payload_headers_unlocked(GstCurlBaseSink * bcsink)530 gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink * bcsink)
531 {
532 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
533 gchar *hdrs;
534 gboolean append_headers = FALSE;
535
536 if (sink->reset_transfer_options) {
537 g_assert (!bcsink->is_live);
538 sink->reset_transfer_options = FALSE;
539
540 /* all data has been sent in the previous transfer, setup headers for
541 * a new transfer */
542 gst_curl_smtp_sink_set_transfer_options_unlocked (bcsink);
543 append_headers = TRUE;
544 }
545
546 if (sink->payload_headers == NULL) {
547 sink->payload_headers = g_byte_array_new ();
548 append_headers = TRUE;
549 }
550
551 if (sink->base64_chunk == NULL) {
552 g_assert (!bcsink->is_live);
553 /* we are just about to send the very first attachment in this transfer.
554 * This is the only place where base64_chunk and its array are allocated.
555 */
556 sink->base64_chunk = g_malloc (sizeof (Base64Chunk));
557 sink->base64_chunk->chunk_array = g_byte_array_new ();
558 append_headers = TRUE;
559 } else {
560 g_assert (sink->base64_chunk->chunk_array != NULL);
561 }
562
563 sink->base64_chunk->state = 0;
564 sink->base64_chunk->save = 0;
565
566 if (G_UNLIKELY (!append_headers)) {
567 g_byte_array_free (sink->base64_chunk->chunk_array, TRUE);
568 sink->base64_chunk->chunk_array = NULL;
569 g_free (sink->base64_chunk);
570 sink->base64_chunk = NULL;
571 return FALSE;
572 }
573
574 hdrs = g_strdup_printf ("\r\n\r\n--%s\r\n"
575 "Content-Type: application/octet-stream; name=\"%s\"\r\n"
576 /* TODO: support for other encodings */
577 "Content-Transfer-Encoding: BASE64\r\n"
578 "Content-Disposition: attachment; filename=\"%s\"\r\n\r\n"
579 "\r\n", BOUNDARY_STRING, bcsink->file_name, bcsink->file_name);
580 g_byte_array_append (sink->payload_headers, (guint8 *) hdrs, strlen (hdrs));
581 g_free (hdrs);
582
583 return TRUE;
584 }
585
586 /* MIME encoded-word syntax (RFC 2047):
587 * =?charset?encoding?encoded text?= */
588 static gchar *
generate_encoded_word(gchar * str)589 generate_encoded_word (gchar * str)
590 {
591 gchar *encoded_word;
592
593 g_assert (str);
594
595 if (g_utf8_validate (str, -1, NULL)) {
596 gchar *base64_str;
597
598 base64_str = g_base64_encode ((const guchar *) str, strlen (str));
599 encoded_word = g_strdup_printf ("=?utf-8?B?%s?=", base64_str);
600 g_free (base64_str);
601 } else {
602 GST_WARNING ("string is not a valid UTF-8 string");
603 encoded_word = g_strdup (str);
604 }
605
606 /* TODO: 75 character limit */
607 return encoded_word;
608 }
609
610 /* Setup header fields (From:/To:/Date: etc) and message body for the e-mail.
611 * This data is supposed to be sent to libcurl just before any media data.
612 * This function is called once for each e-mail:
613 * 1. we are about the send the first attachment
614 * 2. we have sent all the attachments and continue sending new ones within
615 * a new e-mail (transfer options have been reset). */
616 static gboolean
gst_curl_smtp_sink_set_transfer_options_unlocked(GstCurlBaseSink * bcsink)617 gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * bcsink)
618 {
619 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
620 GstCurlTlsSinkClass *parent_class;
621 gchar *request_headers;
622 GDateTime *date;
623 gchar *date_str;
624 gchar **tmp_list = NULL;
625 gchar *subject_header = NULL;
626 gchar *message_body = NULL;
627 gchar *rcpt_header = NULL;
628 gchar *enc_rcpt;
629 gchar *from_header = NULL;
630 gchar *enc_from;
631 gint i;
632 CURLcode res;
633
634 g_assert (sink->payload_headers == NULL);
635 g_assert (sink->mail_rcpt != NULL);
636 g_assert (sink->mail_from != NULL);
637
638 /* time */
639 date = g_date_time_new_now_local ();
640 date_str = g_date_time_format (date, "%a, %e %b %Y %H:%M:%S %z");
641 g_date_time_unref (date);
642
643 /* recipient, sender and subject are all UTF-8 strings, which are additionally
644 * base64-encoded */
645
646 /* recipient */
647 enc_rcpt = generate_encoded_word (sink->mail_rcpt);
648 rcpt_header = g_strdup_printf ("%s <%s>", enc_rcpt, sink->mail_rcpt);
649 g_free (enc_rcpt);
650
651 /* sender */
652 enc_from = generate_encoded_word (sink->mail_from);
653 from_header = g_strdup_printf ("%s <%s>", enc_from, sink->mail_from);
654 g_free (enc_from);
655
656 /* subject */
657 if (sink->subject != NULL) {
658 subject_header = generate_encoded_word (sink->subject);
659 }
660
661 /* message */
662 if (sink->message_body != NULL) {
663 message_body = g_base64_encode ((const guchar *) sink->message_body,
664 strlen (sink->message_body));
665 }
666
667 request_headers = g_strdup_printf (
668 /* headers */
669 "To: %s\r\n"
670 "From: %s\r\n"
671 "Subject: %s\r\n"
672 "Date: %s\r\n"
673 MIME_VERSION "\r\n"
674 "Content-Type: multipart/mixed; boundary=%s\r\n" "\r\n"
675 /* body headers */
676 "--" BOUNDARY_STRING "\r\n"
677 "Content-Type: text/plain; charset=utf-8\r\n"
678 "Content-Transfer-Encoding: BASE64\r\n"
679 /* message body */
680 "\r\n%s\r\n",
681 rcpt_header,
682 from_header,
683 subject_header ? subject_header : "",
684 date_str, BOUNDARY_STRING, message_body ? message_body : "");
685
686 sink->payload_headers = g_byte_array_new ();
687
688 g_byte_array_append (sink->payload_headers, (guint8 *) request_headers,
689 strlen (request_headers));
690 g_free (date_str);
691 g_free (subject_header);
692 g_free (message_body);
693 g_free (rcpt_header);
694 g_free (from_header);
695 g_free (request_headers);
696
697 res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_FROM, sink->mail_from);
698 if (res != CURLE_OK) {
699 bcsink->error =
700 g_strdup_printf ("failed to set SMTP sender email address: %s",
701 curl_easy_strerror (res));
702 return FALSE;
703 }
704
705 if (sink->curl_recipients != NULL) {
706 curl_slist_free_all (sink->curl_recipients);
707 sink->curl_recipients = NULL;
708 }
709
710 tmp_list = g_strsplit_set (sink->mail_rcpt, MAIL_RCPT_DELIMITER, -1);
711 for (i = 0; i < g_strv_length (tmp_list); i++) {
712 sink->curl_recipients = curl_slist_append (sink->curl_recipients,
713 tmp_list[i]);
714 }
715 g_strfreev (tmp_list);
716
717 /* note that the CURLOPT_MAIL_RCPT takes a list, not a char array */
718 res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_RCPT,
719 sink->curl_recipients);
720 if (res != CURLE_OK) {
721 bcsink->error =
722 g_strdup_printf ("failed to set SMTP recipient email address: %s",
723 curl_easy_strerror (res));
724 return FALSE;
725 }
726
727 res = curl_easy_setopt (bcsink->curl, CURLOPT_UPLOAD, 1L);
728 if (res != CURLE_OK) {
729 bcsink->error = g_strdup_printf ("failed to prepare for upload: %s",
730 curl_easy_strerror (res));
731 return FALSE;
732 }
733
734 parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink);
735
736 if (sink->use_ssl) {
737 return parent_class->set_options_unlocked (bcsink);
738 }
739
740 return TRUE;
741 }
742
743 /* FIXME: exactly the same function as in http sink */
744 static void
gst_curl_smtp_sink_set_mime_type(GstCurlBaseSink * bcsink,GstCaps * caps)745 gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink, GstCaps * caps)
746 {
747 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
748 GstStructure *structure;
749 const gchar *mime_type;
750
751 if (sink->content_type != NULL) {
752 return;
753 }
754
755 structure = gst_caps_get_structure (caps, 0);
756 mime_type = gst_structure_get_name (structure);
757 sink->content_type = g_strdup (mime_type);
758 }
759
760 static size_t
gst_curl_smtp_sink_flush_data_unlocked(GstCurlBaseSink * bcsink,void * curl_ptr,size_t block_size,gboolean new_file,gboolean close_transfer)761 gst_curl_smtp_sink_flush_data_unlocked (GstCurlBaseSink * bcsink,
762 void *curl_ptr, size_t block_size, gboolean new_file,
763 gboolean close_transfer)
764 {
765 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
766 Base64Chunk *chunk = sink->base64_chunk;
767 gint state = chunk->state;
768 gint save = chunk->save;
769 GByteArray *array = chunk->chunk_array;
770 size_t bytes_to_send;
771 gint len;
772 gchar *data_out;
773
774 GST_DEBUG
775 ("live: %d, num attachments: %d, curr_attachment: %d, "
776 "eos: %d, close_transfer: %d, final boundary: %d, array_len: %d",
777 bcsink->is_live, sink->nbr_attachments, sink->curr_attachment,
778 sink->eos, close_transfer, sink->final_boundary_added, array->len);
779
780
781 if ((bcsink->is_live && (sink->curr_attachment == sink->nbr_attachments))
782 || (sink->nbr_attachments == 1) || sink->eos
783 || sink->final_boundary_added) {
784 bcsink->is_live = FALSE;
785 sink->reset_transfer_options = TRUE;
786 sink->final_boundary_added = FALSE;
787 sink->curr_attachment = 1;
788
789 GST_DEBUG ("returning 0, no more data to send in this transfer");
790
791 return 0;
792 }
793
794 /* it will need up to 5 bytes if line-breaking is enabled, however an
795 * additional byte is needed for <CR> as it is not automatically added by
796 * glib */
797 data_out = g_malloc (6);
798 len = g_base64_encode_close (TRUE, data_out, &state, &save);
799 chunk->state = state;
800 chunk->save = save;
801 /* workaround */
802 data_out[len - 1] = '\r';
803 data_out[len] = '\n';
804 /* +1 for CR */
805 g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1));
806 g_free (data_out);
807
808 if (new_file) {
809 sink->curr_attachment++;
810 bcsink->is_live = TRUE;
811
812 /* reset flag */
813 bcsink->new_file = FALSE;
814
815 /* set payload headers for new file */
816 gst_curl_smtp_sink_set_payload_headers_unlocked (bcsink);
817 }
818
819
820 if (close_transfer && !sink->final_boundary_added)
821 add_final_boundary_unlocked (sink);
822
823 bytes_to_send = MIN (block_size, array->len);
824 memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
825 g_byte_array_remove_range (array, 0, bytes_to_send);
826
827 return bytes_to_send;
828 }
829
830 static size_t
transfer_chunk(void * curl_ptr,TransferBuffer * buffer,Base64Chunk * chunk,size_t block_size,guint * last_chunk)831 transfer_chunk (void *curl_ptr, TransferBuffer * buffer, Base64Chunk * chunk,
832 size_t block_size, guint * last_chunk)
833 {
834 size_t bytes_to_send;
835 const guchar *data_in = buffer->ptr;
836 size_t data_in_offset = buffer->offset;
837 gint state = chunk->state;
838 gint save = chunk->save;
839 GByteArray *array = chunk->chunk_array;
840 gchar *data_out;
841
842 bytes_to_send = MIN (block_size, buffer->len);
843
844 if (bytes_to_send == 0) {
845 bytes_to_send = MIN (block_size, array->len);
846 }
847
848 /* base64 encode data */
849 if (buffer->len > 0) {
850 gsize len;
851 gchar *ptr_in;
852 gchar *ptr_out;
853 gsize size_out;
854 gint i;
855
856 /* if line-breaking is enabled, at least: ((len / 3 + 1) * 4 + 4) / 72 + 1
857 * bytes of extra space is required. However, additional <CR>'s are
858 * required, thus we need ((len / 3 + 2) * 4 + 4) / 72 + 2 extra bytes.
859 */
860 size_out = (bytes_to_send / 3 + 1) * 4 + 4 + bytes_to_send +
861 ((bytes_to_send / 3 + 2) * 4 + 4) / 72 + 2;
862
863 data_out = g_malloc (size_out);
864 len = g_base64_encode_step (data_in + data_in_offset, bytes_to_send, TRUE,
865 data_out, &state, &save);
866 chunk->state = state;
867 chunk->save = save;
868
869 /* LF->CRLF filter */
870 ptr_in = ptr_out = data_out;
871 for (i = 0; i < len; i++) {
872 if (*ptr_in == '\n') {
873 *ptr_in = '\r';
874 g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out);
875 g_byte_array_append (array, (guint8 *) "\r\n", strlen ("\r\n"));
876 ptr_out = ptr_in + 1;
877 }
878 ptr_in++;
879 }
880 if (ptr_in - ptr_out) {
881 g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out);
882 }
883
884 g_free (data_out);
885 data_out = NULL;
886
887 buffer->offset += bytes_to_send;
888 buffer->len -= bytes_to_send;
889
890 bytes_to_send = MIN (block_size, array->len);
891 memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
892 g_byte_array_remove_range (array, 0, bytes_to_send);
893
894 if (array->len == 0) {
895 *last_chunk = 1;
896
897 }
898
899 return bytes_to_send;
900 }
901
902 /* at this point all data has been encoded */
903 memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send);
904 g_byte_array_remove_range (array, 0, bytes_to_send);
905 if (array->len == 0) {
906 *last_chunk = 1;
907 }
908
909 return bytes_to_send;
910 }
911
912 static size_t
gst_curl_smtp_sink_transfer_data_buffer(GstCurlBaseSink * bcsink,void * curl_ptr,size_t block_size,guint * last_chunk)913 gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * bcsink,
914 void *curl_ptr, size_t block_size, guint * last_chunk)
915 {
916 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
917 size_t bytes_to_send;
918
919 if (sink->payload_headers && sink->payload_headers->len) {
920 return transfer_payload_headers (sink, curl_ptr, block_size);
921 }
922
923 if (sink->base64_chunk != NULL) {
924 bytes_to_send =
925 transfer_chunk (curl_ptr, bcsink->transfer_buf, sink->base64_chunk,
926 block_size, last_chunk);
927
928 /* if last chunk of current buffer and max attachments per mail is reached
929 * then add final boundary */
930 if (*last_chunk && sink->curr_attachment == sink->nbr_attachments &&
931 !sink->final_boundary_added) {
932 add_final_boundary_unlocked (sink);
933 /* now that we've added the final boundary to the array we have on more
934 * chunk to send */
935 *last_chunk = 0;
936 }
937
938 GST_OBJECT_LOCK (sink);
939 if (sink->eos) {
940 gst_curl_smtp_sink_notify_transfer_end_unlocked (sink);
941 }
942 GST_OBJECT_UNLOCK (sink);
943
944 return bytes_to_send;
945 }
946
947 /* we should never get here */
948 return 0;
949 }
950
951 static size_t
transfer_payload_headers(GstCurlSmtpSink * sink,void * curl_ptr,size_t block_size)952 transfer_payload_headers (GstCurlSmtpSink * sink,
953 void *curl_ptr, size_t block_size)
954 {
955 size_t bytes_to_send;
956 GByteArray *headers = sink->payload_headers;
957
958 bytes_to_send = MIN (block_size, headers->len);
959 memcpy ((guint8 *) curl_ptr, headers->data, bytes_to_send);
960 g_byte_array_remove_range (headers, 0, bytes_to_send);
961
962
963 if (headers->len == 0) {
964 g_byte_array_free (headers, TRUE);
965 sink->payload_headers = NULL;
966 }
967
968 return bytes_to_send;
969 }
970
971 static gboolean
gst_curl_smtp_sink_prepare_transfer(GstCurlBaseSink * bcsink)972 gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink)
973 {
974 GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink);
975 CURLcode res;
976 gboolean ret = TRUE;
977
978 if (sink->pop_location && strlen (sink->pop_location)) {
979 if ((sink->pop_curl = curl_easy_init ()) == NULL) {
980 bcsink->error = g_strdup ("POP protocol: failed to create handler");
981 return FALSE;
982 }
983
984 res = curl_easy_setopt (sink->pop_curl, CURLOPT_URL, sink->pop_location);
985 if (res != CURLE_OK) {
986 bcsink->error = g_strdup_printf ("failed to set URL: %s",
987 curl_easy_strerror (res));
988 return FALSE;
989 }
990
991 if (sink->pop_user != NULL && strlen (sink->pop_user) &&
992 sink->pop_passwd != NULL && strlen (sink->pop_passwd)) {
993 res = curl_easy_setopt (sink->pop_curl, CURLOPT_USERNAME, sink->pop_user);
994 if (res != CURLE_OK) {
995 bcsink->error = g_strdup_printf ("failed to set user name: %s",
996 curl_easy_strerror (res));
997 return FALSE;
998 }
999
1000 res = curl_easy_setopt (sink->pop_curl, CURLOPT_PASSWORD,
1001 sink->pop_passwd);
1002 if (res != CURLE_OK) {
1003 bcsink->error = g_strdup_printf ("failed to set user name: %s",
1004 curl_easy_strerror (res));
1005 return FALSE;
1006 }
1007 }
1008 }
1009
1010 if (sink->pop_curl != NULL) {
1011 /* ready to initialize connection to POP server */
1012 res = curl_easy_perform (sink->pop_curl);
1013 if (res != CURLE_OK) {
1014 bcsink->error = g_strdup_printf ("POP transfer failed: %s",
1015 curl_easy_strerror (res));
1016 ret = FALSE;
1017 }
1018
1019 curl_easy_cleanup (sink->pop_curl);
1020 sink->pop_curl = NULL;
1021 }
1022
1023 return ret;
1024 }
1025