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);

    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 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);
    g_assert (sink->base64_chunk->chunk_array->len == 0);
  }

  sink->base64_chunk->state = 0;
  sink->base64_chunk->save = 0;

  if (G_UNLIKELY (!append_headers)) {
    if (sink->base64_chunk != NULL) {
      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;
}
static size_t
gst_curl_smtp_sink_flush_data_unlocked (GstCurlBaseSink * bcsink,
    void *curl_ptr, size_t block_size, gboolean new_file)
{
  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;

  if ((bcsink->is_live && (sink->nbr_attachments_left == sink->nbr_attachments))
      || (sink->nbr_attachments == 1) || sink->eos) {
    bcsink->is_live = FALSE;
    sink->reset_transfer_options = TRUE;

    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->nbr_attachments_left--;

    bcsink->is_live = TRUE;
    if (sink->nbr_attachments_left <= 1) {
      sink->nbr_attachments_left = sink->nbr_attachments;
    }

    /* reset flag */
    bcsink->new_file = FALSE;

    /* set payload headers for new file */
    gst_curl_smtp_sink_set_payload_headers_unlocked (bcsink);
  }

  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 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;
}
static void                     // FIXME: exactly the same function as in http sink
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 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_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) {
      GST_DEBUG_OBJECT (sink, "POP protocol: failed to create handler");
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE,
          ("POP protocol: failed to create handler"), (NULL));

      return FALSE;
    }

    curl_easy_setopt (sink->pop_curl, CURLOPT_URL, sink->pop_location);
    if (sink->pop_user != NULL && strlen (sink->pop_user) &&
        sink->pop_passwd != NULL && strlen (sink->pop_passwd)) {
      curl_easy_setopt (sink->pop_curl, CURLOPT_USERNAME, sink->pop_user);
      curl_easy_setopt (sink->pop_curl, CURLOPT_PASSWORD, sink->pop_passwd);
    }
  }

  if (sink->pop_curl == NULL) {
    goto end;
  }

  /* ready to initialize connection to POP server */
  res = curl_easy_perform (sink->pop_curl);
  if (res != CURLE_OK) {
    GST_DEBUG_OBJECT (sink, "POP transfer failed: %s",
        curl_easy_strerror (res));
    GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("POP transfer failed: %s",
            curl_easy_strerror (res)), (NULL));

    ret = FALSE;
  }

  curl_easy_cleanup (sink->pop_curl);
  sink->pop_curl = NULL;

end:
  return ret;
}
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_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 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 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;
      GST_OBJECT_UNLOCK (sink);

      if (sink->base64_chunk != NULL)
        add_final_boundary_unlocked (sink);

      gst_curl_base_sink_transfer_thread_notify_unlocked (bcsink);

      GST_OBJECT_LOCK (sink);
      if (sink->base64_chunk != NULL && bcsink->flow_ret == GST_FLOW_OK) {
        gst_curl_smtp_sink_wait_for_transfer_end_unlocked (sink);
      }
      GST_OBJECT_UNLOCK (sink);

      gst_curl_base_sink_transfer_thread_close (bcsink);

      break;

    default:
      break;
  }

  return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
}
/* 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;

  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");
  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);

  curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_FROM, sink->mail_from);

  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 */
  curl_easy_setopt (bcsink->curl, CURLOPT_MAIL_RCPT, sink->curl_recipients);

  parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink);

  if (sink->use_ssl) {
    return parent_class->set_options_unlocked (bcsink);
  }

  return TRUE;
}
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->nbr_attachments_left = sink->nbr_attachments;
        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 gboolean
gst_curl_smtp_sink_event (GstBaseSink * bsink, GstEvent * event)
{
  GstCurlBaseSink *bcsink = GST_CURL_BASE_SINK (bsink);
  GstCurlSmtpSink *sink = GST_CURL_SMTP_SINK (bsink);

  GByteArray *array;
  gchar *boundary_end;

  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;
      GST_OBJECT_UNLOCK (sink);

      if (sink->base64_chunk != NULL) {
        gsize len;
        gint save, state;
        gchar *data_out;

        array = sink->base64_chunk->chunk_array;
        g_assert (array);

        GST_DEBUG ("adding final boundary");

        /* 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);
      }

      gst_curl_base_sink_transfer_thread_notify_unlocked (bcsink);

      GST_OBJECT_LOCK (sink);
      if (sink->base64_chunk != NULL && bcsink->flow_ret == GST_FLOW_OK) {
        gst_curl_smtp_sink_wait_for_transfer_end_unlocked (sink);
      }
      GST_OBJECT_UNLOCK (sink);

      gst_curl_base_sink_transfer_thread_close (bcsink);

      break;

    default:
      break;
  }

  return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
}