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-curlftpsink
22 * @title: curlftpsink
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 FTP server.
28 *
29 * ## Example launch line
30 *
31 * Upload a JPEG file to /home/test/images * directory)
32 *
33 * |[
34 * gst-launch-1.0 filesrc location=image.jpg ! jpegparse ! curlftpsink \
35 * file-name=image.jpg \
36 * location=ftp://192.168.0.1/images/
37 * ]|
38 *
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include <curl/curl.h>
46 #include <string.h>
47 #include <stdio.h>
48
49 #if HAVE_SYS_SOCKET_H
50 #include <sys/socket.h>
51 #endif
52 #include <sys/types.h>
53 #if HAVE_NETINET_IN_H
54 #include <netinet/in.h>
55 #endif
56 #include <unistd.h>
57 #if HAVE_NETINET_IP_H
58 #include <netinet/ip.h>
59 #endif
60 #if HAVE_NETINET_TCP_H
61 #include <netinet/tcp.h>
62 #endif
63 #include <sys/stat.h>
64 #include <fcntl.h>
65
66 #include "gstcurltlssink.h"
67 #include "gstcurlftpsink.h"
68
69 /* Default values */
70 #define GST_CAT_DEFAULT gst_curl_ftp_sink_debug
71 #define RENAME_TO "RNTO "
72 #define RENAME_FROM "RNFR "
73
74 /* Plugin specific settings */
75
76 GST_DEBUG_CATEGORY_STATIC (gst_curl_ftp_sink_debug);
77
78 enum
79 {
80 PROP_0,
81 PROP_FTP_PORT_ARG,
82 PROP_EPSV_MODE,
83 PROP_CREATE_TEMP_FILE,
84 PROP_CREATE_TEMP_FILE_NAME,
85 PROP_CREATE_DIRS
86 };
87
88
89 /* Object class function declarations */
90
91
92 /* private functions */
93 static void gst_curl_ftp_sink_set_property (GObject * object, guint prop_id,
94 const GValue * value, GParamSpec * pspec);
95 static void gst_curl_ftp_sink_get_property (GObject * object, guint prop_id,
96 GValue * value, GParamSpec * pspec);
97 static void gst_curl_ftp_sink_finalize (GObject * gobject);
98
99 static gboolean set_ftp_options_unlocked (GstCurlBaseSink * curlbasesink);
100 static gboolean set_ftp_dynamic_options_unlocked
101 (GstCurlBaseSink * curlbasesink);
102
103 #define gst_curl_ftp_sink_parent_class parent_class
104 G_DEFINE_TYPE (GstCurlFtpSink, gst_curl_ftp_sink, GST_TYPE_CURL_TLS_SINK);
105
106 static void
gst_curl_ftp_sink_class_init(GstCurlFtpSinkClass * klass)107 gst_curl_ftp_sink_class_init (GstCurlFtpSinkClass * klass)
108 {
109 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
110 GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass;
111 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
112
113 GST_DEBUG_CATEGORY_INIT (gst_curl_ftp_sink_debug, "curlftpsink", 0,
114 "curl ftp sink element");
115 GST_DEBUG_OBJECT (klass, "class_init");
116
117 gst_element_class_set_static_metadata (element_class,
118 "Curl ftp sink",
119 "Sink/Network",
120 "Upload data over FTP protocol using libcurl",
121 "Patricia Muscalu <patricia@axis.com>");
122
123 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_ftp_sink_finalize);
124
125 gobject_class->set_property = gst_curl_ftp_sink_set_property;
126 gobject_class->get_property = gst_curl_ftp_sink_get_property;
127
128 gstcurlbasesink_class->set_protocol_dynamic_options_unlocked =
129 set_ftp_dynamic_options_unlocked;
130 gstcurlbasesink_class->set_options_unlocked = set_ftp_options_unlocked;
131
132 g_object_class_install_property (gobject_class, PROP_FTP_PORT_ARG,
133 g_param_spec_string ("ftp-port", "IP address for FTP PORT instruction",
134 "The PORT instruction tells the remote server to connect to"
135 " the IP address", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
136 g_object_class_install_property (gobject_class, PROP_EPSV_MODE,
137 g_param_spec_boolean ("epsv-mode", "Extended passive mode",
138 "Enable the use of the EPSV command when doing passive FTP transfers",
139 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
140 g_object_class_install_property (gobject_class, PROP_CREATE_TEMP_FILE,
141 g_param_spec_boolean ("create-tmp-file", "Enable or disable temporary file transfer", "Use a temporary file name when uploading a a file. When the transfer is complete, \
142 this temporary file is renamed to the final file name. This is useful for ensuring \
143 that remote systems do not read a partially uploaded file", FALSE, G_PARAM_READWRITE |
144 G_PARAM_STATIC_STRINGS));
145 g_object_class_install_property (gobject_class, PROP_CREATE_TEMP_FILE_NAME,
146 g_param_spec_string ("temp-file-name",
147 "Creates a temporary file name with date and time",
148 "Filename pattern to use when generating a temporary filename for uploads",
149 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
150 g_object_class_install_property (gobject_class, PROP_CREATE_DIRS,
151 g_param_spec_boolean ("create-dirs", "Create missing directories",
152 "Attempt to create missing directory included in the path", FALSE,
153 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154 }
155
156 static void
gst_curl_ftp_sink_init(GstCurlFtpSink * sink)157 gst_curl_ftp_sink_init (GstCurlFtpSink * sink)
158 {
159 }
160
161 static void
gst_curl_ftp_sink_finalize(GObject * gobject)162 gst_curl_ftp_sink_finalize (GObject * gobject)
163 {
164 GstCurlFtpSink *this = GST_CURL_FTP_SINK (gobject);
165
166 GST_DEBUG ("finalizing curlftpsink");
167 g_free (this->ftp_port_arg);
168 g_free (this->tmpfile_name);
169
170 if (this->headerlist) {
171 curl_slist_free_all (this->headerlist);
172 this->headerlist = NULL;
173 }
174
175 G_OBJECT_CLASS (parent_class)->finalize (gobject);
176 }
177
178 static gboolean
set_ftp_dynamic_options_unlocked(GstCurlBaseSink * basesink)179 set_ftp_dynamic_options_unlocked (GstCurlBaseSink * basesink)
180 {
181 gchar *tmp = NULL;
182 GstCurlFtpSink *sink = GST_CURL_FTP_SINK (basesink);
183 CURLcode res;
184
185 if (sink->tmpfile_create) {
186 gchar *rename_from = NULL;
187 gchar *rename_to = NULL;
188 gchar *uploadfile_as = NULL;
189 gchar *last_slash = NULL;
190 gchar *tmpfile_name = NULL;
191
192 if (sink->headerlist != NULL) {
193 curl_slist_free_all (sink->headerlist);
194 sink->headerlist = NULL;
195 }
196
197 if (sink->tmpfile_name != NULL) {
198 tmpfile_name = g_strdup_printf ("%s", sink->tmpfile_name);
199 } else {
200 tmpfile_name =
201 g_strdup_printf (".tmp.%04X%04X", g_random_int (), g_random_int ());
202 }
203
204 rename_from = g_strdup_printf ("%s%s", RENAME_FROM, tmpfile_name);
205
206 last_slash = strrchr (basesink->file_name, '/');
207 if (last_slash != NULL) {
208 gchar *dir_name =
209 g_strndup (basesink->file_name, last_slash - basesink->file_name);
210 rename_to = g_strdup_printf ("%s%s", RENAME_TO, last_slash + 1);
211 uploadfile_as = g_strdup_printf ("%s/%s", dir_name, tmpfile_name);
212 g_free (dir_name);
213 } else {
214 rename_to = g_strdup_printf ("%s%s", RENAME_TO, basesink->file_name);
215 uploadfile_as = g_strdup_printf ("%s", tmpfile_name);
216 }
217 g_free (tmpfile_name);
218
219 tmp = g_strdup_printf ("%s%s", basesink->url, uploadfile_as);
220 g_free (uploadfile_as);
221
222 sink->headerlist = curl_slist_append (sink->headerlist, rename_from);
223 sink->headerlist = curl_slist_append (sink->headerlist, rename_to);
224 g_free (rename_from);
225 g_free (rename_to);
226
227 res = curl_easy_setopt (basesink->curl, CURLOPT_URL, tmp);
228 g_free (tmp);
229 if (res != CURLE_OK) {
230 basesink->error = g_strdup_printf ("failed to set URL: %s",
231 curl_easy_strerror (res));
232 return FALSE;
233 }
234
235 res =
236 curl_easy_setopt (basesink->curl, CURLOPT_POSTQUOTE, sink->headerlist);
237 if (res != CURLE_OK) {
238 basesink->error = g_strdup_printf ("failed to set post quote: %s",
239 curl_easy_strerror (res));
240 return FALSE;
241 }
242
243 if (last_slash != NULL) {
244 *last_slash = '\0';
245 }
246 } else {
247 tmp = g_strdup_printf ("%s%s", basesink->url, basesink->file_name);
248 res = curl_easy_setopt (basesink->curl, CURLOPT_URL, tmp);
249 g_free (tmp);
250 if (res != CURLE_OK) {
251 basesink->error = g_strdup_printf ("failed to set URL: %s",
252 curl_easy_strerror (res));
253 return FALSE;
254 }
255 }
256
257 return TRUE;
258 }
259
260 static gboolean
set_ftp_options_unlocked(GstCurlBaseSink * basesink)261 set_ftp_options_unlocked (GstCurlBaseSink * basesink)
262 {
263 GstCurlFtpSink *sink = GST_CURL_FTP_SINK (basesink);
264 CURLcode res;
265
266 res = curl_easy_setopt (basesink->curl, CURLOPT_UPLOAD, 1L);
267 if (res != CURLE_OK) {
268 basesink->error = g_strdup_printf ("failed to prepare for upload: %s",
269 curl_easy_strerror (res));
270 return FALSE;
271 }
272
273 if (sink->ftp_port_arg != NULL && (strlen (sink->ftp_port_arg) > 0)) {
274 /* Connect data stream actively. */
275 res = curl_easy_setopt (basesink->curl, CURLOPT_FTPPORT,
276 sink->ftp_port_arg);
277
278 if (res != CURLE_OK) {
279 basesink->error = g_strdup_printf ("failed to set up active mode: %s",
280 curl_easy_strerror (res));
281 return FALSE;
282 }
283 } else {
284 /* Connect data stream passively.
285 * libcurl will always attempt to use EPSV before PASV.
286 */
287 if (!sink->epsv_mode) {
288 /* send only plain PASV command */
289 res = curl_easy_setopt (basesink->curl, CURLOPT_FTP_USE_EPSV, 0);
290 if (res != CURLE_OK) {
291 basesink->error =
292 g_strdup_printf ("failed to set extended passive mode: %s",
293 curl_easy_strerror (res));
294 return FALSE;
295 }
296 }
297 }
298
299 if (sink->create_dirs) {
300 res = curl_easy_setopt (basesink->curl, CURLOPT_FTP_CREATE_MISSING_DIRS,
301 1L);
302 if (res != CURLE_OK) {
303 basesink->error =
304 g_strdup_printf ("failed to set create missing dirs: %s",
305 curl_easy_strerror (res));
306 return FALSE;
307 }
308 }
309
310 return TRUE;
311 }
312
313 static void
gst_curl_ftp_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)314 gst_curl_ftp_sink_set_property (GObject * object, guint prop_id,
315 const GValue * value, GParamSpec * pspec)
316 {
317 GstCurlFtpSink *sink;
318 GstState cur_state;
319
320 g_return_if_fail (GST_IS_CURL_FTP_SINK (object));
321 sink = GST_CURL_FTP_SINK (object);
322
323 gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
324 if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) {
325 GST_OBJECT_LOCK (sink);
326
327 switch (prop_id) {
328 case PROP_FTP_PORT_ARG:
329 g_free (sink->ftp_port_arg);
330 sink->ftp_port_arg = g_value_dup_string (value);
331 GST_DEBUG_OBJECT (sink, "ftp-port set to %s", sink->ftp_port_arg);
332 break;
333 case PROP_EPSV_MODE:
334 sink->epsv_mode = g_value_get_boolean (value);
335 GST_DEBUG_OBJECT (sink, "epsv-mode set to %d", sink->epsv_mode);
336 break;
337 case PROP_CREATE_TEMP_FILE:
338 sink->tmpfile_create = g_value_get_boolean (value);
339 GST_DEBUG_OBJECT (sink, "create-tmp-file set to %d",
340 sink->tmpfile_create);
341 break;
342 case PROP_CREATE_TEMP_FILE_NAME:
343 g_free (sink->tmpfile_name);
344 sink->tmpfile_name = g_value_dup_string (value);
345 GST_DEBUG_OBJECT (sink, "tmp-file-name set to %s", sink->tmpfile_name);
346 break;
347 case PROP_CREATE_DIRS:
348 sink->create_dirs = g_value_get_boolean (value);
349 GST_DEBUG_OBJECT (sink, "create-dirs set to %d", sink->create_dirs);
350 break;
351
352 default:
353 GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
354 break;
355 }
356
357 GST_OBJECT_UNLOCK (sink);
358 }
359 }
360
361 static void
gst_curl_ftp_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)362 gst_curl_ftp_sink_get_property (GObject * object, guint prop_id,
363 GValue * value, GParamSpec * pspec)
364 {
365 GstCurlFtpSink *sink;
366
367 g_return_if_fail (GST_IS_CURL_FTP_SINK (object));
368 sink = GST_CURL_FTP_SINK (object);
369
370 switch (prop_id) {
371 case PROP_FTP_PORT_ARG:
372 g_value_set_string (value, sink->ftp_port_arg);
373 break;
374 case PROP_EPSV_MODE:
375 g_value_set_boolean (value, sink->epsv_mode);
376 break;
377 case PROP_CREATE_TEMP_FILE:
378 g_value_set_boolean (value, sink->tmpfile_create);
379 break;
380 case PROP_CREATE_TEMP_FILE_NAME:
381 g_value_set_string (value, sink->tmpfile_name);
382 break;
383 case PROP_CREATE_DIRS:
384 g_value_set_boolean (value, sink->create_dirs);
385 break;
386 default:
387 GST_DEBUG_OBJECT (sink, "invalid property id");
388 break;
389 }
390 }
391