• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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