| /* GStreamer |
| * Copyright (C) 2011 Axis Communications <dev-gstreamer@axis.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-curlsink |
| * @title: curlsink |
| * @short_description: sink that uploads data to a server using libcurl |
| * @see_also: |
| * |
| * This is a network sink that uses libcurl as a client to upload data to |
| * an SMTP server. |
| * |
| * ## Example launch line |
| * |
| * Upload a JPEG file to an SMTP server. |
| * |
| * |[ |
| * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlsmtpsink \ |
| * file-name=image.jpg \ |
| * location=smtp://smtp.gmail.com:507 \ |
| * user=test passwd=test \ |
| * subject=my image \ |
| * mail-from="me@gmail.com" \ |
| * mail-rcpt="you@gmail.com,she@gmail.com" \ |
| * use-ssl=TRUE \ |
| * insecure=TRUE |
| * ]| |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <curl/curl.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #if HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #if HAVE_PWD_H |
| #include <pwd.h> |
| #endif |
| #if HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| #include <unistd.h> |
| #if HAVE_NETINET_IP_H |
| #include <netinet/ip.h> |
| #endif |
| #if HAVE_NETINET_TCP_H |
| #include <netinet/tcp.h> |
| #endif |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include "gstcurltlssink.h" |
| #include "gstcurlsmtpsink.h" |
| |
| /* Default values */ |
| #define GST_CAT_DEFAULT gst_curl_smtp_sink_debug |
| #define DEFAULT_USE_SSL FALSE |
| #define DEFAULT_NBR_ATTACHMENTS 1 |
| |
| /* MIME definitions */ |
| #define MIME_VERSION "MIME-version: 1.0" |
| #define BOUNDARY_STRING "curlsink-boundary" |
| #define BOUNDARY_STRING_END "--curlsink-boundary--" |
| |
| #define MAIL_RCPT_DELIMITER "," |
| |
| /* Plugin specific settings */ |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_curl_smtp_sink_debug); |
| |
| enum |
| { |
| PROP_0, |
| PROP_MAIL_RCPT, |
| PROP_MAIL_FROM, |
| PROP_SUBJECT, |
| PROP_MESSAGE_BODY, |
| PROP_POP_USER_NAME, |
| PROP_POP_USER_PASSWD, |
| PROP_POP_LOCATION, |
| PROP_NBR_ATTACHMENTS, |
| PROP_CONTENT_TYPE, |
| PROP_USE_SSL |
| }; |
| |
| |
| /* Object class function declarations */ |
| static void gst_curl_smtp_sink_finalize (GObject * gobject); |
| static void gst_curl_smtp_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_curl_smtp_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static gboolean gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink |
| * sink); |
| static gboolean |
| gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * sink); |
| static void gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink, |
| GstCaps * caps); |
| static gboolean gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink); |
| static size_t gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * sink, |
| void *curl_ptr, size_t block_size, guint * last_chunk); |
| 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); |
| |
| /* private functions */ |
| |
| static size_t transfer_payload_headers (GstCurlSmtpSink * sink, void *curl_ptr, |
| size_t block_size); |
| |
| #define gst_curl_smtp_sink_parent_class parent_class |
| G_DEFINE_TYPE (GstCurlSmtpSink, gst_curl_smtp_sink, GST_TYPE_CURL_TLS_SINK); |
| |
| static void |
| gst_curl_smtp_sink_notify_transfer_end_unlocked (GstCurlSmtpSink * sink) |
| { |
| GST_LOG ("transfer completed: %d", sink->transfer_end); |
| sink->transfer_end = TRUE; |
| g_cond_signal (&sink->cond_transfer_end); |
| } |
| |
| static void |
| gst_curl_smtp_sink_wait_for_transfer_end_unlocked (GstCurlSmtpSink * sink) |
| { |
| GST_LOG ("waiting for final data do be sent: %d", sink->transfer_end); |
| |
| while (!sink->transfer_end) { |
| g_cond_wait (&sink->cond_transfer_end, GST_OBJECT_GET_LOCK (sink)); |
| } |
| GST_LOG ("final data sent"); |
| } |
| |
| static void |
| add_final_boundary_unlocked (GstCurlSmtpSink * sink) |
| { |
| GByteArray *array; |
| gchar *boundary_end; |
| gsize len; |
| gint save, state; |
| gchar *data_out; |
| |
| GST_DEBUG ("adding final boundary"); |
| |
| array = sink->base64_chunk->chunk_array; |
| g_assert (array); |
| |
| /* it will need up to 5 bytes if line-breaking is enabled |
| * additional byte is needed for <CR> as it is not automatically added by |
| * glib */ |
| data_out = g_malloc (6); |
| save = sink->base64_chunk->save; |
| state = sink->base64_chunk->state; |
| len = g_base64_encode_close (TRUE, data_out, &state, &save); |
| |
| /* workaround */ |
| data_out[len - 1] = '\r'; |
| data_out[len] = '\n'; |
| |
| /* +1 for CR */ |
| g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1)); |
| g_free (data_out); |
| |
| boundary_end = g_strdup_printf ("\r\n%s\r\n", BOUNDARY_STRING_END); |
| g_byte_array_append (array, (guint8 *) boundary_end, strlen (boundary_end)); |
| g_free (boundary_end); |
| |
| sink->final_boundary_added = TRUE; |
| } |
| |
| static gboolean |
| gst_curl_smtp_sink_event (GstBaseSink * bsink, GstEvent * event) |
| { |
| GstCurlBaseSink *bcsink = GST_CURL_BASE_SINK (bsink); |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bsink); |
| |
| switch (event->type) { |
| case GST_EVENT_EOS: |
| GST_DEBUG_OBJECT (sink, "received EOS"); |
| gst_curl_base_sink_set_live (bcsink, FALSE); |
| |
| GST_OBJECT_LOCK (sink); |
| sink->eos = TRUE; |
| if (bcsink->flow_ret == GST_FLOW_OK && sink->base64_chunk != NULL |
| && !sink->final_boundary_added) { |
| add_final_boundary_unlocked (sink); |
| gst_curl_base_sink_transfer_thread_notify_unlocked (bcsink); |
| GST_FIXME_OBJECT (sink, "if gstpoll errors in transfer thread, then " |
| "this wait will never timeout because the transfer thread does " |
| "not signal it upon errors"); |
| gst_curl_smtp_sink_wait_for_transfer_end_unlocked (sink); |
| } |
| GST_OBJECT_UNLOCK (sink); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event); |
| } |
| |
| static gboolean |
| gst_curl_smtp_sink_has_buffered_data_unlocked (GstCurlBaseSink * bcsink) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| Base64Chunk *chunk; |
| GByteArray *array = NULL; |
| gboolean ret = FALSE; |
| |
| chunk = sink->base64_chunk; |
| |
| if (chunk) { |
| array = chunk->chunk_array; |
| if (array) |
| ret = (array->len == 0 && sink->final_boundary_added) ? FALSE : TRUE; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_curl_smtp_sink_class_init (GstCurlSmtpSinkClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; |
| GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass; |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_curl_smtp_sink_debug, "curlsmtpsink", 0, |
| "curl smtp sink element"); |
| GST_DEBUG_OBJECT (klass, "class_init"); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "Curl smtp sink", |
| "Sink/Network", |
| "Upload data over SMTP protocol using libcurl", |
| "Patricia Muscalu <patricia@axis.com>"); |
| |
| gstcurlbasesink_class->set_protocol_dynamic_options_unlocked = |
| gst_curl_smtp_sink_set_payload_headers_unlocked; |
| gstcurlbasesink_class->set_options_unlocked = |
| gst_curl_smtp_sink_set_transfer_options_unlocked; |
| gstcurlbasesink_class->set_mime_type = gst_curl_smtp_sink_set_mime_type; |
| gstcurlbasesink_class->prepare_transfer = gst_curl_smtp_sink_prepare_transfer; |
| gstcurlbasesink_class->transfer_data_buffer = |
| gst_curl_smtp_sink_transfer_data_buffer; |
| gstcurlbasesink_class->flush_data_unlocked = |
| gst_curl_smtp_sink_flush_data_unlocked; |
| gstcurlbasesink_class->has_buffered_data_unlocked = |
| gst_curl_smtp_sink_has_buffered_data_unlocked; |
| |
| gstbasesink_class->event = gst_curl_smtp_sink_event; |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_smtp_sink_finalize); |
| gobject_class->set_property = gst_curl_smtp_sink_set_property; |
| gobject_class->get_property = gst_curl_smtp_sink_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_MAIL_RCPT, |
| g_param_spec_string ("mail-rcpt", "Mail recipient", |
| "Single address that the given mail should get sent to", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_MAIL_FROM, |
| g_param_spec_string ("mail-from", "Mail sender", |
| "Single address that the given mail should get sent from", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, |
| g_param_spec_string ("content-type", "Content type", |
| "The mime type of the body of the request", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_SUBJECT, |
| g_param_spec_string ("subject", "UTF-8 encoded mail subject", |
| "Mail subject", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_MESSAGE_BODY, |
| g_param_spec_string ("message-body", "UTF-8 encoded message body", |
| "Message body", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USE_SSL, |
| g_param_spec_boolean ("use-ssl", "Use SSL", |
| "Use SSL/TLS for the connection", DEFAULT_USE_SSL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_NBR_ATTACHMENTS, |
| g_param_spec_int ("nbr-attachments", "Number attachments", |
| "Number attachments to send", G_MININT, G_MAXINT, |
| DEFAULT_NBR_ATTACHMENTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_POP_USER_NAME, |
| g_param_spec_string ("pop-user", "User name", |
| "User name to use for POP server authentication", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_POP_USER_PASSWD, |
| g_param_spec_string ("pop-passwd", "User password", |
| "User password to use for POP server authentication", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_POP_LOCATION, |
| g_param_spec_string ("pop-location", "POP location", |
| "URL POP used for authentication", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| } |
| |
| static void |
| gst_curl_smtp_sink_init (GstCurlSmtpSink * sink) |
| { |
| sink->curl_recipients = NULL; |
| sink->mail_rcpt = NULL; |
| sink->mail_from = NULL; |
| sink->subject = NULL; |
| sink->message_body = NULL; |
| sink->payload_headers = NULL; |
| sink->base64_chunk = NULL; |
| |
| g_cond_init (&sink->cond_transfer_end); |
| sink->transfer_end = FALSE; |
| sink->eos = FALSE; |
| sink->final_boundary_added = FALSE; |
| |
| sink->reset_transfer_options = FALSE; |
| sink->use_ssl = DEFAULT_USE_SSL; |
| |
| sink->pop_user = NULL; |
| sink->pop_passwd = NULL; |
| sink->pop_location = NULL; |
| sink->pop_curl = NULL; |
| } |
| |
| static void |
| gst_curl_smtp_sink_finalize (GObject * gobject) |
| { |
| GstCurlSmtpSink *this = GST_CURL_SMTP_SINK (gobject); |
| |
| GST_DEBUG ("finalizing curlsmtpsink"); |
| |
| if (this->curl_recipients != NULL) { |
| curl_slist_free_all (this->curl_recipients); |
| } |
| g_free (this->mail_rcpt); |
| g_free (this->mail_from); |
| g_free (this->subject); |
| g_free (this->message_body); |
| g_free (this->content_type); |
| |
| g_cond_clear (&this->cond_transfer_end); |
| |
| if (this->base64_chunk != NULL) { |
| if (this->base64_chunk->chunk_array != NULL) { |
| g_byte_array_free (this->base64_chunk->chunk_array, TRUE); |
| } |
| g_free (this->base64_chunk); |
| } |
| |
| if (this->payload_headers != NULL) { |
| g_byte_array_free (this->payload_headers, TRUE); |
| } |
| |
| g_free (this->pop_user); |
| g_free (this->pop_passwd); |
| if (this->pop_curl != NULL) { |
| curl_easy_cleanup (this->pop_curl); |
| this->pop_curl = NULL; |
| } |
| g_free (this->pop_location); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (gobject); |
| } |
| |
| static void |
| gst_curl_smtp_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstCurlSmtpSink *sink; |
| GstState cur_state; |
| |
| g_return_if_fail (GST_IS_CURL_SMTP_SINK (object)); |
| sink = GST_CURL_SMTP_SINK (object); |
| |
| gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0); |
| if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) { |
| GST_OBJECT_LOCK (sink); |
| |
| switch (prop_id) { |
| case PROP_MAIL_RCPT: |
| g_free (sink->mail_rcpt); |
| sink->mail_rcpt = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "mail-rcpt set to %s", sink->mail_rcpt); |
| break; |
| case PROP_MAIL_FROM: |
| g_free (sink->mail_from); |
| sink->mail_from = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "mail-from set to %s", sink->mail_from); |
| break; |
| case PROP_SUBJECT: |
| g_free (sink->subject); |
| sink->subject = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "subject set to %s", sink->subject); |
| break; |
| case PROP_MESSAGE_BODY: |
| g_free (sink->message_body); |
| sink->message_body = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "message-body set to %s", sink->message_body); |
| break; |
| case PROP_CONTENT_TYPE: |
| g_free (sink->content_type); |
| sink->content_type = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "content-type set to %s", sink->content_type); |
| break; |
| case PROP_USE_SSL: |
| sink->use_ssl = g_value_get_boolean (value); |
| GST_DEBUG_OBJECT (sink, "use-ssl set to %d", sink->use_ssl); |
| break; |
| case PROP_NBR_ATTACHMENTS: |
| sink->nbr_attachments = g_value_get_int (value); |
| sink->curr_attachment = 1; |
| GST_DEBUG_OBJECT (sink, "nbr-attachments set to %d", |
| sink->nbr_attachments); |
| break; |
| case PROP_POP_USER_NAME: |
| g_free (sink->pop_user); |
| sink->pop_user = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "pop-user set to %s", sink->pop_user); |
| break; |
| case PROP_POP_USER_PASSWD: |
| g_free (sink->pop_passwd); |
| sink->pop_passwd = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "pop-passwd set to %s", sink->pop_passwd); |
| break; |
| case PROP_POP_LOCATION: |
| g_free (sink->pop_location); |
| sink->pop_location = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "pop-location set to %s", sink->pop_location); |
| break; |
| |
| default: |
| GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id); |
| break; |
| } |
| |
| GST_OBJECT_UNLOCK (sink); |
| |
| return; |
| } |
| |
| /* in PLAYING or PAUSED state */ |
| GST_OBJECT_LOCK (sink); |
| |
| switch (prop_id) { |
| case PROP_CONTENT_TYPE: |
| g_free (sink->content_type); |
| sink->content_type = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type); |
| break; |
| default: |
| GST_WARNING_OBJECT (sink, "cannot set property when PLAYING"); |
| break; |
| } |
| |
| GST_OBJECT_UNLOCK (sink); |
| } |
| |
| static void |
| gst_curl_smtp_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstCurlSmtpSink *sink; |
| |
| g_return_if_fail (GST_IS_CURL_SMTP_SINK (object)); |
| sink = GST_CURL_SMTP_SINK (object); |
| |
| switch (prop_id) { |
| case PROP_MAIL_RCPT: |
| g_value_set_string (value, sink->mail_rcpt); |
| break; |
| case PROP_MAIL_FROM: |
| g_value_set_string (value, sink->mail_from); |
| break; |
| case PROP_SUBJECT: |
| g_value_set_string (value, sink->subject); |
| break; |
| case PROP_MESSAGE_BODY: |
| g_value_set_string (value, sink->message_body); |
| break; |
| case PROP_CONTENT_TYPE: |
| g_value_set_string (value, sink->content_type); |
| break; |
| case PROP_USE_SSL: |
| g_value_set_boolean (value, sink->use_ssl); |
| break; |
| case PROP_NBR_ATTACHMENTS: |
| g_value_set_int (value, sink->nbr_attachments); |
| break; |
| case PROP_POP_USER_NAME: |
| g_value_set_string (value, sink->pop_user); |
| break; |
| case PROP_POP_USER_PASSWD: |
| g_value_set_string (value, sink->pop_passwd); |
| break; |
| case PROP_POP_LOCATION: |
| g_value_set_string (value, sink->pop_location); |
| break; |
| |
| default: |
| GST_DEBUG_OBJECT (sink, "invalid property id"); |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_curl_smtp_sink_set_payload_headers_unlocked (GstCurlBaseSink * bcsink) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| gchar *hdrs; |
| gboolean append_headers = FALSE; |
| |
| if (sink->reset_transfer_options) { |
| g_assert (!bcsink->is_live); |
| sink->reset_transfer_options = FALSE; |
| |
| /* all data has been sent in the previous transfer, setup headers for |
| * a new transfer */ |
| gst_curl_smtp_sink_set_transfer_options_unlocked (bcsink); |
| append_headers = TRUE; |
| } |
| |
| if (sink->payload_headers == NULL) { |
| sink->payload_headers = g_byte_array_new (); |
| append_headers = TRUE; |
| } |
| |
| if (sink->base64_chunk == NULL) { |
| g_assert (!bcsink->is_live); |
| /* we are just about to send the very first attachment in this transfer. |
| * This is the only place where base64_chunk and its array are allocated. |
| */ |
| sink->base64_chunk = g_malloc (sizeof (Base64Chunk)); |
| sink->base64_chunk->chunk_array = g_byte_array_new (); |
| append_headers = TRUE; |
| } else { |
| g_assert (sink->base64_chunk->chunk_array != NULL); |
| } |
| |
| sink->base64_chunk->state = 0; |
| sink->base64_chunk->save = 0; |
| |
| if (G_UNLIKELY (!append_headers)) { |
| g_byte_array_free (sink->base64_chunk->chunk_array, TRUE); |
| sink->base64_chunk->chunk_array = NULL; |
| g_free (sink->base64_chunk); |
| sink->base64_chunk = NULL; |
| return FALSE; |
| } |
| |
| hdrs = g_strdup_printf ("\r\n\r\n--%s\r\n" |
| "Content-Type: application/octet-stream; name=\"%s\"\r\n" |
| /* TODO: support for other encodings */ |
| "Content-Transfer-Encoding: BASE64\r\n" |
| "Content-Disposition: attachment; filename=\"%s\"\r\n\r\n" |
| "\r\n", BOUNDARY_STRING, bcsink->file_name, bcsink->file_name); |
| g_byte_array_append (sink->payload_headers, (guint8 *) hdrs, strlen (hdrs)); |
| g_free (hdrs); |
| |
| return TRUE; |
| } |
| |
| /* MIME encoded-word syntax (RFC 2047): |
| * =?charset?encoding?encoded text?= */ |
| static gchar * |
| generate_encoded_word (gchar * str) |
| { |
| gchar *encoded_word; |
| |
| g_assert (str); |
| |
| if (g_utf8_validate (str, -1, NULL)) { |
| gchar *base64_str; |
| |
| base64_str = g_base64_encode ((const guchar *) str, strlen (str)); |
| encoded_word = g_strdup_printf ("=?utf-8?B?%s?=", base64_str); |
| g_free (base64_str); |
| } else { |
| GST_WARNING ("string is not a valid UTF-8 string"); |
| encoded_word = g_strdup (str); |
| } |
| |
| /* TODO: 75 character limit */ |
| return encoded_word; |
| } |
| |
| /* Setup header fields (From:/To:/Date: etc) and message body for the e-mail. |
| * This data is supposed to be sent to libcurl just before any media data. |
| * This function is called once for each e-mail: |
| * 1. we are about the send the first attachment |
| * 2. we have sent all the attachments and continue sending new ones within |
| * a new e-mail (transfer options have been reset). */ |
| static gboolean |
| gst_curl_smtp_sink_set_transfer_options_unlocked (GstCurlBaseSink * bcsink) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| GstCurlTlsSinkClass *parent_class; |
| gchar *request_headers; |
| GDateTime *date; |
| gchar *date_str; |
| gchar **tmp_list = NULL; |
| gchar *subject_header = NULL; |
| gchar *message_body = NULL; |
| gchar *rcpt_header = NULL; |
| gchar *enc_rcpt; |
| gchar *from_header = NULL; |
| gchar *enc_from; |
| gint i; |
| CURLcode res; |
| |
| g_assert (sink->payload_headers == NULL); |
| g_assert (sink->mail_rcpt != NULL); |
| g_assert (sink->mail_from != NULL); |
| |
| /* time */ |
| date = g_date_time_new_now_local (); |
| date_str = g_date_time_format (date, "%a %b %e %H:%M:%S %Y %z"); |
| g_date_time_unref (date); |
| |
| /* recipient, sender and subject are all UTF-8 strings, which are additionally |
| * base64-encoded */ |
| |
| /* recipient */ |
| enc_rcpt = generate_encoded_word (sink->mail_rcpt); |
| rcpt_header = g_strdup_printf ("%s <%s>", enc_rcpt, sink->mail_rcpt); |
| g_free (enc_rcpt); |
| |
| /* sender */ |
| enc_from = generate_encoded_word (sink->mail_from); |
| from_header = g_strdup_printf ("%s <%s>", enc_from, sink->mail_from); |
| g_free (enc_from); |
| |
| /* subject */ |
| if (sink->subject != NULL) { |
| subject_header = generate_encoded_word (sink->subject); |
| } |
| |
| /* message */ |
| if (sink->message_body != NULL) { |
| message_body = g_base64_encode ((const guchar *) sink->message_body, |
| strlen (sink->message_body)); |
| } |
| |
| request_headers = g_strdup_printf ( |
| /* headers */ |
| "To: %s\r\n" |
| "From: %s\r\n" |
| "Subject: %s\r\n" |
| "Date: %s\r\n" |
| MIME_VERSION "\r\n" |
| "Content-Type: multipart/mixed; boundary=%s\r\n" "\r\n" |
| /* body headers */ |
| "--" BOUNDARY_STRING "\r\n" |
| "Content-Type: text/plain; charset=utf-8\r\n" |
| "Content-Transfer-Encoding: BASE64\r\n" |
| /* message body */ |
| "\r\n%s\r\n", |
| rcpt_header, |
| from_header, |
| subject_header ? subject_header : "", |
| date_str, BOUNDARY_STRING, message_body ? message_body : ""); |
| |
| sink->payload_headers = g_byte_array_new (); |
| |
| g_byte_array_append (sink->payload_headers, (guint8 *) request_headers, |
| strlen (request_headers)); |
| g_free (date_str); |
| g_free (subject_header); |
| g_free (message_body); |
| g_free (rcpt_header); |
| g_free (from_header); |
| g_free (request_headers); |
| |
| res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_FROM, sink->mail_from); |
| if (res != CURLE_OK) { |
| bcsink->error = |
| g_strdup_printf ("failed to set SMTP sender email address: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| |
| if (sink->curl_recipients != NULL) { |
| curl_slist_free_all (sink->curl_recipients); |
| sink->curl_recipients = NULL; |
| } |
| |
| tmp_list = g_strsplit_set (sink->mail_rcpt, MAIL_RCPT_DELIMITER, -1); |
| for (i = 0; i < g_strv_length (tmp_list); i++) { |
| sink->curl_recipients = curl_slist_append (sink->curl_recipients, |
| tmp_list[i]); |
| } |
| g_strfreev (tmp_list); |
| |
| /* note that the CURLOPT_MAIL_RCPT takes a list, not a char array */ |
| res = curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_RCPT, |
| sink->curl_recipients); |
| if (res != CURLE_OK) { |
| bcsink->error = |
| g_strdup_printf ("failed to set SMTP recipient email address: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| |
| res = curl_easy_setopt (bcsink->curl, CURLOPT_UPLOAD, 1L); |
| if (res != CURLE_OK) { |
| bcsink->error = g_strdup_printf ("failed to prepare for upload: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| |
| parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink); |
| |
| if (sink->use_ssl) { |
| return parent_class->set_options_unlocked (bcsink); |
| } |
| |
| return TRUE; |
| } |
| |
| /* FIXME: exactly the same function as in http sink */ |
| static void |
| gst_curl_smtp_sink_set_mime_type (GstCurlBaseSink * bcsink, GstCaps * caps) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| GstStructure *structure; |
| const gchar *mime_type; |
| |
| if (sink->content_type != NULL) { |
| return; |
| } |
| |
| structure = gst_caps_get_structure (caps, 0); |
| mime_type = gst_structure_get_name (structure); |
| sink->content_type = g_strdup (mime_type); |
| } |
| |
| 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) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| Base64Chunk *chunk = sink->base64_chunk; |
| gint state = chunk->state; |
| gint save = chunk->save; |
| GByteArray *array = chunk->chunk_array; |
| size_t bytes_to_send; |
| gint len; |
| gchar *data_out; |
| |
| GST_DEBUG |
| ("live: %d, num attachments: %d, curr_attachment: %d, " |
| "eos: %d, close_transfer: %d, final boundary: %d, array_len: %d", |
| bcsink->is_live, sink->nbr_attachments, sink->curr_attachment, |
| sink->eos, close_transfer, sink->final_boundary_added, array->len); |
| |
| |
| if ((bcsink->is_live && (sink->curr_attachment == sink->nbr_attachments)) |
| || (sink->nbr_attachments == 1) || sink->eos |
| || sink->final_boundary_added) { |
| bcsink->is_live = FALSE; |
| sink->reset_transfer_options = TRUE; |
| sink->final_boundary_added = FALSE; |
| sink->curr_attachment = 1; |
| |
| GST_DEBUG ("returning 0, no more data to send in this transfer"); |
| |
| return 0; |
| } |
| |
| /* it will need up to 5 bytes if line-breaking is enabled, however an |
| * additional byte is needed for <CR> as it is not automatically added by |
| * glib */ |
| data_out = g_malloc (6); |
| len = g_base64_encode_close (TRUE, data_out, &state, &save); |
| chunk->state = state; |
| chunk->save = save; |
| /* workaround */ |
| data_out[len - 1] = '\r'; |
| data_out[len] = '\n'; |
| /* +1 for CR */ |
| g_byte_array_append (array, (guint8 *) data_out, (guint) (len + 1)); |
| g_free (data_out); |
| |
| if (new_file) { |
| sink->curr_attachment++; |
| bcsink->is_live = TRUE; |
| |
| /* reset flag */ |
| bcsink->new_file = FALSE; |
| |
| /* set payload headers for new file */ |
| gst_curl_smtp_sink_set_payload_headers_unlocked (bcsink); |
| } |
| |
| |
| if (close_transfer && !sink->final_boundary_added) |
| add_final_boundary_unlocked (sink); |
| |
| bytes_to_send = MIN (block_size, array->len); |
| memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send); |
| g_byte_array_remove_range (array, 0, bytes_to_send); |
| |
| return bytes_to_send; |
| } |
| |
| static size_t |
| transfer_chunk (void *curl_ptr, TransferBuffer * buffer, Base64Chunk * chunk, |
| size_t block_size, guint * last_chunk) |
| { |
| size_t bytes_to_send; |
| const guchar *data_in = buffer->ptr; |
| size_t data_in_offset = buffer->offset; |
| gint state = chunk->state; |
| gint save = chunk->save; |
| GByteArray *array = chunk->chunk_array; |
| gchar *data_out; |
| |
| bytes_to_send = MIN (block_size, buffer->len); |
| |
| if (bytes_to_send == 0) { |
| bytes_to_send = MIN (block_size, array->len); |
| } |
| |
| /* base64 encode data */ |
| if (buffer->len > 0) { |
| gsize len; |
| gchar *ptr_in; |
| gchar *ptr_out; |
| gsize size_out; |
| gint i; |
| |
| /* if line-breaking is enabled, at least: ((len / 3 + 1) * 4 + 4) / 72 + 1 |
| * bytes of extra space is required. However, additional <CR>'s are |
| * required, thus we need ((len / 3 + 2) * 4 + 4) / 72 + 2 extra bytes. |
| */ |
| size_out = (bytes_to_send / 3 + 1) * 4 + 4 + bytes_to_send + |
| ((bytes_to_send / 3 + 2) * 4 + 4) / 72 + 2; |
| |
| data_out = g_malloc (size_out); |
| len = g_base64_encode_step (data_in + data_in_offset, bytes_to_send, TRUE, |
| data_out, &state, &save); |
| chunk->state = state; |
| chunk->save = save; |
| |
| /* LF->CRLF filter */ |
| ptr_in = ptr_out = data_out; |
| for (i = 0; i < len; i++) { |
| if (*ptr_in == '\n') { |
| *ptr_in = '\r'; |
| g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out); |
| g_byte_array_append (array, (guint8 *) "\r\n", strlen ("\r\n")); |
| ptr_out = ptr_in + 1; |
| } |
| ptr_in++; |
| } |
| if (ptr_in - ptr_out) { |
| g_byte_array_append (array, (guint8 *) ptr_out, ptr_in - ptr_out); |
| } |
| |
| g_free (data_out); |
| data_out = NULL; |
| |
| buffer->offset += bytes_to_send; |
| buffer->len -= bytes_to_send; |
| |
| bytes_to_send = MIN (block_size, array->len); |
| memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send); |
| g_byte_array_remove_range (array, 0, bytes_to_send); |
| |
| if (array->len == 0) { |
| *last_chunk = 1; |
| |
| } |
| |
| return bytes_to_send; |
| } |
| |
| /* at this point all data has been encoded */ |
| memcpy ((guint8 *) curl_ptr, array->data, bytes_to_send); |
| g_byte_array_remove_range (array, 0, bytes_to_send); |
| if (array->len == 0) { |
| *last_chunk = 1; |
| } |
| |
| return bytes_to_send; |
| } |
| |
| static size_t |
| gst_curl_smtp_sink_transfer_data_buffer (GstCurlBaseSink * bcsink, |
| void *curl_ptr, size_t block_size, guint * last_chunk) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| size_t bytes_to_send; |
| |
| if (sink->payload_headers && sink->payload_headers->len) { |
| return transfer_payload_headers (sink, curl_ptr, block_size); |
| } |
| |
| if (sink->base64_chunk != NULL) { |
| bytes_to_send = |
| transfer_chunk (curl_ptr, bcsink->transfer_buf, sink->base64_chunk, |
| block_size, last_chunk); |
| |
| /* if last chunk of current buffer and max attachments per mail is reached |
| * then add final boundary */ |
| if (*last_chunk && sink->curr_attachment == sink->nbr_attachments && |
| !sink->final_boundary_added) { |
| add_final_boundary_unlocked (sink); |
| /* now that we've added the final boundary to the array we have on more |
| * chunk to send */ |
| *last_chunk = 0; |
| } |
| |
| GST_OBJECT_LOCK (sink); |
| if (sink->eos) { |
| gst_curl_smtp_sink_notify_transfer_end_unlocked (sink); |
| } |
| GST_OBJECT_UNLOCK (sink); |
| |
| return bytes_to_send; |
| } |
| |
| /* we should never get here */ |
| return 0; |
| } |
| |
| static size_t |
| transfer_payload_headers (GstCurlSmtpSink * sink, |
| void *curl_ptr, size_t block_size) |
| { |
| size_t bytes_to_send; |
| GByteArray *headers = sink->payload_headers; |
| |
| bytes_to_send = MIN (block_size, headers->len); |
| memcpy ((guint8 *) curl_ptr, headers->data, bytes_to_send); |
| g_byte_array_remove_range (headers, 0, bytes_to_send); |
| |
| |
| if (headers->len == 0) { |
| g_byte_array_free (headers, TRUE); |
| sink->payload_headers = NULL; |
| } |
| |
| return bytes_to_send; |
| } |
| |
| static gboolean |
| gst_curl_smtp_sink_prepare_transfer (GstCurlBaseSink * bcsink) |
| { |
| GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bcsink); |
| CURLcode res; |
| gboolean ret = TRUE; |
| |
| if (sink->pop_location && strlen (sink->pop_location)) { |
| if ((sink->pop_curl = curl_easy_init ()) == NULL) { |
| bcsink->error = g_strdup ("POP protocol: failed to create handler"); |
| return FALSE; |
| } |
| |
| res = curl_easy_setopt (sink->pop_curl, CURLOPT_URL, sink->pop_location); |
| if (res != CURLE_OK) { |
| bcsink->error = g_strdup_printf ("failed to set URL: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| |
| if (sink->pop_user != NULL && strlen (sink->pop_user) && |
| sink->pop_passwd != NULL && strlen (sink->pop_passwd)) { |
| res = curl_easy_setopt (sink->pop_curl, CURLOPT_USERNAME, sink->pop_user); |
| if (res != CURLE_OK) { |
| bcsink->error = g_strdup_printf ("failed to set user name: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| |
| res = curl_easy_setopt (sink->pop_curl, CURLOPT_PASSWORD, |
| sink->pop_passwd); |
| if (res != CURLE_OK) { |
| bcsink->error = g_strdup_printf ("failed to set user name: %s", |
| curl_easy_strerror (res)); |
| return FALSE; |
| } |
| } |
| } |
| |
| if (sink->pop_curl != NULL) { |
| /* ready to initialize connection to POP server */ |
| res = curl_easy_perform (sink->pop_curl); |
| if (res != CURLE_OK) { |
| bcsink->error = g_strdup_printf ("POP transfer failed: %s", |
| curl_easy_strerror (res)); |
| ret = FALSE; |
| } |
| |
| curl_easy_cleanup (sink->pop_curl); |
| sink->pop_curl = NULL; |
| } |
| |
| return ret; |
| } |