static int
ssl_verify_callback (void *data, int failures, const ne_ssl_certificate * cert)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (data);

    if ((failures & NE_SSL_UNTRUSTED) &&
            src->accept_self_signed && !ne_ssl_cert_signedby (cert)) {
        GST_ELEMENT_INFO (src, RESOURCE, READ,
                          (NULL), ("Accepting self-signed server certificate"));

        failures &= ~NE_SSL_UNTRUSTED;
    }

    if (failures & NE_SSL_NOTYETVALID)
        GST_ELEMENT_ERROR (src, RESOURCE, READ,
                           (NULL), ("Server certificate not valid yet"));
    if (failures & NE_SSL_EXPIRED)
        GST_ELEMENT_ERROR (src, RESOURCE, READ,
                           (NULL), ("Server certificate has expired"));
    if (failures & NE_SSL_IDMISMATCH)
        GST_ELEMENT_ERROR (src, RESOURCE, READ,
                           (NULL), ("Server certificate doesn't match hostname"));
    if (failures & NE_SSL_UNTRUSTED)
        GST_ELEMENT_ERROR (src, RESOURCE, READ,
                           (NULL), ("Server certificate signer not trusted"));

    GST_DEBUG_OBJECT (src, "failures: %d\n", failures);

    return failures;
}
static const gchar *
gst_neonhttp_src_uri_get_uri (GstURIHandler * handler)
{
  GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);

  return src->location;
}
static gboolean
gst_neonhttp_src_query (GstBaseSrc * bsrc, GstQuery * query)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
    gboolean ret;

    switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_URI:
        gst_query_set_uri (query, src->location);
        ret = TRUE;
        break;
    default:
        ret = FALSE;
        break;
    }

    if (!ret)
        ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);

    switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_SCHEDULING: {
        GstSchedulingFlags flags;
        gint minsize, maxsize, align;

        gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
        flags |= GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
        gst_query_set_scheduling (query, flags, minsize, maxsize, align);
        break;
    }
    default:
        break;
    }

    return ret;
}
static void
gst_neonhttp_src_dispose (GObject * gobject)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (gobject);

    ne_uri_free (&src->uri);
    ne_uri_free (&src->proxy);

    g_free (src->user_agent);

    if (src->cookies) {
        g_strfreev (src->cookies);
        src->cookies = NULL;
    }

    if (src->request) {
        ne_request_destroy (src->request);
        src->request = NULL;
    }

    if (src->session) {
        ne_close_connection (src->session);
        ne_session_destroy (src->session);
        src->session = NULL;
    }

    if (src->location) {
        ne_free (src->location);
    }
    if (src->query_string) {
        ne_free (src->query_string);
    }

    G_OBJECT_CLASS (parent_class)->dispose (gobject);
}
static gboolean
gst_neonhttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
{
    GstNeonhttpSrc *src;
    gint res;
    ne_session *session = NULL;
    ne_request *request = NULL;

    src = GST_NEONHTTP_SRC (bsrc);

    if (!src->seekable)
        return FALSE;

    if (src->read_position == segment->start)
        return TRUE;

    res = gst_neonhttp_src_send_request_and_redirect (src,
            &session, &request, segment->start, src->automatic_redirect);

    /* if we are able to seek, replace the session */
    if (res == NE_OK && session) {
        gst_neonhttp_src_close_session (src);
        src->session = session;
        src->request = request;
        src->read_position = segment->start;
        return TRUE;
    }

    return FALSE;
}
static gboolean
gst_neonhttp_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
{
  GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);

  return gst_neonhttp_src_set_location (src, uri);
}
static gchar *
gst_neonhttp_src_uri_get_uri (GstURIHandler * handler)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);

    /* FIXME: make thread-safe */
    return g_strdup (src->location);
}
static gboolean
gst_neonhttp_src_get_size (GstBaseSrc * bsrc, guint64 * size)
{
    GstNeonhttpSrc *src;

    src = GST_NEONHTTP_SRC (bsrc);

    if (src->content_size == -1)
        return FALSE;

    *size = src->content_size;

    return TRUE;
}
static GstFlowReturn
gst_neonhttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
  GstNeonhttpSrc *src;
  GstBaseSrc *basesrc;
  GstFlowReturn ret;
  gint read;

  src = GST_NEONHTTP_SRC (psrc);
  basesrc = GST_BASE_SRC_CAST (psrc);

  /* The caller should know the number of bytes and not read beyond EOS. */
  if (G_UNLIKELY (src->eos))
    goto eos;

  /* Create the buffer. */
  ret = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc),
      basesrc->segment.last_stop, basesrc->blocksize,
      src->icy_caps ? src->icy_caps :
      GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)), outbuf);

  if (G_UNLIKELY (ret != GST_FLOW_OK))
    goto done;

  read = gst_neonhttp_src_request_dispatch (src, *outbuf);
  if (G_UNLIKELY (read < 0))
    goto read_error;

  GST_LOG_OBJECT (src, "returning %u bytes", GST_BUFFER_SIZE (*outbuf));

