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