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