done:
  return ret;

  /* ERRORS */
eos:
  {
    GST_DEBUG_OBJECT (src, "EOS reached");
    return GST_FLOW_UNEXPECTED;
  }
read_error:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, READ,
        (NULL), ("Could not read any bytes (%i, %s)", read,
            ne_get_error (src->session)));
    gst_buffer_unref (*outbuf);
    *outbuf = NULL;
    return GST_FLOW_ERROR;
  }
}
/* close the socket and associated resources
 * used both to recover from errors and go to NULL state */
static gboolean
gst_neonhttp_src_stop (GstBaseSrc * bsrc)
{
  GstNeonhttpSrc *src;

  src = GST_NEONHTTP_SRC (bsrc);

  if (src->iradio_name) {
    g_free (src->iradio_name);
    src->iradio_name = NULL;
  }

  if (src->iradio_genre) {
    g_free (src->iradio_genre);
    src->iradio_genre = NULL;
  }

  if (src->iradio_url) {
    g_free (src->iradio_url);
    src->iradio_url = NULL;
  }

  if (src->icy_caps) {
    gst_caps_unref (src->icy_caps);
    src->icy_caps = NULL;
  }

  src->eos = FALSE;
  src->content_size = -1;
  src->read_position = 0;
  src->seekable = TRUE;

  gst_neonhttp_src_close_session (src);

#ifndef GST_DISABLE_GST_DEBUG
  ne_debug_init (NULL, 0);
#endif
  ne_oom_callback (NULL);
  ne_sock_exit ();

  return TRUE;
}
static gboolean
gst_neonhttp_src_query (GstBaseSrc * bsrc, GstQuery * query)
{
  GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
  gboolean ret;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_URI:
      gst_query_set_uri (query, src->location);
      ret = TRUE;
      break;
    default:
      ret = FALSE;
      break;
  }

  if (!ret)
    ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);

  return ret;
}
/* close the socket and associated resources
 * used both to recover from errors and go to NULL state */
