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