From 601fad02cca850754036735c34ddb1908aa7be2d Mon Sep 17 00:00:00 2001 From: Roel Standaert Date: Wed, 1 Feb 2017 14:49:10 +0100 Subject: [PATCH] Several changes: - Release notes for Wt 3.3.7, bump version - Respond with 505 to strange versions, and only with HTTP/1.0 or HTTP/1.1 - WAxis constructor should be public - Chart series selection: only select closest series if it is within 20px of the given point - Added touch drag documentation - Document new methods in WTimeEdit - Install instructions: set minimum Boost to the lowest version we test with - WFileDropWidget: use a different WResource for each upload, so we can't get a dataReceived() signal from a canceled upload, fixed cancel race condition - added PDF mimetype - fix unorthodox use of request to forward dataExceeded info broken by async handling in websession event queue - Fixed progressive bootstrap for WFileDropWidget --- CMakeLists.txt | 26 +++++------ Doxyfile | 2 +- INSTALL | 38 ++++++++-------- INSTALL.html | 2 +- INSTALL.win32.html | 2 +- ReleaseNotes.html | 82 ++++++++++++++++++++++++++++++++++ examples/Doxyfile | 2 +- src/Wt/Chart/WAxis | 6 ++- src/Wt/Chart/WCartesianChart | 2 +- src/Wt/Chart/WCartesianChart.C | 32 +++++++++---- src/Wt/WFileDropWidget | 5 +++ src/Wt/WFileDropWidget.C | 76 +++++++++++++++++++++++++------ src/Wt/WFileUpload | 1 + src/Wt/WFileUpload.C | 35 +++++++-------- src/Wt/WInteractWidget | 3 ++ src/Wt/WPaintedWidget.C | 15 +++---- src/Wt/WResource | 3 ++ src/Wt/WTimeEdit | 27 +++++++++++ src/http/MimeTypes.C | 1 + src/http/Reply.C | 14 +++--- src/http/Reply.h | 3 +- src/http/Request.h | 2 + src/http/RequestHandler.C | 2 +- src/http/StockReply.C | 10 +++++ src/js/WFileDropWidget.js | 14 +++--- src/js/WFileDropWidget.min.js | 12 ++--- src/web/WebController.C | 10 +++-- 27 files changed, 313 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80150beed5..e7fdbd488f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,19 +18,19 @@ SET(CMAKE_MODULE_PATH SET(VERSION_SERIES 3) SET(VERSION_MAJOR 3) -SET(VERSION_MINOR 6) - -SET(WT_SOVERSION 40) -SET(WTEXT_SOVERSION 40) -SET(WTHTTP_SOVERSION 40) -SET(WTFCGI_SOVERSION 40) -SET(WTISAPI_SOVERSION 16) -SET(WTDBO_SOVERSION 40) -SET(WTDBOSQLITE3_SOVERSION 40) -SET(WTDBOPOSTGRES_SOVERSION 40) -SET(WTDBOFIREBIRD_SOVERSION 40) -SET(WTDBOMYSQL_SOVERSION 40) -SET(WTTEST_SOVERSION 10) +SET(VERSION_MINOR 7) + +SET(WT_SOVERSION 41) +SET(WTEXT_SOVERSION 41) +SET(WTHTTP_SOVERSION 41) +SET(WTFCGI_SOVERSION 41) +SET(WTISAPI_SOVERSION 17) +SET(WTDBO_SOVERSION 41) +SET(WTDBOSQLITE3_SOVERSION 41) +SET(WTDBOPOSTGRES_SOVERSION 41) +SET(WTDBOFIREBIRD_SOVERSION 41) +SET(WTDBOMYSQL_SOVERSION 41) +SET(WTTEST_SOVERSION 11) IF(NOT SHARED_LIBS) IF(WIN32) diff --git a/Doxyfile b/Doxyfile index 795e282ab5..649946ce42 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = Wt # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 3.3.6 +PROJECT_NUMBER = 3.3.7 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer diff --git a/INSTALL b/INSTALL index eeb55cbbc6..9ee787fc9f 100644 --- a/INSTALL +++ b/INSTALL @@ -14,12 +14,10 @@ Requirements you can have both of them. The built-in web server is more convenient during development and is - easier to setup. + easier to setup. It also allows you to use WebSockets. - The FastCGI based solution provides more flexibility for deployment of - the application. The built-in web server runs all sessions in a single - process, while the FastCGI based solution allows different deployment - schemes including dedicated processes per sessions. + The FastCGI based solution can be more convenient for deployment behind + another web server. Each of these two choices correspond to a library, a so-called connector library. Below it is outlined how to configure the build @@ -38,7 +36,7 @@ Requirements Preferably CMake 2.6, which comes with a usable script for finding boost libraries, but CMake 2.4 is still supported using Wt's own boost find script. - * [5]C++ boost library (preferably version 1.41 or higher), with or + * [5]C++ boost library (preferably version 1.46.1 or higher), with or without thread support. You can verify you have a thread-enabled boost installation by locating the libboost_thread library. Thread support is not essential: Wt functionality is not affected except @@ -49,10 +47,8 @@ Requirements features will be disabled that depend on the revised versions of spirit, namely JSON parsing and improved SQL parsing (for Wt::Dbo). * Optionally, [6]OpenSSL, which is used to support the HTTPS protocol - in the web client, the HTTPS protocol in the built-in wthttpd - connector, additional cryptographic hash functions in the Auth - library, and WebSockets (which requires a SHA-1 hash - implementation). + in the web client, and the HTTPS protocol in the built-in wthttpd + connector. * Optionally, [7]Haru Free PDF Library, which is used to provide support for painting to PDF (WPdfImage). * Optionally, [8]GraphicsMagick, for supporting painting to raster @@ -80,8 +76,7 @@ Requirements to enable optional features (you can also build without them), but otherwise no extra dependencies are required. * Optionally, zlib (libz), for compression over HTTP. - * Optionally, OpenSSL (libopenssl), for HTTPS and WebSockets (which - requires a SHA-1 hash implementation). + * Optionally, OpenSSL (libopenssl), for HTTPS. 2 Additional and optional requirements for some of the examples @@ -124,10 +119,14 @@ Building and installing the Wt library may help CMake by setting some variables to help CMake locate the libraries. This may be done on the command-line using -Dvar=value or using the interactive program: - $ ccmake . + $ ccmake ../ - Variables that you may set to configure Wt's built-in boost finding - method: + or + $ cmake-gui ../ + + The GUI lists all variables that are configurable in Wt's build + process. Variables that you may set to configure Wt's built-in boost + finding method: BOOST_COMPILER The boost compiler signature. For a library @@ -142,7 +141,8 @@ Building and installing the Wt library lib/ and include/ are located for your boost installation. Other variables specify several build and configuration aspects of Wt, - of which the most relevant ones are: + of which the most relevant ones are (there are many more visible in the + GUI): CMAKE_INSTALL_PREFIX Installation prefix for the library and include files) @@ -333,9 +333,9 @@ HTTPS server options: References - 1. file://localhost/home/koen/project/wt/git/wt/INSTALL.html#requirements - 2. file://localhost/home/koen/project/wt/git/wt/INSTALL.html#build - 3. file://localhost/home/koen/project/wt/git/wt/INSTALL.html#examples + 1. file:///home/roel/project/wt/git/wt/INSTALL.html#requirements + 2. file:///home/roel/project/wt/git/wt/INSTALL.html#build + 3. file:///home/roel/project/wt/git/wt/INSTALL.html#examples 4. http://www.cmake.org/ 5. http://www.boost.org/ 6. http://www.openssl.org/ diff --git a/INSTALL.html b/INSTALL.html index aa07c17dd7..0a73d76f11 100644 --- a/INSTALL.html +++ b/INSTALL.html @@ -57,7 +57,7 @@