static gboolean
gst_neonhttp_src_stop (GstBaseSrc * bsrc)
{
    GstNeonhttpSrc *src;

    src = GST_NEONHTTP_SRC (bsrc);

    src->eos = FALSE;
    src->content_size = -1;
    src->read_position = 0;
    src->seekable = TRUE;

    gst_neonhttp_src_close_session (src);

#ifndef GST_DISABLE_GST_DEBUG
    ne_debug_init (NULL, 0);
#endif
    ne_oom_callback (NULL);
    ne_sock_exit ();

    return TRUE;
}
static GstFlowReturn
gst_neonhttp_src_fill (GstPushSrc * psrc, GstBuffer * outbuf)
{
    GstNeonhttpSrc *src;
    gint read;

    src = GST_NEONHTTP_SRC (psrc);

    /* The caller should know the number of bytes and not read beyond EOS. */
    if (G_UNLIKELY (src->eos))
        goto eos;

    read = gst_neonhttp_src_request_dispatch (src, outbuf);
    if (G_UNLIKELY (read < 0))
        goto read_error;

    GST_LOG_OBJECT (src, "returning %" G_GSIZE_FORMAT " bytes, "
                    "offset %" G_GUINT64_FORMAT, gst_buffer_get_size (outbuf),
                    GST_BUFFER_OFFSET (outbuf));

    return GST_FLOW_OK;

    /* ERRORS */
eos:
    {
        GST_DEBUG_OBJECT (src, "EOS reached");
        return GST_FLOW_EOS;
    }
read_error:
    {
        GST_ELEMENT_ERROR (src, RESOURCE, READ,
                           (NULL), ("Could not read any bytes (%i, %s)", read,
                                    ne_get_error (src->session)));
        return GST_FLOW_ERROR;
    }
}
static void
gst_neonhttp_src_set_property (GObject * object, guint prop_id,
                               const GValue * value, GParamSpec * pspec)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (object);

    switch (prop_id) {
    case PROP_PROXY:
    {
        const gchar *proxy;

        proxy = g_value_get_string (value);

        if (proxy == NULL) {
            GST_WARNING ("proxy property cannot be NULL");
            goto done;
        }
        if (!gst_neonhttp_src_set_proxy (src, proxy)) {
            GST_WARNING ("badly formated proxy");
            goto done;
        }
        break;
    }
    case PROP_LOCATION:
    {
        const gchar *location;

        location = g_value_get_string (value);

        if (location == NULL) {
            GST_WARNING ("location property cannot be NULL");
            goto done;
        }
        if (!gst_neonhttp_src_set_location (src, location, NULL)) {
            GST_WARNING ("badly formated location");
            goto done;
        }
        break;
    }
    case PROP_USER_AGENT:
        g_free (src->user_agent);
        src->user_agent = g_strdup (g_value_get_string (value));
        break;
    case PROP_COOKIES:
        if (src->cookies)
            g_strfreev (src->cookies);
        src->cookies = (gchar **) g_value_dup_boxed (value);
        break;
    case PROP_AUTOMATIC_REDIRECT:
        src->automatic_redirect = g_value_get_boolean (value);
        break;
    case PROP_ACCEPT_SELF_SIGNED:
        src->accept_self_signed = g_value_get_boolean (value);
        break;
    case PROP_CONNECT_TIMEOUT:
        src->connect_timeout = g_value_get_uint (value);
        break;
    case PROP_READ_TIMEOUT:
        src->read_timeout = g_value_get_uint (value);
        break;
#ifndef GST_DISABLE_GST_DEBUG
    case PROP_NEON_HTTP_DEBUG:
        src->neon_http_debug = g_value_get_boolean (value);
        break;
#endif
    case PROP_IRADIO_MODE:
        src->iradio_mode = g_value_get_boolean (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
done:
    return;
}
/* create a socket for connecting to remote server */
static gboolean
gst_neonhttp_src_start (GstBaseSrc * bsrc)
{
    GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
    const gchar *content_length;
    gint res;

#ifndef GST_DISABLE_GST_DEBUG
    if (src->neon_http_debug)
        ne_debug_init (stderr, NE_DBG_HTTP);
#endif

    ne_oom_callback (oom_callback);

    res = ne_sock_init ();
    if (res != 0)
        goto init_failed;

    res = gst_neonhttp_src_send_request_and_redirect (src,
            &src->session, &src->request, 0, src->automatic_redirect);

    if (res != NE_OK || !src->session) {
        if (res == HTTP_SOCKET_ERROR) {
            goto socket_error;
        } else if (res == HTTP_REQUEST_WRONG_PROXY) {
            goto wrong_proxy;
        } else {
            goto begin_req_failed;
        }
    }

    content_length = ne_get_response_header (src->request, "Content-Length");

    if (content_length)
        src->content_size = g_ascii_strtoull (content_length, NULL, 10);
    else
        src->content_size = -1;

    if (TRUE) {
        /* Icecast stuff */
        const gchar *str_value;
        GstTagList *tags;
        gchar *iradio_name;
        gchar *iradio_url;
        gchar *iradio_genre;
        gint icy_metaint;

        tags = gst_tag_list_new_empty ();

        str_value = ne_get_response_header (src->request, "icy-metaint");
        if (str_value) {
            if (sscanf (str_value, "%d", &icy_metaint) == 1) {
                GstCaps *icy_caps;

                icy_caps = gst_caps_new_simple ("application/x-icy",
                                                "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
                gst_base_src_set_caps (GST_BASE_SRC (src), icy_caps);
            }
        }

        /* FIXME: send tags with name, genre, url */
        str_value = ne_get_response_header (src->request, "icy-name");
        if (str_value) {
            iradio_name = gst_neonhttp_src_unicodify (str_value);
            if (iradio_name) {
                gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
                                  iradio_name, NULL);
                g_free (iradio_name);
            }
        }
        str_value = ne_get_response_header (src->request, "icy-genre");
        if (str_value) {
            iradio_genre = gst_neonhttp_src_unicodify (str_value);
            if (iradio_genre) {
                gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
                                  iradio_genre, NULL);
                g_free (iradio_genre);
            }
        }
        str_value = ne_get_response_header (src->request, "icy-url");
        if (str_value) {
            iradio_url = gst_neonhttp_src_unicodify (str_value);
            if (iradio_url) {
                gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
                                  iradio_url, NULL);
                g_free (iradio_url);
            }
        }
        if (!gst_tag_list_is_empty (tags)) {
            GST_DEBUG_OBJECT (src, "pushing tag list %" GST_PTR_FORMAT, tags);
            gst_pad_push_event (GST_BASE_SRC_PAD (src), gst_event_new_tag (tags));
        } else {
            gst_tag_list_unref (tags);
        }
    }

    return TRUE;

    /* ERRORS */
