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    */
multiple()130   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    */
fileTextSize()138   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    */
uploadedFiles()188   const std::vector<Http::UploadedFile>& uploadedFiles() const
189     { 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    */
canUpload()197   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    */
fileTooLarge()232   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    */
progressBar()301   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    */
dataReceived()311   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_
392