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