1 Wt requirements

  • C++ boost library (preferably - version 1.41 or higher), with or without thread support. You can + version 1.46.1 or higher), with or without thread support. You can verify you have a thread-enabled boost installation by locating the libboost_thread library. Thread support is not essential: Wt functionality is not affected except for exotic things like server diff --git a/INSTALL.win32.html b/INSTALL.win32.html index 1ffa9c2c38..6d860d5a61 100644 --- a/INSTALL.win32.html +++ b/INSTALL.win32.html @@ -30,7 +30,7 @@

    Requirements

    build on the Express Edition, which is free (as in beer) to use.
  • CMake cross-platform build system (www.cmake.org): cmake-2.6.x, Windows version (2.8 or newer recommended).
  • -
  • Boost 1.36 (or later; a recent version is recommended)
  • +
  • Boost 1.46.1 (or later; a recent version is recommended)
  • diff --git a/ReleaseNotes.html b/ReleaseNotes.html index 1648c2d2e9..d509399dbc 100644 --- a/ReleaseNotes.html +++ b/ReleaseNotes.html @@ -22,6 +22,88 @@

    Wt Release notes

    the way you build Wt, the way you configure Wt or the Wt API and behaviour. +

    Release 3.3.7 (February ?, 2017)

    +

    + This release fixes many bugs, but also introduces some new features: +

    +
    +
    + WFileDropWidget +
    +
    + The WFileDropWidget is a new widget that allows you to upload a file or multiple files by dragging them onto an area. +
    +
    + Scroll visibility +
    +
    + Some applications require you to know whether a widget is currently visible within the viewport, or whether it is + scrolled out of view, e.g. to load more content as you scroll down the page. You can now enable scroll visibility + detection with + WWidget::setScrollVisibilityEnabled(bool), + and react to changes in visibility with + WWidget::scrollVisibilityChanged(). + A new scrollvisibility feature example has been added to demonstrate this infinite scrolling application. +
    +
    + Touch events +
    +
    + Although Wt already supported touch interactions in the charting library, touch events were previously + not exposed by Wt. Now, + we've added WTouchEvent, and the touchStarted, touchEnded, and touchMoved + events have been added to + WInteractWidget. + Also, draggable widgets can now also + be dragged after a long press, and you can select a range using a double touch in + WTableView and WTreeView. +
    +
    + Combined session tracking mode +
    +
    +

    + The default session tracking method for Wt is URL rewriting, using JavaScript to hide the session id from + the address bar. Alternatively, cookies can be used with the Auto option, falling back to URL rewriting when + cookies are not available. However, the cookie-based method did not allow for multiple sessions within the + same browser. +

    +

    + In order to make the URL rewriting method with requirement 6.5.10 of the PCI Data Security Standard, while not + sacrificing the ability to have multiple sessions, a new Combined session tracking strategy has been added. + Wt already makes it difficult to steal a session when the session id is discovered, but resources are not as + protected. The Combined session tracking strategy uses URL rewriting in combination with a cookie that is shared + between sessions as an extra measure against session hijacking. This is the most secure strategy, but it will + deny access if cookie support is not available. +

    +
    +
    + Wraparound for WSpinBox and WTimeEdit +
    +
    + WSpinBox will now wrap around from its maximum to its minimum if you enable wraparound. WTimeEdit will take advantage of this feature by default. +
    +
    + Some minor extra features: +
    +
    +
      +
    • + It's now possible to retrieve a vector of all request headers with Wt::Http::Request::headers() in handleRequest when implementing a WResource. It is still recommended, and more efficient, to use headerValue, but retrieving a vector of all headers could be useful for debugging purposes. +
    • +
    • + In an effort to reduce the amount of JavaScript generated by the charting API, the + WPainter::drawStencilAlongPath() + method was added to WPainter. +
    • +
    • + Previously, WDialogs were movable by default. It's now possible to disable this with + WDialog::setMovable(). +
    • +
    +
    +
    +

    Release 3.3.6 (July 13, 2016)

    This release has a focus on bug fixes and some new features: diff --git a/examples/Doxyfile b/examples/Doxyfile index 8f0ca606ee..5190763e71 100644 --- a/examples/Doxyfile +++ b/examples/Doxyfile @@ -4,7 +4,7 @@ # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = "Wt examples" -PROJECT_NUMBER = 3.3.6 +PROJECT_NUMBER = 3.3.7 OUTPUT_DIRECTORY = ../doc/examples CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English diff --git a/src/Wt/Chart/WAxis b/src/Wt/Chart/WAxis index ae8c79c18b..665885e4cd 100644 --- a/src/Wt/Chart/WAxis +++ b/src/Wt/Chart/WAxis @@ -188,6 +188,10 @@ public: */ static const double AUTO_MAXIMUM; + /*! \brief Constructor + */ + WAxis(); + /*! \brief Destructor */ virtual ~WAxis(); @@ -970,8 +974,6 @@ protected: TickLabel(double v, TickLength length, const WString& l = WString()); }; - WAxis(); - /*! \brief Returns the label (and ticks) information for this axis. */ virtual void getLabelTicks(std::vector& ticks, int segment, AxisConfig config) const; diff --git a/src/Wt/Chart/WCartesianChart b/src/Wt/Chart/WCartesianChart index d8f62be1d5..36025ad833 100644 --- a/src/Wt/Chart/WCartesianChart +++ b/src/Wt/Chart/WCartesianChart @@ -1194,7 +1194,7 @@ public: /*! \brief A signal that notifies the selection of a new curve. * * This signal is emitted if a series is selected using a mouse - * click or long press. The first argument is the model column of the selected + * click or long press. The first argument is the selected * series. The second argument is the point that was selected, in model * coordinates. * diff --git a/src/Wt/Chart/WCartesianChart.C b/src/Wt/Chart/WCartesianChart.C index 45f68e2271..e523709c8f 100644 --- a/src/Wt/Chart/WCartesianChart.C +++ b/src/Wt/Chart/WCartesianChart.C @@ -46,6 +46,7 @@ namespace { const int TICK_LENGTH = 5; const int CURVE_LABEL_PADDING = 10; const int DEFAULT_CURVE_LABEL_WIDTH = 100; +const int CURVE_SELECTION_DISTANCE_SQUARED = 400; // Maximum selection distance in pixels, squared inline int toZoomLevel(double zoomFactor) { @@ -4064,11 +4065,14 @@ void WCartesianChart::yTransformChanged() void WCartesianChart::jsSeriesSelected(double x, double y) { - if (!seriesSelectionEnabled()) return; - WPointF p = zoomRangeTransform(xTransformHandle_.value(), yTransformHandle_.value()).inverted().map(WPointF(x,y)); + if (!seriesSelectionEnabled()) + return; + WTransform transform = zoomRangeTransform(xTransformHandle_.value(), yTransformHandle_.value()); + WPointF p = transform.inverted().map(WPointF(x,y)); double smallestSqDistance = std::numeric_limits::infinity(); const WDataSeries *closestSeries = 0; - WPointF closestPoint; + WPointF closestPointPx; + WPointF closestPointBeforeSeriesTransform; for (std::size_t i = 0; i < series_.size(); ++i) { const WDataSeries &series = *series_[i]; if (!series.isHidden() && (series.type() == LineSeries || series.type() == CurveSeries)) { @@ -4082,22 +4086,32 @@ void WCartesianChart::jsSeriesSelected(double x, double y) WPointF segP = t.map(WPointF(seg.x(), seg.y())); double dx = p.x() - segP.x(); double dy = p.y() - segP.y(); - double d = dx * dx + dy * dy; - if (d < smallestSqDistance) { - smallestSqDistance = d; + double d2 = dx * dx + dy * dy; + if (d2 < smallestSqDistance) { + smallestSqDistance = d2; closestSeries = &series; - closestPoint = p; + closestPointPx = segP; + closestPointBeforeSeriesTransform = WPointF(seg.x(), seg.y()); } } } } } + { + WPointF closestDisplayPoint = transform.map(closestPointPx); + double dx = closestDisplayPoint.x() - x; + double dy = closestDisplayPoint.y() - y; + double d2 = dx * dx + dy * dy; + if (d2 > CURVE_SELECTION_DISTANCE_SQUARED) { + return; + } + } setSelectedSeries(closestSeries); if (closestSeries) { seriesSelected_.emit(closestSeries, - mapFromDeviceWithoutTransform(closestPoint, closestSeries->axis())); + mapFromDeviceWithoutTransform(closestPointBeforeSeriesTransform, closestSeries->axis())); } else { - seriesSelected_.emit(0, mapFromDeviceWithoutTransform(closestPoint, YAxis)); + seriesSelected_.emit(0, mapFromDeviceWithoutTransform(closestPointBeforeSeriesTransform, YAxis)); } } diff --git a/src/Wt/WFileDropWidget b/src/Wt/WFileDropWidget index df459e70a4..8220183219 100644 --- a/src/Wt/WFileDropWidget +++ b/src/Wt/WFileDropWidget @@ -178,6 +178,9 @@ public: */ Signal& uploadFailed() { return uploadFailed_; } +protected: + virtual void enableAjax() WT_CXX11ONLY(override); + private: class WFileDropUploadResource : public WResource { public: @@ -194,12 +197,14 @@ private: File *currentFile_; }; + void setup(); void handleDrop(const std::string& newDrops); void handleTooLarge(::uint64_t size); void handleSendRequest(int id); void emitUploaded(int id); void stopReceiving(); void onData(::uint64_t current, ::uint64_t total); + void onDataExceeded(::uint64_t dataExceeded); // Functions for handling incoming requests void setUploadedFile(Http::UploadedFile file); diff --git a/src/Wt/WFileDropWidget.C b/src/Wt/WFileDropWidget.C index dd682579c4..1d849e6d34 100644 --- a/src/Wt/WFileDropWidget.C +++ b/src/Wt/WFileDropWidget.C @@ -104,7 +104,7 @@ void NestedResource::handleRequest(const Http::Request& request, WFileDropWidget::WFileDropWidget(WContainerWidget *parent) : WContainerWidget(parent), - resource_(new WFileDropUploadResource(this)), + resource_(0), currentFileIdx_(0), dropSignal_(this, "dropsignal"), requestSend_(this, "requestsend"), @@ -116,15 +116,26 @@ WFileDropWidget::WFileDropWidget(WContainerWidget *parent) if (!app->environment().ajax()) return; + setup(); +} + +void WFileDropWidget::enableAjax() +{ + setup(); +} + +void WFileDropWidget::setup() +{ + WApplication *app = WApplication::instance(); + LOAD_JAVASCRIPT(app, "js/WFileDropWidget.js", "WFileDropWidget", wtjs1); std::string maxFileSize = boost::lexical_cast( WApplication::instance()->maximumRequestSize()); setJavaScriptMember(" WFileDropWidget", "new " WT_CLASS ".WFileDropWidget(" - + app->javaScriptClass() + "," + jsRef() + ",'" - + resource_->generateUrl() + "', " - + maxFileSize + ");"); + + app->javaScriptClass() + "," + jsRef() + "," + + maxFileSize + ");"); dropSignal_.connect(this, &WFileDropWidget::handleDrop); @@ -133,8 +144,6 @@ WFileDropWidget::WFileDropWidget(WContainerWidget *parent) uploadFinished_.connect(this, &WFileDropWidget::emitUploaded); doneSending_.connect(this, &WFileDropWidget::stopReceiving); - resource_->dataReceived().connect(this, &WFileDropWidget::onData); - addStyleClass("Wt-filedropzone"); } @@ -200,10 +209,15 @@ void WFileDropWidget::handleSendRequest(int id) if (uploads_[i]->uploadId() == id) { fileFound = true; currentFileIdx_ = i; - doJavaScript(jsRef() + ".send();"); + delete resource_; + resource_ = new WFileDropUploadResource(this); + resource_->dataReceived().connect(this, &WFileDropWidget::onData); + resource_->dataExceeded().connect(this, &WFileDropWidget::onDataExceeded); + doJavaScript(jsRef() + ".send('" + resource_->url() + "');"); uploadStart_.emit(uploads_[currentFileIdx_]); break; } else { + // If a previous upload was not cancelled, it must have failed if (!uploads_[i]->cancelled()) uploadFailed_.emit(uploads_[i]); } @@ -219,6 +233,12 @@ void WFileDropWidget::handleSendRequest(int id) void WFileDropWidget::handleTooLarge(::uint64_t size) { + if (currentFileIdx_ >= uploads_.size()) { + // This shouldn't happen, but a mischievous client might emit + // this signal a few times, causing currentFileIdx_ + // to go out of bounds + return; + } tooLarge_.emit(uploads_[currentFileIdx_], size); currentFileIdx_++; } @@ -240,6 +260,13 @@ void WFileDropWidget::stopReceiving() // Note: args by value, since this is handled after handleRequest is finished void WFileDropWidget::setUploadedFile(Http::UploadedFile file) { + if (currentFileIdx_ >= uploads_.size()) { + // This shouldn't happen, but a mischievous client might emit + // the filetoolarge signal too many times, causing currentFileIdx_ + // to go out of bounds + return; + } + File *f = uploads_[currentFileIdx_]; currentFileIdx_++; @@ -255,7 +282,7 @@ void WFileDropWidget::setUploadedFile(Http::UploadedFile file) void WFileDropWidget::emitUploaded(int id) { - for (unsigned i=0; i < currentFileIdx_; i++) { + for (unsigned i=0; i < currentFileIdx_ && i < uploads_.size(); i++) { File *f = uploads_[i]; if (f->uploadId() == id) { f->uploaded().emit(); @@ -266,6 +293,12 @@ void WFileDropWidget::emitUploaded(int id) bool WFileDropWidget::incomingIdCheck(int id) { + if (currentFileIdx_ >= uploads_.size()) { + // This shouldn't happen, but a mischievous client might emit + // the filetoolarge signal too many times, causing currentFileIdx_ + // to go out of bounds + return false; + } if (uploads_[currentFileIdx_]->uploadId() == id) return true; else { @@ -283,7 +316,7 @@ void WFileDropWidget::cancelUpload(File *file) bool WFileDropWidget::remove(File *file) { - for (unsigned i=0; i < currentFileIdx_; i++) { + for (unsigned i=0; i < currentFileIdx_ && i < uploads_.size(); i++) { if (uploads_[i] == file) { uploads_.erase(uploads_.begin()+i); currentFileIdx_--; @@ -295,17 +328,32 @@ bool WFileDropWidget::remove(File *file) void WFileDropWidget::onData(::uint64_t current, ::uint64_t total) { - WebSession::Handler *h = WebSession::Handler::instance(); - - ::int64_t dataExceeded = h->request()->postDataExceeded(); - h->setRequest(0, 0); // so that triggerUpdate() will work - + if (currentFileIdx_ >= uploads_.size()) { + // This shouldn't happen, but a mischievous client might emit + // the filetoolarge signal too many times, causing currentFileIdx_ + // to go out of bounds + return; + } File *file = uploads_[currentFileIdx_]; file->dataReceived().emit(current, total); WApplication::instance()->triggerUpdate(); } +void WFileDropWidget::onDataExceeded(::uint64_t dataExceeded) +{ + if (currentFileIdx_ >= uploads_.size()) { + // This shouldn't happen, but a mischievous client might emit + // the filetoolarge signal too many times, causing currentFileIdx_ + // to go out of bounds + return; + } + tooLarge_.emit(uploads_[currentFileIdx_], dataExceeded); + + WApplication *app = WApplication::instance(); + app->triggerUpdate(); +} + void WFileDropWidget::setHoverStyleClass(const std::string& className) { doJavaScript(jsRef() + ".configureHoverClass('" + className + "');"); diff --git a/src/Wt/WFileUpload b/src/Wt/WFileUpload index eaabca8f3d..76019470b0 100644 --- a/src/Wt/WFileUpload +++ b/src/Wt/WFileUpload @@ -342,6 +342,7 @@ private: void create(); void onData(::uint64_t current, ::uint64_t total); + void onDataExceeded(::uint64_t dataExceeded); virtual void setRequestTooLarge(::int64_t size); diff --git a/src/Wt/WFileUpload.C b/src/Wt/WFileUpload.C index a0321d85ad..e581d630ee 100644 --- a/src/Wt/WFileUpload.C +++ b/src/Wt/WFileUpload.C @@ -146,6 +146,7 @@ void WFileUpload::create() fileUploadTarget_ = new WFileUploadResource(this); fileUploadTarget_->setUploadProgress(true); fileUploadTarget_->dataReceived().connect(this, &WFileUpload::onData); + fileUploadTarget_->dataExceeded().connect(this, &WFileUpload::onDataExceeded); setJavaScriptMember(WT_RESIZE_JS, "function(self, w, h) {" @@ -179,26 +180,6 @@ void WFileUpload::onData(::uint64_t current, ::uint64_t total) { dataReceived_.emit(current, total); - WebSession::Handler *h = WebSession::Handler::instance(); - - ::int64_t dataExceeded = h->request()->postDataExceeded(); - h->setRequest(0, 0); // so that triggerUpdate() will work - - if (dataExceeded) { - doJavaScript(WT_CLASS ".$('if" + id() + "').src='" - + fileUploadTarget_->url() + "';"); - if (flags_.test(BIT_UPLOADING)) { - flags_.reset(BIT_UPLOADING); - handleFileTooLarge(dataExceeded); - - WApplication *app = WApplication::instance(); - app->triggerUpdate(); - app->enableUpdates(false); - } - - return; - } - if (progressBar_ && flags_.test(BIT_UPLOADING)) { progressBar_->setRange(0, (double)total); progressBar_->setValue((double)current); @@ -208,6 +189,20 @@ void WFileUpload::onData(::uint64_t current, ::uint64_t total) } } +void WFileUpload::onDataExceeded(::uint64_t dataExceeded) +{ + doJavaScript(WT_CLASS ".$('if" + id() + "').src='" + + fileUploadTarget_->url() + "';"); + if (flags_.test(BIT_UPLOADING)) { + flags_.reset(BIT_UPLOADING); + handleFileTooLarge(dataExceeded); + + WApplication *app = WApplication::instance(); + app->triggerUpdate(); + app->enableUpdates(false); + } +} + void WFileUpload::enableAjax() { create(); diff --git a/src/Wt/WInteractWidget b/src/Wt/WInteractWidget index ce72fb35a6..de62eb5906 100644 --- a/src/Wt/WInteractWidget +++ b/src/Wt/WInteractWidget @@ -310,6 +310,9 @@ public: * The widget to be identified as source in the dropEvent may be given * explicitly, and will default to this widget otherwise. * + * When using a touch interface, the widget can also be dragged after + * a long press. + * * \note When JavaScript is disabled, drag&drop does not work. * * \sa WWidget::dropEvent(), WWidget::acceptDrops(), WDropEvent diff --git a/src/Wt/WPaintedWidget.C b/src/Wt/WPaintedWidget.C index 9668811ef1..422054f3ec 100644 --- a/src/Wt/WPaintedWidget.C +++ b/src/Wt/WPaintedWidget.C @@ -204,16 +204,15 @@ void WPaintedWidget::defineJavaScript() if (getMethod() == HtmlCanvas) { LOAD_JAVASCRIPT(app, "js/WPaintedWidget.js", "WPaintedWidget", wtjs10); LOAD_JAVASCRIPT(app, "js/WPaintedWidget.js", "gfxUtils", wtjs11); - } - - if (app && getMethod() == HtmlCanvas && jsObjects_.size() > 0) { - setFormObject(true); + if (jsObjects_.size() > 0) { + setFormObject(true); - LOAD_JAVASCRIPT(app, "js/WJavaScriptObjectStorage.js", "WJavaScriptObjectStorage", wtjs20); + LOAD_JAVASCRIPT(app, "js/WJavaScriptObjectStorage.js", "WJavaScriptObjectStorage", wtjs20); - jsDefined_ = true; - } else { - jsDefined_ = false; + jsDefined_ = true; + } else { + jsDefined_ = false; + } } } diff --git a/src/Wt/WResource b/src/Wt/WResource index 1ac8ab34b6..dc4a08fab7 100644 --- a/src/Wt/WResource +++ b/src/Wt/WResource @@ -328,6 +328,8 @@ public: */ Signal< ::uint64_t, ::uint64_t >& dataReceived() { return dataReceived_; } + Signal< ::uint64_t >& dataExceeded() { return dataExceeded_; } + /*! \brief Stream the resource to a stream. * * This is a convenience method to serialize to a stream (for @@ -420,6 +422,7 @@ private: Signal dataChanged_; Signal< ::uint64_t, ::uint64_t > dataReceived_; + Signal< ::uint64_t > dataExceeded_; bool trackUploadProgress_; diff --git a/src/Wt/WTimeEdit b/src/Wt/WTimeEdit index cfff41052e..edf9904784 100644 --- a/src/Wt/WTimeEdit +++ b/src/Wt/WTimeEdit @@ -81,19 +81,46 @@ public: */ WTime top() const; + /*! \brief Sets the step size for the hours + */ void setHourStep(int step); + + /*! \brief Returns the step size for the hours + */ int hourStep() const; + /*! \brief Sets the step size for the minutes + */ void setMinuteStep(int step); + + /*! \brief Returns the step size for the minutes + */ int minuteStep() const; + /*! \brief Sets the step size for the seconds + */ void setSecondStep(int step); + + /*! \brief Returns the step size for the seconds + */ int secondStep() const; + /*! \brief Sets the step size for the milliseconds + */ void setMillisecondStep(int step); + + /*! \brief Returns the step size for the milliseconds + */ int millisecondStep() const; + /*! \brief Enables or disables wraparound + * + * Wraparound is enabled by default + */ void setWrapAroundEnabled(bool rollover); + + /*! \brief Returns whether wraparound is enabled + */ bool wrapAroundEnabled() const; virtual void load(); diff --git a/src/http/MimeTypes.C b/src/http/MimeTypes.C index 36b76c3b97..b3db8d84b3 100644 --- a/src/http/MimeTypes.C +++ b/src/http/MimeTypes.C @@ -42,6 +42,7 @@ struct mapping { "svg", "image/svg+xml" }, { "webm", "video/webm" }, { "xml", "application/xml" }, + { "pdf", "application/pdf" }, { 0, 0 } // Marks end of list. }; diff --git a/src/http/Reply.C b/src/http/Reply.C index 4e88529269..f7fe2aa973 100644 --- a/src/http/Reply.C +++ b/src/http/Reply.C @@ -159,6 +159,9 @@ void toText(S& stream, Reply::status_type status) case Reply::service_unavailable: stream << "503 Service Unavailable\r\n"; break; + case Reply::version_not_supported: + stream << "505 HTTP Version Not Supported\r\n"; + break; case Reply::no_status: case Reply::internal_server_error: stream << "500 Internal Server Error\r\n"; @@ -329,12 +332,11 @@ bool Reply::nextBuffers(std::vector& result) * Status line. */ - buf_ << "HTTP/"; - buf_ << request_.http_version_major; - buf_ << '.'; - buf_ << request_.http_version_minor; - - buf_ << ' '; + if (http10) { + buf_ << "HTTP/1.0 "; + } else { + buf_ << "HTTP/1.1 "; + } status_strings::toText(buf_, status_); diff --git a/src/http/Reply.h b/src/http/Reply.h index b55d194ad7..e810e21b1b 100644 --- a/src/http/Reply.h +++ b/src/http/Reply.h @@ -84,7 +84,8 @@ class WTHTTP_API Reply : public boost::enable_shared_from_this internal_server_error = 500, not_implemented = 501, bad_gateway = 502, - service_unavailable = 503 + service_unavailable = 503, + version_not_supported = 505 }; enum ws_opcode { diff --git a/src/http/Request.h b/src/http/Request.h index c812883948..afbd47eef5 100644 --- a/src/http/Request.h +++ b/src/http/Request.h @@ -89,6 +89,8 @@ class Request #ifdef HTTP_WITH_SSL ssl = 0; #endif + http_version_major = -1; + http_version_minor = -1; } enum State { Partial, Complete, Error }; diff --git a/src/http/RequestHandler.C b/src/http/RequestHandler.C index 9958b99e7f..0deb43274e 100644 --- a/src/http/RequestHandler.C +++ b/src/http/RequestHandler.C @@ -105,7 +105,7 @@ ReplyPtr RequestHandler::handleRequest(Request& req, if ((req.http_version_major != 1) || (req.http_version_minor != 0 && req.http_version_minor != 1)) - return ReplyPtr(new StockReply(req, Reply::not_implemented, "", config_)); + return ReplyPtr(new StockReply(req, Reply::version_not_supported, "", config_)); // Decode url to path. if (!url_decode(req.uri, req.request_path, req.request_query)) { diff --git a/src/http/StockReply.C b/src/http/StockReply.C index 88edc5fff6..274d609056 100644 --- a/src/http/StockReply.C +++ b/src/http/StockReply.C @@ -144,6 +144,12 @@ const std::string service_unavailable = "

    503 Service Unavailable

    " ""; const std::string service_unavailable_name ="503-service-unavailable.html"; +const std::string version_not_supported = + "" + "HTTP Version Not Supported" + "

    505 HTTP Version Not Supported

    " + ""; +const std::string version_not_supported_name = "505-version-not-supported.html"; const std::string& toText(Reply::status_type status) { @@ -189,6 +195,8 @@ const std::string& toText(Reply::status_type status) return bad_gateway; case Reply::service_unavailable: return service_unavailable; + case Reply::version_not_supported: + return version_not_supported; default: return internal_server_error; } @@ -238,6 +246,8 @@ const std::string& toName(Reply::status_type status) return bad_gateway_name; case Reply::service_unavailable: return service_unavailable_name; + case Reply::version_not_supported: + return version_not_supported_name; default: return internal_server_error_name; } diff --git a/src/js/WFileDropWidget.js b/src/js/WFileDropWidget.js index 648ae9500c..6d60549aa6 100644 --- a/src/js/WFileDropWidget.js +++ b/src/js/WFileDropWidget.js @@ -8,7 +8,7 @@ WT_DECLARE_WT_MEMBER (1, JavaScriptConstructor, "WFileDropWidget", - function(APP, dropwidget, url, maxFileSize) { + function(APP, dropwidget, maxFileSize) { jQuery.data(dropwidget, 'lobj', this); @@ -29,13 +29,13 @@ WT_DECLARE_WT_MEMBER return items || types; }; - this.validFileCheck = function(file, callback) { + this.validFileCheck = function(file, callback, url) { var reader = new FileReader(); reader.onload = function() { - callback(true); + callback(true, url); } reader.onerror = function() { - callback(false); + callback(false, url); } reader.readAsText(file); // try reading first 10 bytes } @@ -125,7 +125,7 @@ WT_DECLARE_WT_MEMBER Wt.emit(dropwidget, 'requestsend', uploads[0].id); } - dropwidget.send = function() { + dropwidget.send = function(url) { console.log('sending file'); xhr = uploads[0] if (xhr.file.size > maxFileSize) { @@ -133,11 +133,11 @@ WT_DECLARE_WT_MEMBER self.uploadFinished(null); return; } else { - self.validFileCheck(xhr.file, self.actualSend); + self.validFileCheck(xhr.file, self.actualSend, url); } } - this.actualSend = function(isValid) { + this.actualSend = function(isValid, url) { if (!isValid) { self.uploadFinished(null); return; diff --git a/src/js/WFileDropWidget.min.js b/src/js/WFileDropWidget.min.js index 4efdecca74..373585fe15 100644 --- a/src/js/WFileDropWidget.min.js +++ b/src/js/WFileDropWidget.min.js @@ -1,6 +1,6 @@ -WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"WFileDropWidget",function(n,c,l,m){jQuery.data(c,"lobj",this);var e=this,j="Wt-filedropzone-hover",b=[],k=false,i=true;this.eventContainsFile=function(a){var d=a.dataTransfer.types!=null&&a.dataTransfer.types.length>0&&a.dataTransfer.types[0]=="Files";return a.dataTransfer.items!=null&&a.dataTransfer.items.length>0&&a.dataTransfer.items[0].kind=="file"||d};this.validFileCheck=function(a,d){var g=new FileReader;g.onload=function(){d(true)};g.onerror=function(){d(false)}; -g.readAsText(a)};c.setAcceptDrops=function(a){i=a};c.ondragenter=function(a){i&&e.eventContainsFile(a)&&e.setHoverStyle(true)};c.ondragleave=function(){i&&e.setHoverStyle(false)};c.ondragover=function(a){a.preventDefault()};c.ondrop=function(a){a.preventDefault();if(i){e.setHoverStyle(false);if(!(window.FormData===undefined||a.dataTransfer.files==null||a.dataTransfer.files.length==0)){Math.floor(Math.random()*32768);for(var d=[],g=0;gm){Wt.emit(c,"filetoolarge",xhr.file.size);e.uploadFinished(null)}else e.validFileCheck(xhr.file,e.actualSend)};this.actualSend=function(a){if(a){xhr=b[0];xhr.addEventListener("load",e.uploadFinished);xhr.addEventListener("error",e.uploadFinished);xhr.addEventListener("abort",e.uploadFinished);xhr.addEventListener("timeout",e.uploadFinished);xhr.open("POST",l);a=new FormData;a.append("file-id",xhr.id);a.append("data",xhr.file); -xhr.send(a)}else e.uploadFinished(null)};this.uploadFinished=function(a){console.log("finished sending (type = "+a+")");a!=null&&a.type=="load"&&a.currentTarget.status==200&&Wt.emit(c,"uploadfinished",b[0].id);b.splice(0,1);if(b[0]&&b[0].ready)e.requestSend();else{k=false;Wt.emit(c,"donesending")}};c.cancelUpload=function(a){if(b[0].id==a)b[0].abort();else for(var d=1;d0&&a.dataTransfer.types[0]=="Files";return a.dataTransfer.items!=null&&a.dataTransfer.items.length>0&&a.dataTransfer.items[0].kind=="file"||b};this.validFileCheck=function(a,b,g){var e=new FileReader;e.onload=function(){b(true,g)};e.onerror= +function(){b(false,g)};e.readAsText(a)};d.setAcceptDrops=function(a){i=a};d.ondragenter=function(a){i&&f.eventContainsFile(a)&&f.setHoverStyle(true)};d.ondragleave=function(){i&&f.setHoverStyle(false)};d.ondragover=function(a){a.preventDefault()};d.ondrop=function(a){a.preventDefault();if(i){f.setHoverStyle(false);if(!(window.FormData===undefined||a.dataTransfer.files==null||a.dataTransfer.files.length==0)){Math.floor(Math.random()*32768);for(var b=[],g=0;gl){Wt.emit(d,"filetoolarge",xhr.file.size);f.uploadFinished(null)}else f.validFileCheck(xhr.file,f.actualSend,a)};this.actualSend=function(a,b){if(a){xhr=c[0];xhr.addEventListener("load",f.uploadFinished);xhr.addEventListener("error",f.uploadFinished);xhr.addEventListener("abort",f.uploadFinished);xhr.addEventListener("timeout",f.uploadFinished);xhr.open("POST",b);a=new FormData;a.append("file-id",xhr.id); +a.append("data",xhr.file);xhr.send(a)}else f.uploadFinished(null)};this.uploadFinished=function(a){console.log("finished sending (type = "+a+")");a!=null&&a.type=="load"&&a.currentTarget.status==200&&Wt.emit(d,"uploadfinished",c[0].id);c.splice(0,1);if(c[0]&&c[0].ready)f.requestSend();else{k=false;Wt.emit(d,"donesending")}};d.cancelUpload=function(a){if(c[0].id==a)c[0].abort();else for(var b=1;bsetRequest(request, (WebResponse *)request); WApplication *app = WApplication::instance(); const std::string *requestE = request->getParameter("request"); @@ -496,8 +495,13 @@ void WebController::updateResourceProgress(WebRequest *request, resource = app->decodeExposedResource(*resourceE); } - if (resource) - resource->dataReceived().emit(current, total); + if (resource) { + ::int64_t dataExceeded = request->postDataExceeded(); + if (dataExceeded) + resource->dataExceeded().emit(dataExceeded); + else + resource->dataReceived().emit(current, total); + } } bool WebController::handleApplicationEvent(const ApplicationEvent& event)