1/*
2 * Copyright (C) 2012 Igalia S.L.
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 License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "webkitfilechooserrequest.h"
22
23#include "FileChooser.h"
24#include "FileSystem.h"
25#include "webkitfilechooserrequestprivate.h"
26#include "webkitglobalsprivate.h"
27#include <glib/gi18n-lib.h>
28#include <wtf/gobject/GOwnPtr.h>
29#include <wtf/gobject/GRefPtr.h>
30#include <wtf/text/CString.h>
31
32using namespace WebCore;
33
34/**
35 * SECTION:webkitfilechooserrequest
36 * @Short_description: A request to open a file chooser
37 * @Title: WebKitFileChooserRequest
38 * @See_also: #WebKitWebView
39 *
40 * Whenever the user interacts with an &lt;input type='file' /&gt;
41 * HTML element, WebKit will need to show a dialog to choose one or
42 * more files to be uploaded to the server along with the rest of the
43 * form data. For that to happen in a general way, instead of just
44 * opening a #GtkFileChooserDialog (which might be not desirable in
45 * some cases, such as when an embedding applications prefers to use
46 * its own file chooser dialog), WebKit will fire the
47 * #WebKitWebView::run-file-chooser signal with a
48 * #WebKitFileChooserRequest object, which will allow the client
49 * application to specify the files to be selected, to inspect the
50 * details of the request (e.g. if multiple selection should be
51 * allowed) and to cancel the request, in case nothing was selected.
52 *
53 * In case the client application does not wish to handle this signal,
54 * WebKit will provide a default handler which will asynchronously run
55 * a regular #GtkFileChooserDialog for the user to interact with.
56 */
57G_DEFINE_TYPE(WebKitFileChooserRequest, webkit_file_chooser_request, G_TYPE_OBJECT)
58
59struct _WebKitFileChooserRequestPrivate {
60    RefPtr<FileChooser> chooser;
61    GRefPtr<GtkFileFilter> filter;
62    GRefPtr<GPtrArray> mimeTypes;
63    GRefPtr<GPtrArray> selectedFiles;
64};
65
66enum {
67    PROP_0,
68    PROP_FILTER,
69    PROP_MIME_TYPES,
70    PROP_SELECT_MULTIPLE,
71    PROP_SELECTED_FILES,
72};
73
74static void webkit_file_chooser_request_init(WebKitFileChooserRequest* request)
75{
76    request->priv = G_TYPE_INSTANCE_GET_PRIVATE(request, WEBKIT_TYPE_FILE_CHOOSER_REQUEST, WebKitFileChooserRequestPrivate);
77    new (request->priv) WebKitFileChooserRequestPrivate();
78}
79
80static void webkit_file_chooser_request_finalize(GObject* object)
81{
82    WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(object);
83
84    request->priv->~WebKitFileChooserRequestPrivate();
85    G_OBJECT_CLASS(webkit_file_chooser_request_parent_class)->finalize(object);
86}
87
88static void webkit_file_chooser_request_get_property(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
89{
90    WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(object);
91    switch (propId) {
92    case PROP_FILTER:
93        g_value_set_object(value, webkit_file_chooser_request_get_mime_types_filter(request));
94        break;
95    case PROP_MIME_TYPES:
96        g_value_set_boxed(value, webkit_file_chooser_request_get_mime_types(request));
97        break;
98    case PROP_SELECT_MULTIPLE:
99        g_value_set_boolean(value, webkit_file_chooser_request_get_select_multiple(request));
100        break;
101    case PROP_SELECTED_FILES:
102        g_value_set_boxed(value, webkit_file_chooser_request_get_selected_files(request));
103        break;
104    default:
105        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
106        break;
107    }
108}
109
110static void webkit_file_chooser_request_class_init(WebKitFileChooserRequestClass* requestClass)
111{
112    GObjectClass* objectClass = G_OBJECT_CLASS(requestClass);
113    objectClass->finalize = webkit_file_chooser_request_finalize;
114    objectClass->get_property = webkit_file_chooser_request_get_property;
115    g_type_class_add_private(requestClass, sizeof(WebKitFileChooserRequestPrivate));
116
117    /**
118     * WebKitFileChooserRequest:filter:
119     *
120     * The filter currently associated with the request. See
121     * webkit_file_chooser_request_get_mime_types_filter() for more
122     * details.
123     *
124     * Since: 1.10
125     */
126    g_object_class_install_property(objectClass,
127                                    PROP_FILTER,
128                                    g_param_spec_object("filter",
129                                                      _("MIME types filter"),
130                                                      _("The filter currently associated with the request"),
131                                                      GTK_TYPE_FILE_FILTER,
132                                                      WEBKIT_PARAM_READABLE));
133    /**
134     * WebKitFileChooserRequest:mime-types:
135     *
136     * A %NULL-terminated array of strings containing the list of MIME
137     * types the file chooser dialog should handle. See
138     * webkit_file_chooser_request_get_mime_types() for more details.
139     *
140     * Since: 1.10
141     */
142    g_object_class_install_property(objectClass,
143                                    PROP_MIME_TYPES,
144                                    g_param_spec_boxed("mime-types",
145                                                      _("MIME types"),
146                                                      _("The list of MIME types associated with the request"),
147                                                      G_TYPE_STRV,
148                                                      WEBKIT_PARAM_READABLE));    /**
149     * WebKitFileChooserRequest:select-multiple:
150     *
151     * Whether the file chooser should allow selecting multiple
152     * files. See
153     * webkit_file_chooser_request_get_select_multiple() for
154     * more details.
155     *
156     * Since: 1.10
157     */
158    g_object_class_install_property(objectClass,
159                                    PROP_SELECT_MULTIPLE,
160                                    g_param_spec_boolean("select-multiple",
161                                                       _("Select multiple files"),
162                                                       _("Whether the file chooser should allow selecting multiple files"),
163                                                       FALSE,
164                                                       WEBKIT_PARAM_READABLE));
165    /**
166     * WebKitFileChooserRequest:selected-files:
167     *
168     * A %NULL-terminated array of strings containing the list of
169     * selected files associated to the current request. See
170     * webkit_file_chooser_request_get_selected_files() for more details.
171     *
172     * Since: 1.10
173     */
174    g_object_class_install_property(objectClass,
175                                    PROP_SELECTED_FILES,
176                                    g_param_spec_boxed("selected-files",
177                                                      _("Selected files"),
178                                                      _("The list of selected files associated with the request"),
179                                                      G_TYPE_STRV,
180                                                      WEBKIT_PARAM_READABLE));
181}
182
183WebKitFileChooserRequest* webkit_file_chooser_request_create(PassRefPtr<FileChooser> chooser)
184{
185    WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(g_object_new(WEBKIT_TYPE_FILE_CHOOSER_REQUEST, NULL));
186    request->priv->chooser = chooser;
187    return request;
188}
189
190/**
191 * webkit_file_chooser_request_get_mime_types:
192 * @request: a #WebKitFileChooserRequest
193 *
194 * Get the list of MIME types the file chooser dialog should handle,
195 * in the format specified in RFC 2046 for "media types". Its contents
196 * depend on the value of the 'accept' attribute for HTML input
197 * elements. This function should normally be called before presenting
198 * the file chooser dialog to the user, to decide whether to allow the
199 * user to select multiple files at once or only one.
200 *
201 * Returns: (array zero-terminated=1) (transfer none): a
202 * %NULL-terminated array of strings if a list of accepted MIME types
203 * is defined or %NULL otherwise, meaning that any MIME type should be
204 * accepted. This array and its contents are owned by WebKitGTK+ and
205 * should not be modified or freed.
206 *
207 * Since: 1.10
208 */
209const gchar* const* webkit_file_chooser_request_get_mime_types(WebKitFileChooserRequest* request)
210{
211    g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
212    if (request->priv->mimeTypes)
213        return reinterpret_cast<gchar**>(request->priv->mimeTypes->pdata);
214
215    FileChooserSettings settings = request->priv->chooser->settings();
216    size_t numOfMimeTypes = settings.acceptMIMETypes.size();
217    if (!numOfMimeTypes)
218        return 0;
219
220    request->priv->mimeTypes = adoptGRef(g_ptr_array_new_with_free_func(g_free));
221    for (size_t i = 0; i < numOfMimeTypes; ++i) {
222        String mimeTypeString = settings.acceptMIMETypes[i];
223        if (mimeTypeString.isEmpty())
224            continue;
225        g_ptr_array_add(request->priv->mimeTypes.get(), g_strdup(mimeTypeString.utf8().data()));
226    }
227    g_ptr_array_add(request->priv->mimeTypes.get(), 0);
228
229    return reinterpret_cast<gchar**>(request->priv->mimeTypes->pdata);
230}
231
232/**
233 * webkit_file_chooser_request_get_mime_types_filter:
234 * @request: a #WebKitFileChooserRequest
235 *
236 * Get the filter currently associated with the request, ready to be
237 * used by #GtkFileChooser. This function should normally be called
238 * before presenting the file chooser dialog to the user, to decide
239 * whether to apply a filter so the user would not be allowed to
240 * select files with other MIME types.
241 *
242 * See webkit_file_chooser_request_get_mime_types() if you are
243 * interested in getting the list of accepted MIME types.
244 *
245 * Returns: (transfer none): a #GtkFileFilter if a list of accepted
246 * MIME types is defined or %NULL otherwise. The returned object is
247 * owned by WebKitGTK+ should not be modified or freed.
248 *
249 * Since: 1.10
250 */
251GtkFileFilter* webkit_file_chooser_request_get_mime_types_filter(WebKitFileChooserRequest* request)
252{
253    g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
254    if (request->priv->filter)
255        return request->priv->filter.get();
256
257    FileChooserSettings settings = request->priv->chooser->settings();
258    size_t numOfMimeTypes = settings.acceptMIMETypes.size();
259    if (!numOfMimeTypes)
260        return 0;
261
262    // Do not use adoptGRef here, since we want to sink the floating
263    // reference for the new instance of GtkFileFilter, so we make
264    // sure we keep the ownership during the lifetime of the request.
265    request->priv->filter = gtk_file_filter_new();
266    for (size_t i = 0; i < numOfMimeTypes; ++i) {
267        String mimeTypeString = settings.acceptMIMETypes[i];
268        if (mimeTypeString.isEmpty())
269            continue;
270        gtk_file_filter_add_mime_type(request->priv->filter.get(), mimeTypeString.utf8().data());
271    }
272
273    return request->priv->filter.get();
274}
275
276/**
277 * webkit_file_chooser_request_get_select_multiple:
278 * @request: a #WebKitFileChooserRequest
279 *
280 * Determine whether the file chooser associated to this
281 * #WebKitFileChooserRequest should allow selecting multiple files,
282 * which depends on the HTML input element having a 'multiple'
283 * attribute defined.
284 *
285 * Returns: %TRUE if the file chooser should allow selecting multiple files or %FALSE otherwise.
286 *
287 * Since: 1.10
288 */
289gboolean webkit_file_chooser_request_get_select_multiple(WebKitFileChooserRequest* request)
290{
291    g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), FALSE);
292    return request->priv->chooser->settings().allowsMultipleFiles;
293}
294
295/**
296 * webkit_file_chooser_request_select_files:
297 * @request: a #WebKitFileChooserRequest
298 * @files: (array zero-terminated=1) (transfer none): a
299 * %NULL-terminated array of strings, containing paths to local files.
300 *
301 * Ask WebKit to select local files for upload and complete the
302 * request.
303 *
304 * Since: 1.10
305 */
306void webkit_file_chooser_request_select_files(WebKitFileChooserRequest* request, const gchar* const* files)
307{
308    g_return_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request));
309    g_return_if_fail(files);
310
311    Vector<String> names;
312    GRefPtr<GPtrArray> selectedFiles = adoptGRef(g_ptr_array_new_with_free_func(g_free));
313
314    for (int i = 0; files[i]; i++) {
315        names.append(filenameToString(files[i]));
316        g_ptr_array_add(selectedFiles.get(), g_strdup(files[i]));
317    }
318
319    g_ptr_array_add(selectedFiles.get(), 0);
320    request->priv->chooser->chooseFiles(names);
321    request->priv->selectedFiles = selectedFiles;
322}
323
324/**
325 * webkit_file_chooser_request_get_selected_files:
326 * @request: a #WebKitFileChooserRequest
327 *
328 * Get the list of selected files currently associated to the
329 * request. Initially, the return value of this method contains any
330 * files selected in previous file chooser requests for this HTML
331 * input element. Once webkit_file_chooser_request_select_files, the
332 * value will reflect whatever files are given.
333 *
334 * This function should normally be called only before presenting the
335 * file chooser dialog to the user, to decide whether to perform some
336 * extra action, like pre-selecting the files from a previous request.
337 *
338 * Returns: (array zero-terminated=1) (transfer none): a
339 * %NULL-terminated array of strings if there are selected files
340 * associated with the request or %NULL otherwise. This array and its
341 * contents are owned by WebKitGTK+ and should not be modified or
342 * freed.
343 *
344 * Since: 1.10
345 */
346const gchar* const* webkit_file_chooser_request_get_selected_files(WebKitFileChooserRequest* request)
347{
348    g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
349    if (request->priv->selectedFiles)
350        return reinterpret_cast<gchar**>(request->priv->selectedFiles->pdata);
351
352    FileChooserSettings settings = request->priv->chooser->settings();
353    size_t numOfFiles = settings.selectedFiles.size();
354    if (!numOfFiles)
355        return 0;
356
357    request->priv->selectedFiles = adoptGRef(g_ptr_array_new_with_free_func(g_free));
358    for (size_t i = 0; i < numOfFiles; ++i) {
359        if (settings.selectedFiles[i].isEmpty())
360            continue;
361        CString filename = fileSystemRepresentation(settings.selectedFiles[i]);
362        g_ptr_array_add(request->priv->selectedFiles.get(), g_strdup(filename.data()));
363    }
364    g_ptr_array_add(request->priv->selectedFiles.get(), 0);
365
366    return reinterpret_cast<gchar**>(request->priv->selectedFiles->pdata);
367}
368