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