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