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  * @see_also:
25  *
26  * This is a network sink that uses libcurl as a client to upload data to
27  * an HTTP server.
28  *
29  * ## Example launch line
30  *
31  * Upload a JPEG file to an HTTP server.
32  *
33  * |[
34  * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlhttpsink  \
35  *     file-name=image.jpg  \
36  *     location=http://192.168.0.1:8080/cgi-bin/patupload.cgi/  \
37  *     user=test passwd=test  \
38  *     content-type=image/jpeg  \
39  *     use-content-length=false
40  * ]|
41  */
42 
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif
46 
47 #include <curl/curl.h>
48 #include <string.h>
49 #include <stdio.h>
50 
51 #if HAVE_SYS_SOCKET_H
52 #include <sys/socket.h>
53 #endif
54 #include <sys/types.h>
55 #if HAVE_NETINET_IN_H
56 #include <netinet/in.h>
57 #endif
58 #include <unistd.h>
59 #if HAVE_NETINET_IP_H
60 #include <netinet/ip.h>
61 #endif
62 #if HAVE_NETINET_TCP_H
63 #include <netinet/tcp.h>
64 #endif
65 #include <sys/stat.h>
66 #include <fcntl.h>
67 
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 
116 /* private functions */
117 
118 static gboolean proxy_setup (GstCurlBaseSink * bcsink);
119 
120 static void
gst_curl_http_sink_class_init(GstCurlHttpSinkClass * klass)121 gst_curl_http_sink_class_init (GstCurlHttpSinkClass * klass)
122 {
123   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
124   GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass;
125   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
126 
127   GST_DEBUG_CATEGORY_INIT (gst_curl_http_sink_debug, "curlhttpsink", 0,
128       "curl http sink element");
129   GST_DEBUG_OBJECT (klass, "class_init");
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     tmp =
340         g_strdup_printf ("Content-Length: %d", (int) bcsink->transfer_buf->len);
341     sink->header_list = curl_slist_append (sink->header_list, tmp);
342     g_free (tmp);
343   } else {
344     /* when sending a POST request to a HTTP 1.1 server, you can send data
345      * without knowing the size before starting the POST if you use chunked
346      * encoding */
347     sink->header_list = curl_slist_append (sink->header_list,
348         "Transfer-Encoding: chunked");
349   }
350 
351   tmp = g_strdup_printf ("Content-Type: %s", sink->content_type);
352   sink->header_list = curl_slist_append (sink->header_list, tmp);
353   g_free (tmp);
354 
355 set_headers:
356 
357   if (bcsink->file_name) {
358     tmp = g_strdup_printf ("Content-Disposition: attachment; filename="
359         "\"%s\"", bcsink->file_name);
360     sink->header_list = curl_slist_append (sink->header_list, tmp);
361     g_free (tmp);
362   }
363   res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPHEADER, sink->header_list);
364   if (res != CURLE_OK) {
365     bcsink->error = g_strdup_printf ("failed to set HTTP headers: %s",
366         curl_easy_strerror (res));
367     return FALSE;
368   }
369 
370   return TRUE;
371 }
372 
373 static gboolean
gst_curl_http_sink_set_options_unlocked(GstCurlBaseSink * bcsink)374 gst_curl_http_sink_set_options_unlocked (GstCurlBaseSink * bcsink)
375 {
376   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
377   GstCurlTlsSinkClass *parent_class;
378   CURLcode res;
379 
380   /* proxy settings */
381   if (sink->proxy != NULL) {
382     if (!proxy_setup (bcsink)) {
383       return FALSE;
384     }
385   }
386 
387   res = curl_easy_setopt (bcsink->curl, CURLOPT_POST, 1L);
388   if (res != CURLE_OK) {
389     bcsink->error = g_strdup_printf ("failed to set HTTP POST: %s",
390         curl_easy_strerror (res));
391     return FALSE;
392   }
393 
394   /* FIXME: check user & passwd */
395   res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
396   if (res != CURLE_OK) {
397     bcsink->error =
398         g_strdup_printf ("failed to set HTTP authentication methods: %s",
399         curl_easy_strerror (res));
400     return FALSE;
401   }
402 
403   parent_class = GST_CURL_TLS_SINK_GET_CLASS (sink);
404 
405   if (g_str_has_prefix (bcsink->url, "https://")) {
406     GST_DEBUG_OBJECT (bcsink, "setting up tls options");
407     return parent_class->set_options_unlocked (bcsink);
408   }
409 
410   return TRUE;
411 }
412 
413 static gboolean
gst_curl_http_sink_transfer_verify_response_code(GstCurlBaseSink * bcsink)414 gst_curl_http_sink_transfer_verify_response_code (GstCurlBaseSink * bcsink)
415 {
416   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
417   glong resp;
418 
419   curl_easy_getinfo (bcsink->curl, CURLINFO_RESPONSE_CODE, &resp);
420   GST_DEBUG_OBJECT (sink, "response code: %ld", resp);
421 
422   if (resp < 100 || resp >= 300) {
423     bcsink->error = g_strdup_printf ("HTTP response error: (received: %ld)",
424         resp);
425     return FALSE;
426   }
427 
428   return TRUE;
429 }
430 
431 static void
gst_curl_http_sink_transfer_prepare_poll_wait(GstCurlBaseSink * bcsink)432 gst_curl_http_sink_transfer_prepare_poll_wait (GstCurlBaseSink * bcsink)
433 {
434   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
435 
436   if (!sink->proxy_conn_established
437       && (sink->proxy_resp != RESPONSE_CONNECT_PROXY)
438       && sink->proxy_auth) {
439     GST_DEBUG_OBJECT (sink, "prep transfers: connecting proxy");
440     curl_easy_getinfo (bcsink->curl, CURLINFO_HTTP_CONNECTCODE,
441         &sink->proxy_resp);
442     if (sink->proxy_resp == RESPONSE_CONNECT_PROXY) {
443       GST_LOG ("received HTTP/1.0 200 Connection Established");
444       /* Workaround: redefine HTTP headers before connecting to HTTP server.
445        * When talking to proxy, the Content-Length: 0 is send with the request.
446        */
447       curl_multi_remove_handle (bcsink->multi_handle, bcsink->curl);
448       gst_curl_http_sink_set_header_unlocked (bcsink);
449       curl_multi_add_handle (bcsink->multi_handle, bcsink->curl);
450       sink->proxy_conn_established = TRUE;
451     }
452   }
453 }
454 
455 // FIXME check this: why critical when no mime is set???
456 static void
gst_curl_http_sink_set_mime_type(GstCurlBaseSink * bcsink,GstCaps * caps)457 gst_curl_http_sink_set_mime_type (GstCurlBaseSink * bcsink, GstCaps * caps)
458 {
459   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
460   GstStructure *structure;
461   const gchar *mime_type;
462 
463   structure = gst_caps_get_structure (caps, 0);
464   mime_type = gst_structure_get_name (structure);
465 
466   g_free (sink->content_type);
467   if (!g_strcmp0 (mime_type, "multipart/form-data") &&
468       gst_structure_has_field_typed (structure, "boundary", G_TYPE_STRING)) {
469     const gchar *boundary;
470 
471     boundary = gst_structure_get_string (structure, "boundary");
472     sink->content_type = g_strconcat (mime_type, "; boundary=", boundary, NULL);
473   } else {
474     sink->content_type = g_strdup (mime_type);
475   }
476 }
477 
478 static gboolean
proxy_setup(GstCurlBaseSink * bcsink)479 proxy_setup (GstCurlBaseSink * bcsink)
480 {
481   GstCurlHttpSink *sink = GST_CURL_HTTP_SINK (bcsink);
482   CURLcode res;
483 
484   res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXY, sink->proxy);
485   if (res != CURLE_OK) {
486     bcsink->error = g_strdup_printf ("failed to set proxy: %s",
487         curl_easy_strerror (res));
488     return FALSE;
489   }
490 
491   res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYPORT, sink->proxy_port);
492   if (res != CURLE_OK) {
493     bcsink->error = g_strdup_printf ("failed to set proxy port: %s",
494         curl_easy_strerror (res));
495     return FALSE;
496   }
497 
498   if (sink->proxy_user != NULL &&
499       strlen (sink->proxy_user) &&
500       sink->proxy_passwd != NULL && strlen (sink->proxy_passwd)) {
501     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYUSERNAME,
502         sink->proxy_user);
503     if (res != CURLE_OK) {
504       bcsink->error = g_strdup_printf ("failed to set proxy user name: %s",
505           curl_easy_strerror (res));
506       return FALSE;
507     }
508 
509     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYPASSWORD,
510         sink->proxy_passwd);
511     if (res != CURLE_OK) {
512       bcsink->error = g_strdup_printf ("failed to set proxy password: %s",
513           curl_easy_strerror (res));
514       return FALSE;
515     }
516 
517     res = curl_easy_setopt (bcsink->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
518     if (res != CURLE_OK) {
519       bcsink->error =
520           g_strdup_printf ("failed to set proxy authentication method: %s",
521           curl_easy_strerror (res));
522       return FALSE;
523     }
524 
525     sink->proxy_auth = TRUE;
526   }
527 
528   if (g_str_has_prefix (bcsink->url, "https://")) {
529     /* tunnel all operations through a given HTTP proxy */
530     res = curl_easy_setopt (bcsink->curl, CURLOPT_HTTPPROXYTUNNEL, 1L);
531     if (res != CURLE_OK) {
532       bcsink->error = g_strdup_printf ("failed to set HTTP proxy tunnel: %s",
533           curl_easy_strerror (res));
534       return FALSE;
535     }
536   }
537 
538   sink->use_proxy = TRUE;
539 
540   return TRUE;
541 }
542