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