• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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