init_failed:
    {
        GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL),
                           ("ne_sock_init() failed: %d", res));
        return FALSE;
    }
socket_error:
    {
        GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
                           ("HTTP Request failed when opening socket: %d", res));
        return FALSE;
    }
wrong_proxy:
    {
        GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL),
                           ("Proxy Server URI is invalid - make sure that either both proxy host "
                            "and port are specified or neither."));
        return FALSE;
    }
begin_req_failed:
    {
        GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
                           ("Could not begin request: %d", res));
        return FALSE;
    }
}
static void
gst_neonhttp_src_get_property (GObject * object, guint prop_id,
                               GValue * value, GParamSpec * pspec)
{
    GstNeonhttpSrc *neonhttpsrc = GST_NEONHTTP_SRC (object);

    switch (prop_id) {
    case PROP_PROXY:
    {
        gchar *str;

        if (neonhttpsrc->proxy.host) {
            str = ne_uri_unparse (&neonhttpsrc->proxy);
            if (!str)
                break;
            g_value_set_string (value, str);
            ne_free (str);
        } else {
            g_value_set_static_string (value, "");
        }
        break;
    }
    case PROP_LOCATION:
    {
        gchar *str;

        if (neonhttpsrc->uri.host) {
            str = ne_uri_unparse (&neonhttpsrc->uri);
            if (!str)
                break;
            g_value_set_string (value, str);
            ne_free (str);
        } else {
            g_value_set_static_string (value, "");
        }
        break;
    }
    case PROP_USER_AGENT:
        g_value_set_string (value, neonhttpsrc->user_agent);
        break;
    case PROP_COOKIES:
        g_value_set_boxed (value, neonhttpsrc->cookies);
        break;
    case PROP_AUTOMATIC_REDIRECT:
        g_value_set_boolean (value, neonhttpsrc->automatic_redirect);
        break;
    case PROP_ACCEPT_SELF_SIGNED:
        g_value_set_boolean (value, neonhttpsrc->accept_self_signed);
        break;
    case PROP_CONNECT_TIMEOUT:
        g_value_set_uint (value, neonhttpsrc->connect_timeout);
        break;
    case PROP_READ_TIMEOUT:
        g_value_set_uint (value, neonhttpsrc->read_timeout);
        break;
#ifndef GST_DISABLE_GST_DEBUG
    case PROP_NEON_HTTP_DEBUG:
        g_value_set_boolean (value, neonhttpsrc->neon_http_debug);
        break;
#endif
    case PROP_IRADIO_MODE:
        g_value_set_boolean (value, neonhttpsrc->iradio_mode);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}
/* create a socket for connecting to remote server */
static gboolean
gst_neonhttp_src_start (GstBaseSrc * bsrc)
{
  GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
  const gchar *content_length;
  gint res;

#ifndef GST_DISABLE_GST_DEBUG
  if (src->neon_http_debug)
    ne_debug_init (stderr, NE_DBG_HTTP);
#endif

  ne_oom_callback (oom_callback);

  res = ne_sock_init ();
  if (res != 0)
    goto init_failed;

  res = gst_neonhttp_src_send_request_and_redirect (src,
      &src->session, &src->request, 0, src->automatic_redirect);

  if (res != NE_OK || !src->session) {
    if (res == HTTP_SOCKET_ERROR) {
      goto socket_error;
    } else if (res == HTTP_REQUEST_WRONG_PROXY) {
      goto wrong_proxy;
    } else {
      goto begin_req_failed;
    }
  }

  content_length = ne_get_response_header (src->request, "Content-Length");

  if (content_length)
    src->content_size = g_ascii_strtoull (content_length, NULL, 10);
  else
    src->content_size = -1;

  if (src->iradio_mode) {
    /* Icecast stuff */
    const gchar *str_value;
    gint gint_value;

    str_value = ne_get_response_header (src->request, "icy-metaint");
    if (str_value) {
      if (sscanf (str_value, "%d", &gint_value) == 1) {
        if (src->icy_caps) {
          gst_caps_unref (src->icy_caps);
          src->icy_caps = NULL;
        }
        src->icy_metaint = gint_value;
        src->icy_caps = gst_caps_new_simple ("application/x-icy",
            "metadata-interval", G_TYPE_INT, src->icy_metaint, NULL);
      }
    }

    str_value = ne_get_response_header (src->request, "icy-name");
    if (str_value) {
      if (src->iradio_name) {
        g_free (src->iradio_name);
        src->iradio_name = NULL;
      }
      src->iradio_name = gst_neonhttp_src_unicodify (str_value);
    }
    str_value = ne_get_response_header (src->request, "icy-genre");
    if (str_value) {
      if (src->iradio_genre) {
        g_free (src->iradio_genre);
        src->iradio_genre = NULL;
      }
      src->iradio_genre = gst_neonhttp_src_unicodify (str_value);
    }
    str_value = ne_get_response_header (src->request, "icy-url");
    if (str_value) {
      if (src->iradio_url) {
        g_free (src->iradio_url);
        src->iradio_url = NULL;
      }
      src->iradio_url = gst_neonhttp_src_unicodify (str_value);
    }
  }

  return TRUE;

  /* ERRORS */
init_failed:
  {
    GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL),
        ("ne_sock_init() failed: %d", res));
    return FALSE;
  }
socket_error:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
        ("HTTP Request failed when opening socket: %d", res));
    return FALSE;
  }
wrong_proxy:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL),
        ("Proxy Server URI is invalid - make sure that either both proxy host "
            "and port are specified or neither."));
    return FALSE;
  }
begin_req_failed:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
        ("Could not begin request: %d", res));
    return FALSE;
  }
}