Coverage Report

Created: 2026-02-09 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WFileUpload.h
Line
Count
Source
1
// This may look like C code, but it's really -*- C++ -*-
2
/*
3
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
4
 *
5
 * See the LICENSE file for terms of use.
6
 */
7
#ifndef WFILEUPLOAD_H_
8
#define WFILEUPLOAD_H_
9
10
#include <Wt/WJavaScriptSlot.h>
11
#include <Wt/WWebWidget.h>
12
13
namespace Wt {
14
15
/*! \class WFileUpload Wt/WFileUpload.h Wt/WFileUpload.h
16
 *  \brief A widget that allows a file to be uploaded.
17
 *
18
 * This widget is displayed as a box in which a filename can be
19
 * entered and a browse button.
20
 *
21
 * Depending on availability of JavaScript, the behaviour of the widget
22
 * is different, but the API is designed in a way which facilitates
23
 * a portable use.
24
 *
25
 * When JavaScript is available, the file will not be uploaded until
26
 * upload() is called. This will start an asynchronous upload (and
27
 * thus return immediately). \if cpp While the file is being uploaded,
28
 * the dataReceived() signal is emitted when data is being received
29
 * and if the connector library provides support (see also
30
 * WResource::setUploadProgress() for a more detailed
31
 * discussion). Although you can modify the GUI from this signal, you
32
 * still need to have a mechanism in place to update the client
33
 * interface (using a WTimer or using \link
34
 * WApplication::enableUpdates() server-push\endlink). When the file
35
 * has been uploaded, the uploaded() signal is emitted, or if the file
36
 * was too large, the fileTooLarge() signal is emitted. You may
37
 * configure a progress bar that is used to show the upload progress
38
 * using setProgressBar(). \endif
39
 *
40
 * When no JavaScript is available, the file will be uploaded with the
41
 * next click event. Thus, upload() has no effect -- the file will
42
 * already be uploaded, and the corresponding signals will already be
43
 * emitted. To test if upload() will start an upload, you may check
44
 * using the canUpload() call.
45
 *
46
 * Thus, to properly use the widget, one needs to follow these
47
 * rules:
48
 * <ul>
49
 *   <li>Be prepared to handle the uploaded() or fileTooLarge() signals
50
 *       also when upload() was not called.</li>
51
 *   <li>Check using canUpload() if upload() will schedule a new
52
 *       upload. if (!canUpload()) then upload() will not have any
53
 *       effect. if (canUpload()), upload() will start a new file upload,
54
 *       which completes succesfully using an uploaded() signal or a
55
 *       fileTooLarge() signals gets emitted.
56
 *   </li>
57
 * </ul>
58
 *
59
 * The %WFileUpload widget must be hidden or deleted when a file is
60
 * received. In addition it is wise to prevent the user from uploading
61
 * the file twice as in the example below.
62
 *
63
 * The uploaded file is automatically spooled to a local temporary
64
 * file which will be deleted together with the WFileUpload widget,
65
 * unless stealSpooledFile() is called.
66
 *
67
 * \if cpp
68
 * Usage example:
69
 * \code
70
 * Wt::WFileUpload *upload = addWidget(std::make_unique<Wt::WFileUpload>());
71
 * upload->setFileTextSize(40);
72
 *
73
 * // Provide a button
74
 * Wt::WPushButton *uploadButton =
75
 *   addWidget(std::make_unique<Wt::WPushButton>("Send"));
76
77
 * // Upload when the button is clicked.
78
 * uploadButton->clicked().connect(upload, &Wt::WFileUpload::upload);
79
 * uploadButton->clicked().connect(uploadButton, &Wt::WPushButton::disable);
80
 *
81
 * // Upload automatically when the user entered a file.
82
 * upload->changed().connect(upload, &WFileUpload::upload);
83
 * upload->changed().connect(uploadButton, &Wt::WPushButton::disable);
84
 *
85
 * // React to a succesfull upload.
86
 * upload->uploaded().connect(this, &MyWidget::fileUploaded);
87
 *
88
 * // React to a fileupload problem.
89
 * upload->fileTooLarge().connect(this, &MyWidget::fileTooLarge);
90
 * \endcode
91
 * \endif
92
 *
93
 * %WFileUpload is an \link WWidget::setInline(bool) inline \endlink widget.
94
 *
95
 * <h3>CSS</h3>
96
 *
97
 * The file upload itself corresponds to a <tt>&lt;input
98
 * type="file"&gt;</tt> tag, but may be wrapped in a
99
 * <tt>&lt;form&gt;</tt> tag for an Ajax session to implement the
100
 * asynchronous upload action. This widget does not provide styling,
101
 * and styling through CSS is not well supported across browsers.
102
 */
103
class WT_API WFileUpload : public WWebWidget
104
{
105
public:
106
  /*! \brief Creates a file upload widget
107
   */
108
  WFileUpload();
109
110
  ~WFileUpload();
111
112
  /*! \brief Sets whether the file upload accepts multiple files.
113
   *
114
   * In browsers which support the "multiple" attribute for the file
115
   * upload (to be part of HTML5) control, this will allow the user to
116
   * select multiple files at once.
117
   *
118
   * All uploaded files are available from uploadedFiles(). The
119
   * single-file API will return only information on the first
120
   * uploaded file.
121
   *
122
   * The default value is \c false.
123
   */
124
  void setMultiple(bool multiple);
125
126
  /*! \brief Returns whether multiple files can be uploaded.
127
   *
128
   * \sa setMultiple()
129
   */
130
0
  bool multiple() const { return flags_.test(BIT_MULTIPLE); }
131
132
  /*! \brief Sets the size of the file input.
133
   */
134
  void setFileTextSize(int chars);
135
136
  /*! \brief Returns the size of the file input.
137
   */
138
0
  int fileTextSize() const { return textSize_; }
139
140
  /*! \brief Returns the spooled location of the uploaded file.
141
   *
142
   * Returns the temporary filename in which the uploaded file was
143
   * spooled. The file is guaranteed to exist as long as the
144
   * WFileUpload widget is not deleted, or a new file is not uploaded.
145
   *
146
   * When multiple files were uploaded, this returns the information
147
   * from the first file.
148
   *
149
   * \sa stealSpooledFile()
150
   * \sa uploaded
151
   */
152
  std::string spoolFileName() const;
153
154
  /*! \brief Returns the client filename.
155
   *
156
   * When multiple files were uploaded, this returns the information
157
   * from the first file.
158
   *
159
   * \note Depending on the browser this is an absolute path
160
   *       or only the file name.
161
   */
162
  WT_USTRING clientFileName() const;
163
164
  /*! \brief Returns the client content description.
165
   *
166
   * When multiple files were uploaded, this returns the information
167
   * from the first file.
168
   */
169
  WT_USTRING contentDescription() const;
170
171
  /*! \brief Steals the spooled file.
172
   *
173
   * By stealing the file, the spooled file will no longer be deleted
174
   * together with this widget, which means you need to take care of
175
   * managing that.
176
   *
177
   * When multiple files were uploaded, this returns the information
178
   * from the first file.
179
   */
180
  void stealSpooledFile();
181
182
  /*! \brief Returns whether one or more files have been uploaded.
183
   */
184
  bool empty() const;
185
186
  /*! \brief Returns the uploaded files.
187
   */
188
  const std::vector<Http::UploadedFile>& uploadedFiles() const
189
0
    { return uploadedFiles_; }
190
191
  /*! \brief Returns whether upload() will start a new file upload.
192
   *
193
   * A call to upload() will only start a new file upload if there is
194
   * no JavaScript support. Otherwise, the most recent file will
195
   * already be uploaded.
196
   */
197
0
  bool canUpload() const { return fileUploadTarget_ != nullptr; }
198
199
  /*! \brief Use the click signal of another widget to open the file picker.
200
   *
201
   * This hides the default WFileUpload widget and uses the click-signal of the argument to
202
   * open the file picker. The upload logic is still handled by WFileUpload behind the scenes.
203
   * This action cannot be undone.
204
   *
205
   * WFileUpload does not take ownership of the widget, nor does it display it. You must still
206
   * place it in the widget tree yourself.
207
   */
208
  void setDisplayWidget(WInteractWidget *widget);
209
210
  /*! \brief %Signal emitted when a new file was uploaded.
211
   *
212
   * This signal is emitted when file upload has been completed.  It
213
   * is good practice to hide or delete the WFileUpload widget when a
214
   * file has been uploaded succesfully.
215
   *
216
   * \sa upload()
217
   * \sa fileTooLarge()
218
   */
219
  EventSignal<>& uploaded();
220
221
  /*! \brief %Signal emitted when the user tried to upload a too large file.
222
   *
223
   * The parameter is the (approximate) size of the file (in bytes) the user
224
   * tried to upload.
225
   *
226
   * The maximum file size is determined by the maximum request size,
227
   * which may be configured in the configuration file (<max-request-size>).
228
   *
229
   * \sa uploaded()
230
   * \sa WApplication::requestTooLarge()
231
   */
232
0
  JSignal< ::int64_t>& fileTooLarge() { return fileTooLarge_; }
233
234
  /*! \brief %Signal emitted when the user selected a new file.
235
   *
236
   * One could react on the user selecting a (new) file, by uploading
237
   * the file immediately.
238
   *
239
   * Caveat: this signal is not emitted with konqueror and possibly
240
   * other browsers. Thus, in the above scenario you should still provide
241
   * an alternative way to call the upload() method.
242
   */
243
  EventSignal<>& changed();
244
245
  /*! \brief Starts the file upload.
246
   *
247
   * The uploaded() signal is emitted when a file is uploaded, or the
248
   * fileTooLarge() signal is emitted when the file size exceeded the
249
   * maximum request size.
250
   *
251
   * \sa uploaded()
252
   * \sa canUpload()
253
   */
254
  void upload();
255
256
  /*! \brief Sets a progress bar to indicate upload progress.
257
   *
258
   * When the file is being uploaded, upload progress is indicated
259
   * using the provided progress bar. Both the progress bar range and
260
   * values are configured when the upload starts.
261
   *
262
   * The file upload itself is hidden as soon as the upload starts.
263
   *
264
   * The default progress bar is 0 (no upload progress is indicated).
265
   *
266
   * \if java
267
   * To update the progess bar server push is used, you should only
268
   * use this functionality when using a Servlet 3.0 compatible servlet
269
   * container.
270
   * \endif
271
   *
272
   * \sa dataReceived()
273
   */
274
  void setProgressBar(WProgressBar *progressBar);
275
276
  /*! \brief Sets a progress bar to indicate upload progress.
277
   *
278
   * When the file is being uploaded, upload progress is indicated
279
   * using the provided progress bar. Both the progress bar range and
280
   * values are configured when the upload starts.
281
   *
282
   * The bar becomes part of the file upload, and replaces the file
283
   * prompt when the upload is started.
284
   *
285
   * The default progress bar is 0 (no upload progress is indicated).
286
   *
287
   * \if java
288
   * To update the progess bar server push is used, you should only
289
   * use this functionality when using a Servlet 3.0 compatible servlet
290
   * container.
291
   * \endif
292
   *
293
   * \sa dataReceived()
294
   */
295
  void setProgressBar(std::unique_ptr<WProgressBar> progressBar);
296
297
  /*! \brief Returns the progress bar.
298
   *
299
   * \sa setProgressBar()
300
   */
301
0
  WProgressBar *progressBar() const { return progressBar_; }
302
303
  /*! \brief %Signal emitted while a file is being uploaded.
304
   *
305
   * When supported by the connector library, you can track the
306
   * progress of the file upload by listening to this signal.
307
   *
308
   * The first argument is the number of bytes received so far,
309
   * and the second argument is the total number of bytes.
310
   */
311
0
  Signal< ::uint64_t, ::uint64_t>& dataReceived() { return dataReceived_; }
312
313
  virtual void enableAjax() override;
314
315
  ///
316
  /// \brief Sets input accept attributes
317
  ///
318
  /// The accept attribute may be specified to provide user agents with a hint
319
  /// of what file types will be accepted.
320
  /// Use html input accept attributes as input.
321
  ///
322
  /// \code
323
  /// WFileUpload *fu = new WFileUpload(root());
324
  /// fu->setFilters("image/*");
325
  /// \endcode
326
  ///
327
  void setFilters(const std::string& acceptAttributes);
328
329
private:
330
  static const char *CHANGE_SIGNAL;
331
  static const char *UPLOADED_SIGNAL;
332
333
  static const int BIT_DO_UPLOAD                = 0;
334
  static const int BIT_ENABLE_AJAX              = 1;
335
  static const int BIT_UPLOADING                = 2;
336
  static const int BIT_MULTIPLE                 = 3;
337
  static const int BIT_ENABLED_CHANGED          = 4;
338
  static const int BIT_ACCEPT_ATTRIBUTE_CHANGED = 5;
339
  static const int BIT_USE_DISPLAY_WIDGET       = 6;
340
341
  std::bitset<7> flags_;
342
343
  int textSize_;
344
345
  std::vector<Http::UploadedFile> uploadedFiles_;
346
347
  JSignal< ::int64_t> fileTooLarge_;
348
349
  Signal< ::uint64_t, ::uint64_t> dataReceived_;
350
351
  Core::observing_ptr<WInteractWidget> displayWidget_;
352
  JSlot displayWidgetRedirect_;
353
354
  std::unique_ptr<WResource> fileUploadTarget_;
355
  std::unique_ptr<WProgressBar> containedProgressBar_;
356
  WProgressBar *progressBar_;
357
358
  std::string acceptAttributes_;
359
  void create();
360
361
  void onData(::uint64_t current, ::uint64_t total);
362
  void onDataExceeded(::uint64_t dataExceeded);
363
364
  std::string displayWidgetClickJS();
365
366
  virtual void setRequestTooLarge(::int64_t size) override;
367
368
protected:
369
  virtual void           updateDom(DomElement& element, bool all) override;
370
  virtual DomElement    *createDomElement(WApplication *app) override;
371
  virtual DomElementType domElementType() const override;
372
  virtual void           propagateRenderOk(bool deep) override;
373
  virtual void           getDomChanges(std::vector<DomElement *>& result,
374
                                       WApplication *app) override;
375
  virtual void propagateSetEnabled(bool enabled) override;
376
  virtual std::string renderRemoveJs(bool recursive) override;
377
378
private:
379
  void handleFileTooLarge(::int64_t fileSize);
380
381
  void onUploaded();
382
383
  virtual void setFormData(const FormData& formData) override;
384
  void setFiles(const std::vector<Http::UploadedFile>& files);
385
386
  friend class WFileUploadResource;
387
};
388
389
}
390
391
#endif // WFILEUPLOAD_H_