• 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-curlhttpsink
22  * @title: curlhttpsink
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 HTTP server.
27  *
28  * ## Example launch line
29  *
30  * Upload a JPEG file to an HTTP server.
31  *
32  * |[
33  * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlhttpsink  \
34  *     file-name=image.jpg  \
35  *     location=http://192.168.0.1:8080/cgi-bin/patupload.cgi/  \
36  *     user=test passwd=test  \
37  *     content-type=image/jpeg  \
38  *     use-content-length=false
39  * ]|
40  */
41 
42 #ifdef HAVE_CONFIG_H
43 #include "config.h"
44 #endif
45 
46 #include <curl/curl.h>
47 #include <string.h>
48 #include <stdio.h>
49 
50 #if HAVE_SYS_SOCKET_H
51 #include <sys/socket.h>
52 #endif
53 #include <sys/types.h>
54 #if HAVE_NETINET_IN_H
55 #include <netinet/in.h>
56 #endif
57 #include <unistd.h>
58 #if HAVE_NETINET_IP_H
59 #include <netinet/ip.h>
60 #endif
61 #if HAVE_NETINET_TCP_H
62 #include <netinet/tcp.h>
63 #endif
64 #include <sys/stat.h>
65 #include <fcntl.h>
66 
67 #include "gstcurlelements.h"
68 #include "gstcurltlssink.h"
69 #include "gstcurlhttpsink.h"
70 
71 /* Default values */
72 #define GST_CAT_DEFAULT                gst_curl_http_sink_debug
73 #define DEFAULT_TIMEOUT                30
74 #define DEFAULT_PROXY_PORT             3128
75 #define DEFAULT_USE_CONTENT_LENGTH     FALSE
76 
77 #define RESPONSE_CONNECT_PROXY         200
78 
79 /* Plugin specific settings */
80 
81 GST_DEBUG_CATEGORY_STATIC (gst_curl_http_sink_debug);
82 
83 enum
84 {
85   PROP_0,
86   PROP_PROXY,
87   PROP_PROXY_PORT,
88   PROP_PROXY_USER_NAME,
89   PROP_PROXY_USER_PASSWD,
90   PROP_USE_CONTENT_LENGTH,
91   PROP_CONTENT_TYPE
92 };
93 
94 
95 /* Object class function declarations */
96 
97 static void gst_curl_http_sink_set_property (GObject * object, guint prop_id,
98     const GValue * value, GParamSpec * pspec);
99 static void gst_curl_http_sink_get_property (GObject * object, guint prop_id,
100     GValue * value, GParamSpec * pspec);
101 static void gst_curl_http_sink_finalize (GObject * gobject);
102 static gboolean gst_curl_http_sink_set_header_unlocked
103     (GstCurlBaseSink * bcsink);
104 static gboolean gst_curl_http_sink_set_options_unlocked
105     (GstCurlBaseSink * bcsink);
106 static void gst_curl_http_sink_set_mime_type
107     (GstCurlBaseSink * bcsink, GstCaps * caps);
108 static gboolean gst_curl_http_sink_transfer_verify_response_code
109     (GstCurlBaseSink * bcsink);
110 static void gst_curl_http_sink_transfer_prepare_poll_wait
111     (GstCurlBaseSink * bcsink);
112 
113 #define gst_curl_http_sink_parent_class parent_class
114 G_DEFINE_TYPE (GstCurlHttpSink, gst_curl_http_sink, GST_TYPE_CURL_TLS_SINK);
115 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (curlhttpsink, "curlhttpsink",
116     GST_RANK_NONE, GST_TYPE_CURL_HTTP_SINK, curl_element_init (plugin));
117 /* private functions */
118 
119 static gboolean proxy_setup (GstCurlBaseSink * bcsink);
120 
121 static void
gst_curl_http_sink_class_init(GstCurlHttpSinkClass * klass)122 gst_curl_http_sink_class_init (GstCurlHttpSinkClass * klass)
123 {
124   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
125   GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass;
126   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
127 
128   GST_DEBUG_CATEGORY_INIT (gst_curl_http_sink_debug, "curlhttpsink", 0,
129       "curl http sink element");
130 
131   gst_element_class_set_static_metadata (element_class,
132       "Curl http sink",
133       "Sink/Network",
134       "Upload data over HTTP/HTTPS protocol using libcurl",
135       "Patricia Muscalu <patricia@axis.com>");
136 
137   gstcurlbasesink_class->set_protocol_dynamic_options_unlocked =
138       gst_curl_http_sink_set_header_unlocked;
139   gstcurlbasesink_class->set_options_unlocked =
140       gst_curl_http_sink_set_options_unlocked;
141   gstcurlbasesink_class->set_mime_type = gst_curl_http_sink_set_mime_type;
142   gstcurlbasesink_class->transfer_verify_response_code =
143       gst_curl_http_sink_transfer_verify_response_code;
144   gstcurlbasesink_class->transfer_prepare_poll_wait =
145       gst_curl_http_sink_transfer_prepare_poll_wait;
146 
147   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_http_sink_finalize);
148 
149   gobject_class->set_property = gst_curl_http_sink_set_property;
150   gobject_class->get_property = gst_curl_http_sink_get_property;
151 
152   g_object_class_install_property (gobject_class, PROP_PROXY,
153       g_param_spec_string ("proxy", "Proxy", "HTTP proxy server URI", NULL,
154           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
155   g_object_class_install_property (gobject_class, PROP_PROXY_PORT,
156       g_param_spec_int ("proxy-port", "Proxy port",
157           "HTTP proxy server port", 0, G_MAXINT, DEFAULT_PROXY_PORT,
158           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159   g_object_class_install_property (gobject_class, PROP_PROXY_USER_NAME,
160       g_param_spec_string ("proxy-user", "Proxy user name",
161           "Proxy user name to use for proxy authentication",
162           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
163   g_object_class_install_property (gobject_class, PROP_PROXY_USER_PASSWD,
164       g_param_spec_string ("proxy-passwd", "Proxy user password",
165           "Proxy user password to use for proxy authentication",
166           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
167   g_object_class_install_property (gobject_class, PROP_USE_CONTENT_LENGTH,
168       g_param_spec_boolean ("use-content-length", "Use content length header",
169           "Use the Content-Length HTTP header instead of "
170           "Transfer-Encoding header", DEFAULT_USE_CONTENT_LENGTH,
171           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
172   g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
173       g_param_spec_string ("content-type", "Content type",
174           "The mime type of the body of the request", NULL,
175           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
176 }
177 
178 static void
gst_curl_http_sink_init(GstCurlHttpSink * sink)179 gst_curl_http_sink_init (GstCurlHttpSink * sink)
180 {
181   sink->header_list = NULL;
182   sink->use_content_length = DEFAULT_USE_CONTENT_LENGTH;
183   sink->content_type = NULL;
184 
185   sink->proxy_port = DEFAULT_PROXY_PORT;
186   sink->proxy_headers_set = FALSE;
187   sink->proxy_auth = FALSE;
188   sink->use_proxy = FALSE;
189   sink->proxy_conn_established = FALSE;
190   sink->proxy_resp = -1;
191 }
192 
193 static void
gst_curl_http_sink_finalize(GObject * gobject)194 gst_curl_http_sink_finalize (GObject * gobject)
195 {
196   GstCurlHttpSink *this = GST_CURL_HTTP_SINK (gobject);
197 
198   GST_DEBUG ("finalizing curlhttpsink");
199   g_free (this->proxy);
200   g_free (this->proxy_user);
201   g_free (this->proxy_passwd);
202   g_free (this->content_type);
203 
204   if (this->header_list) {
205     curl_slist_free_all (this->header_list);
206     this->header_list = NULL;
207   }
208 
209   G_OBJECT_CLASS (parent_class)->finalize (gobject);
210 }
211 
212 static void
gst_curl_http_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)213 gst_curl_http_sink_set_property (GObject * object, guint prop_id,
214     const GValue * value, GParamSpec * pspec)
215 {
216   GstCurlHttpSink *sink;
217   GstState cur_state;
218 
219   g_return_if_fail (GST_IS_CURL_HTTP_SINK (object));
220   sink = GST_CURL_HTTP_SINK (object);
221 
222   gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
223   if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) {
224     GST_OBJECT_LOCK (sink);
225 
226     switch (prop_id) {
227       case PROP_PROXY:
228         g_free (sink->proxy);
229         sink->proxy = g_value_dup_string (value);
230         GST_DEBUG_OBJECT (sink, "proxy set to %s", sink->proxy);
231         break;
232       case PROP_PROXY_PORT:
233         sink->proxy_port = g_value_get_int (value);
234         GST_DEBUG_OBJECT (sink, "proxy port set to %d", sink->proxy_port);
235         break;
236       case PROP_PROXY_USER_NAME:
237         g_free (sink->proxy_user);
238         sink->proxy_user = g_value_dup_string (value);
239         GST_DEBUG_OBJECT (sink, "proxy user set to %s", sink->proxy_user);
240         break;
241       case PROP_PROXY_USER_PASSWD:
242         g_free (sink->proxy_passwd);
243         sink->proxy_passwd = g_value_dup_string (value);
244         GST_DEBUG_OBJECT (sink, "proxy password set to %s", sink->proxy_passwd);
245         break;
246       case PROP_USE_CONTENT_LENGTH:
247         sink->use_content_length = g_value_get_boolean (value);
248         GST_DEBUG_OBJECT (sink, "use_content_length set to %d",
249             sink->use_content_length);
250         break;
251       case PROP_CONTENT_TYPE:
252         g_free (sink->content_type);
253         sink->content_type = g_value_dup_string (value);
254         GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type);
255         break;
256       default:
257         GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
258         break;
259     }
260 
261     GST_OBJECT_UNLOCK (sink);
262 
263     return;
264   }
265 
266   /* in PLAYING or PAUSED state */
267   GST_OBJECT_LOCK (sink);
268 
269   switch (prop_id) {
270     case PROP_CONTENT_TYPE:
271       g_free (sink->content_type);
272       sink->content_type = g_value_dup_string (value);
273       GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type);
274       break;
275     default:
276       GST_WARNING_OBJECT (sink, "cannot set property when PLAYING");
277       break;
278   }
279 
280   GST_OBJECT_UNLOCK (sink);
281 }
282 
283 static void
gst_curl_http_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)284 gst_curl_http_sink_get_property (GObject * object, guint prop_id,
285     GValue * value, GParamSpec * pspec)
286 {
287   GstCurlHttpSink *sink;
288 
289   g_return_if_fail (GST_IS_CURL_HTTP_SINK (object));
290   sink = GST_CURL_HTTP_SINK (object);
291 
292   switch (prop_id) {
293     case PROP_PROXY:
294       g_value_set_string (value, sink->proxy);
295       break;
296     case PROP_PROXY_PORT:
297       g_value_set_int (value, sink->proxy_port);
298       break;
299     case PROP_PROXY_USER_NAME:
300       g_value_set_string (value, sink->proxy_user);
301       break;
302     case PROP_PROXY_USER_PASSWD:
303       g_value_set_string (value, sink->proxy_passwd);
304       break;
305     case PROP_USE_CONTENT_LENGTH:
306       g_value_set_boolean (value, sink->use_content_length);
307       break;
308     case PROP_CONTENT_TYPE:
309       g_value_set_string (value, sink->content_type);
310       break;
311     default:
312       GST_DEBUG_OBJECT (sink, "invalid property id");
313       break;
314   }
315 }
316 
317 static gboolean
gst_curl_http_sink_set_header_unlocked(GstCurlBaseSink * bcsink)318 gst_curl_http_sink_set_header_unlocked (GstCurlBaseSink * bcsink)
319 {
320   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
321   gchar *tmp;
322   CURLcode res;
323 
324   if (sink->header_list) {
325     curl_slist_free_all (sink->header_list);
326     sink->header_list = NULL;
327   }
328 
329   if (!sink->proxy_headers_set && sink->use_proxy) {
330     sink->header_list = curl_slist_append (sink->header_list,
331         "Content-Length: 0");
332     sink->proxy_headers_set = TRUE;
333     goto set_headers;
334   }
335 
336   if (sink->use_content_length) {
337     /* if content length is used we assume that every buffer is one
338      * entire file, which is the case when uploading several jpegs */
339     res =
340         curl_easy_setopt (bcsink->curl, CURLOPT_POSTFIELDSIZE,
341         (long) bcsink->transfer_buf->len);
342     if (res != CURLE_OK) {
343       bcsink->error = g_strdup_printf ("failed to set HTTP content-length: %s",
344           curl_easy_strerror (res));
345       return FALSE;
346     }
347   } else {
348     /* when sending a POST request to a HTTP 1.1 server, you can send data
349      * without knowing the size before starting the POST if you use chunked
350      * encoding */
351     sink->header_list = curl_slist_append (sink->header_list,
352         "Transfer-Encoding: chunked");
353   }
354 
355   tmp = g_strdup_printf ("Content-Type: %s", sink->content_type);
356   sink->header_list = curl_slist_append (sink->header_list, tmp);
357   g_free (tmp);
358 
359 set_headers:
360 
361   if (bcsink->file_name) {
362     tmp = g_strdup_printf ("Content-Disposition: attachment; filename="
363         "\"%s\"", bcsink->file_name);
364     sink->header_list = curl_slist_append (sink->header_list, tmp);
365     g_free (tmp);
366   }
367 
368   /* set 'Expect: 100-continue'-header explicitly */
369   if (sink->use_content_length) {
370     sink->header_list =
371         curl_slist_append (sink->header_list, "Expect: 100-continue");
372   }
373 
374   res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPHEADER, sink->header_list);
375   if (res != CURLE_OK) {
376     bcsink->error = g_strdup_printf ("failed to set HTTP headers: %s",
377         curl_easy_strerror (res));
378     return FALSE;
379   }
380 
381   return TRUE;
382 }
383 
384 static gboolean
gst_curl_http_sink_set_options_unlocked(GstCurlBaseSink * bcsink)385 gst_curl_http_sink_set_options_unlocked (GstCurlBaseSink * bcsink)
386 {
387   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
388   GstCurlTlsSinkClass *parent_class;
389   CURLcode res;
390 
391   /* proxy settings */
392   if (sink->proxy != NULL) {
393     if (!proxy_setup (bcsink)) {
394       return FALSE;
395     }
396   }
397 
398   res = curl_easy_setopt (bcsink->curl, CURLOPT_POST, 1L);
399   if (res != CURLE_OK) {
400     bcsink->error = g_strdup_printf ("failed to set HTTP POST: %s",
401         curl_easy_strerror (res));
402     return FALSE;
403   }
404 
405   /* FIXME: check user & passwd */
406   res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
407   if (res != CURLE_OK) {
408     bcsink->error =
409         g_strdup_printf ("failed to set HTTP authentication methods: %s",
410         curl_easy_strerror (res));
411     return FALSE;
412   }
413 
414   parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink);
415 
416   if (g_str_has_prefix (bcsink->url, "https://")) {
417     GST_DEBUG_OBJECT (bcsink, "setting up tls options");
418     return parent_class->set_options_unlocked (bcsink);
419   }
420 
421   return TRUE;
422 }
423 
424 static gboolean
gst_curl_http_sink_transfer_verify_response_code(GstCurlBaseSink * bcsink)425 gst_curl_http_sink_transfer_verify_response_code (GstCurlBaseSink * bcsink)
426 {
427   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
428   glong resp;
429 
430   curl_easy_getinfo (bcsink->curl, CURLINFO_RESPONSE_CODE, &resp);
431   GST_DEBUG_OBJECT (sink, "response code: %ld", resp);
432 
433   if (resp < 100 || resp >= 300) {
434     bcsink->error = g_strdup_printf ("HTTP response error: (received: %ld)",
435         resp);
436     return FALSE;
437   }
438 
439   return TRUE;
440 }
441 
442 static void
gst_curl_http_sink_transfer_prepare_poll_wait(GstCurlBaseSink * bcsink)443 gst_curl_http_sink_transfer_prepare_poll_wait (GstCurlBaseSink * bcsink)
444 {
445   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
446 
447   if (!sink->proxy_conn_established
448       && (sink->proxy_resp != RESPONSE_CONNECT_PROXY)
449       && sink->proxy_auth) {
450     GST_DEBUG_OBJECT (sink, "prep transfers: connecting proxy");
451     curl_easy_getinfo (bcsink->curl, CURLINFO_HTTP_CONNECTCODE,
452         &sink->proxy_resp);
453     if (sink->proxy_resp == RESPONSE_CONNECT_PROXY) {
454       GST_LOG ("received HTTP/1.0 200 Connection Established");
455       /* Workaround: redefine HTTP headers before connecting to HTTP server.
456        * When talking to proxy, the Content-Length: 0 is send with the request.
457        */
458       curl_multi_remove_handle (bcsink->multi_handle, bcsink->curl);
459       gst_curl_http_sink_set_header_unlocked (bcsink);
460       curl_multi_add_handle (bcsink->multi_handle, bcsink->curl);
461       sink->proxy_conn_established = TRUE;
462     }
463   }
464 }
465 
466 // FIXME check this: why critical when no mime is set???
467 static void
gst_curl_http_sink_set_mime_type(GstCurlBaseSink * bcsink,GstCaps * caps)468 gst_curl_http_sink_set_mime_type (GstCurlBaseSink * bcsink, GstCaps * caps)
469 {
470   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
471   GstStructure *structure;
472   const gchar *mime_type;
473 
474   structure = gst_caps_get_structure (caps, 0);
475   mime_type = gst_structure_get_name (structure);
476 
477   g_free (sink->content_type);
478   if (!g_strcmp0 (mime_type, "multipart/form-data") &&
479       gst_structure_has_field_typed (structure, "boundary", G_TYPE_STRING)) {
480     const gchar *boundary;
481 
482     boundary = gst_structure_get_string (structure, "boundary");
483     sink->content_type = g_strconcat (mime_type, "; boundary=", boundary, NULL);
484   } else {
485     sink->content_type = g_strdup (mime_type);
486   }
487 }
488 
489 static gboolean
proxy_setup(GstCurlBaseSink * bcsink)490 proxy_setup (GstCurlBaseSink * bcsink)
491 {
492   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
493   CURLcode res;
494 
495   res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXY, sink->proxy);
496   if (res != CURLE_OK) {
497     bcsink->error = g_strdup_printf ("failed to set proxy: %s",
498         curl_easy_strerror (res));
499     return FALSE;
500   }
501 
502   res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYPORT, sink->proxy_port);
503   if (res != CURLE_OK) {
504     bcsink->error = g_strdup_printf ("failed to set proxy port: %s",
505         curl_easy_strerror (res));
506     return FALSE;
507   }
508 
509   if (sink->proxy_user != NULL &&
510       strlen (sink->proxy_user) &&
511       sink->proxy_passwd != NULL && strlen (sink->proxy_passwd)) {
512     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYUSERNAME,
513         sink->proxy_user);
514     if (res != CURLE_OK) {
515       bcsink->error = g_strdup_printf ("failed to set proxy user name: %s",
516           curl_easy_strerror (res));
517       return FALSE;
518     }
519 
520     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYPASSWORD,
521         sink->proxy_passwd);
522     if (res != CURLE_OK) {
523       bcsink->error = g_strdup_printf ("failed to set proxy password: %s",
524           curl_easy_strerror (res));
525       return FALSE;
526     }
527 
528     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
529     if (res != CURLE_OK) {
530       bcsink->error =
531           g_strdup_printf ("failed to set proxy authentication method: %s",
532           curl_easy_strerror (res));
533       return FALSE;
534     }
535 
536     sink->proxy_auth = TRUE;
537   }
538 
539   if (g_str_has_prefix (bcsink->url, "https://")) {
540     /* tunnel all operations through a given HTTP proxy */
541     res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPPROXYTUNNEL, 1L);
542     if (res != CURLE_OK) {
543       bcsink->error = g_strdup_printf ("failed to set HTTP proxy tunnel: %s",
544           curl_easy_strerror (res));
545       return FALSE;
546     }
547   }
548 
549   sink->use_proxy = TRUE;
550 
551   return TRUE;
552 }
553