diff -Nru phantomjs-1.3.0+dfsg/bin/Info.plist phantomjs-1.4.0+dfsg/bin/Info.plist --- phantomjs-1.3.0+dfsg/bin/Info.plist 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/bin/Info.plist 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,12 @@ + + + + +CFBundleExecutable +phantomjs +CFBundleIdentifier +org.phantomjs +LSUIElement +1 + + diff -Nru phantomjs-1.3.0+dfsg/ChangeLog phantomjs-1.4.0+dfsg/ChangeLog --- phantomjs-1.3.0+dfsg/ChangeLog 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/ChangeLog 2011-12-27 13:46:57.000000000 +0000 @@ -1,5 +1,25 @@ Please see also http://code.google.com/p/phantomjs/wiki/ReleaseNotes. +2011-12-22: Version 1.4.0 "Glory of the Snow" + + New features + + * Added embedded HTTP server (issue 115) + * Added convenient build script for Linux (issue 197) + * Added support for SOCKS5 proxy (issue 266) + * Updated CoffeeScript compiler to version 1.2 (issue 312) + + Bug fixes + + * Fix potential crash in QUrl with Qt 4.8 (issue 304) + * Fix bug in CookieJar with QSettings and string (PyPhantomJS issue 10) + * Prevent showing the icon on Mac OS X Dock (issue 281) + + Examples + + * Added a new example to detect browsers sniffing (issue 263) + * Added HTTP server example (issue 115) + 2011-09-23: Version 1.3.0 "Water Lily" Bug fixes diff -Nru phantomjs-1.3.0+dfsg/debian/changelog phantomjs-1.4.0+dfsg/debian/changelog --- phantomjs-1.3.0+dfsg/debian/changelog 2011-12-06 13:37:41.000000000 +0000 +++ phantomjs-1.4.0+dfsg/debian/changelog 2011-12-27 13:56:43.000000000 +0000 @@ -1,3 +1,11 @@ +phantomjs (1.4.0+dfsg-1) unstable; urgency=low + + * New upstream + * debian/patches/0001-build-with-libjs-coffeesciprt.patch: Follow + upstream changes. + + -- TANIGUCHI Takaki Tue, 27 Dec 2011 22:56:20 +0900 + phantomjs (1.3.0+dfsg-4) unstable; urgency=low * debian/patches/0001-build-with-libjs-coffeesciprt.patch: Fixed diff -Nru phantomjs-1.3.0+dfsg/debian/patches/0001-build-with-libjs-coffeesciprt.patch phantomjs-1.4.0+dfsg/debian/patches/0001-build-with-libjs-coffeesciprt.patch --- phantomjs-1.3.0+dfsg/debian/patches/0001-build-with-libjs-coffeesciprt.patch 2011-12-06 13:37:41.000000000 +0000 +++ phantomjs-1.4.0+dfsg/debian/patches/0001-build-with-libjs-coffeesciprt.patch 2011-12-27 13:56:43.000000000 +0000 @@ -9,54 +9,53 @@ src/phantomjs.qrc | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) -diff --git a/python/pyphantomjs/csconverter.py b/python/pyphantomjs/csconverter.py -index b2df0a3..64a331b 100644 ---- a/python/pyphantomjs/csconverter.py -+++ b/python/pyphantomjs/csconverter.py -@@ -38,7 +38,7 @@ class CSConverter(QObject): +Index: phantomjs/python/pyphantomjs/csconverter.py +=================================================================== +--- phantomjs.orig/python/pyphantomjs/csconverter.py 2011-12-27 22:47:02.000000000 +0900 ++++ phantomjs/python/pyphantomjs/csconverter.py 2011-12-27 22:50:37.014372207 +0900 +@@ -36,7 +36,7 @@ self.m_webPage = QWebPage(self) - with QPyFile(':/resources/coffee-script.js') as f: -+ with QPyFile('/usr/lib/javascript/coffeescript/coffee-script.js') as f: - script = f.readAll() ++ with QPyFile('/usr/share/javascript/coffeescript/coffee-script.js') as f: + self.m_webPage.mainFrame().evaluateJavaScript(f.readAll()) + self.m_webPage.mainFrame().addToJavaScriptWindowObject('converter', self) - self.m_webPage.mainFrame().evaluateJavaScript(script) -diff --git a/python/pyphantomjs/resources.qrc b/python/pyphantomjs/resources.qrc -index 0e23785..e31950b 100644 ---- a/python/pyphantomjs/resources.qrc -+++ b/python/pyphantomjs/resources.qrc -@@ -4,7 +4,6 @@ - configurator.js +Index: phantomjs/python/pyphantomjs/resources.qrc +=================================================================== +--- phantomjs.orig/python/pyphantomjs/resources.qrc 2011-12-27 22:47:02.000000000 +0900 ++++ phantomjs/python/pyphantomjs/resources.qrc 2011-12-27 22:50:49.146431522 +0900 +@@ -5,7 +5,6 @@ modules/fs.js modules/webpage.js + modules/webserver.js - resources/coffee-script.js resources/pyphantomjs-icon.png -diff --git a/src/csconverter.cpp b/src/csconverter.cpp -index 0f4a1d1..46d7737 100644 ---- a/src/csconverter.cpp -+++ b/src/csconverter.cpp -@@ -48,7 +48,7 @@ CSConverter *CSConverter::instance() +Index: phantomjs/src/csconverter.cpp +=================================================================== +--- phantomjs.orig/src/csconverter.cpp 2011-12-27 22:47:02.000000000 +0900 ++++ phantomjs/src/csconverter.cpp 2011-12-27 22:50:23.542306344 +0900 +@@ -48,7 +48,7 @@ CSConverter::CSConverter() : QObject(QCoreApplication::instance()) { -- QFile file(":/coffee-script.js"); -+ QFile file("/usr/lib/javascript/coffeescript/coffee-script.js"); - if (!file.open(QFile::ReadOnly)) { - Terminal::instance()->cerr("CoffeeScript compiler is not available!"); - exit(1); -diff --git a/src/phantomjs.qrc b/src/phantomjs.qrc -index b8577dd..43fed06 100644 ---- a/src/phantomjs.qrc -+++ b/src/phantomjs.qrc -@@ -1,7 +1,6 @@ - - +- m_webPage.mainFrame()->evaluateJavaScript(Utils::readResourceFileUtf8(":/coffee-script.js")); ++ m_webPage.mainFrame()->evaluateJavaScript(Utils::readResourceFileUtf8("/usr/share/javascript/coffeescript/coffee-script.js")); + m_webPage.mainFrame()->addToJavaScriptWindowObject("converter", this); + } + +Index: phantomjs/src/phantomjs.qrc +=================================================================== +--- phantomjs.orig/src/phantomjs.qrc 2011-12-27 22:47:02.000000000 +0900 ++++ phantomjs/src/phantomjs.qrc 2011-12-27 22:49:32.810058325 +0900 +@@ -3,7 +3,6 @@ + debug_harness.html + debug_wrapper.js phantomjs-icon.png - coffee-script.js usage.txt bootstrap.js configurator.js --- diff -Nru phantomjs-1.3.0+dfsg/deploy/build-linux.sh phantomjs-1.4.0+dfsg/deploy/build-linux.sh --- phantomjs-1.3.0+dfsg/deploy/build-linux.sh 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/deploy/build-linux.sh 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,118 @@ +#!/bin/bash + +QT_VERSION=0 +QT_FOLDER="" +COMPILE_JOBS=4 + +if [ "$1" = "--qt-4.8" ] +then + echo "Building Qt 4.8" + QT_VERSION=4.8 + QT_FOLDER=Qt-$QT_VERSION + QT_URL=git://gitorious.org/qt/qt.git + + echo "Cloning Qt from gitorious into $QT_FOLDER..." + if [ ! -d $QT_FOLDER ] + then + git clone $QT_URL $QT_FOLDER + pushd $QT_FOLDER + git checkout -b 4.8 origin/4.8 + else + pushd $QT_FOLDER + git checkout -f + git clean -xdf + git checkout 4.8 + fi + + popd +else + echo "Building Qt 4.7" + + QT_VERSION=4.7.4 + QT_FOLDER=Qt-$QT_VERSION + QT_TARBALL=qt-everywhere-opensource-src-$QT_VERSION.tar.gz + + # Tip: change this to local/shared mirror + QT_URL=http://get.qt.nokia.com/qt/source/$QT_TARBALL + + + # Step 1: Download Qt source tarball + # Note: only if it does not exist yet in the current directory + if [ ! -f $QT_TARBALL ] + then + echo "Downloading Qt $QT_VERSION from Nokia. Please wait..." + if ! curl -C - -O -S $QT_URL + then + echo + echo "Fatal error: fail to download from $QT_URL !" + exit 1 + fi + fi + + # Step 2: Extract Qt source + + [ -d $QT_FOLDER ] && rm -rf $QT_FOLDER + echo "Extracting Qt $QT_VERSION source tarball..." + echo + tar xzf $QT_TARBALL + mv qt-everywhere-opensource-src-$QT_VERSION Qt-$QT_VERSION + +fi + + + +# Step 3: Build Qt + +pushd $QT_FOLDER + +EXTRA_FLAGS="" +if [ $QT_VERSION = 4.8 ] ; then + echo "Patching Qt 4.8" + patch -p1 < ../qt48_enable_debugger.patch + patch -p1 < ../qt48_fix_inspector.patch + patch -p1 < ../qt48_headless_and_pdf_fixes.patch + # Build in lighthose mode for an x-less build + if [ "$2" = "--headless" ] ; then + echo "Building 4.8 in qpa headless mode" + EXTRA_FLAGS="-qpa" + fi +else + echo "Patching Qt 4.7" + patch configure ../allow-static-qtwebkit.patch + # Qt 4.8 doesn't allow static builds of QtWebkit-2.2 + EXTRA_FLAGS="-static" +fi + +patch -p1 < ../qapplication_skip_qtmenu.patch +echo "Building Qt $QT_VERSION. Please wait..." +echo +./configure -opensource -confirm-license -release -webkit -graphicssystem raster -no-exceptions -no-dbus -no-glib -no-gstreamer -no-stl -no-xmlpatterns -no-phonon -no-multimedia -no-qt3support -no-opengl -no-openvg -no-svg -no-declarative -no-gtkstyle -no-xkb -no-xinput -no-xinerama -no-sm -no-cups -qt-libpng -qt-libjpeg -no-libmng -no-libtiff -D QT_NO_STYLE_CDE -D QT_NO_STYLE_CLEANLOOKS -D QT_NO_STYLE_MOTIF -D QT_NO_STYLE_PLASTIQUE -prefix $PWD -nomake demos -nomake examples -nomake tools -nomake docs -nomake translations $EXTRA_FLAGS +make -j$COMPILE_JOBS +popd + + +if [ $QT_VERSION != 4.8 ] ; then + # Extra step: copy JavaScriptCore/release, needed for jscore static lib + mkdir ../JavaScriptCore + cp -rp $QT_FOLDER/src/3rdparty/webkit/JavaScriptCore/release ../JavaScriptCore/ +fi + +# Step 4: Build PhantomJS + +echo "Building PhantomJS. Please wait..." +echo +cd .. +[ -f Makefile ] && make distclean +deploy/$QT_FOLDER/bin/qmake +make -j$COMPILE_JOBS + +# Step 5: Prepare for deployment + +echo "Compressing PhantomJS executable..." +echo +strip bin/phantomjs +if [ `command -v upx` ]; then + upx -9 bin/phantomjs +else + echo "You don't have UPX. Consider installing it to reduce the executable size." +fi diff -Nru phantomjs-1.3.0+dfsg/deploy/build-mac.sh phantomjs-1.4.0+dfsg/deploy/build-mac.sh --- phantomjs-1.3.0+dfsg/deploy/build-mac.sh 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/deploy/build-mac.sh 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ #!/bin/bash -QT_VERSION=4.7.4 +QT_VERSION=4.8.0 QT_FOLDER=Qt-$QT_VERSION QT_TARBALL=qt-everywhere-opensource-src-$QT_VERSION.tar.gz @@ -31,22 +31,88 @@ tar xzf $QT_TARBALL mv qt-everywhere-opensource-src-$QT_VERSION Qt-$QT_VERSION -# Step 3: Build Qt +# Step 3: Apply some patches cd $QT_FOLDER patch configure ../allow-static-qtwebkit.patch patch -p1 < ../qapplication_skip_qtmenu.patch + +rm -rf src/3rdparty/webkit/Source/WebKit/qt/tests + +# Step 4: Build Qt + echo "Building Qt $QT_VERSION. Please wait..." echo -./configure -opensource -confirm-license -release -static -no-exceptions -no-stl -no-xmlpatterns -no-phonon -no-qt3support -no-opengl -no-declarative -qt-libpng -qt-libjpeg -no-libmng -no-libtiff -D QT_NO_STYLE_CDE -D QT_NO_STYLE_CLEANLOOKS -D QT_NO_STYLE_MOTIF -D QT_NO_STYLE_PLASTIQUE -cocoa -prefix $PWD -arch x86 -nomake demos -nomake examples -nomake tools + +CFG='' + +CFG+=' -opensource' # Use the open-source license +CFG+=' -confirm-license' # Silently acknowledge the license confirmation + +CFG+=' -release' # Build only for release (no debugging support) +CFG+=' -static' # Compile for static libraries +CFG+=' -fast' # Accelerate Makefiles generation +CFG+=' -nomake demos' # Don't build with the demos +CFG+=' -nomake docs' # Don't generate the documentatio +CFG+=' -nomake examples' # Don't build any examples +CFG+=' -nomake translations' # Ignore the translations +CFG+=' -nomake tools' # Don't built the tools + +CFG+=' -no-exceptions' # Don't use C++ exception +CFG+=' -no-stl' # No need for STL compatibility + +# Irrelevant Qt features +CFG+=' -no-libmng' +CFG+=' -no-libtiff' + +# Unnecessary Qt modules +CFG+=' -no-declarative' +CFG+=' -no-multimedia' +CFG+=' -no-opengl' +CFG+=' -no-openvg' +CFG+=' -no-phonon' +CFG+=' -no-qt3support' +CFG+=' -no-script' +CFG+=' -no-scripttools' +CFG+=' -no-svg' +CFG+=' -no-xmlpatterns' + +# Sets the default graphics system to the raster engine +CFG+=' -graphicssystem raster' + +# Mac +CFG+=' -cocoa' # Cocoa only, ignore Carbon +CFG+=' -no-cups' # Disable CUPS support +CFG+=' -no-dwarf2' + +# Unix +CFG+=' -no-dbus' # Disable D-Bus feature +CFG+=' -no-glib' # No need for Glib integration +CFG+=' -no-gstreamer' # Turn off GStreamer support +CFG+=' -no-gtkstyle' # Disable theming integration with Gtk+ +CFG+=' -no-sm' +CFG+=' -no-xinerama' +CFG+=' -no-xkb' + +# Use the bundled libraries, vs system-installed +CFG+=' -qt-libjpeg' +CFG+=' -qt-libpng' + +# Useless styles +CFG+=' -D QT_NO_STYLESHEET' +CFG+=' -D QT_NO_STYLE_CDE' +CFG+=' -D QT_NO_STYLE_CLEANLOOKS' +CFG+=' -D QT_NO_STYLE_MOTIF' + +./configure -prefix $PWD $CFG -arch x86 make -j$COMPILE_JOBS cd .. -# Extra step: copy JavaScriptCore/release, needed for jscore static lib -mkdir ../JavaScriptCore -cp -rp $QT_FOLDER/src/3rdparty/webkit/JavaScriptCore/release ../JavaScriptCore/ +# Extra step to ensure the static libraries are found +cp -rp $QT_FOLDER/src/3rdparty/webkit/Source/JavaScriptCore/release/* $QT_FOLDER/lib +cp -rp $QT_FOLDER/src/3rdparty/webkit/Source/WebCore/release/* $QT_FOLDER/lib -# Step 4: Build PhantomJS +# Step 5: Build PhantomJS echo "Building PhantomJS. Please wait..." echo @@ -55,7 +121,7 @@ deploy/$QT_FOLDER/bin/qmake make -j$COMPILE_JOBS -# Step 5: Prepare for deployment +# Step 6: Prepare for deployment echo "Compressing PhantomJS executable..." echo diff -Nru phantomjs-1.3.0+dfsg/deploy/qt48_enable_debugger.patch phantomjs-1.4.0+dfsg/deploy/qt48_enable_debugger.patch --- phantomjs-1.3.0+dfsg/deploy/qt48_enable_debugger.patch 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/deploy/qt48_enable_debugger.patch 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,35 @@ +diff --git a/src/3rdparty/webkit/Source/WebCore/inspector/front-end/Settings.js b/src/3rdparty/webkit/Source/WebCore/inspector/front-end/Settings.js +index 62bf84a..e830f85 100644 +--- a/src/3rdparty/webkit/Source/WebCore/inspector/front-end/Settings.js ++++ b/src/3rdparty/webkit/Source/WebCore/inspector/front-end/Settings.js +@@ -40,7 +40,7 @@ var Preferences = { + showMissingLocalizedStrings: false, + samplingCPUProfiler: false, + showColorNicknames: true, +- debuggerAlwaysEnabled: false, ++ debuggerAlwaysEnabled: true, + profilerAlwaysEnabled: false, + onlineDetectionEnabled: true, + nativeInstrumentationEnabled: false, +@@ -58,7 +58,7 @@ WebInspector.Settings = function() + { + this.installApplicationSetting("colorFormat", "hex"); + this.installApplicationSetting("consoleHistory", []); +- this.installApplicationSetting("debuggerEnabled", false); ++ this.installApplicationSetting("debuggerEnabled", true); + this.installApplicationSetting("domWordWrap", true); + this.installApplicationSetting("profilerEnabled", false); + this.installApplicationSetting("eventListenersFilter", "all"); +diff --git a/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebinspector.cpp b/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebinspector.cpp +index 6706f2a..bec3d1c 100644 +--- a/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebinspector.cpp ++++ b/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebinspector.cpp +@@ -161,7 +161,7 @@ void QWebInspector::showEvent(QShowEvent* event) + #if ENABLE(INSPECTOR) + // Allows QWebInspector::show() to init the inspector. + if (d->page) +- d->page->d->inspectorController()->show(); ++ d->page->d->inspectorController()->showAndEnableDebugger(); + #endif + } + diff -Nru phantomjs-1.3.0+dfsg/deploy/qt48_fix_inspector.patch phantomjs-1.4.0+dfsg/deploy/qt48_fix_inspector.patch --- phantomjs-1.3.0+dfsg/deploy/qt48_fix_inspector.patch 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/deploy/qt48_fix_inspector.patch 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,268 @@ +diff --git a/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp b/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp +index 92b7d5c..c5e5bd5 100644 +--- a/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp ++++ b/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp +@@ -22,7 +22,8 @@ + + #include "InspectorClientQt.h" + #include "InspectorController.h" +-#include "MD5.h" ++#include "Base64.h" ++#include "SHA1.h" + #include "Page.h" + #include "qwebpage.h" + #include "qwebpage_p.h" +@@ -44,43 +45,17 @@ namespace WebCore { + /*! + Computes the WebSocket handshake response given the two challenge numbers and key3. + */ +-static void generateWebSocketChallengeResponse(uint32_t number1, uint32_t number2, const unsigned char key3[8], unsigned char response[16]) ++static QByteArray generateWebSocketChallengeResponse(const QByteArray& key) + { +- uint8_t challenge[16]; +- qToBigEndian(number1, &challenge[0]); +- qToBigEndian(number2, &challenge[4]); +- memcpy(&challenge[8], key3, 8); +- MD5 md5; +- md5.addBytes(challenge, sizeof(challenge)); +- Vector digest; +- md5.checksum(digest); +- memcpy(response, digest.data(), 16); +-} +- +-/*! +- Parses and returns a WebSocket challenge number according to the +- method specified in the WebSocket protocol. +- +- The field contains numeric digits interspersed with spaces and +- non-numeric digits. The protocol ignores the characters that are +- neither digits nor spaces. The digits are concatenated and +- interpreted as a long int. The result is this number divided by +- the number of spaces. +- */ +-static quint32 parseWebSocketChallengeNumber(QString field) +-{ +- QString nString; +- int numSpaces = 0; +- for (int i = 0; i < field.size(); i++) { +- QChar c = field[i]; +- if (c == QLatin1Char(' ')) +- numSpaces++; +- else if ((c >= QLatin1Char('0')) && (c <= QLatin1Char('9'))) +- nString.append(c); +- } +- quint32 num = nString.toLong(); +- quint32 result = (numSpaces ? (num / numSpaces) : num); +- return result; ++ SHA1 sha1; ++ Vector digest; ++ Vector encoded; ++ QByteArray toHash("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); ++ toHash.prepend(key); ++ sha1.addBytes((uint8_t*)toHash.data(), toHash.size()); ++ sha1.computeHash(digest); ++ base64Encode((char*)digest.data(), digest.size(), encoded); ++ return QByteArray(encoded.data(), encoded.size()); + } + + static InspectorServerQt* s_inspectorServer; +@@ -194,7 +169,7 @@ void InspectorServerRequestHandlerQt::tcpReadyRead() + m_path = header.path(); + m_contentType = header.contentType().toLatin1(); + m_contentLength = header.contentLength(); +- if (header.hasKey(QLatin1String("Upgrade")) && (header.value(QLatin1String("Upgrade")) == QLatin1String("WebSocket"))) ++ if (header.hasKey(QLatin1String("Upgrade")) && (header.value(QLatin1String("Upgrade")) == QLatin1String("websocket"))) + isWebSocket = true; + + m_data.clear(); +@@ -211,25 +186,19 @@ void InspectorServerRequestHandlerQt::tcpReadyRead() + // switch to websocket-style WebSocketService messaging + if (m_tcpConnection) { + m_tcpConnection->disconnect(SIGNAL(readyRead())); +- connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(webSocketReadyRead())); +- +- QByteArray key3 = m_tcpConnection->read(8); +- +- quint32 number1 = parseWebSocketChallengeNumber(header.value(QLatin1String("Sec-WebSocket-Key1"))); +- quint32 number2 = parseWebSocketChallengeNumber(header.value(QLatin1String("Sec-WebSocket-Key2"))); +- +- char responseData[16]; +- generateWebSocketChallengeResponse(number1, number2, (unsigned char*)key3.data(), (unsigned char*)responseData); +- QByteArray response(responseData, sizeof(responseData)); ++ connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(webSocketReadyRead()), Qt::QueuedConnection); ++ ++ QByteArray key = header.value(QLatin1String("Sec-WebSocket-Key")).toLatin1(); ++ QString accept = QString::fromLatin1(generateWebSocketChallengeResponse(key)); + + QHttpResponseHeader responseHeader(101, QLatin1String("WebSocket Protocol Handshake"), 1, 1); + responseHeader.setValue(QLatin1String("Upgrade"), header.value(QLatin1String("Upgrade"))); + responseHeader.setValue(QLatin1String("Connection"), header.value(QLatin1String("Connection"))); +- responseHeader.setValue(QLatin1String("Sec-WebSocket-Origin"), header.value(QLatin1String("Origin"))); +- responseHeader.setValue(QLatin1String("Sec-WebSocket-Location"), (QLatin1String("ws://") + header.value(QLatin1String("Host")) + m_path)); +- responseHeader.setContentLength(response.size()); ++ responseHeader.setValue(QLatin1String("Sec-WebSocket-Accept"), accept); ++ // responseHeader.setValue(QLatin1String("Sec-WebSocket-Origin"), header.value(QLatin1String("Sec-WebSocket-Origin"))); ++ // responseHeader.setValue(QLatin1String("Sec-WebSocket-Location"), (QLatin1String("ws://") + header.value(QLatin1String("Host")) + m_path)); + m_tcpConnection->write(responseHeader.toString().toLatin1()); +- m_tcpConnection->write(response); ++ //m_tcpConnection->write(response); + m_tcpConnection->flush(); + + if ((words.size() == 4) +@@ -308,26 +277,54 @@ void InspectorServerRequestHandlerQt::tcpConnectionDisconnected() + m_tcpConnection = 0; + } + +-int InspectorServerRequestHandlerQt::webSocketSend(QByteArray payload) ++int InspectorServerRequestHandlerQt::webSocketSend(const QString& message) + { +- Q_ASSERT(m_tcpConnection); +- m_tcpConnection->putChar(0x00); +- int nBytes = m_tcpConnection->write(payload); +- m_tcpConnection->putChar(0xFF); +- m_tcpConnection->flush(); +- return nBytes; ++ QByteArray payload = message.toUtf8(); ++ return webSocketSend(payload.data(), payload.size()); + } + + int InspectorServerRequestHandlerQt::webSocketSend(const char* data, size_t length) + { + Q_ASSERT(m_tcpConnection); +- m_tcpConnection->putChar(0x00); ++ m_tcpConnection->putChar(0x81); ++ if (length <= 125) ++ m_tcpConnection->putChar(static_cast(length)); ++ else if (length <= pow(2,16)) { ++ m_tcpConnection->putChar(126); ++ quint16 length16 = qToBigEndian(static_cast(length)); ++ m_tcpConnection->write(reinterpret_cast(&length16), 2); ++ } else { ++ m_tcpConnection->putChar(127); ++ quint64 length64 = qToBigEndian(static_cast(length)); ++ m_tcpConnection->write(reinterpret_cast(&length64), 8); ++ } + int nBytes = m_tcpConnection->write(data, length); +- m_tcpConnection->putChar(0xFF); + m_tcpConnection->flush(); + return nBytes; + } + ++static QByteArray applyMask(const QByteArray& payload, const QByteArray& maskingKey) ++{ ++ Q_ASSERT(maskingKey.size() == 4); ++ QByteArray unmaskedPayload; ++ for (int i = 0; i < payload.size(); ++i) { ++ char unmaskedByte = payload[i] ^ maskingKey[i % 4]; ++ unmaskedPayload.append(unmaskedByte); ++ } ++ return unmaskedPayload; ++} ++ ++#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d" ++#define BYTETOBINARY(byte) \ ++ (byte & 0x80 ? 1 : 0), \ ++ (byte & 0x40 ? 1 : 0), \ ++ (byte & 0x20 ? 1 : 0), \ ++ (byte & 0x10 ? 1 : 0), \ ++ (byte & 0x08 ? 1 : 0), \ ++ (byte & 0x04 ? 1 : 0), \ ++ (byte & 0x02 ? 1 : 0), \ ++ (byte & 0x01 ? 1 : 0) ++ + void InspectorServerRequestHandlerQt::webSocketReadyRead() + { + Q_ASSERT(m_tcpConnection); +@@ -336,38 +333,49 @@ void InspectorServerRequestHandlerQt::webSocketReadyRead() + QByteArray content = m_tcpConnection->read(m_tcpConnection->bytesAvailable()); + m_data.append(content); + while (m_data.size() > 0) { +- // first byte in websocket frame should be 0 +- Q_ASSERT(!m_data[0]); ++ bool isMasked = m_data[1] & 0x80; ++ quint64 payloadLen = m_data[1] & 0x7F; ++ int pos = 2; + +- // Start of WebSocket frame is indicated by 0 +- if (m_data[0]) { +- qCritical() << "webSocketReadyRead: unknown frame type" << m_data[0]; +- m_data.clear(); +- m_tcpConnection->close(); +- return; ++ if (payloadLen == 126) { ++ payloadLen = qFromBigEndian(*reinterpret_cast(m_data.mid(pos, 2).data())); ++ pos = 4; ++ } else if (payloadLen == 127) { ++ payloadLen = qFromBigEndian(*reinterpret_cast(m_data.mid(pos, 8).data())); ++ pos = 8; + } + +- // End of WebSocket frame indicated by 0xff. +- int pos = m_data.indexOf(0xff, 1); +- if (pos < 1) +- return; +- +- // After above checks, length will be >= 0. +- size_t length = pos - 1; +- if (length <= 0) +- return; +- +- QByteArray payload = m_data.mid(1, length); ++ QByteArray payload; ++ if (isMasked) { ++ QByteArray maskingKey = m_data.mid(pos, 4); ++ pos += 4; ++ payload = applyMask(m_data.mid(pos, payloadLen), maskingKey); ++ } else { ++ payload = m_data.mid(pos, payloadLen); ++ } + ++ // Handle fragmentation ++ if ((m_data[0] & 0x80) == 0) { // Non-last fragmented payload ++ m_fragmentedPayload.append(payload); ++ ++ m_data = m_data.mid(pos + payloadLen); ++ continue; ++ } else { ++ if ((m_data[0] & 0x0F) == 0) { // Last fragment ++ m_fragmentedPayload.append(payload); ++ payload = m_fragmentedPayload; ++ m_fragmentedPayload.clear(); ++ } ++ } ++ ++ // Remove this WebSocket message from m_data (payload, start-of-frame byte, end-of-frame byte). ++ m_data = m_data.mid(pos + payloadLen); + #if ENABLE(INSPECTOR) + if (m_inspectorClient) { + InspectorController* inspectorController = m_inspectorClient->m_inspectedWebPage->d->page->inspectorController(); + inspectorController->dispatchMessageFromFrontend(QString::fromUtf8(payload)); + } + #endif +- +- // Remove this WebSocket message from m_data (payload, start-of-frame byte, end-of-frame byte). +- m_data = m_data.mid(length + 2); + } + } + +diff --git a/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.h b/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.h +index 922b63e..e1265b9 100644 +--- a/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.h ++++ b/src/3rdparty/webkit/Source/WebKit/qt/WebCoreSupport/InspectorServerQt.h +@@ -83,7 +83,7 @@ public: + + InspectorServerRequestHandlerQt(QTcpSocket *tcpConnection, InspectorServerQt *server); + virtual ~InspectorServerRequestHandlerQt(); +- virtual int webSocketSend(QByteArray payload); ++ virtual int webSocketSend(const QString& message); + virtual int webSocketSend(const char *payload, size_t length); + + private slots: +@@ -100,6 +100,7 @@ private: + int m_contentLength; + bool m_endOfHeaders; + QByteArray m_data; ++ QByteArray m_fragmentedPayload; + InspectorClientQt* m_inspectorClient; + + void handleInspectorRequest(QStringList words); diff -Nru phantomjs-1.3.0+dfsg/deploy/qt48_headless_and_pdf_fixes.patch phantomjs-1.4.0+dfsg/deploy/qt48_headless_and_pdf_fixes.patch --- phantomjs-1.3.0+dfsg/deploy/qt48_headless_and_pdf_fixes.patch 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/deploy/qt48_headless_and_pdf_fixes.patch 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,2452 @@ +diff --git a/src/3rdparty/webkit/Source/WebKit.pri b/src/3rdparty/webkit/Source/WebKit.pri +index 5bd9577..ceeff95 100644 +--- a/src/3rdparty/webkit/Source/WebKit.pri ++++ b/src/3rdparty/webkit/Source/WebKit.pri +@@ -93,7 +93,6 @@ CONFIG -= warn_on + + # Treat warnings as errors on x86/Linux/GCC + linux-g++* { +- isEqual(QT_ARCH,x86_64)|isEqual(QT_ARCH,i386): QMAKE_CXXFLAGS += -Werror + + greaterThan(QT_GCC_MAJOR_VERSION, 3):greaterThan(QT_GCC_MINOR_VERSION, 5) { + if (!contains(QMAKE_CXXFLAGS, -std=c++0x) && !contains(QMAKE_CXXFLAGS, -std=gnu++0x)) { +diff --git a/src/gui/painting/qpaintengine.h b/src/gui/painting/qpaintengine.h +index 6befdd8..c175b73 100644 +--- a/src/gui/painting/qpaintengine.h ++++ b/src/gui/painting/qpaintengine.h +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + + QT_BEGIN_HEADER + +@@ -162,6 +163,19 @@ public: + virtual void drawRects(const QRect *rects, int rectCount); + virtual void drawRects(const QRectF *rects, int rectCount); + ++ virtual void addHyperlink(const QRectF &r, const QUrl &url) {Q_UNUSED(r); Q_UNUSED(url);} ++ virtual void addAnchor(const QRectF &r, const QString &name) {Q_UNUSED(r); Q_UNUSED(name);} ++ virtual void addLink(const QRectF &r, const QString &anchor) {Q_UNUSED(r); Q_UNUSED(anchor);} ++ virtual void addTextField(const QRectF &r, const QString &text, const QString &name, bool multiLine, bool password, bool readOnly, int maxLength) { ++ Q_UNUSED(r); Q_UNUSED(text); Q_UNUSED(name); Q_UNUSED(multiLine); Q_UNUSED(password); Q_UNUSED(readOnly); Q_UNUSED(maxLength); ++ } ++ virtual void addCheckBox(const QRectF &r, bool checked, const QString &name, bool readOnly) { ++ Q_UNUSED(r); Q_UNUSED(checked); Q_UNUSED(name); Q_UNUSED(readOnly); ++ } ++ virtual void addRadioButton(const QRectF &r, const QString & group="", bool checked=false, const QString &name="", bool readOnly=false) { ++ Q_UNUSED(r); Q_UNUSED(checked); Q_UNUSED(name); Q_UNUSED(readOnly); Q_UNUSED(group); ++ } ++ + virtual void drawLines(const QLine *lines, int lineCount); + virtual void drawLines(const QLineF *lines, int lineCount); + +@@ -177,6 +191,11 @@ public: + virtual void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) = 0; ++ virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr, const QByteArray * data) { ++ Q_UNUSED(data); ++ drawPixmap(r,pm,sr); ++ } ++ + virtual void drawTextItem(const QPointF &p, const QTextItem &textItem); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, +diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp +index bcc5f9d..c50640b 100644 +--- a/src/gui/painting/qpaintengine_raster.cpp ++++ b/src/gui/painting/qpaintengine_raster.cpp +@@ -2248,11 +2248,11 @@ namespace { + /*! + \reimp + */ +-void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, ++void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &_img, const QRectF &_sr, + Qt::ImageConversionFlags) + { + #ifdef QT_DEBUG_DRAW +- qDebug() << " - QRasterPaintEngine::drawImage(), r=" << r << " sr=" << sr << " image=" << img.size() << "depth=" << img.depth(); ++ qDebug() << " - QRasterPaintEngine::drawImage(), r=" << r << " sr=" << _sr << " image=" << _img.size() << "depth=" << img.depth(); + #endif + + if (r.isEmpty()) +@@ -2260,6 +2260,17 @@ void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRe + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); ++ ++ QImage img; ++ QRectF sr=_sr; ++ if (s->matrix.isAffine()) { ++ img = _img.copy(sr.toRect()).scaled( ++ s->matrix.mapRect(r).size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); ++ sr = img.rect(); ++ } else { ++ img=_img; ++ } ++ + int sr_l = qFloor(sr.left()); + int sr_r = qCeil(sr.right()) - 1; + int sr_t = qFloor(sr.top()); +diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp +index efb016e..4952b4b 100644 +--- a/src/gui/painting/qpainter.cpp ++++ b/src/gui/painting/qpainter.cpp +@@ -5393,7 +5393,7 @@ void QPainter::drawPixmap(const QPointF &p, const QPixmap &pm) + } + } + +-void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) ++void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr, const QByteArray * data) + { + #if defined QT_DEBUG_DRAW + if (qt_show_painter_debug_output) +@@ -5518,7 +5518,7 @@ void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } +- d->engine->drawPixmap(QRectF(x, y, w, h), pm, QRectF(sx, sy, sw, sh)); ++ d->engine->drawPixmap(QRectF(x, y, w, h), pm, QRectF(sx, sy, sw, sh), data); + } + } + +@@ -7254,6 +7254,200 @@ void QPainter::fillRect(const QRectF &r, const QColor &color) + \since 4.5 + */ + ++ ++/*! ++ \fn void QPainter::addAnchor(int x, int y, int w, int h, const QString &name); ++ ++ \overload ++ ++ Add an anchor to the current page at the rect specified by \a x, \a y, \a w and \a h ++ named \a name. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addLink() ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void QPainter::addAnchor(const QRect &r, const QString &name); ++ ++ \overload ++ ++ Add an anchor to the current page at the rect specified by \a r named \a name. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addLink() ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void addAnchor(const QRectF &r, const QString &name); ++ ++ \overload ++ ++ Add an anchor to the current page at the rect specified by \a r named \a name. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addLink() ++ ++ \since 4.7 ++*/ ++void QPainter::addAnchor(const QRectF &r, const QString &name) ++{ ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addAnchor: Painter not active"); ++ return; ++ } ++ d->engine->addAnchor(worldTransform().mapRect(r), name); ++} ++ ++/*! ++ \fn void QPainter::addLink(int x, int y, int w, int h, const QString &anchor); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a x, \a y, \a w and \a h ++ linking to the anchor named \a anchor. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addAnchor() ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void QPainter::addLink(const QRect &r, const QString &anchor); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a r ++ linking to the anchor named \a anchor. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addAnchor() ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void QPainter::addLink(const QRectF &r, const QString &anchor); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a r ++ linking to the anchor named \a anchor. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \sa addAnchor() ++ ++ \since 4.7 ++*/ ++void QPainter::addLink(const QRectF &r, const QString &anchor) ++{ ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addLink: Painter not active"); ++ return; ++ } ++ ++ d->engine->addLink(worldTransform().mapRect(r), anchor); ++} ++ ++ ++/*! ++ \fn void QPainter::addHyperlink(int x, int y, int w, int h, const QUrl &url); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a x, \a y, \a w and \a h ++ linking to \a url. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void QPainter::addHyperlink(const QRect &r, const QUrl &url); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a r ++ linking to \a url. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \since 4.7 ++*/ ++ ++/*! ++ \fn void QPainter::addHyperlink(const QRectF &r, const QUrl &url); ++ ++ \overload ++ ++ Add a link to the current page at the rect specified by \a r ++ linking to \a url. ++ ++ Note that for output formats not supporting links, currently all other then PDF, ++ this call has no effect. ++ ++ \since 4.7 ++*/ ++void QPainter::addHyperlink(const QRectF &r, const QUrl &url) ++{ ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addHyperlink: Painter not active"); ++ return; ++ } ++ d->engine->addHyperlink(worldTransform().mapRect(r), url); ++} ++ ++void QPainter::addTextField(const QRectF &r, const QString &text, const QString &name, bool multiLine, bool password, bool readOnly, int maxLength) { ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addTextField: Painter not active"); ++ return; ++ } ++ d->engine->addTextField(worldTransform().mapRect(r), text, name, multiLine, password, readOnly, maxLength); ++} ++ ++void QPainter::addCheckBox(const QRectF &r, bool checked, const QString &name, bool readOnly) { ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addCheckBox: Painter not active"); ++ return; ++ } ++ d->engine->addCheckBox(worldTransform().mapRect(r), checked, name, readOnly); ++} ++ ++ ++void QPainter::addRadioButton(const QRectF &r, const QString & group, bool checked, const QString &name, bool readOnly) { ++ Q_D(QPainter); ++ if (!d->engine) { ++ qWarning("QPainter::addRadioButton: Painter not active"); ++ return; ++ } ++ d->engine->addRadioButton(worldTransform().mapRect(r), group, checked, name, readOnly); ++} ++ + /*! + Sets the given render \a hint on the painter if \a on is true; + otherwise clears the render hint. +diff --git a/src/gui/painting/qpainter.h b/src/gui/painting/qpainter.h +index 12a14a2..9a8845d 100644 +--- a/src/gui/painting/qpainter.h ++++ b/src/gui/painting/qpainter.h +@@ -364,7 +364,7 @@ public: + inline void drawPicture(const QPoint &p, const QPicture &picture); + #endif + +- void drawPixmap(const QRectF &targetRect, const QPixmap &pixmap, const QRectF &sourceRect); ++ void drawPixmap(const QRectF &targetRect, const QPixmap &pixmap, const QRectF &sourceRect, const QByteArray * data=0); + inline void drawPixmap(const QRect &targetRect, const QPixmap &pixmap, const QRect &sourceRect); + inline void drawPixmap(int x, int y, int w, int h, const QPixmap &pm, + int sx, int sy, int sw, int sh); +@@ -447,6 +447,22 @@ public: + inline void fillRect(const QRect &r, Qt::BrushStyle style); + inline void fillRect(const QRectF &r, Qt::BrushStyle style); + ++ inline void addAnchor(int x, int y, int w, int h, const QString &name); ++ inline void addAnchor(const QRect &r, const QString &name); ++ void addAnchor(const QRectF &r, const QString &name); ++ ++ inline void addLink(int x, int y, int w, int h, const QString &anchor); ++ inline void addLink(const QRect &r, const QString &anchor); ++ void addLink(const QRectF &r, const QString &anchor); ++ ++ void addTextField(const QRectF &r, const QString &text=QString(), const QString &name=QString(), bool multiLine=false, bool password=false, bool readOnly=false, int maxLength=-1); ++ void addCheckBox(const QRectF &r, bool checked=false, const QString &name=QString(), bool readOnly=false); ++ void addRadioButton(const QRectF &r, const QString & group=QString(), bool checked=false, const QString &name=QString(), bool readOnly=false);; ++ ++ inline void addHyperlink(int x, int y, int w, int h, const QUrl &url); ++ inline void addHyperlink(const QRect &r, const QUrl &url); ++ void addHyperlink(const QRectF &r, const QUrl &url); ++ + void eraseRect(const QRectF &); + inline void eraseRect(int x, int y, int w, int h); + inline void eraseRect(const QRect &); +@@ -821,6 +837,35 @@ inline void QPainter::fillRect(const QRectF &r, Qt::BrushStyle style) + fillRect(r, QBrush(style)); + } + ++inline void QPainter::addAnchor(int x, int y, int w, int h, const QString &name) ++{ ++ addAnchor(QRectF(x, y, w, h), name); ++} ++ ++inline void QPainter::addAnchor(const QRect &r, const QString &name) ++{ ++ addAnchor(QRectF(r), name); ++} ++ ++inline void QPainter::addLink(int x, int y, int w, int h, const QString &anchor) ++{ ++ addLink(QRectF(x, y, w, h), anchor); ++} ++ ++inline void QPainter::addLink(const QRect &r, const QString &anchor) ++{ ++ addLink(QRectF(r), anchor); ++} ++ ++inline void QPainter::addHyperlink(int x, int y, int w, int h, const QUrl &url) ++{ ++ addHyperlink(QRectF(x, y, w, h), url); ++} ++ ++inline void QPainter::addHyperlink(const QRect &r, const QUrl &url) ++{ ++ addHyperlink(QRectF(r), url); ++} + + inline void QPainter::setBrushOrigin(int x, int y) + { +diff --git a/src/gui/painting/qprintengine.h b/src/gui/painting/qprintengine.h +index da4fe2a..53015ec 100644 +--- a/src/gui/painting/qprintengine.h ++++ b/src/gui/painting/qprintengine.h +@@ -88,8 +88,11 @@ public: + PPK_PageMargins, + PPK_CopyCount, + PPK_SupportsMultipleCopies, +- PPK_PaperSize = PPK_PageSize, + ++ PPK_UseCompression, ++ PPK_ImageQuality, ++ PPK_ImageDPI, ++ PPK_PaperSize = PPK_PageSize, + PPK_CustomBase = 0xff00 + }; + +@@ -97,6 +100,8 @@ public: + virtual QVariant property(PrintEnginePropertyKey key) const = 0; + + virtual bool newPage() = 0; ++ virtual void beginSectionOutline(const QString &text, const QString &anchor) {Q_UNUSED(text); Q_UNUSED(anchor);} ++ virtual void endSectionOutline() {} + virtual bool abort() = 0; + + virtual int metric(QPaintDevice::PaintDeviceMetric) const = 0; +diff --git a/src/gui/painting/qprintengine_pdf.cpp b/src/gui/painting/qprintengine_pdf.cpp +index 2b0bca8..c44dd1b 100644 +--- a/src/gui/painting/qprintengine_pdf.cpp ++++ b/src/gui/painting/qprintengine_pdf.cpp +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + + #ifndef QT_NO_PRINTER + #include +@@ -77,12 +78,6 @@ extern qint64 qt_image_id(const QImage &image); + // Can't use it though, as gs generates completely wrong images if this is true. + static const bool interpolateImages = false; + +-#ifdef QT_NO_COMPRESS +-static const bool do_compress = false; +-#else +-static const bool do_compress = true; +-#endif +- + QPdfPage::QPdfPage() + : QPdf::ByteStream(true) // Enable file backing + { +@@ -109,6 +104,30 @@ inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() + return f; + } + ++void QPdfEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) { ++ Q_D(QPdfEngine); ++ if (key==PPK_UseCompression) ++ d->doCompress = value.toBool(); ++ else if (key==PPK_ImageQuality) ++ d->imageQuality = value.toInt(); ++ else if (key==PPK_ImageDPI) ++ d->imageDPI = value.toInt(); ++ else ++ QPdfBaseEngine::setProperty(key, value); ++} ++ ++QVariant QPdfEngine::property(PrintEnginePropertyKey key) const { ++ Q_D(const QPdfEngine); ++ if (key==PPK_UseCompression) ++ return d->doCompress; ++ else if (key==PPK_ImageQuality) ++ return d->imageQuality; ++ else if (key==PPK_ImageDPI) ++ return d->imageDPI; ++ else ++ return QPdfBaseEngine::property(key); ++} ++ + QPdfEngine::QPdfEngine(QPrinter::PrinterMode m) + : QPdfBaseEngine(*new QPdfEnginePrivate(m), qt_pdf_decide_features()) + { +@@ -156,6 +175,51 @@ bool QPdfEngine::begin(QPaintDevice *pdev) + bool QPdfEngine::end() + { + Q_D(QPdfEngine); ++ ++ if (d->outlineRoot) { ++ d->outlineRoot->obj = d->requestObject(); ++ d->writeOutlineChildren(d->outlineRoot); ++ d->addXrefEntry(d->outlineRoot->obj); ++ d->xprintf("<>\nendobj\n", ++ d->outlineRoot->firstChild->obj, d->outlineRoot->lastChild->obj); ++ } ++ ++ if (d->formFields.size()) { ++ uint font = d->addXrefEntry(-1); ++ d->xprintf("<>\n" ++ "endobj\n"); ++ d->addXrefEntry(d->formFieldList); ++ d->xprintf("<formFields) ++ d->xprintf("%d 0 R ",i); ++ d->xprintf("]\n" ++ "/DR<>>>\n" ++ "/DA(/Helv 0 Tf 0 g)\n" ++ ">>\n" ++ "endobj\n", font); ++ } ++ ++ d->catalog = d->addXrefEntry(-1); ++ d->xprintf("<<\n" ++ "/Type /Catalog\n" ++ "/Pages %d 0 R\n", d->pageRoot); ++ if (d->outlineRoot) ++ d->xprintf("/Outlines %d 0 R\n" ++ "/PageMode /UseOutlines\n", d->outlineRoot->obj); ++ if (d->formFields.size()) ++ d->xprintf("/AcroForm %d 0 R\n", d->formFieldList); ++ if (d->anchors.size()) { ++ d->xprintf("/Dests <<\n"); ++ for (QHash::iterator i=d->anchors.begin(); ++ i != d->anchors.end(); ++i) { ++ d->printAnchor(i.key()); ++ d->xprintf(" %d 0 R\n", i.value()); ++ } ++ d->xprintf(">>\n"); ++ } ++ d->xprintf(">>\n" ++ "endobj\n"); ++ + d->writeTail(); + + d->stream->unsetDevice(); +@@ -165,8 +229,83 @@ bool QPdfEngine::end() + return true; + } + ++void QPdfEngine::addCheckBox(const QRectF &r, bool checked, const QString &name, bool readOnly) { ++ Q_D(QPdfEngine); ++ uint obj = d->addXrefEntry(-1); ++ char buf[256]; ++ QRectF rr = d->pageMatrix().mapRect(r); ++ //Note that the pdf spec sayes that we should add some sort of default appearence atleast for yes, which we dont ghost script provides one, however acroread does not ++ if (d->formFieldList == -1) d->formFieldList = d->requestObject(); ++ d->xprintf("<<\n" ++ "/Type /Annot\n" ++ "/Parrent %d 0 R\n" ++ "/Rect[", d->formFieldList); ++ d->xprintf("%s ", qt_real_to_string(rr.left(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.top(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.right(),buf)); ++ d->xprintf("%s", qt_real_to_string(rr.bottom(),buf)); ++ d->xprintf("]\n" ++ "/FT/Btn\n" ++ "/Subtype/Widget\n" ++ "/P %d 0 R\n", d->pages.back()); ++ if (checked) ++ d->xprintf("/AS /Yes\n"); ++ if (!name.isEmpty()) { ++ d->xprintf("/T"); ++ d->printString(name); ++ d->xprintf("\n"); ++ } ++ d->xprintf("/Ff %d\n" ++ ">>\n" ++ "endobj\n", ++ (readOnly?1:0)<<0); ++ d->currentPage->annotations.push_back(obj); ++ d->formFields.push_back(obj); ++} + +-void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr) ++void QPdfEngine::addTextField(const QRectF &r, const QString &text, const QString &name, bool multiLine, bool password, bool readOnly, int maxLength) ++{ ++ Q_D(QPdfEngine); ++ uint obj = d->addXrefEntry(-1); ++ char buf[256]; ++ QRectF rr = d->pageMatrix().mapRect(r); ++ if (d->formFieldList == -1) d->formFieldList = d->requestObject(); ++ d->xprintf("<<\n" ++ "/Type /Annot\n" ++ "/Parrent %d 0 R\n" ++ "/Rect[", d->formFieldList); ++ d->xprintf("%s ", qt_real_to_string(rr.left(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.top(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.right(),buf)); ++ d->xprintf("%s", qt_real_to_string(rr.bottom(),buf)); ++ d->xprintf("]\n" ++ "/BS<>\n" ++ "/FT/Tx\n" ++ "/Subtype/Widget\n" ++ "/P %d 0 R\n", d->pages.back()); ++ if (!text.isEmpty()) { ++ d->xprintf("/V"); ++ d->printString(text); ++ d->xprintf("\n"); ++ } ++ if (!name.isEmpty()) { ++ d->xprintf("/T"); ++ d->printString(name); ++ d->xprintf("\n"); ++ } ++ if (maxLength >= 0) ++ d->xprintf("/MaxLen %d\n",maxLength); ++ d->xprintf("/DA(/Helv 12 Tf 0 g)\n" ++ "/Ff %d\n" ++ ">>\n" ++ "endobj\n", ++ (readOnly?1:0)<<0 | (password?1:0)<<13 | (multiLine?1:0)<<12 ++ ); ++ d->currentPage->annotations.push_back(obj); ++ d->formFields.push_back(obj); ++} ++ ++void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr, const QByteArray * data) + { + if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull()) + return; +@@ -176,22 +315,35 @@ void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, con + + QRect sourceRect = sr.toRect(); + QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap; +- QImage image = pm.toImage(); ++ QImage unscaled = pm.toImage(); ++ QImage image = unscaled; ++ ++ QRectF a = d->stroker.matrix.mapRect(rectangle); ++ QRectF c = d->paperRect(); ++ int maxWidth = int(a.width() / c.width() * d->width() / 72.0 * d->imageDPI); ++ int maxHeight = int(a.height() / c.height() * d->height() / 72.0 * d->imageDPI); ++ if (image.width() > maxWidth || image.height() > maxHeight) ++ image = unscaled.scaled( image.size().boundedTo( QSize(maxWidth, maxHeight) ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); ++ ++ bool useScaled=true; + bool bitmap = true; +- const int object = d->addImage(image, &bitmap, pm.cacheKey()); ++ const int object = d->addImage(image, &bitmap, pm.cacheKey(), &unscaled, (sr == pixmap.rect()?data:0), &useScaled ); ++ int width = useScaled?image.width():unscaled.width(); ++ int height = useScaled?image.height():unscaled.height(); ++ + if (object < 0) + return; + + *d->currentPage << "q\n/GSa gs\n"; + *d->currentPage +- << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), ++ << QPdf::generateMatrix(QTransform(rectangle.width() / width, 0, 0, rectangle.height() / height, + rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); + if (bitmap) { + // set current pen as d->brush + d->brush = d->pen.brush(); + } + setBrush(); +- d->currentPage->streamImage(image.width(), image.height(), object); ++ d->currentPage->streamImage(width, height, object); + *d->currentPage << "Q\n"; + + d->brush = b; +@@ -301,18 +453,67 @@ QPdfEnginePrivate::QPdfEnginePrivate(QPrinter::PrinterMode m) + : QPdfBaseEnginePrivate(m) + { + streampos = 0; ++ doCompress = true; ++ imageDPI = 1400; ++ imageQuality = 94; + + stream = new QDataStream; + pageOrder = QPrinter::FirstPageFirst; + orientation = QPrinter::Portrait; ++ outlineRoot = NULL; ++ outlineCurrent = NULL; + fullPage = false; + } + + QPdfEnginePrivate::~QPdfEnginePrivate() + { ++ if (outlineRoot) ++ delete outlineRoot; + delete stream; + } + ++void QPdfEnginePrivate::printAnchor(const QString &name) { ++ QByteArray a = name.toUtf8(); ++ if (a.size() >= 127) ++ a = QCryptographicHash::hash(a,QCryptographicHash::Sha1); ++ xprintf("/"); ++ for (int i=0; i < a.size(); ++i) { ++ unsigned char c = a[i]; ++ if (('a' <= c && c <= 'z') || ++ ('A' <= c && c <= 'Z') || ++ ('0' <= c && c <= '9') || ++ c == '.' || c == '_') ++ xprintf("%c", c); ++ else ++ xprintf("#%02x", c); ++ } ++} ++ ++void QPdfEnginePrivate::writeOutlineChildren(OutlineItem * node) { ++ for (OutlineItem * i = node->firstChild; i != NULL; i = i->next) ++ i->obj = requestObject(); ++ for (OutlineItem * i = node->firstChild; i != NULL; i = i->next) { ++ QPdfEnginePrivate::writeOutlineChildren(i); ++ addXrefEntry(i->obj); ++ xprintf("<text); ++ xprintf("\n" ++ " /Parent %d 0 R\n" ++ " /Dest ", i->parent->obj); ++ printAnchor(i->anchor); ++ xprintf("\n /Count 0\n"); ++ if (i->next) ++ xprintf(" /Next %d 0 R\n", i->next->obj); ++ if (i->prev) ++ xprintf(" /Prev %d 0 R\n", i->prev->obj); ++ if (i->firstChild) ++ xprintf(" /First %d 0 R\n", i->firstChild->obj); ++ if (i->lastChild) ++ xprintf(" /Last %d 0 R\n", i->lastChild->obj); ++ xprintf(">>\n" ++ "endobj\n"); ++ } ++} + + #ifdef USE_NATIVE_GRADIENTS + int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QMatrix &matrix, int *gStateObject) +@@ -520,10 +721,34 @@ int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, + return patternObj; + } + ++ ++void QPdfEnginePrivate::convertImage(const QImage & image, QByteArray & imageData) { ++ int w = image.width(); ++ int h = image.height(); ++ imageData.resize(colorMode == QPrinter::GrayScale ? w * h : 3 * w * h); ++ uchar *data = (uchar *)imageData.data(); ++ for (int y = 0; y < h; ++y) { ++ const QRgb *rgb = (const QRgb *)image.scanLine(y); ++ if (colorMode == QPrinter::GrayScale) { ++ for (int x = 0; x < w; ++x) { ++ *(data++) = qGray(*rgb); ++ ++rgb; ++ } ++ } else { ++ for (int x = 0; x < w; ++x) { ++ *(data++) = qRed(*rgb); ++ *(data++) = qGreen(*rgb); ++ *(data++) = qBlue(*rgb); ++ ++rgb; ++ } ++ } ++ } ++} ++ + /*! + * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. + */ +-int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_no) ++int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_no, const QImage * noneScaled, const QByteArray * data, bool * useScaled) + { + if (img.isNull()) + return -1; +@@ -564,65 +789,91 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_n + } + object = writeImage(data, w, h, d, 0, 0); + } else { +- QByteArray softMaskData; +- bool dct = false; + QByteArray imageData; +- bool hasAlpha = false; +- bool hasMask = false; +- ++ uLongf target=1024*1024*1024; ++ bool uns=false; ++ bool dct = false; + if (QImageWriter::supportedImageFormats().contains("jpeg") && colorMode != QPrinter::GrayScale) { +- QBuffer buffer(&imageData); ++ QByteArray imageData2; ++ ++ QBuffer buffer(&imageData2); + QImageWriter writer(&buffer, "jpeg"); +- writer.setQuality(94); ++ writer.setQuality(imageQuality); + writer.write(image); +- dct = true; ++ ++ if ((uLongf)imageData2.size() < target) { ++ imageData=imageData2; ++ target = imageData2.size(); ++ dct = true; ++ uns=false; ++ } ++ } + +- if (format != QImage::Format_RGB32) { +- softMaskData.resize(w * h); +- uchar *sdata = (uchar *)softMaskData.data(); +- for (int y = 0; y < h; ++y) { +- const QRgb *rgb = (const QRgb *)image.scanLine(y); +- for (int x = 0; x < w; ++x) { +- uchar alpha = qAlpha(*rgb); +- *sdata++ = alpha; +- hasMask |= (alpha < 255); +- hasAlpha |= (alpha != 0 && alpha != 255); +- ++rgb; +- } +- } ++ if (noneScaled && noneScaled->rect() != image.rect()) { ++ QByteArray imageData2; ++ convertImage(*noneScaled, imageData2); ++ uLongf len = imageData2.size(); ++ uLongf destLen = len + len/100 + 13; // zlib requirement ++ Bytef* dest = new Bytef[destLen]; ++ if (Z_OK == ::compress(dest, &destLen, (const Bytef*) imageData2.data(), (uLongf)len) && ++ (uLongf)destLen < target) { ++ imageData=imageData2; ++ target=destLen; ++ dct=false; ++ uns=true; + } +- } else { +- imageData.resize(colorMode == QPrinter::GrayScale ? w * h : 3 * w * h); +- uchar *data = (uchar *)imageData.data(); ++ delete[] dest; ++ } ++ ++ { ++ QByteArray imageData2; ++ convertImage(image, imageData2); ++ uLongf len = imageData2.size(); ++ uLongf destLen = len + len/100 + 13; // zlib requirement ++ Bytef* dest = new Bytef[destLen]; ++ if (Z_OK == ::compress(dest, &destLen, (const Bytef*) imageData2.data(), (uLongf)len) && ++ (uLongf)destLen < target) { ++ imageData=imageData2; ++ target=destLen; ++ dct=false; ++ uns=false; ++ } ++ delete[] dest; ++ } ++ ++ if (colorMode != QPrinter::GrayScale && noneScaled != 0 && data != 0 && ++ data->size() > 2 && (unsigned char)data->data()[0] == 0xff && (unsigned char)data->data()[1] == 0xd8 && ++ (uLongf)data->size()*10 < target*13) { ++ imageData = *data; ++ target=data->size(); ++ dct=true; ++ uns=true; ++ } ++ ++ if (uns) { ++ w = noneScaled->width(); ++ h = noneScaled->height(); ++ } ++ if (useScaled) *useScaled = (uns?false:true); ++ QByteArray softMaskData; ++ bool hasAlpha = false; ++ bool hasMask = false; ++ ++ if (format != QImage::Format_RGB32) { + softMaskData.resize(w * h); + uchar *sdata = (uchar *)softMaskData.data(); + for (int y = 0; y < h; ++y) { +- const QRgb *rgb = (const QRgb *)image.scanLine(y); +- if (colorMode == QPrinter::GrayScale) { +- for (int x = 0; x < w; ++x) { +- *(data++) = qGray(*rgb); +- uchar alpha = qAlpha(*rgb); +- *sdata++ = alpha; +- hasMask |= (alpha < 255); +- hasAlpha |= (alpha != 0 && alpha != 255); +- ++rgb; +- } +- } else { +- for (int x = 0; x < w; ++x) { +- *(data++) = qRed(*rgb); +- *(data++) = qGreen(*rgb); +- *(data++) = qBlue(*rgb); +- uchar alpha = qAlpha(*rgb); +- *sdata++ = alpha; +- hasMask |= (alpha < 255); +- hasAlpha |= (alpha != 0 && alpha != 255); +- ++rgb; +- } ++ const QRgb *rgb = (const QRgb *)(uns?noneScaled->scanLine(y):image.scanLine(y)); ++ for (int x = 0; x < w; ++x) { ++ uchar alpha = qAlpha(*rgb); ++ *sdata++ = alpha; ++ hasMask |= (alpha < 255); ++ hasAlpha |= (alpha != 0 && alpha != 255); ++ ++rgb; + } + } +- if (format == QImage::Format_RGB32) +- hasAlpha = hasMask = false; + } ++ + int maskObject = 0; + int softMaskObject = 0; + if (hasAlpha) { +@@ -754,7 +1005,7 @@ void QPdfEnginePrivate::xprintf(const char* fmt, ...) + int QPdfEnginePrivate::writeCompressed(QIODevice *dev) + { + #ifndef QT_NO_COMPRESS +- if (do_compress) { ++ if (doCompress) { + int size = QPdfPage::chunkSize(); + int sum = 0; + ::z_stream zStruct; +@@ -828,7 +1079,7 @@ int QPdfEnginePrivate::writeCompressed(QIODevice *dev) + int QPdfEnginePrivate::writeCompressed(const char *src, int len) + { + #ifndef QT_NO_COMPRESS +- if(do_compress) { ++ if(doCompress) { + uLongf destLen = len + len/100 + 13; // zlib requirement + Bytef* dest = new Bytef[destLen]; + if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) { +@@ -881,7 +1132,7 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, + write(data); + len = data.length(); + } else { +- if (do_compress) ++ if (doCompress) + xprintf("/Filter /FlateDecode\n>>\nstream\n"); + else + xprintf(">>\nstream\n"); +@@ -904,14 +1155,9 @@ void QPdfEnginePrivate::writeHeader() + + writeInfo(); + +- catalog = addXrefEntry(-1); + pageRoot = requestObject(); +- xprintf("<<\n" +- "/Type /Catalog\n" +- "/Pages %d 0 R\n" +- ">>\n" +- "endobj\n", pageRoot); +- ++ ++ formFieldList = -1; + // graphics state + graphicsState = addXrefEntry(-1); + xprintf("<<\n" +@@ -943,13 +1189,22 @@ void QPdfEnginePrivate::writeInfo() + QDateTime now = QDateTime::currentDateTime().toUTC(); + QTime t = now.time(); + QDate d = now.date(); +- xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d)\n", ++ xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d)", + d.year(), + d.month(), + d.day(), + t.hour(), + t.minute(), + t.second()); ++ QDateTime fake=now; ++ fake.setTimeSpec(Qt::UTC); ++ int offset = now.secsTo(fake); ++ if (offset == 0) ++ xprintf("Z)\n"); ++ else if (offset < 0) ++ xprintf("-%02d'%02d')\n", (-offset)/60/60 , ((-offset)/60) % 60); ++ else if (offset > 0) ++ xprintf("+%02d'%02d')\n", offset/60/60 , (offset/60) % 60); + xprintf(">>\n" + "endobj\n"); + } +@@ -1035,7 +1290,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) + s << "<<\n" + "/Length1 " << fontData.size() << "\n" + "/Length " << length_object << "0 R\n"; +- if (do_compress) ++ if (doCompress) + s << "/Filter /FlateDecode\n"; + s << ">>\n" + "stream\n"; +@@ -1097,6 +1352,101 @@ void QPdfEnginePrivate::writeFonts() + fonts.clear(); + } + ++ ++void QPdfEngine::addHyperlink(const QRectF &r, const QUrl &url) ++{ ++ Q_D(QPdfEngine); ++ char buf[256]; ++ QRectF rr = d->pageMatrix().mapRect(r); ++ uint annot = d->addXrefEntry(-1); ++ QByteArray urlascii = url.toString().toLatin1(); ++ int len = urlascii.size(); ++ char *url_esc = new char[len * 2 + 1]; ++ const char * urldata = urlascii.constData(); ++ int k = 0; ++ for (int j = 0; j < len; j++, k++){ ++ if (urldata[j] == '(' || ++ urldata[j] == ')' || ++ urldata[j] == '\\'){ ++ url_esc[k] = '\\'; ++ k++; ++ } ++ url_esc[k] = urldata[j]; ++ } ++ url_esc[k] = 0; ++ d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect ["); ++ d->xprintf("%s ", qt_real_to_string(rr.left(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.top(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.right(),buf)); ++ d->xprintf("%s", qt_real_to_string(rr.bottom(),buf)); ++ d->xprintf("]\n/Border [0 0 0]\n/A <<\n"); ++ d->xprintf("/Type /Action\n/S /URI\n/URI (%s)\n", url_esc); ++ d->xprintf(">>\n>>\n"); ++ d->xprintf("endobj\n"); ++ d->currentPage->annotations.append(annot); ++ delete[] url_esc; ++} ++ ++void QPdfEngine::addLink(const QRectF &r, const QString &anchor) ++{ ++ Q_D(QPdfEngine); ++ char buf[256]; ++ QRectF rr = d->pageMatrix().mapRect(r); ++ uint annot = d->addXrefEntry(-1); ++ d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect ["); ++ d->xprintf("%s ", qt_real_to_string(rr.left(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.top(),buf)); ++ d->xprintf("%s ", qt_real_to_string(rr.right(),buf)); ++ d->xprintf("%s", qt_real_to_string(rr.bottom(),buf)); ++ d->xprintf("]\n/Border [0 0 0]\n/Dest "); ++ d->printAnchor(anchor); ++ d->xprintf("\n>>\n"); ++ d->xprintf("endobj\n"); ++ d->currentPage->annotations.append(annot); ++} ++ ++void QPdfEngine::addAnchor(const QRectF &r, const QString &name) ++{ ++ Q_D(QPdfEngine); ++ char buf[256]; ++ QRectF rr = d->pageMatrix().mapRect(r); ++ uint anchor = d->addXrefEntry(-1); ++ d->xprintf("[%d /XYZ %s \n", ++ d->pages.size() - 1, ++ qt_real_to_string(rr.left(), buf)); ++ d->xprintf("%s 0]\n", ++ qt_real_to_string(rr.bottom(), buf)); ++ d->xprintf("endobj\n"); ++ d->anchors[name] = anchor; ++} ++ ++void QPdfEngine::beginSectionOutline(const QString &text, const QString &anchor) ++{ ++ Q_D(QPdfEngine); ++ if (d->outlineCurrent == NULL) { ++ if (d->outlineRoot) ++ delete d->outlineRoot; ++ d->outlineCurrent = d->outlineRoot = new QPdfEnginePrivate::OutlineItem(QString(), QString()); ++ } ++ ++ QPdfEnginePrivate::OutlineItem *i = new QPdfEnginePrivate::OutlineItem(text, anchor); ++ i->parent = d->outlineCurrent; ++ i->prev = d->outlineCurrent->lastChild; ++ if (d->outlineCurrent->firstChild) ++ d->outlineCurrent->lastChild->next = i; ++ else ++ d->outlineCurrent->firstChild = i; ++ d->outlineCurrent->lastChild = i; ++ d->outlineCurrent = i; ++} ++ ++void QPdfEngine::endSectionOutline() ++{ ++ Q_D(QPdfEngine); ++ if (d->outlineCurrent) ++ d->outlineCurrent = d->outlineCurrent->parent; ++} ++ + void QPdfEnginePrivate::writePage() + { + if (pages.empty()) +@@ -1167,7 +1517,7 @@ void QPdfEnginePrivate::writePage() + addXrefEntry(pageStream); + xprintf("<<\n" + "/Length %d 0 R\n", pageStreamLength); // object number for stream length object +- if (do_compress) ++ if (doCompress) + xprintf("/Filter /FlateDecode\n"); + + xprintf(">>\n"); +diff --git a/src/gui/painting/qprintengine_pdf_p.h b/src/gui/painting/qprintengine_pdf_p.h +index ee77e15..d0d87fd 100644 +--- a/src/gui/painting/qprintengine_pdf_p.h ++++ b/src/gui/painting/qprintengine_pdf_p.h +@@ -1,3 +1,4 @@ ++ + /**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +@@ -92,7 +93,12 @@ public: + // reimplementations QPaintEngine + bool begin(QPaintDevice *pdev); + bool end(); +- void drawPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QRectF & sr); ++ ++ void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr, const QByteArray * data=0); ++ void drawPixmap(const QRectF & rectangle, const QPixmap & pixmap, const QRectF & sr) { ++ drawPixmap(rectangle, pixmap, sr, 0); ++ } ++ + void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + void drawTiledPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QPointF & point); +@@ -108,12 +114,23 @@ public: + + void setBrush(); + ++ virtual void addHyperlink(const QRectF &r, const QUrl &url); ++ virtual void addAnchor(const QRectF &r, const QString &name); ++ virtual void addLink(const QRectF &r, const QString &anchor); ++ virtual void addTextField(const QRectF &r, const QString &text, const QString &name, bool multiLine, bool password, bool readOnly, int maxLength); ++ virtual void addCheckBox(const QRectF &r, bool checked, const QString &name, bool readOnly); ++ + // ### unused, should have something for this in QPrintEngine + void setAuthor(const QString &author); + QString author() const; + + void setDevice(QIODevice* dev); + ++ void beginSectionOutline(const QString &text, const QString &anchor); ++ void endSectionOutline(); ++ ++ void setProperty(PrintEnginePropertyKey key, const QVariant &value); ++ QVariant property(PrintEnginePropertyKey key) const; + private: + Q_DISABLE_COPY(QPdfEngine) + +@@ -124,6 +141,35 @@ class QPdfEnginePrivate : public QPdfBaseEnginePrivate + { + Q_DECLARE_PUBLIC(QPdfEngine) + public: ++ ++ class OutlineItem { ++ public: ++ OutlineItem *parent; ++ OutlineItem *next; ++ OutlineItem *prev; ++ OutlineItem *firstChild; ++ OutlineItem *lastChild; ++ uint obj; ++ QString text; ++ QString anchor; ++ ++ OutlineItem(const QString &t, const QString &a): ++ parent(NULL), next(NULL), prev(NULL), firstChild(NULL), lastChild(NULL), ++ obj(0), text(t), anchor(a) {} ++ ~OutlineItem() { ++ OutlineItem *i = firstChild; ++ while(i != NULL) { ++ OutlineItem *n = i->next; ++ delete i; ++ i=n; ++ } ++ } ++ }; ++ ++ OutlineItem *outlineRoot; ++ OutlineItem *outlineCurrent; ++ void writeOutlineChildren(OutlineItem *node); ++ + QPdfEnginePrivate(QPrinter::PrinterMode m); + ~QPdfEnginePrivate(); + +@@ -141,7 +187,9 @@ public: + void writeHeader(); + void writeTail(); + +- int addImage(const QImage &image, bool *bitmap, qint64 serial_no); ++ void convertImage(const QImage & image, QByteArray & imageData); ++ ++ int addImage(const QImage &image, bool *bitmap, qint64 serial_no, const QImage * noneScaled=0, const QByteArray * data=0, bool * useScaled=0); + int addConstantAlphaObject(int brushAlpha, int penAlpha = 255); + int addBrushPattern(const QTransform &matrix, bool *specifyColor, int *gStateObject); + +@@ -161,16 +209,24 @@ private: + void writeFonts(); + void embedFont(QFontSubset *font); + ++ int formFieldList; ++ QVector formFields; + QVector xrefPositions; + QDataStream* stream; + int streampos; ++ bool doCompress; ++ int imageDPI; ++ int imageQuality; + + int writeImage(const QByteArray &data, int width, int height, int depth, + int maskObject, int softMaskObject, bool dct = false); + void writePage(); + + int addXrefEntry(int object, bool printostr = true); ++ + void printString(const QString &string); ++ void printAnchor(const QString &name); ++ + void xprintf(const char* fmt, ...); + inline void write(const QByteArray &data) { + stream->writeRawData(data.constData(), data.size()); +@@ -183,6 +239,8 @@ private: + + // various PDF objects + int pageRoot, catalog, info, graphicsState, patternColorSpace; ++ QVector dests; ++ QHash anchors; + QVector pages; + QHash imageCache; + QHash, uint > alphaCache; +diff --git a/src/gui/painting/qprinter.cpp b/src/gui/painting/qprinter.cpp +index 74a8f6a..e8b40fa 100644 +--- a/src/gui/painting/qprinter.cpp ++++ b/src/gui/painting/qprinter.cpp +@@ -933,6 +933,39 @@ void QPrinter::setOutputFileName(const QString &fileName) + d->addToManualSetList(QPrintEngine::PPK_OutputFileName); + } + ++/*! ++ Add a section to the document outline. All following sections will be added ++ to as subsections to this section, until endSectionOutline() has been called. ++ ++ \a name is the name of the added section. \a anchor is the name of an anchor ++ indicating the beginning of the section. This anchor must be added by calling ++ QPainter::addAnchor(). ++ ++ Note that for output formats not supporting outlines, currently all other then PDF, ++ this call has no effect. ++ ++ \sa endSectionOutline() QPainter::addAnchor() ++ ++ \since 4.7 ++*/ ++void QPrinter::beginSectionOutline(const QString &name, const QString &anchor) ++{ ++ Q_D(QPrinter); ++ d->printEngine->beginSectionOutline(name, anchor); ++} ++ ++/*! ++ End the current section. ++ ++ \sa beginSectionOutline() ++ ++ \since 4.7 ++*/ ++void QPrinter::endSectionOutline() ++{ ++ Q_D(QPrinter); ++ d->printEngine->endSectionOutline(); ++} + + /*! + Returns the name of the program that sends the print output to the +diff --git a/src/gui/painting/qprinter.h b/src/gui/painting/qprinter.h +index 58db612..457bd13 100644 +--- a/src/gui/painting/qprinter.h ++++ b/src/gui/painting/qprinter.h +@@ -147,6 +147,9 @@ public: + enum PrinterOption { PrintToFile, PrintSelection, PrintPageRange }; + #endif // QT3_SUPPORT + ++ void beginSectionOutline(const QString &text, const QString &anchor); ++ void endSectionOutline(); ++ + void setOutputFormat(OutputFormat format); + OutputFormat outputFormat() const; + +diff --git a/src/gui/styles/qstyle.cpp b/src/gui/styles/qstyle.cpp +index c5eb330..cf3a2f7 100644 +--- a/src/gui/styles/qstyle.cpp ++++ b/src/gui/styles/qstyle.cpp +@@ -47,6 +47,7 @@ + #include "qpixmapcache.h" + #include "qstyleoption.h" + #include "private/qstyle_p.h" ++#include "private/qapplication_p.h" + #ifndef QT_NO_DEBUG + #include "qdebug.h" + #endif +@@ -2229,7 +2230,7 @@ QPalette QStyle::standardPalette() const + { + #ifdef Q_WS_X11 + QColor background; +- if (QX11Info::appDepth() > 8) ++ if (!qt_is_gui_used || QX11Info::appDepth() > 8) + background = QColor(0xd4, 0xd0, 0xc8); // win 2000 grey + else + background = QColor(192, 192, 192); +diff --git a/src/plugins/platforms/minimal/minimal.pro b/src/plugins/platforms/minimal/minimal.pro +index 438a88e..2886996 100644 +--- a/src/plugins/platforms/minimal/minimal.pro ++++ b/src/plugins/platforms/minimal/minimal.pro +@@ -5,9 +5,15 @@ QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/platforms + + SOURCES = main.cpp \ + qminimalintegration.cpp \ +- qminimalwindowsurface.cpp ++ qminimalwindowsurface.cpp \ ++ qfontconfigdatabase.cpp ++ + HEADERS = qminimalintegration.h \ +- qminimalwindowsurface.h ++ qminimalwindowsurface.h \ ++ qfontconfigdatabase.h ++ ++include(../fontdatabases/basicunix/basicunix.pri) + + target.path += $$[QT_INSTALL_PLUGINS]/platforms + INSTALLS += target ++LIBS += -lfontconfig +\ No newline at end of file +diff --git a/src/plugins/platforms/minimal/qfontconfigdatabase.cpp b/src/plugins/platforms/minimal/qfontconfigdatabase.cpp +new file mode 100644 +index 0000000..377d655 +--- /dev/null ++++ b/src/plugins/platforms/minimal/qfontconfigdatabase.cpp +@@ -0,0 +1,601 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ++** All rights reserved. ++** Contact: Nokia Corporation (qt-info@nokia.com) ++** ++** This file is part of the plugins of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** GNU Lesser General Public License Usage ++** This file may be used under the terms of the GNU Lesser General Public ++** License version 2.1 as published by the Free Software Foundation and ++** appearing in the file LICENSE.LGPL included in the packaging of this ++** file. Please review the following information to ensure the GNU Lesser ++** General Public License version 2.1 requirements will be met: ++** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ++** ++** In addition, as a special exception, Nokia gives you certain additional ++** rights. These rights are described in the Nokia Qt LGPL Exception ++** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU General ++** Public License version 3.0 as published by the Free Software Foundation ++** and appearing in the file LICENSE.GPL included in the packaging of this ++** file. Please review the following information to ensure the GNU General ++** Public License version 3.0 requirements will be met: ++** http://www.gnu.org/copyleft/gpl.html. ++** ++** Other Usage ++** Alternatively, this file may be used in accordance with the terms and ++** conditions contained in a signed written agreement between you and Nokia. ++** ++** ++** ++** ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "qfontconfigdatabase.h" ++ ++#include ++#include ++ ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++ ++ ++#include ++#include FT_TRUETYPE_TABLES_H ++ ++#include ++ ++#define SimplifiedChineseCsbBit 18 ++#define TraditionalChineseCsbBit 20 ++#define JapaneseCsbBit 17 ++#define KoreanCsbBit 21 ++ ++static inline bool requiresOpenType(int writingSystem) ++{ ++ return ((writingSystem >= QFontDatabase::Syriac && writingSystem <= QFontDatabase::Sinhala) ++ || writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko); ++} ++static inline bool scriptRequiresOpenType(int script) ++{ ++ return ((script >= QUnicodeTables::Syriac && script <= QUnicodeTables::Sinhala) ++ || script == QUnicodeTables::Khmer || script == QUnicodeTables::Nko); ++} ++ ++static int getFCWeight(int fc_weight) ++{ ++ int qtweight = QFont::Black; ++ if (fc_weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_MEDIUM) / 2) ++ qtweight = QFont::Light; ++ else if (fc_weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) ++ qtweight = QFont::Normal; ++ else if (fc_weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) ++ qtweight = QFont::DemiBold; ++ else if (fc_weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_BLACK) / 2) ++ qtweight = QFont::Bold; ++ ++ return qtweight; ++} ++ ++static const char *specialLanguages[] = { ++ "en", // Common ++ "el", // Greek ++ "ru", // Cyrillic ++ "hy", // Armenian ++ "he", // Hebrew ++ "ar", // Arabic ++ "syr", // Syriac ++ "div", // Thaana ++ "hi", // Devanagari ++ "bn", // Bengali ++ "pa", // Gurmukhi ++ "gu", // Gujarati ++ "or", // Oriya ++ "ta", // Tamil ++ "te", // Telugu ++ "kn", // Kannada ++ "ml", // Malayalam ++ "si", // Sinhala ++ "th", // Thai ++ "lo", // Lao ++ "bo", // Tibetan ++ "my", // Myanmar ++ "ka", // Georgian ++ "ko", // Hangul ++ "", // Ogham ++ "", // Runic ++ "km", // Khmer ++ "" // N'Ko ++}; ++enum { SpecialLanguageCount = sizeof(specialLanguages) / sizeof(const char *) }; ++ ++static const ushort specialChars[] = { ++ 0, // English ++ 0, // Greek ++ 0, // Cyrillic ++ 0, // Armenian ++ 0, // Hebrew ++ 0, // Arabic ++ 0, // Syriac ++ 0, // Thaana ++ 0, // Devanagari ++ 0, // Bengali ++ 0, // Gurmukhi ++ 0, // Gujarati ++ 0, // Oriya ++ 0, // Tamil ++ 0xc15, // Telugu ++ 0xc95, // Kannada ++ 0xd15, // Malayalam ++ 0xd9a, // Sinhala ++ 0, // Thai ++ 0, // Lao ++ 0, // Tibetan ++ 0x1000, // Myanmar ++ 0, // Georgian ++ 0, // Hangul ++ 0x1681, // Ogham ++ 0x16a0, // Runic ++ 0, // Khmer ++ 0x7ca // N'Ko ++}; ++enum { SpecialCharCount = sizeof(specialChars) / sizeof(ushort) }; ++ ++// this could become a list of all languages used for each writing ++// system, instead of using the single most common language. ++static const char *languageForWritingSystem[] = { ++ 0, // Any ++ "en", // Latin ++ "el", // Greek ++ "ru", // Cyrillic ++ "hy", // Armenian ++ "he", // Hebrew ++ "ar", // Arabic ++ "syr", // Syriac ++ "div", // Thaana ++ "hi", // Devanagari ++ "bn", // Bengali ++ "pa", // Gurmukhi ++ "gu", // Gujarati ++ "or", // Oriya ++ "ta", // Tamil ++ "te", // Telugu ++ "kn", // Kannada ++ "ml", // Malayalam ++ "si", // Sinhala ++ "th", // Thai ++ "lo", // Lao ++ "bo", // Tibetan ++ "my", // Myanmar ++ "ka", // Georgian ++ "km", // Khmer ++ "zh-cn", // SimplifiedChinese ++ "zh-tw", // TraditionalChinese ++ "ja", // Japanese ++ "ko", // Korean ++ "vi", // Vietnamese ++ 0, // Symbol ++ 0, // Ogham ++ 0, // Runic ++ 0 // N'Ko ++}; ++enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) }; ++ ++// Unfortunately FontConfig doesn't know about some languages. We have to test these through the ++// charset. The lists below contain the systems where we need to do this. ++static const ushort sampleCharForWritingSystem[] = { ++ 0, // Any ++ 0, // Latin ++ 0, // Greek ++ 0, // Cyrillic ++ 0, // Armenian ++ 0, // Hebrew ++ 0, // Arabic ++ 0, // Syriac ++ 0, // Thaana ++ 0, // Devanagari ++ 0, // Bengali ++ 0, // Gurmukhi ++ 0, // Gujarati ++ 0, // Oriya ++ 0, // Tamil ++ 0xc15, // Telugu ++ 0xc95, // Kannada ++ 0xd15, // Malayalam ++ 0xd9a, // Sinhala ++ 0, // Thai ++ 0, // Lao ++ 0, // Tibetan ++ 0x1000, // Myanmar ++ 0, // Georgian ++ 0, // Khmer ++ 0, // SimplifiedChinese ++ 0, // TraditionalChinese ++ 0, // Japanese ++ 0, // Korean ++ 0, // Vietnamese ++ 0, // Symbol ++ 0x1681, // Ogham ++ 0x16a0, // Runic ++ 0x7ca // N'Ko ++}; ++enum { SampleCharCount = sizeof(sampleCharForWritingSystem) / sizeof(ushort) }; ++ ++// Newer FontConfig let's us sort out fonts that contain certain glyphs, but no ++// open type tables for is directly. Do this so we don't pick some strange ++// pseudo unicode font ++static const char *openType[] = { ++ 0, // Any ++ 0, // Latin ++ 0, // Greek ++ 0, // Cyrillic ++ 0, // Armenian ++ 0, // Hebrew ++ 0, // Arabic ++ "syrc", // Syriac ++ "thaa", // Thaana ++ "deva", // Devanagari ++ "beng", // Bengali ++ "guru", // Gurmukhi ++ "gurj", // Gujarati ++ "orya", // Oriya ++ "taml", // Tamil ++ "telu", // Telugu ++ "knda", // Kannada ++ "mlym", // Malayalam ++ "sinh", // Sinhala ++ 0, // Thai ++ 0, // Lao ++ "tibt", // Tibetan ++ "mymr", // Myanmar ++ 0, // Georgian ++ "khmr", // Khmer ++ 0, // SimplifiedChinese ++ 0, // TraditionalChinese ++ 0, // Japanese ++ 0, // Korean ++ 0, // Vietnamese ++ 0, // Symbol ++ 0, // Ogham ++ 0, // Runic ++ "nko " // N'Ko ++}; ++ ++static const char *getFcFamilyForStyleHint(const QFont::StyleHint style) ++{ ++ const char *stylehint = 0; ++ switch (style) { ++ case QFont::SansSerif: ++ stylehint = "sans-serif"; ++ break; ++ case QFont::Serif: ++ stylehint = "serif"; ++ break; ++ case QFont::TypeWriter: ++ stylehint = "monospace"; ++ break; ++ default: ++ break; ++ } ++ return stylehint; ++} ++ ++void QFontconfigDatabase::populateFontDatabase() ++{ ++ FcFontSet *fonts; ++ ++ QString familyName; ++ FcChar8 *value = 0; ++ int weight_value; ++ int slant_value; ++ int spacing_value; ++ FcChar8 *file_value; ++ int indexValue; ++ FcChar8 *foundry_value; ++ FcBool scalable; ++ FcBool antialias; ++ ++ { ++ FcObjectSet *os = FcObjectSetCreate(); ++ FcPattern *pattern = FcPatternCreate(); ++ const char *properties [] = { ++ FC_FAMILY, FC_WEIGHT, FC_SLANT, ++ FC_SPACING, FC_FILE, FC_INDEX, ++ FC_LANG, FC_CHARSET, FC_FOUNDRY, FC_SCALABLE, FC_PIXEL_SIZE, FC_WEIGHT, ++ FC_WIDTH, ++#if FC_VERSION >= 20297 ++ FC_CAPABILITY, ++#endif ++ (const char *)0 ++ }; ++ const char **p = properties; ++ while (*p) { ++ FcObjectSetAdd(os, *p); ++ ++p; ++ } ++ fonts = FcFontList(0, pattern, os); ++ FcObjectSetDestroy(os); ++ FcPatternDestroy(pattern); ++ } ++ ++ for (int i = 0; i < fonts->nfont; i++) { ++ if (FcPatternGetString(fonts->fonts[i], FC_FAMILY, 0, &value) != FcResultMatch) ++ continue; ++ // capitalize(value); ++ familyName = QString::fromUtf8((const char *)value); ++ slant_value = FC_SLANT_ROMAN; ++ weight_value = FC_WEIGHT_MEDIUM; ++ spacing_value = FC_PROPORTIONAL; ++ file_value = 0; ++ indexValue = 0; ++ scalable = FcTrue; ++ ++ ++ if (FcPatternGetInteger (fonts->fonts[i], FC_SLANT, 0, &slant_value) != FcResultMatch) ++ slant_value = FC_SLANT_ROMAN; ++ if (FcPatternGetInteger (fonts->fonts[i], FC_WEIGHT, 0, &weight_value) != FcResultMatch) ++ weight_value = FC_WEIGHT_MEDIUM; ++ if (FcPatternGetInteger (fonts->fonts[i], FC_SPACING, 0, &spacing_value) != FcResultMatch) ++ spacing_value = FC_PROPORTIONAL; ++ if (FcPatternGetString (fonts->fonts[i], FC_FILE, 0, &file_value) != FcResultMatch) ++ file_value = 0; ++ if (FcPatternGetInteger (fonts->fonts[i], FC_INDEX, 0, &indexValue) != FcResultMatch) ++ indexValue = 0; ++ if (FcPatternGetBool(fonts->fonts[i], FC_SCALABLE, 0, &scalable) != FcResultMatch) ++ scalable = FcTrue; ++ if (FcPatternGetString(fonts->fonts[i], FC_FOUNDRY, 0, &foundry_value) != FcResultMatch) ++ foundry_value = 0; ++ if(FcPatternGetBool(fonts->fonts[i],FC_ANTIALIAS,0,&antialias) != FcResultMatch) ++ antialias = true; ++ ++ QSupportedWritingSystems writingSystems; ++ FcLangSet *langset = 0; ++ FcResult res = FcPatternGetLangSet(fonts->fonts[i], FC_LANG, 0, &langset); ++ if (res == FcResultMatch) { ++ for (int i = 1; i < LanguageCount; ++i) { ++ const FcChar8 *lang = (const FcChar8*) languageForWritingSystem[i]; ++ if (lang) { ++ FcLangResult langRes = FcLangSetHasLang(langset, lang); ++ if (langRes != FcLangDifferentLang) ++ writingSystems.setSupported(QFontDatabase::WritingSystem(i)); ++ } ++ } ++ } else { ++ // we set Other to supported for symbol fonts. It makes no ++ // sense to merge these with other ones, as they are ++ // special in a way. ++ writingSystems.setSupported(QFontDatabase::Other); ++ } ++ ++ FcCharSet *cs = 0; ++ res = FcPatternGetCharSet(fonts->fonts[i], FC_CHARSET, 0, &cs); ++ if (res == FcResultMatch) { ++ // some languages are not supported by FontConfig, we rather check the ++ // charset to detect these ++ for (int i = 1; i < SampleCharCount; ++i) { ++ if (!sampleCharForWritingSystem[i]) ++ continue; ++ if (FcCharSetHasChar(cs, sampleCharForWritingSystem[i])) ++ writingSystems.setSupported(QFontDatabase::WritingSystem(i)); ++ } ++ } ++ ++#if FC_VERSION >= 20297 ++ for (int j = 1; j < LanguageCount; ++j) { ++ if (writingSystems.supported(QFontDatabase::WritingSystem(j)) ++ && requiresOpenType(j) && openType[j]) { ++ FcChar8 *cap; ++ res = FcPatternGetString (fonts->fonts[i], FC_CAPABILITY, 0, &cap); ++ if (res != FcResultMatch || !strstr((const char *)cap, openType[j])) ++ writingSystems.setSupported(QFontDatabase::WritingSystem(j),false); ++ } ++ } ++#endif ++ ++ FontFile *fontFile = new FontFile; ++ fontFile->fileName = QLatin1String((const char *)file_value); ++ fontFile->indexValue = indexValue; ++ ++ QFont::Style style = (slant_value == FC_SLANT_ITALIC) ++ ? QFont::StyleItalic ++ : ((slant_value == FC_SLANT_OBLIQUE) ++ ? QFont::StyleOblique ++ : QFont::StyleNormal); ++ QFont::Weight weight = QFont::Weight(getFCWeight(weight_value)); ++ ++ double pixel_size = 0; ++ if (!scalable) { ++ int width = 100; ++ FcPatternGetInteger (fonts->fonts[i], FC_WIDTH, 0, &width); ++ FcPatternGetDouble (fonts->fonts[i], FC_PIXEL_SIZE, 0, &pixel_size); ++ } ++ ++ QFont::Stretch stretch = QFont::Unstretched; ++ QPlatformFontDatabase::registerFont(familyName,QLatin1String((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,writingSystems,fontFile); ++// qDebug() << familyName << (const char *)foundry_value << weight << style << &writingSystems << scalable << true << pixel_size; ++ } ++ ++ FcFontSetDestroy (fonts); ++ ++ struct FcDefaultFont { ++ const char *qtname; ++ const char *rawname; ++ bool fixed; ++ }; ++ const FcDefaultFont defaults[] = { ++ { "Serif", "serif", false }, ++ { "Sans Serif", "sans-serif", false }, ++ { "Monospace", "monospace", true }, ++ { 0, 0, false } ++ }; ++ const FcDefaultFont *f = defaults; ++ // aliases only make sense for 'common', not for any of the specials ++ QSupportedWritingSystems ws; ++ ws.setSupported(QFontDatabase::Latin); ++ ++ ++ while (f->qtname) { ++ registerFont(f->qtname,QLatin1String(""),QFont::Normal,QFont::StyleNormal,QFont::Unstretched,true,true,0,ws,0); ++ registerFont(f->qtname,QLatin1String(""),QFont::Normal,QFont::StyleItalic,QFont::Unstretched,true,true,0,ws,0); ++ registerFont(f->qtname,QLatin1String(""),QFont::Normal,QFont::StyleOblique,QFont::Unstretched,true,true,0,ws,0); ++ ++f; ++ } ++ ++ //Lighthouse has very lazy population of the font db. We want it to be initialized when ++ //QApplication is constructed, so that the population procedure can do something like this to ++ //set the default font ++// const FcDefaultFont *s = defaults; ++// QFont font("Sans Serif"); ++// font.setPointSize(9); ++// QApplication::setFont(font); ++} ++ ++QFontEngine *QFontconfigDatabase::fontEngine(const QFontDef &f, QUnicodeTables::Script script, void *usrPtr) ++{ ++ if (!usrPtr) ++ return 0; ++ QFontDef fontDef = f; ++ ++ QFontEngineFT *engine; ++ FontFile *fontfile = static_cast (usrPtr); ++ QFontEngine::FaceId fid; ++ fid.filename = fontfile->fileName.toLocal8Bit(); ++ fid.index = fontfile->indexValue; ++ ++ //try and get the pattern ++ FcPattern *pattern = FcPatternCreate(); ++ ++ bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias); ++ QFontEngineFT::GlyphFormat format = antialias? QFontEngineFT::Format_A8 : QFontEngineFT::Format_Mono; ++ ++ engine = new QFontEngineFT(fontDef); ++ ++ FcValue value; ++ value.type = FcTypeString; ++ QByteArray cs = fontDef.family.toUtf8(); ++ value.u.s = (const FcChar8 *)cs.data(); ++ FcPatternAdd(pattern,FC_FAMILY,value,true); ++ ++ ++ value.u.s = (const FcChar8 *)fid.filename.data(); ++ FcPatternAdd(pattern,FC_FILE,value,true); ++ ++ value.type = FcTypeInteger; ++ value.u.i = fid.index; ++ FcPatternAdd(pattern,FC_INDEX,value,true); ++ ++ QFontEngineFT::HintStyle default_hint_style; ++ ++ if (FcConfigSubstitute(0,pattern,FcMatchPattern)) { ++ ++ //hinting ++ int hint_style = 0; ++ if (FcPatternGetInteger (pattern, FC_HINT_STYLE, 0, &hint_style) == FcResultNoMatch) ++ hint_style = QFontEngineFT::HintFull; ++ switch (hint_style) { ++ case FC_HINT_NONE: ++ default_hint_style = QFontEngineFT::HintNone; ++ break; ++ case FC_HINT_SLIGHT: ++ default_hint_style = QFontEngineFT::HintLight; ++ break; ++ case FC_HINT_MEDIUM: ++ default_hint_style = QFontEngineFT::HintMedium; ++ break; ++ default: ++ default_hint_style = QFontEngineFT::HintFull; ++ break; ++ } ++ } ++ ++ engine->setDefaultHintStyle(default_hint_style); ++ if (!engine->init(fid,antialias,format)) { ++ delete engine; ++ engine = 0; ++ return engine; ++ } ++ if (engine->invalid()) { ++ delete engine; ++ engine = 0; ++ } else if (scriptRequiresOpenType(script)) { ++ HB_Face hbFace = engine->harfbuzzFace(); ++ if (!hbFace || !hbFace->supported_scripts[script]) { ++ delete engine; ++ engine = 0; ++ } ++ } ++ ++ return engine; ++} ++ ++QStringList QFontconfigDatabase::fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const ++{ ++ QStringList fallbackFamilies; ++ FcPattern *pattern = FcPatternCreate(); ++ if (!pattern) ++ return fallbackFamilies; ++ ++ FcValue value; ++ value.type = FcTypeString; ++ QByteArray cs = family.toUtf8(); ++ value.u.s = (const FcChar8 *)cs.data(); ++ FcPatternAdd(pattern,FC_FAMILY,value,true); ++ ++ int slant_value = FC_SLANT_ROMAN; ++ if (style == QFont::StyleItalic) ++ slant_value = FC_SLANT_ITALIC; ++ else if (style == QFont::StyleOblique) ++ slant_value = FC_SLANT_OBLIQUE; ++ FcPatternAddInteger(pattern, FC_SLANT, slant_value); ++ ++ if (script != QUnicodeTables::Common && *specialLanguages[script] != '\0') { ++ Q_ASSERT(script < QUnicodeTables::ScriptCount); ++ FcLangSet *ls = FcLangSetCreate(); ++ FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]); ++ FcPatternAddLangSet(pattern, FC_LANG, ls); ++ FcLangSetDestroy(ls); ++ } ++ ++ const char *stylehint = getFcFamilyForStyleHint(styleHint); ++ if (stylehint) { ++ value.u.s = (const FcChar8 *)stylehint; ++ FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); ++ } ++ ++ FcConfigSubstitute(0, pattern, FcMatchPattern); ++ FcConfigSubstitute(0, pattern, FcMatchFont); ++ ++ FcResult result = FcResultMatch; ++ FcFontSet *fontSet = FcFontSort(0,pattern,FcFalse,0,&result); ++ ++ if (fontSet && result == FcResultMatch) ++ { ++ for (int i = 0; i < fontSet->nfont; i++) { ++ FcChar8 *value = 0; ++ if (FcPatternGetString(fontSet->fonts[i], FC_FAMILY, 0, &value) != FcResultMatch) ++ continue; ++ // capitalize(value); ++ QString familyName = QString::fromUtf8((const char *)value); ++ if (!fallbackFamilies.contains(familyName,Qt::CaseInsensitive)) { ++ fallbackFamilies << familyName; ++ } ++ ++ } ++ } ++// qDebug() << "fallbackFamilies for:" << family << fallbackFamilies; ++ ++ return fallbackFamilies; ++} +diff --git a/src/plugins/platforms/minimal/qfontconfigdatabase.h b/src/plugins/platforms/minimal/qfontconfigdatabase.h +new file mode 100644 +index 0000000..61700e3 +--- /dev/null ++++ b/src/plugins/platforms/minimal/qfontconfigdatabase.h +@@ -0,0 +1,56 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ++** All rights reserved. ++** Contact: Nokia Corporation (qt-info@nokia.com) ++** ++** This file is part of the plugins of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** GNU Lesser General Public License Usage ++** This file may be used under the terms of the GNU Lesser General Public ++** License version 2.1 as published by the Free Software Foundation and ++** appearing in the file LICENSE.LGPL included in the packaging of this ++** file. Please review the following information to ensure the GNU Lesser ++** General Public License version 2.1 requirements will be met: ++** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ++** ++** In addition, as a special exception, Nokia gives you certain additional ++** rights. These rights are described in the Nokia Qt LGPL Exception ++** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU General ++** Public License version 3.0 as published by the Free Software Foundation ++** and appearing in the file LICENSE.GPL included in the packaging of this ++** file. Please review the following information to ensure the GNU General ++** Public License version 3.0 requirements will be met: ++** http://www.gnu.org/copyleft/gpl.html. ++** ++** Other Usage ++** Alternatively, this file may be used in accordance with the terms and ++** conditions contained in a signed written agreement between you and Nokia. ++** ++** ++** ++** ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#ifndef QFONTCONFIGDATABASE_H ++#define QFONTCONFIGDATABASE_H ++ ++#include ++#include "qbasicunixfontdatabase.h" ++ ++class QFontconfigDatabase : public QBasicUnixFontDatabase ++{ ++public: ++ void populateFontDatabase(); ++ QFontEngine *fontEngine(const QFontDef &fontDef, QUnicodeTables::Script script, void *handle); ++ QStringList fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const; ++}; ++ ++#endif // QFONTCONFIGDATABASE_H +diff --git a/src/plugins/platforms/minimal/qminimalintegration.cpp b/src/plugins/platforms/minimal/qminimalintegration.cpp +index b9ab528..b6028ff 100644 +--- a/src/plugins/platforms/minimal/qminimalintegration.cpp ++++ b/src/plugins/platforms/minimal/qminimalintegration.cpp +@@ -41,11 +41,22 @@ + + #include "qminimalintegration.h" + #include "qminimalwindowsurface.h" ++#include "qfontconfigdatabase.h" + + #include + #include + ++ ++QSize QMinimalScreen::physicalSize() const ++{ ++ static const int dpi = 85; ++ int width = geometry().width() / dpi * qreal(25.4) ; ++ int height = geometry().height() / dpi * qreal(25.4) ; ++ return QSize(width,height); ++} ++ + QMinimalIntegration::QMinimalIntegration() ++ : mFontDb(new QFontconfigDatabase()) + { + QMinimalScreen *mPrimaryScreen = new QMinimalScreen(); + +@@ -56,6 +67,11 @@ QMinimalIntegration::QMinimalIntegration() + mScreens.append(mPrimaryScreen); + } + ++QPlatformFontDatabase *QMinimalIntegration::fontDatabase() const ++{ ++ return mFontDb; ++} ++ + bool QMinimalIntegration::hasCapability(QPlatformIntegration::Capability cap) const + { + switch (cap) { +diff --git a/src/plugins/platforms/minimal/qminimalintegration.h b/src/plugins/platforms/minimal/qminimalintegration.h +index d1fcc42..13dd8a4 100644 +--- a/src/plugins/platforms/minimal/qminimalintegration.h ++++ b/src/plugins/platforms/minimal/qminimalintegration.h +@@ -47,6 +47,8 @@ + + QT_BEGIN_NAMESPACE + ++class QPlatformFontDatabase; ++ + class QMinimalScreen : public QPlatformScreen + { + public: +@@ -56,6 +58,7 @@ public: + QRect geometry() const { return mGeometry; } + int depth() const { return mDepth; } + QImage::Format format() const { return mFormat; } ++ QSize physicalSize() const; + + public: + QRect mGeometry; +@@ -74,11 +77,14 @@ public: + QPixmapData *createPixmapData(QPixmapData::PixelType type) const; + QPlatformWindow *createPlatformWindow(QWidget *widget, WId winId) const; + QWindowSurface *createWindowSurface(QWidget *widget, WId winId) const; ++ ++ QPlatformFontDatabase *fontDatabase() const; + + QList screens() const { return mScreens; } + + private: + QList mScreens; ++ QPlatformFontDatabase *mFontDb; + }; + + QT_END_NAMESPACE +diff --git a/src/plugins/platforms/minimal/qminimalwindowsurface.cpp b/src/plugins/platforms/minimal/qminimalwindowsurface.cpp +index 91c68d1..c24fbaf 100644 +--- a/src/plugins/platforms/minimal/qminimalwindowsurface.cpp ++++ b/src/plugins/platforms/minimal/qminimalwindowsurface.cpp +@@ -68,10 +68,12 @@ void QMinimalWindowSurface::flush(QWidget *widget, const QRegion ®ion, const + Q_UNUSED(region); + Q_UNUSED(offset); + ++/* Don't save to a temporary file + static int c = 0; + QString filename = QString("output%1.png").arg(c++, 4, 10, QLatin1Char('0')); + qDebug() << "QMinimalWindowSurface::flush() saving contents to" << filename.toLocal8Bit().constData(); + mImage.save(filename); ++*/ + } + + void QMinimalWindowSurface::resize(const QSize &size) +diff --git a/src/svg/qsvggenerator.cpp b/src/svg/qsvggenerator.cpp +index 746e2b3..ba23360 100644 +--- a/src/svg/qsvggenerator.cpp ++++ b/src/svg/qsvggenerator.cpp +@@ -103,6 +103,7 @@ public: + + afterFirstUpdate = false; + numGradients = 0; ++ clip = false; + } + + QSize size; +@@ -129,6 +130,9 @@ public: + + QString currentGradientName; + int numGradients; ++ QString stateString; ++ QString oldStateString; ++ bool clip; + + struct _attributes { + QString document_title; +@@ -141,6 +145,18 @@ public: + QString dashPattern, dashOffset; + QString fill, fillOpacity; + } attributes; ++ ++ void emitState() { ++ if (stateString == oldStateString) return; ++ ++ // close old state and start a new one... ++ if (afterFirstUpdate) ++ *stream << "\n\n"; ++ ++ *stream << stateString; ++ afterFirstUpdate = true; ++ oldStateString = stateString; ++ } + }; + + static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures() +@@ -322,7 +338,7 @@ public: + } + + +- void qpenToSvg(const QPen &spen) ++ void qpenToSvg(const QPen &spen, QTextStream & s) + { + QString width; + +@@ -330,7 +346,7 @@ public: + + switch (spen.style()) { + case Qt::NoPen: +- stream() << QLatin1String("stroke=\"none\" "); ++ s << QLatin1String("stroke=\"none\" "); + + d_func()->attributes.stroke = QLatin1String("none"); + d_func()->attributes.strokeOpacity = QString(); +@@ -344,8 +360,8 @@ public: + d_func()->attributes.stroke = color; + d_func()->attributes.strokeOpacity = colorOpacity; + +- stream() << QLatin1String("stroke=\"")<attributes.dashPattern = dashPattern; + d_func()->attributes.dashOffset = dashOffset; + +- stream() << QLatin1String("stroke=\"")<brush = sbrush; + switch (sbrush.style()) { + case Qt::SolidPattern: { + QString color, colorOpacity; + translate_color(sbrush.color(), &color, &colorOpacity); +- stream() << "fill=\"" << color << "\" " ++ s << "fill=\"" << color << "\" " + "fill-opacity=\"" + << colorOpacity << "\" "; + d_func()->attributes.fill = color; +@@ -434,22 +450,22 @@ public: + saveLinearGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); +- stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); ++ s << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::RadialGradientPattern: + saveRadialGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); +- stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); ++ s << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::ConicalGradientPattern: + saveConicalGradientBrush(sbrush.gradient()); + d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName); + d_func()->attributes.fillOpacity = QString(); +- stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); ++ s << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" "); + break; + case Qt::NoBrush: +- stream() << QLatin1String("fill=\"none\" "); ++ s << QLatin1String("fill=\"none\" "); + d_func()->attributes.fill = QLatin1String("none"); + d_func()->attributes.fillOpacity = QString(); + return; +@@ -458,7 +474,7 @@ public: + break; + } + } +- void qfontToSvg(const QFont &sfont) ++ void qfontToSvg(const QFont &sfont, QTextStream & s) + { + Q_D(QSvgPaintEngine); + +@@ -488,12 +504,23 @@ public: + d->attributes.font_family = d->font.family(); + d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal"); + +- *d->stream << "font-family=\"" << d->attributes.font_family << "\" " +- "font-size=\"" << d->attributes.font_size << "\" " +- "font-weight=\"" << d->attributes.font_weight << "\" " +- "font-style=\"" << d->attributes.font_style << "\" " +- << endl; ++ s << "font-family=\"" << d->attributes.font_family << "\" " ++ "font-size=\"" << d->attributes.font_size << "\" " ++ "font-weight=\"" << d->attributes.font_weight << "\" " ++ "font-style=\"" << d->attributes.font_style << "\" " ++ << endl; ++ } ++ ++ void setViewBoxClip(bool clip) { ++ Q_D(QSvgPaintEngine); ++ d->clip = clip; + } ++ ++ bool viewBoxClip() const { ++ Q_D(const QSvgPaintEngine); ++ return d->clip; ++ } ++ + }; + + class QSvgGeneratorPrivate +@@ -808,6 +835,27 @@ int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const + return 0; + } + ++/*! ++ \property QSvgGenerator::resolution ++ \brief do not draw objects outside the viewBox ++ \since 4.7 ++ ++ When specified objects drawn compleatly outsite the viewBox ++ are not include in the output SVG. ++ ++ \sa viewBox ++*/ ++ ++bool QSvgGenerator::viewBoxClip() const { ++ Q_D(const QSvgGenerator); ++ return d->engine->viewBoxClip(); ++} ++ ++void QSvgGenerator::setViewBoxClip(bool clip) { ++ Q_D(QSvgGenerator); ++ d->engine->setViewBoxClip(clip); ++} ++ + /***************************************************************************** + * class QSvgPaintEngine + */ +@@ -908,10 +956,13 @@ void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image, + const QRectF &sr, + Qt::ImageConversionFlag flags) + { +- //Q_D(QSvgPaintEngine); ++ Q_D(QSvgPaintEngine); + + Q_UNUSED(sr); + Q_UNUSED(flags); ++ if (d->clip && !d->matrix.mapRect(r).intersects(d->viewBox)) return; ++ d->emitState(); ++ + stream() << "afterFirstUpdate) +- *d->stream << "\n\n"; +- +- *d->stream << "matrix = state.matrix(); +- *d->stream << "transform=\"matrix(" << d->matrix.m11() << ',' +- << d->matrix.m12() << ',' +- << d->matrix.m21() << ',' << d->matrix.m22() << ',' +- << d->matrix.dx() << ',' << d->matrix.dy() +- << ")\"" +- << endl; +- } +- +- if (flags & QPaintEngine::DirtyFont) { +- qfontToSvg(state.font()); +- } +- +- if (flags & QPaintEngine::DirtyOpacity) { +- if (!qFuzzyIsNull(state.opacity() - 1)) +- stream() << "opacity=\""<stream << '>' << endl; +- +- d->afterFirstUpdate = true; ++ d->stateString=""; ++ QTextStream stateStream(&d->stateString); ++ stateStream << "matrix = state.matrix(); ++ stateStream << "transform=\"matrix(" << d->matrix.m11() << ',' ++ << d->matrix.m12() << ',' ++ << d->matrix.m21() << ',' << d->matrix.m22() << ',' ++ << d->matrix.dx() << ',' << d->matrix.dy() ++ << ")\"" ++ << endl; ++ qfontToSvg(state.font(), stateStream); ++ ++ if (!qFuzzyIsNull(state.opacity() - 1)) ++ stateStream << "opacity=\""<' << endl; + } + + void QSvgPaintEngine::drawPath(const QPainterPath &p) + { + Q_D(QSvgPaintEngine); + ++ if (d->clip && !d->matrix.mapRect(p.boundingRect()).intersects(d->viewBox)) return; ++ d->emitState(); ++ + *d->stream << "pen().isCosmetic() ? "non-scaling-stroke" : "none") + << "\" fill-rule=\"" +@@ -1024,12 +1057,15 @@ void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount, + { + Q_ASSERT(pointCount >= 2); + +- //Q_D(QSvgPaintEngine); ++ Q_D(QSvgPaintEngine); + + QPainterPath path(points[0]); + for (int i=1; iclip && !d->matrix.mapRect(path.boundingRect()).intersects(d->viewBox)) return; ++ d->emitState(); ++ + if (mode == PolylineMode) { + stream() << "pen().isCosmetic() ? "non-scaling-stroke" : "none") +@@ -1051,6 +1087,12 @@ void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem) + if (d->pen.style() == Qt::NoPen) + return; + ++ if (d->clip) { ++ QRectF b=painter()->boundingRect( QRectF(pt, QSize()) , Qt::AlignLeft, textItem.text()); ++ if (!d->matrix.mapRect(b).intersects(d->viewBox)) return; ++ } ++ d->emitState(); ++ + const QTextItemInt &ti = static_cast(textItem); + QString s = QString::fromRawData(ti.chars, ti.num_chars); + +@@ -1060,7 +1102,7 @@ void QSvgPaintEngine::drawTextItem(const QPointF &pt, const QTextItem &textItem) + "stroke=\"none\" " + "xml:space=\"preserve\" " + "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" "; +- qfontToSvg(textItem.font()); ++ qfontToSvg(textItem.font(), *d->stream); + *d->stream << " >" + << Qt::escape(s) + << "" +diff --git a/src/svg/qsvggenerator.h b/src/svg/qsvggenerator.h +index dd51235..b207552 100644 +--- a/src/svg/qsvggenerator.h ++++ b/src/svg/qsvggenerator.h +@@ -96,6 +96,9 @@ public: + + void setResolution(int dpi); + int resolution() const; ++ ++ void setViewBoxClip(bool clip); ++ bool viewBoxClip() const; + protected: + QPaintEngine *paintEngine() const; + int metric(QPaintDevice::PaintDeviceMetric metric) const; diff -Nru phantomjs-1.3.0+dfsg/examples/colorwheel.coffee phantomjs-1.4.0+dfsg/examples/colorwheel.coffee --- phantomjs-1.3.0+dfsg/examples/colorwheel.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/colorwheel.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() page.viewportSize = { width: 400, height : 400 } page.content = '' diff -Nru phantomjs-1.3.0+dfsg/examples/colorwheel.js phantomjs-1.4.0+dfsg/examples/colorwheel.js --- phantomjs-1.3.0+dfsg/examples/colorwheel.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/colorwheel.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage; +var page = require('webpage').create(); page.viewportSize = { width: 400, height : 400 }; page.content = ''; page.evaluate(function() { diff -Nru phantomjs-1.3.0+dfsg/examples/detectsniff.coffee phantomjs-1.4.0+dfsg/examples/detectsniff.coffee --- phantomjs-1.3.0+dfsg/examples/detectsniff.coffee 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/detectsniff.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,40 @@ +page = require('webpage').create() + +page.onInitialized = -> + page.evaluate -> + userAgent = window.navigator.userAgent + platform = window.navigator.platform + window.navigator = + appCodeName: 'Mozilla' + appName: 'Netscape' + cookieEnabled: false + sniffed: false + + window.navigator.__defineGetter__ 'userAgent', -> + window.navigator.sniffed = true + userAgent + + window.navigator.__defineGetter__ 'platform', -> + window.navigator.sniffed = true + platform + +if phantom.args.length is 0 + console.log 'Usage: unsniff.js ' + phantom.exit() +else + address = phantom.args[0] + console.log 'Checking ' + address + '...' + page.open address, (status) -> + if status isnt 'success' + console.log 'FAIL to load the address' + else + window.setTimeout -> + sniffed = page.evaluate(-> + navigator.sniffed + ) + if sniffed + console.log 'The page tried to sniff the user agent.' + else + console.log 'The page did not try to sniff the user agent.' + phantom.exit() + , 1500 diff -Nru phantomjs-1.3.0+dfsg/examples/detectsniff.js phantomjs-1.4.0+dfsg/examples/detectsniff.js --- phantomjs-1.3.0+dfsg/examples/detectsniff.js 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/detectsniff.js 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,57 @@ +// Detect if a web page sniffs the user agent or not. + +var page = require('webpage').create(), + sniffed, + address; + +page.onInitialized = function () { + page.evaluate(function () { + + (function () { + var userAgent = window.navigator.userAgent, + platform = window.navigator.platform; + + window.navigator = { + appCodeName: 'Mozilla', + appName: 'Netscape', + cookieEnabled: false, + sniffed: false + }; + + window.navigator.__defineGetter__('userAgent', function () { + window.navigator.sniffed = true; + return userAgent; + }); + + window.navigator.__defineGetter__('platform', function () { + window.navigator.sniffed = true; + return platform; + }); + })(); + }); +}; + +if (phantom.args.length === 0) { + console.log('Usage: unsniff.js '); + phantom.exit(); +} else { + address = phantom.args[0]; + console.log('Checking ' + address + '...'); + page.open(address, function (status) { + if (status !== 'success') { + console.log('FAIL to load the address'); + } else { + window.setTimeout(function () { + sniffed = page.evaluate(function () { + return navigator.sniffed; + }); + if (sniffed) { + console.log('The page tried to sniff the user agent.'); + } else { + console.log('The page did not try to sniff the user agent.'); + } + phantom.exit(); + }, 1500); + } + }); +} diff -Nru phantomjs-1.3.0+dfsg/examples/direction.coffee phantomjs-1.4.0+dfsg/examples/direction.coffee --- phantomjs-1.3.0+dfsg/examples/direction.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/direction.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Get driving direction using Google Directions API. -page = new WebPage() +page = require('webpage').create() if phantom.args.length < 2 console.log 'Usage: direction.js origin destination' diff -Nru phantomjs-1.3.0+dfsg/examples/direction.js phantomjs-1.4.0+dfsg/examples/direction.js --- phantomjs-1.3.0+dfsg/examples/direction.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/direction.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Get driving direction using Google Directions API. -var page = new WebPage(), +var page = require('webpage').create(), origin, dest, steps; if (phantom.args.length < 2) { diff -Nru phantomjs-1.3.0+dfsg/examples/follow.coffee phantomjs-1.4.0+dfsg/examples/follow.coffee --- phantomjs-1.3.0+dfsg/examples/follow.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/follow.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -4,16 +4,18 @@ 'sencha' 'aconran' 'adityabansod' - 'arnebech', + 'ambisinister' + 'arnebech' 'ariyahidayat' + 'arthurakay' 'bmoeskau' 'darrellmeyer' + 'davidfoelber' 'DavidKaneda' 'donovanerba' 'edspencer' 'evantrimboli' 'ExtAnimal' - 'jamespearce' 'jamieavins' 'jarrednicholls' 'jayrobinson' @@ -25,12 +27,14 @@ 'philstrong' 'rdougan' 'SubtleGradient' + '__ted__' 'tmaintz' + 'WesleyMoy' 'whereisthysting' ] follow = (user, callback) -> - page = new WebPage() + page = require('webpage').create() page.open 'http://mobile.twitter.com/' + user, (status) -> if status is 'fail' console.log user + ': ?' diff -Nru phantomjs-1.3.0+dfsg/examples/follow.js phantomjs-1.4.0+dfsg/examples/follow.js --- phantomjs-1.3.0+dfsg/examples/follow.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/follow.js 2011-12-27 13:46:57.000000000 +0000 @@ -3,16 +3,18 @@ var users = ['sencha', 'aconran', 'adityabansod', + 'ambisinister', 'arnebech', 'ariyahidayat', + 'arthurakay', 'bmoeskau', 'darrellmeyer', + 'davidfoelber', 'DavidKaneda', 'donovanerba', 'edspencer', 'evantrimboli', 'ExtAnimal', - 'jamespearce', 'jamieavins', 'jarrednicholls', 'jayrobinson', @@ -24,11 +26,13 @@ 'philstrong', 'rdougan', 'SubtleGradient', + '__ted__', 'tmaintz', + 'WesleyMoy', 'whereisthysting']; function follow(user, callback) { - var page = new WebPage(); + var page = require('webpage').create(); page.open('http://mobile.twitter.com/' + user, function (status) { if (status === 'fail') { console.log(user + ': ?'); diff -Nru phantomjs-1.3.0+dfsg/examples/imagebin.coffee phantomjs-1.4.0+dfsg/examples/imagebin.coffee --- phantomjs-1.3.0+dfsg/examples/imagebin.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/imagebin.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Upload an image to imagebin.org -page = new WebPage() +page = require('webpage').create() if phantom.args.length isnt 1 console.log 'Usage: imagebin.coffee filename' diff -Nru phantomjs-1.3.0+dfsg/examples/imagebin.js phantomjs-1.4.0+dfsg/examples/imagebin.js --- phantomjs-1.3.0+dfsg/examples/imagebin.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/imagebin.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,7 @@ // Upload an image to imagebin.org -var page = new WebPage(), fname; +var page = require('webpage').create(), + fname; if (phantom.args.length !== 1) { console.log('Usage: imagebin.js filename'); diff -Nru phantomjs-1.3.0+dfsg/examples/injectme.coffee phantomjs-1.4.0+dfsg/examples/injectme.coffee --- phantomjs-1.3.0+dfsg/examples/injectme.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/injectme.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,7 +1,7 @@ # Use 'page.injectJs()' to load the script itself in the Page context if phantom? - page = new WebPage() + page = require('webpage').create() # Route "console.log()" calls from within the Page context to the main # Phantom context (i.e. current "this") diff -Nru phantomjs-1.3.0+dfsg/examples/injectme.js phantomjs-1.4.0+dfsg/examples/injectme.js --- phantomjs-1.3.0+dfsg/examples/injectme.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/injectme.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,7 +1,7 @@ // Use 'page.injectJs()' to load the script itself in the Page context if ( typeof(phantom) !== "undefined" ) { - var page = new WebPage(); + var page = require('webpage').create(); // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { diff -Nru phantomjs-1.3.0+dfsg/examples/loadspeed.coffee phantomjs-1.4.0+dfsg/examples/loadspeed.coffee --- phantomjs-1.3.0+dfsg/examples/loadspeed.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/loadspeed.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() if phantom.args.length is 0 console.log 'Usage: loadspeed.js ' diff -Nru phantomjs-1.3.0+dfsg/examples/loadspeed.js phantomjs-1.4.0+dfsg/examples/loadspeed.js --- phantomjs-1.3.0+dfsg/examples/loadspeed.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/loadspeed.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage(), +var page = require('webpage').create(), t, address; if (phantom.args.length === 0) { diff -Nru phantomjs-1.3.0+dfsg/examples/netlog.coffee phantomjs-1.4.0+dfsg/examples/netlog.coffee --- phantomjs-1.3.0+dfsg/examples/netlog.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/netlog.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() address = phantom.args[0] if phantom.args.length is 0 diff -Nru phantomjs-1.3.0+dfsg/examples/netlog.js phantomjs-1.4.0+dfsg/examples/netlog.js --- phantomjs-1.3.0+dfsg/examples/netlog.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/netlog.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage(), +var page = require('webpage').create(), address = phantom.args[0]; if (phantom.args.length === 0) { diff -Nru phantomjs-1.3.0+dfsg/examples/netsniff.coffee phantomjs-1.4.0+dfsg/examples/netsniff.coffee --- phantomjs-1.3.0+dfsg/examples/netsniff.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/netsniff.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -73,7 +73,7 @@ ] entries: entries -page = new WebPage() +page = require('webpage').create() if phantom.args.length is 0 console.log 'Usage: netsniff.js ' diff -Nru phantomjs-1.3.0+dfsg/examples/netsniff.js phantomjs-1.4.0+dfsg/examples/netsniff.js --- phantomjs-1.3.0+dfsg/examples/netsniff.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/netsniff.js 2011-12-27 13:46:57.000000000 +0000 @@ -84,7 +84,7 @@ }; } -var page = new WebPage(); +var page = require('webpage').create(); if (phantom.args.length === 0) { console.log('Usage: netsniff.js '); diff -Nru phantomjs-1.3.0+dfsg/examples/phantomwebintro.coffee phantomjs-1.4.0+dfsg/examples/phantomwebintro.coffee --- phantomjs-1.3.0+dfsg/examples/phantomwebintro.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/phantomwebintro.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Read the Phantom webpage '#intro' element text using jQuery and "includeJs" -page = new WebPage() +page = require('webpage').create() page.onConsoleMessage = (msg) -> console.log msg diff -Nru phantomjs-1.3.0+dfsg/examples/phantomwebintro.js phantomjs-1.4.0+dfsg/examples/phantomwebintro.js --- phantomjs-1.3.0+dfsg/examples/phantomwebintro.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/phantomwebintro.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Read the Phantom webpage '#intro' element text using jQuery and "includeJs" -var page = new WebPage(); +var page = require('webpage').create(); page.onConsoleMessage = function(msg) { console.log(msg); diff -Nru phantomjs-1.3.0+dfsg/examples/pizza.coffee phantomjs-1.4.0+dfsg/examples/pizza.coffee --- phantomjs-1.3.0+dfsg/examples/pizza.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/pizza.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Find pizza in Mountain View using Yelp -page = new WebPage() +page = require('webpage').create() url = 'http://lite.yelp.com/search?find_desc=pizza&find_loc=94040&find_submit=Search' page.open url, diff -Nru phantomjs-1.3.0+dfsg/examples/pizza.js phantomjs-1.4.0+dfsg/examples/pizza.js --- phantomjs-1.3.0+dfsg/examples/pizza.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/pizza.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Find pizza in Mountain View using Yelp -var page = new WebPage(), +var page = require('webpage').create(), url = 'http://lite.yelp.com/search?find_desc=pizza&find_loc=94040&find_submit=Search'; page.open(url, function (status) { diff -Nru phantomjs-1.3.0+dfsg/examples/post.coffee phantomjs-1.4.0+dfsg/examples/post.coffee --- phantomjs-1.3.0+dfsg/examples/post.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/post.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Example using HTTP POST operation -page = new WebPage() +page = require('webpage').create() server = 'http://posttestserver.com/post.php?dump' data = 'universe=expanding&answer=42' diff -Nru phantomjs-1.3.0+dfsg/examples/post.js phantomjs-1.4.0+dfsg/examples/post.js --- phantomjs-1.3.0+dfsg/examples/post.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/post.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Example using HTTP POST operation -var page = new WebPage(), +var page = require('webpage').create(), server = 'http://posttestserver.com/post.php?dump', data = 'universe=expanding&answer=42'; diff -Nru phantomjs-1.3.0+dfsg/examples/rasterize.coffee phantomjs-1.4.0+dfsg/examples/rasterize.coffee --- phantomjs-1.3.0+dfsg/examples/rasterize.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/rasterize.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() if phantom.args.length < 2 or phantom.args.length > 3 console.log 'Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat]' diff -Nru phantomjs-1.3.0+dfsg/examples/rasterize.js phantomjs-1.4.0+dfsg/examples/rasterize.js --- phantomjs-1.3.0+dfsg/examples/rasterize.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/rasterize.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage(), +var page = require('webpage').create(), address, output, size; if (phantom.args.length < 2 || phantom.args.length > 3) { diff -Nru phantomjs-1.3.0+dfsg/examples/render_multi_url.coffee phantomjs-1.4.0+dfsg/examples/render_multi_url.coffee --- phantomjs-1.3.0+dfsg/examples/render_multi_url.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/render_multi_url.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -11,7 +11,7 @@ # @param file File to render to # @param callback Callback function renderUrlToFile = (url, file, callback) -> - page = new WebPage() + page = require('webpage').create() page.viewportSize = { width: 800, height : 600 } page.settings.userAgent = 'Phantom.js bot' diff -Nru phantomjs-1.3.0+dfsg/examples/render_multi_url.js phantomjs-1.4.0+dfsg/examples/render_multi_url.js --- phantomjs-1.3.0+dfsg/examples/render_multi_url.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/render_multi_url.js 2011-12-27 13:46:57.000000000 +0000 @@ -16,7 +16,7 @@ * @param callback Callback function */ function renderUrlToFile(url, file, callback) { - var page = new WebPage(); + var page = require('webpage').create(); page.viewportSize = { width: 800, height : 600 }; page.settings.userAgent = "Phantom.js bot"; diff -Nru phantomjs-1.3.0+dfsg/examples/run-jasmine.coffee phantomjs-1.4.0+dfsg/examples/run-jasmine.coffee --- phantomjs-1.3.0+dfsg/examples/run-jasmine.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/run-jasmine.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -33,7 +33,7 @@ console.log 'Usage: run-jasmine.coffee URL' phantom.exit() -page = new WebPage() +page = require('webpage').create() # Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = (msg) -> diff -Nru phantomjs-1.3.0+dfsg/examples/run-jasmine.js phantomjs-1.4.0+dfsg/examples/run-jasmine.js --- phantomjs-1.3.0+dfsg/examples/run-jasmine.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/run-jasmine.js 2011-12-27 13:46:57.000000000 +0000 @@ -30,7 +30,7 @@ clearInterval(interval); //< Stop this interval } } - }, 100); //< repeat check every 250ms + }, 100); //< repeat check every 100ms }; @@ -39,7 +39,7 @@ phantom.exit(); } -var page = new WebPage(); +var page = require('webpage').create(); // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { diff -Nru phantomjs-1.3.0+dfsg/examples/run-qunit.coffee phantomjs-1.4.0+dfsg/examples/run-qunit.coffee --- phantomjs-1.3.0+dfsg/examples/run-qunit.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/run-qunit.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -33,7 +33,7 @@ console.log 'Usage: run-qunit.coffee URL' phantom.exit(1) -page = new WebPage() +page = require('webpage').create() # Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = (msg) -> diff -Nru phantomjs-1.3.0+dfsg/examples/run-qunit.js phantomjs-1.4.0+dfsg/examples/run-qunit.js --- phantomjs-1.3.0+dfsg/examples/run-qunit.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/run-qunit.js 2011-12-27 13:46:57.000000000 +0000 @@ -39,7 +39,7 @@ phantom.exit(1); } -var page = new WebPage(); +var page = require('webpage').create(); // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { diff -Nru phantomjs-1.3.0+dfsg/examples/simpleserver.coffee phantomjs-1.4.0+dfsg/examples/simpleserver.coffee --- phantomjs-1.3.0+dfsg/examples/simpleserver.coffee 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/simpleserver.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,35 @@ +if phantom.args.length is 0 + console.log "Usage: simpleserver.js " + phantom.exit() +else + port = phantom.args[0] + server = require("webserver").create() + + service = server.listen(port, (request, response) -> + + console.log "Request at " + new Date() + console.log JSON.stringify(request, null, 4) + + response.statusCode = 200 + response.headers = + Cache: "no-cache" + "Content-Type": "text/html" + + response.write "" + response.write "" + response.write "Hello, world!" + response.write "" + response.write "" + response.write "

This is from PhantomJS web server.

" + response.write "

Request data:

" + response.write "
"
+    response.write JSON.stringify(request, null, 4)
+    response.write "
" + response.write "" + response.write "" + ) + if service + console.log "Web server running on port " + port + else + console.log "Error: Could not create web server listening on port " + port + phantom.exit() diff -Nru phantomjs-1.3.0+dfsg/examples/simpleserver.js phantomjs-1.4.0+dfsg/examples/simpleserver.js --- phantomjs-1.3.0+dfsg/examples/simpleserver.js 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/simpleserver.js 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,40 @@ +var port, server, service; + +if (phantom.args.length !== 1) { + console.log('Usage: simpleserver.js '); + phantom.exit(); +} else { + port = phantom.args[0]; + server = require('webserver').create(); + + service = server.listen(port, function (request, response) { + + console.log('Request at ' + new Date()); + console.log(JSON.stringify(request, null, 4)); + + response.statusCode = 200; + response.headers = { + 'Cache': 'no-cache', + 'Content-Type': 'text/html' + }; + response.write(''); + response.write(''); + response.write('Hello, world!'); + response.write(''); + response.write(''); + response.write('

This is from PhantomJS web server.

'); + response.write('

Request data:

'); + response.write('
');
+        response.write(JSON.stringify(request, null, 4));
+        response.write('
'); + response.write(''); + response.write(''); + }); + + if (service) { + console.log('Web server running on port ' + port); + } else { + console.log('Error: Could not create web server listening on port ' + port); + phantom.exit(); + } +} diff -Nru phantomjs-1.3.0+dfsg/examples/technews.coffee phantomjs-1.4.0+dfsg/examples/technews.coffee --- phantomjs-1.3.0+dfsg/examples/technews.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/technews.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() page.viewportSize = { width: 320, height: 480 } diff -Nru phantomjs-1.3.0+dfsg/examples/technews.js phantomjs-1.4.0+dfsg/examples/technews.js --- phantomjs-1.3.0+dfsg/examples/technews.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/technews.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage(); +var page = require('webpage').create(); page.viewportSize = { width: 320, height: 480 }; page.open('http://news.google.com/news/i/section?&topic=t', function (status) { if (status !== 'success') { diff -Nru phantomjs-1.3.0+dfsg/examples/tweets.coffee phantomjs-1.4.0+dfsg/examples/tweets.coffee --- phantomjs-1.3.0+dfsg/examples/tweets.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/tweets.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Get twitter status for given account (or for the default one, "sencha") -page = new WebPage() +page = require('webpage').create() twitterId = 'sencha' #< default value # Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") diff -Nru phantomjs-1.3.0+dfsg/examples/tweets.js phantomjs-1.4.0+dfsg/examples/tweets.js --- phantomjs-1.3.0+dfsg/examples/tweets.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/tweets.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Get twitter status for given account (or for the default one, "sencha") -var page = new WebPage(), +var page = require('webpage').create(), twitterId = "sencha"; //< default value // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") diff -Nru phantomjs-1.3.0+dfsg/examples/unrandomize.coffee phantomjs-1.4.0+dfsg/examples/unrandomize.coffee --- phantomjs-1.3.0+dfsg/examples/unrandomize.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/unrandomize.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,7 +1,7 @@ # Modify global object at the page initialization. # In this example, effectively Math.random() always returns 0.42. -page = new WebPage() +page = require('webpage').create() page.onInitialized = -> page.evaluate -> Math.random = -> diff -Nru phantomjs-1.3.0+dfsg/examples/unrandomize.js phantomjs-1.4.0+dfsg/examples/unrandomize.js --- phantomjs-1.3.0+dfsg/examples/unrandomize.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/unrandomize.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,7 +1,7 @@ // Modify global object at the page initialization. // In this example, effectively Math.random() always returns 0.42. -var page = new WebPage(); +var page = require('webpage').create(); page.onInitialized = function () { page.evaluate(function () { diff -Nru phantomjs-1.3.0+dfsg/examples/useragent.coffee phantomjs-1.4.0+dfsg/examples/useragent.coffee --- phantomjs-1.3.0+dfsg/examples/useragent.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/useragent.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -page = new WebPage() +page = require('webpage').create() console.log 'The default user agent is ' + page.settings.userAgent diff -Nru phantomjs-1.3.0+dfsg/examples/useragent.js phantomjs-1.4.0+dfsg/examples/useragent.js --- phantomjs-1.3.0+dfsg/examples/useragent.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/useragent.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,4 +1,4 @@ -var page = new WebPage(); +var page = require('webpage').create(); console.log('The default user agent is ' + page.settings.userAgent); page.settings.userAgent = 'SpecialAgent'; page.open('http://www.httpuseragent.org', function (status) { diff -Nru phantomjs-1.3.0+dfsg/examples/waitfor.coffee phantomjs-1.4.0+dfsg/examples/waitfor.coffee --- phantomjs-1.3.0+dfsg/examples/waitfor.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/waitfor.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -30,7 +30,7 @@ interval = setInterval f, 250 #< repeat check every 250ms -page = new WebPage() +page = require('webpage').create() # Open Twitter on 'sencha' profile and, onPageLoad, do... page.open 'http://twitter.com/#!/sencha', (status) -> diff -Nru phantomjs-1.3.0+dfsg/examples/waitfor.js phantomjs-1.4.0+dfsg/examples/waitfor.js --- phantomjs-1.3.0+dfsg/examples/waitfor.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/waitfor.js 2011-12-27 13:46:57.000000000 +0000 @@ -34,7 +34,7 @@ }; -var page = new WebPage(); +var page = require('webpage').create(); // Open Twitter on 'sencha' profile and, onPageLoad, do... page.open("http://twitter.com/#!/sencha", function (status) { diff -Nru phantomjs-1.3.0+dfsg/examples/weather.coffee phantomjs-1.4.0+dfsg/examples/weather.coffee --- phantomjs-1.3.0+dfsg/examples/weather.coffee 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/weather.coffee 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ # Get weather info for given address (or for the default one, "Mountain View") -page = new WebPage() +page = require('webpage').create() address = 'Mountain View' #< default value # Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") diff -Nru phantomjs-1.3.0+dfsg/examples/weather.js phantomjs-1.4.0+dfsg/examples/weather.js --- phantomjs-1.3.0+dfsg/examples/weather.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/examples/weather.js 2011-12-27 13:46:57.000000000 +0000 @@ -1,6 +1,6 @@ // Get weather info for given address (or for the default one, "Mountain View") -var page = new WebPage(), +var page = require('webpage').create(), address = "Mountain View"; //< default value // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") diff -Nru phantomjs-1.3.0+dfsg/.gitignore phantomjs-1.4.0+dfsg/.gitignore --- phantomjs-1.3.0+dfsg/.gitignore 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/.gitignore 2011-12-27 13:46:57.000000000 +0000 @@ -2,7 +2,7 @@ *.pro.user* *.xcodeproj Makefile* -bin/ +bin/phantomjs *~ *.moc moc_* diff -Nru phantomjs-1.3.0+dfsg/python/INSTALL.md phantomjs-1.4.0+dfsg/python/INSTALL.md --- phantomjs-1.3.0+dfsg/python/INSTALL.md 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/INSTALL.md 2011-12-27 13:46:57.000000000 +0000 @@ -5,7 +5,8 @@ * PyQt4 >= 4.8.0 * Qt >= 4.7.0 -* python-argparse == (Python 2.6) +* python-argparse >= 1.2.1 (Python 2.6 only) +* PIL (python-imaging-library) >= 1.1.7 (optional; for rendering to GIF) ### INSTALLING ------------------------- @@ -16,11 +17,14 @@ > [Python](http://www.python.org/download/) [PyQt4](http://www.riverbankcomputing.co.uk/software/pyqt/download) - Qt4 - PyQt4 comes packaged with the Qt runtime library(s) +> Qt4 - PyQt4 comes packaged with the Qt runtime library(s) +> + [python-argparse](http://pypi.python.org/pypi/argparse/) + [PIL](http://www.pythonware.com/products/pil/) Ubuntu -> Open a terminal window, and enter the command `sudo apt-get install python-qt4` +> Open a terminal window, and enter the command `sudo apt-get install python-qt4 python-imaging` > All the required packages should be automatically pulled in and installed. diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/arguments.py phantomjs-1.4.0+dfsg/python/pyphantomjs/arguments.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/arguments.py 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/arguments.py 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,264 @@ +''' + This file is part of the PyPhantomJS project. + + Copyright (C) 2011 James Roe + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +import argparse +import codecs +import os +import sys + +from PyQt4.QtCore import qInstallMsgHandler, QObject, qWarning +from PyQt4.QtNetwork import QNetworkProxy +from PyQt4.QtWebKit import QWebPage + +from __init__ import __version__ +from plugincontroller import do_action +from utils import debug, MessageHandler, QPyFile + + +license = ''' + PyPhantomJS Version %s + + Copyright (C) 2011 James Roe + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' % __version__ + + +defaults = { + 'cookiesFile': None, + 'debug': None, + 'diskCache': False, + 'ignoreSslErrors': False, + 'loadImages': True, + 'loadPlugins': False, + 'localToRemoteUrlAccessEnabled': False, + 'maxDiskCacheSize': -1, + 'outputEncoding': 'System', + 'proxy': None, + 'proxyType': QNetworkProxy.HttpProxy, + 'scriptEncoding': 'utf-8', + 'verbose': False +} + + +def argParser(): + class YesOrNoAction(argparse.Action): + '''Converts yes or no arguments to True/False respectively''' + def __call__(self, parser, namespace, value, option_string=None): + answer = True if value == 'yes' else False + setattr(namespace, self.dest, answer) + + def proxyType(type_): + if type_ == QNetworkProxy.HttpProxy: + return 'http' + elif type_ == QNetworkProxy.Socks5Proxy: + return 'socks5' + + yesOrNo = lambda d: 'yes' if d else 'no' + + + parser = argparse.ArgumentParser( + description='Minimalistic headless WebKit-based JavaScript-driven tool', + usage='%(prog)s [options] script.[js|coffee] [script argument [script argument ...]]', + formatter_class=argparse.RawTextHelpFormatter + ) + + parser.add_argument('script', metavar='script.[js|coffee]', nargs='?', + help='The script to execute, and any args to pass to it' + ) + parser.add_argument('-v', '--version', + action='version', version=license, + help="show this program's version and license" + ) + + program = parser.add_argument_group('program options') + script = parser.add_argument_group('script options') + debug = parser.add_argument_group('debug options') + + program.add_argument('--config', metavar='/path/to/config', + help='Specifies path to a JSON-formatted config file' + ) + program.add_argument('--disk-cache', default=defaults['diskCache'], action=YesOrNoAction, + choices=['yes', 'no'], + help='Enable disk cache (default: %s)' % yesOrNo(defaults['diskCache']) + ) + program.add_argument('--ignore-ssl-errors', default=defaults['ignoreSslErrors'], action=YesOrNoAction, + choices=['yes', 'no'], + help='Ignore SSL errors (default: %s)' % yesOrNo(defaults['ignoreSslErrors']) + ) + program.add_argument('--max-disk-cache-size', default=defaults['maxDiskCacheSize'], metavar='size', type=int, + help='Limits the size of disk cache (in KB)' + ) + program.add_argument('--output-encoding', default=defaults['outputEncoding'], metavar='encoding', + help='Sets the encoding used for terminal output (default: %(default)s)' + ) + program.add_argument('--proxy', metavar='address:port', + help='Set the network proxy' + ) + program.add_argument('--proxy-type', default=defaults['proxyType'], metavar='type', + help='Set the network proxy type (default: %s)' % proxyType(defaults['proxyType']) + ) + program.add_argument('--script-encoding', default=defaults['scriptEncoding'], metavar='encoding', + help='Sets the encoding used for scripts (default: %(default)s)' + ) + + script.add_argument('--cookies-file', metavar='/path/to/cookies.txt', + help='Sets the file name to store the persistent cookies' + ) + script.add_argument('--load-images', default=defaults['loadImages'], action=YesOrNoAction, + choices=['yes', 'no'], + help='Load all inlined images (default: %s)' % yesOrNo(defaults['loadImages']) + ) + script.add_argument('--load-plugins', default=defaults['loadPlugins'], action=YesOrNoAction, + choices=['yes', 'no'], + help='Load all plugins (i.e. Flash, Silverlight, ...) (default: %s)' % yesOrNo(defaults['loadPlugins']) + ) + script.add_argument('--local-to-remote-url-access', default=defaults['localToRemoteUrlAccessEnabled'], action=YesOrNoAction, + choices=['yes', 'no'], + help='Local content can access remote URL (default: %s)' % yesOrNo(defaults['localToRemoteUrlAccessEnabled']) + ) + + debug.add_argument('--debug', choices=['exception', 'program'], metavar='option', + help=('Debug the program with pdb\n' + ' exception : Start debugger when program hits exception\n' + ' program : Start the program with the debugger enabled') + ) + debug.add_argument('--verbose', action='store_true', + help='Show verbose debug messages' + ) + + do_action('ArgParser') + + return parser + + +def parseArgs(app, args): + # Handle all command-line options + p = argParser() + arg_data = p.parse_known_args(args) + args = arg_data[0] + args.script_args = arg_data[1] + + # convert script args to unicode + for i, arg in enumerate(args.script_args): + args.script_args[i] = unicode(arg, 'utf-8') + + # register an alternative Message Handler + messageHandler = MessageHandler(args.verbose) + qInstallMsgHandler(messageHandler.process) + + file_check = (args.cookies_file, args.config) + for file_ in file_check: + if file_ is not None and not os.path.exists(file_): + sys.exit("No such file or directory: '%s'" % file_) + + if args.config: + config = Config(app, args.config) + # apply settings + for setting in config.settings: + setattr(args, config.settings[setting]['mapping'], config.property(setting)) + + split_check = ( + (args.proxy, 'proxy'), + ) + for arg, name in split_check: + if arg: + item = arg.split(':') + if len(item) < 2 or not len(item[1]): + p.print_help() + sys.exit(1) + setattr(args, name, item) + + if args.proxy is not None: + if args.proxy_type == 'socks5': + args.proxy_type = QNetworkProxy.Socks5Proxy + + do_action('ParseArgs', args) + + if args.debug: + debug(args.debug) + + # verbose flag got changed on us, so we reload the flag + if messageHandler.verbose != args.verbose: + messageHandler.verbose = args.verbose + + if args.script is None: + p.print_help() + sys.exit(1) + + if not os.path.exists(args.script): + sys.exit("No such file or directory: '%s'" % args.script) + + return args + + +class Config(QObject): + def __init__(self, parent, jsonFile): + super(Config, self).__init__(parent) + + with codecs.open(jsonFile, encoding='utf-8') as f: + json = f.read() + + self.settings = { + 'cookiesFile': { 'mapping': 'cookies_file', 'default': defaults['cookiesFile'] }, + 'debug': { 'mapping': 'debug', 'default': defaults['debug'] }, + 'diskCache': { 'mapping': 'disk_cache', 'default': defaults['diskCache'] }, + 'ignoreSslErrors': { 'mapping': 'ignore_ssl_errors', 'default': defaults['ignoreSslErrors'] }, + 'loadImages': { 'mapping': 'load_images', 'default': defaults['loadImages'] }, + 'loadPlugins': { 'mapping': 'load_plugins', 'default': defaults['loadPlugins'] }, + 'localToRemoteUrlAccessEnabled': { 'mapping': 'local_to_remote_url_access', 'default': defaults['localToRemoteUrlAccessEnabled'] }, + 'maxDiskCacheSize': { 'mapping': 'max_disk_cache_size', 'default': defaults['maxDiskCacheSize'] }, + 'outputEncoding': { 'mapping': 'output_encoding', 'default': defaults['outputEncoding'] }, + 'proxy': { 'mapping': 'proxy', 'default': defaults['proxy'] }, + 'proxyType': { 'mapping': 'proxy_type', 'default': defaults['proxyType'] }, + 'scriptEncoding': { 'mapping': 'script_encoding', 'default': defaults['scriptEncoding'] }, + 'verbose': { 'mapping': 'verbose', 'default': defaults['verbose'] } + } + + do_action('ConfigInit', self.settings) + + # generate dynamic properties + for setting in self.settings: + self.setProperty(setting, self.settings[setting]['default']) + + # now it's time to parse our JSON file + if not json.lstrip().startswith('{') or not json.rstrip().endswith('}'): + qWarning('Config file MUST be in JSON format!') + return + + webPage = QWebPage(self) + + with QPyFile(':/configurator.js') as f: + # add config object + webPage.mainFrame().addToJavaScriptWindowObject('config', self) + # apply settings + webPage.mainFrame().evaluateJavaScript(f.readAll().replace('%1', json)) + + do_action('Config') diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/bootstrap.js phantomjs-1.4.0+dfsg/python/pyphantomjs/bootstrap.js --- phantomjs-1.3.0+dfsg/python/pyphantomjs/bootstrap.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/bootstrap.js 2011-12-27 13:46:57.000000000 +0000 @@ -37,7 +37,7 @@ var code, func, exports; - if (name === 'webpage' || name === 'fs') { + if (name === 'webpage' || name === 'fs' || name == 'webserver') { code = phantom.loadModuleSource(name); func = new Function("exports", "window", code); exports = {}; @@ -55,3 +55,4 @@ // Legacy way to use WebPage window.WebPage = require('webpage').create; +window.WebServer = require('webserver').create; diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/config.py phantomjs-1.4.0+dfsg/python/pyphantomjs/config.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/config.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/config.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -''' - This file is part of the PyPhantomJS project. - - Copyright (C) 2011 James Roe - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' - -import codecs -import sys - -from PyQt4.QtCore import QObject, qWarning -from PyQt4.QtWebKit import QWebPage - -from plugincontroller import do_action -from utils import QPyFile - - -class Config(QObject): - def __init__(self, parent, jsonFile): - super(Config, self).__init__(parent) - - with codecs.open(jsonFile, encoding='utf-8') as fd: - json = fd.read() - - self.settings = { - 'cookiesFile': { 'mapping': 'cookies_file', 'default': None }, - 'debug': { 'mapping': 'debug', 'default': None }, - 'diskCache': { 'mapping': 'disk_cache', 'default': False }, - 'ignoreSslErrors': { 'mapping': 'ignore_ssl_errors', 'default': False }, - 'loadImages': { 'mapping': 'load_images', 'default': True }, - 'loadPlugins': { 'mapping': 'load_plugins', 'default': False }, - 'localToRemoteUrlAccessEnabled': { 'mapping': 'local_to_remote_url_access', 'default': False }, - 'maxDiskCacheSize': { 'mapping': 'max_disk_cache_size', 'default': -1 }, - 'outputEncoding': { 'mapping': 'output_encoding', 'default': 'System' }, - 'proxy': { 'mapping': 'proxy', 'default': None }, - 'scriptEncoding': { 'mapping': 'script_encoding', 'default': 'utf-8' }, - 'verbose': { 'mapping': 'verbose', 'default': False } - } - - do_action('ConfigInit', self.settings) - - # generate dynamic properties - for setting in self.settings: - self.setProperty(setting, self.settings[setting]['default']) - - # now it's time to parse our JSON file - if not json.lstrip().startswith('{') or not json.rstrip().endswith('}'): - qWarning('Config file MUST be in JSON format!') - return - - with QPyFile(':/configurator.js') as f: - configurator = f.readAll() - - webPage = QWebPage(self) - - # add config object - webPage.mainFrame().addToJavaScriptWindowObject('config', self) - # apply settings - webPage.mainFrame().evaluateJavaScript(configurator.replace('%1', json)) - - do_action('Config') diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/cookiejar.py phantomjs-1.4.0+dfsg/python/pyphantomjs/cookiejar.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/cookiejar.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/cookiejar.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' from PyQt4.QtCore import QSettings @@ -33,7 +33,7 @@ settings.beginGroup(url.host()) for cookie in cookieList: - settings.setValue(cookie.name(), cookie.value()) + settings.setValue(str(cookie.name()), str(cookie.value())) settings.sync() diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/csconverter.py phantomjs-1.4.0+dfsg/python/pyphantomjs/csconverter.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/csconverter.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/csconverter.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,15 +10,13 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' -import sys - from PyQt4.QtCore import QObject from PyQt4.QtGui import QApplication from PyQt4.QtWebKit import QWebPage @@ -39,10 +37,8 @@ self.m_webPage = QWebPage(self) with QPyFile(':/resources/coffee-script.js') as f: - script = f.readAll() - - self.m_webPage.mainFrame().evaluateJavaScript(script) - self.m_webPage.mainFrame().addToJavaScriptWindowObject('converter', self) + self.m_webPage.mainFrame().evaluateJavaScript(f.readAll()) + self.m_webPage.mainFrame().addToJavaScriptWindowObject('converter', self) def convert(self, script): self.setProperty('source', script) diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/encoding.py phantomjs-1.4.0+dfsg/python/pyphantomjs/encoding.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/encoding.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/encoding.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import codecs diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/filesystem.py phantomjs-1.4.0+dfsg/python/pyphantomjs/filesystem.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/filesystem.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/filesystem.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import codecs @@ -130,7 +130,7 @@ def _size(self, path): try: return os.path.getsize(path) - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.size - %s: '%s'" % (e, path)) return -1 @@ -154,7 +154,7 @@ try: shutil.copy2(source, target) return True - except IOError as (t, e): + except IOError as (_, e): qDebug("FileSystem.copy - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -163,7 +163,7 @@ try: os.rename(source, target) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.rename - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -176,7 +176,7 @@ try: shutil.copytree(source, target) return True - except IOError as (t, e): + except IOError as (_, e): qDebug("FileSystem.copyTree - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -203,7 +203,7 @@ try: shutil.copytree(source, target, True) return True - except IOError as (t, e): + except IOError as (_, e): qDebug("FileSystem.copyLinkTree - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -234,7 +234,7 @@ try: f = codecs.open(path, mode, encoding='utf-8') return File(self, f) - except (IOError, ValueError) as (t, e): + except (IOError, ValueError) as (_, e): qDebug("FileSystem.open - %s: '%s'" % (e, path)) return @@ -269,11 +269,11 @@ def listTree(self, path): try: listing = [] - for root, dirs, files in os.walk(path): + for root, _, files in os.walk(path): for file_ in files: listing.append(os.path.join(root, file_)) return listing - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.listTree - %s: '%s'" % (e, path)) return @@ -281,11 +281,11 @@ def listDirectoryTree(self, path): try: listing = [] - for root, dirs, files in os.walk(path): + for root, dirs, _ in os.walk(path): for dir_ in dirs: listing.append(os.path.join(root, dir_)) return listing - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.listDirectoryTree - %s: '%s'" % (e, path)) return @@ -298,7 +298,7 @@ try: os.symlink(source, target) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.symbolicLink - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -307,7 +307,7 @@ try: os.link(source, target) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.hardLink - %s: '%s' -> '%s'" % (e, source, target)) return False @@ -315,7 +315,7 @@ def readLink(self, path): try: return os.readlink(path) - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.readLink - %s: '%s'" % (e, path)) return '' @@ -407,8 +407,8 @@ else: os.chown(path, -1, getgrnam(group).gr_gid) return True - except OSError as (t, e): - qDebug("FileSystem.changeGroup - %s: '%s':'%s'" % (e, owner, path)) + except OSError as (_, e): + qDebug("FileSystem.changeGroup - %s: '%s':'%s'" % (e, group, path)) except KeyError as e: qDebug("FileSystem.changeGroup - %s: '%s'" % (e.args[0], path)) return False @@ -422,8 +422,8 @@ else: os.lchown(path, -1, getgrnam(group).gr_gid) return True - except OSError as (t, e): - qDebug("FileSystem.changeLinkGroup - %s: '%s':'%s'" % (e, owner, path)) + except OSError as (_, e): + qDebug("FileSystem.changeLinkGroup - %s: '%s':'%s'" % (e, group, path)) except KeyError as e: qDebug("FileSystem.changeLinkGroup - %s: '%s'" % (e.args[0], path)) return False @@ -437,50 +437,12 @@ else: os.lchown(path, getpwnam(owner).pw_uid, -1) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.changeLinkOwner - %s: '%s':'%s'" % (e, owner, path)) except KeyError as e: qDebug("FileSystem.changeLinkOwner - %s: '%s'" % (e.args[0], path)) return False - @pyqtSlot(str, int, result=bool) - @pyqtSlot(str, 'QVariantMap', result=bool) - def changeLinkPermissions(self, path, permissions): - # permissions uses an object in 4 types: owner, group, others, special - # owner,group,others each has 3 types, read,write,executable, contained in an array - # special uses setuid,setgid,sticky - # - # In order to turn values on or off, just use true or false values. - # - # Permissions can alternatively be a numeric mode to chmod too. - - keys = { - 'owner': {'read': 'S_IRUSR', 'write': 'S_IWUSR', 'executable': 'S_IXUSR'}, - 'group': {'read': 'S_IRGRP', 'write': 'S_IWGRP', 'executable': 'S_IXGRP'}, - 'others': {'read': 'S_IROTH', 'write': 'S_IWOTH', 'executable': 'S_IXOTH'}, - 'special': {'setuid': 'S_ISUID', 'setgid': 'S_ISGID', 'sticky': 'S_ISVTX'} - } - - try: - if isinstance(permissions, int): - os.lchmod(path, permissions) - else: - bitnum = os.lstat(path).st_mode - for section in permissions: - for key in permissions[section]: - try: - if permissions[section][key] is True: - bitnum = bitnum | stat.__dict__[keys[section][key]] - elif permissions[section][key] is False: - bitnum = bitnum & ~stat.__dict__[keys[section][key]] - except KeyError: - pass - os.lchmod(path, bitnum) - return True - except OSError as (t, e): - qDebug("FileSystem.changeLinkPermissions - %s: '%s'" % (e, path)) - return False - @pyqtSlot(str, str, result=bool) @pyqtSlot(str, int, result=bool) def changeOwner(self, path, owner): @@ -490,7 +452,7 @@ else: os.chown(path, getpwnam(owner).pw_uid, -1) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.changeOwner - %s: '%s':'%s'" % (e, owner, path)) except KeyError as e: qDebug("FileSystem.changeOwner - %s: '%s'" % (e.args[0], path)) @@ -530,7 +492,7 @@ pass os.chmod(path, bitnum) return True - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.changePermissions - %s: '%s'" % (e, path)) return False @@ -542,7 +504,7 @@ 'name': getgrgid(finfo.st_gid).gr_name, 'uid': finfo.st_gid } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.group - %s: '%s'" % (e, path)) return @@ -554,7 +516,7 @@ 'name': getpwuid(finfo.st_uid).pw_name, 'uid': finfo.st_uid } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.owner - %s: '%s'" % (e, path)) return @@ -566,7 +528,7 @@ 'name': getgrgid(finfo.st_gid).gr_name, 'uid': finfo.st_gid } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.linkGroup - %s: '%s'" % (e, path)) return @@ -578,7 +540,7 @@ 'name': getpwuid(finfo.st_uid).pw_name, 'uid': finfo.st_uid } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.linkOwner - %s: '%s'" % (e, path)) return @@ -616,7 +578,7 @@ 'others': {'read': isOthRd, 'write': isOthWr, 'executable': isOthEx}, 'special': {'setuid': isSUid, 'setgid': isSGid, 'sticky': isStick} } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.linkPermissions - %s: '%s'" % (e, path)) return @@ -654,7 +616,7 @@ 'others': {'read': isOthRd, 'write': isOthWr, 'executable': isOthEx}, 'special': {'setuid': isSUid, 'setgid': isSGid, 'sticky': isStick} } - except OSError as (t, e): + except OSError as (_, e): qDebug("FileSystem.permissions - %s: '%s'" % (e, path)) return diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/filesystem-shim.js phantomjs-1.4.0+dfsg/python/pyphantomjs/filesystem-shim.js --- phantomjs-1.3.0+dfsg/python/pyphantomjs/filesystem-shim.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/filesystem-shim.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,163 +0,0 @@ -/*jslint sloppy: true, nomen: true */ -/*global window:true,phantom:true,fs:true */ - -/* - This file is part of the PhantomJS project from Ofi Labs. - - Copyright (C) 2011 Ivan De Marino - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// window.fs -// JavaScript "shim" to throw exceptions in case a critical operation fails. - -/** Open and return a "file" object. - * It will throw exception if it fails. - * - * @param path Path of the file to open - * @param mode Open Mode. A string made of 'r', 'w', 'a/+' characters. - * @return "file" object - */ -window.fs.open = function (path, mode) { - var file = window.fs._open(path, mode); - if (file) { - return file; - } - throw "Unable to open file '" + path + "'"; -}; - -/** Open, read and return content of a file. - * It will throw an exception if it fails. - * - * @param path Path of the file to read from - * @return file content - */ -window.fs.read = function (path) { - var f = fs.open(path, 'r'), - content = f.read(); - - f.close(); - return content; -}; - -/** Open and write content to a file - * It will throw an exception if it fails. - * - * @param path Path of the file to read from - * @param content Content to write to the file - * @param mode Open Mode. A string made of 'w' or 'a / +' characters. - */ -window.fs.write = function (path, content, mode) { - var f = fs.open(path, mode); - - f.write(content); - f.close(); -}; - -/** Return the size of a file, in bytes. - * It will throw an exception if it fails. - * - * @param path Path of the file to read the size of - * @return File size in bytes - */ -window.fs.size = function (path) { - var size = fs._size(path); - if (size !== -1) { - return size; - } - throw "Unable to read file '" + path + "' size"; -}; - -/** Copy a file. - * It will throw an exception if it fails. - * - * @param source Path of the source file - * @param destination Path of the destination file - */ -window.fs.copy = function (source, destination) { - if (!fs._copy(source, destination)) { - throw "Unable to copy file '" + source + "' at '" + destination + "'"; - } -}; - -/** Copy a directory tree. - * It will throw an exception if it fails. - * - * @param source Path of the source directory tree - * @param destination Path of the destination directory tree - */ -window.fs.copyTree = function (source, destination) { - if (!fs._copyTree(source, destination)) { - throw "Unable to copy directory tree '" + source + "' at '" + destination + "'"; - } -}; - -/** Move a file. - * It will throw an exception if it fails. - * - * @param source Path of the source file - * @param destination Path of the destination file - */ -window.fs.move = function (source, destination) { - fs.copy(source, destination); - fs.remove(source); -}; - -/** Removes a file. - * It will throw an exception if it fails. - * - * @param path Path of the file to remove - */ -window.fs.remove = function (path) { - if (!fs._remove(path)) { - throw "Unable to remove file '" + path + "'"; - } -}; - -/** Removes a directory. - * It will throw an exception if it fails. - * - * @param path Path of the directory to remove - */ -window.fs.removeDirectory = function (path) { - if (!fs._removeDirectory(path)) { - throw "Unable to remove directory '" + path + "'"; - } -}; - -/** Removes a directory tree. - * It will throw an exception if it fails. - * - * @param path Path of the directory tree to remove - */ -window.fs.removeTree = function (path) { - if (!fs._removeTree(path)) { - throw "Unable to remove directory tree '" + path + "'"; - } -}; - -window.fs.touch = function (path) { - fs.write(path, "", 'a'); -}; \ No newline at end of file diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/__init__.py phantomjs-1.4.0+dfsg/python/pyphantomjs/__init__.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/__init__.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/__init__.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,13 +10,13 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' -__version_info__ = (1, 3, 0) +__version_info__ = (1, 4, 0) __version__ = '.'.join(map(str, __version_info__)) diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/modules/webserver.js phantomjs-1.4.0+dfsg/python/pyphantomjs/modules/webserver.js --- phantomjs-1.3.0+dfsg/python/pyphantomjs/modules/webserver.js 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/modules/webserver.js 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,130 @@ +/*jslint sloppy: true, nomen: true */ +/*global exports:true,phantom:true */ + +/* + This file is part of the PhantomJS project from Ofi Labs. + + Copyright (C) 2011 Ariya Hidayat + Copyright (C) 2011 Ivan De Marino + Copyright (C) 2011 James Roe + Copyright (C) 2011 execjosh, http://execjosh.blogspot.com + Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Milian Wolff + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +exports.create = function (opts) { + var server = phantom.createWebServer(); + var handlers = {}; + + function checkType(o, type) { + return typeof o === type; + } + + function isObject(o) { + return checkType(o, 'object'); + } + + function isUndefined(o) { + return checkType(o, 'undefined'); + } + + function isUndefinedOrNull(o) { + return isUndefined(o) || null === o; + } + + function copyInto(target, source) { + if (target === source || isUndefinedOrNull(source)) { + return target; + } + + target = target || {}; + + // Copy into objects only + if (isObject(target)) { + // Make sure source exists + source = source || {}; + + if (isObject(source)) { + var i, newTarget, newSource; + for (i in source) { + if (source.hasOwnProperty(i)) { + newTarget = target[i]; + newSource = source[i]; + + if (newTarget && isObject(newSource)) { + // Deep copy + newTarget = copyInto(target[i], newSource); + } else { + newTarget = newSource; + } + + if (!isUndefined(newTarget)) { + target[i] = newTarget; + } + } + } + } else { + target = source; + } + } + + return target; + } + + function defineSetter(handlerName, signalName) { + server.__defineSetter__(handlerName, function (f) { + if (handlers && typeof handlers[signalName] === 'function') { + try { + this[signalName].disconnect(handlers[signalName]); + } catch (e) {} + } + handlers[signalName] = f; + this[signalName].connect(handlers[signalName]); + }); + } + + // deep copy + //TODO: use this? +// server.settings = JSON.parse(JSON.stringify(phantom.defaultServerSettings)); + + defineSetter("onNewRequest", "newRequest"); + + server.listen = function (port, handler) { + if (arguments.length === 2 && typeof handler === 'function') { + this.onNewRequest = handler; + //TODO: settings? + return this.listenOnPort(port); + } + throw "Wrong use of WebServer#listen"; + }; + + // Copy options into server + if (opts) { + server = copyInto(server, opts); + } + + return server; +}; diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/networkaccessmanager.py phantomjs-1.4.0+dfsg/python/pyphantomjs/networkaccessmanager.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/networkaccessmanager.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/networkaccessmanager.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' from PyQt4.QtCore import pyqtSignal, QDateTime diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/phantom.py phantomjs-1.4.0+dfsg/python/pyphantomjs/phantom.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/phantom.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/phantom.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,17 +10,16 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import os import sys -import sip from PyQt4.QtCore import pyqtProperty, pyqtSlot, QObject from PyQt4.QtGui import QApplication from PyQt4.QtNetwork import QNetworkProxy, QNetworkProxyFactory @@ -29,8 +28,9 @@ from encoding import Encode from filesystem import FileSystem from plugincontroller import do_action -from utils import injectJsInFrame, QPyFile -from webpage import WebPage +from utils import QPyFile +from webpage import injectJsInFrame, WebPage +from webserver import WebServer class Phantom(QObject): @@ -40,6 +40,7 @@ # variable declarations self.m_defaultPageSettings = {} self.m_pages = [] + self.m_servers = [] self.m_verbose = args.verbose self.m_page = WebPage(self, args) self.m_returnValue = 0 @@ -58,7 +59,7 @@ if args.proxy is None: QNetworkProxyFactory.setUseSystemConfiguration(True) else: - proxy = QNetworkProxy(QNetworkProxy.HttpProxy, args.proxy[0], int(args.proxy[1])) + proxy = QNetworkProxy(args.proxy_type, args.proxy[0], int(args.proxy[1])) QNetworkProxy.setApplicationProxy(proxy) self.m_page.javaScriptConsoleMessageSent.connect(self.printConsoleMessage) @@ -77,8 +78,7 @@ self.m_page.mainFrame().addToJavaScriptWindowObject('phantom', self) with QPyFile(':/bootstrap.js') as f: - bootstrap = f.readAll() - self.m_page.mainFrame().evaluateJavaScript(bootstrap) + self.m_page.mainFrame().evaluateJavaScript(f.readAll()) do_action('PhantomInitPost') @@ -114,6 +114,15 @@ page.libraryPath = os.path.dirname(os.path.abspath(self.m_scriptFile)) return page + @pyqtSlot(result=WebServer) + def createWebServer(self): + server = WebServer(self) + self.m_servers.append(server) + # :TODO: + # page.applySettings(self.m_defaultPageSettings) + # page.libraryPath = os.path.dirname(os.path.abspath(self.m_scriptFile) + return server + @pyqtProperty('QVariantMap') def defaultPageSettings(self): return self.m_defaultPageSettings @@ -125,13 +134,17 @@ self.m_returnValue = code # stop javascript execution in start script; - # delete all the pages C++ objects, then clear + # release all pages, then clear # the page list, and empty the Phantom page for page in self.m_pages: - sip.delete(page) + page.release() del self.m_pages[:] self.m_page = None + for server in self.m_servers: + server.release() + del self.m_servers[:] # not needed, but I'd rather be thorough + QApplication.instance().exit(code) @pyqtSlot(str, result=bool) @@ -143,9 +156,7 @@ moduleSourceFilePath = ':/modules/%s.js' % name with QPyFile(moduleSourceFilePath) as f: - moduleSource = f.readAll() - - return moduleSource + return f.readAll() @pyqtProperty(str) def libraryPath(self): diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/plugincontroller.py phantomjs-1.4.0+dfsg/python/pyphantomjs/plugincontroller.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/plugincontroller.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/plugincontroller.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import os @@ -26,22 +26,26 @@ hooks = defaultdict(dict) -def _checkHookExists(create=False): - '''Decorator that will raise LookupError or create hook if hook doesn't exist''' - def outterWrap(func): - def innerWrap(*args, **kwargs): - if args[0] not in hooks: - if create: - hooks[args[0]]['count'] = 0 - hooks[args[0]]['plugins'] = [] - else: - raise LookupError("Hook '%s' was not found" % args[0]) - return func(*args, **kwargs) - return innerWrap - return outterWrap +def createHook(func): + '''Decorator that will create a hook if the hook doesn't exist''' + def innerWrap(*args, **kwargs): + if args[0] not in hooks: + hooks[args[0]]['count'] = 0 + hooks[args[0]]['plugins'] = [] + return func(*args, **kwargs) + return innerWrap + + +def checkHookExists(func): + '''Decorator that will raise LookupError if the hook doesn't exist''' + def innerWrap(*args, **kwargs): + if args[0] not in hooks: + raise LookupError("Hook '%s' was not found" % args[0]) + return func(*args, **kwargs) + return innerWrap -@_checkHookExists(True) +@createHook def add_action(hook, priority=10): '''Decorator to be used for registering a function to a specific hook. Functions with lower priority are @@ -53,13 +57,13 @@ return register -@_checkHookExists() +@checkHookExists def did_action(hook): '''Find out how many times a hook was fired''' return hooks[hook]['count'] -@_checkHookExists(True) +@createHook def do_action(hook, *args, **kwargs): '''Trigger a hook. It will run any functions that have registered themselves to the hook. Any additional arguments or keyword @@ -101,7 +105,7 @@ raise LookupError("Hook '%s' was not found" % hook) for plugin in hooks[hook]['plugins']: - if plugin[1] == func: + if plugin[1] is func: return True return False @@ -120,13 +124,13 @@ raise LookupError("Hook '%s' was not found" % hook) for i, plugin in enumerate(hooks[hook]['plugins']): - if plugin[1] == func and plugin[0] == priority: + if plugin[1] is func and plugin[0] == priority: del hooks[hook]['plugins'][i] return True return False -@_checkHookExists() +@checkHookExists def remove_all_actions(hook, priority=None): '''Remove all functions that have been registered to hook. If priority is used, remove all actions from that priority @@ -174,20 +178,18 @@ if plugins_path is None: # path is different when frozen if hasattr(sys, 'frozen'): - path = os.path.dirname(os.path.abspath(sys.executable)) + plugins_path = os.path.dirname(os.path.abspath(sys.executable)) else: - path = os.path.dirname(os.path.abspath(__file__)) + plugins_path = os.path.dirname(os.path.abspath(__file__)) - generateModuleName = lambda p: '.'.join(('plugins', p[0], p[1])) - plugin_list = glob(os.path.join(path, 'plugins/*/*.py')) + plugins_path = os.path.join(plugins_path, 'plugins') else: # make sure it's an absolute path plugins_path = os.path.abspath(plugins_path) - # append directory for module loading - sys.path[1:1] = plugins_path, - generateModuleName = lambda p: '.'.join((p[0], p[1])) - plugin_list = glob(os.path.join(plugins_path, '*/*.py')) + sys.path.insert(1, plugins_path) + + plugin_list = glob(os.path.join(plugins_path, '*/*.py')) # now convert list to [('plugin_folder', 'file'), ...] plugin_list = [(os.path.split(os.path.dirname(f))[1], os.path.splitext(os.path.split(f)[1])[0]) for f in plugin_list] @@ -195,5 +197,5 @@ # initialize plugins for plugin in plugin_list: if plugin[0] == plugin[1]: - moduleName = generateModuleName(plugin) + moduleName = '.'.join((plugin[0], plugin[1])) __import__(moduleName, globals(), locals(), [], -1) diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/pyphantomjs.py phantomjs-1.4.0+dfsg/python/pyphantomjs/pyphantomjs.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/pyphantomjs.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/pyphantomjs.py 2011-12-27 13:46:57.000000000 +0000 @@ -11,11 +11,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' # automatically convert Qt types by using api 2 @@ -24,10 +24,8 @@ 'QUrl', 'QVariant'): sip.setapi(item, 2) -import os import sys -from PyQt4.QtCore import qInstallMsgHandler from PyQt4.QtGui import QApplication, QIcon from plugincontroller import do_action @@ -38,9 +36,8 @@ import resources from __init__ import __version__ -from config import Config +from arguments import parseArgs from phantom import Phantom -from utils import argParser, MessageHandler # make keyboard interrupt quit program import signal @@ -52,83 +49,8 @@ sys.stderr = SafeStreamFilter(sys.stderr) -def debug(debug_type): - def excepthook(type_, value, tb): - import traceback - - # print the exception... - traceback.print_exception(type_, value, tb) - print - # ...then start the debugger in post-mortem mode - pdb.pm() - - # we are NOT in interactive mode - if not hasattr(sys, 'ps1') or sys.stderr.target.isatty(): - import pdb - - from PyQt4.QtCore import pyqtRemoveInputHook - pyqtRemoveInputHook() - - if debug_type == 'exception': - sys.excepthook = excepthook - elif debug_type == 'program': - pdb.set_trace() - - -def parseArgs(app, args): - # Handle all command-line options - p = argParser() - arg_data = p.parse_known_args(args) - args = arg_data[0] - args.script_args = arg_data[1] - - # register an alternative Message Handler - messageHandler = MessageHandler(args.verbose) - qInstallMsgHandler(messageHandler.process) - - file_check = (args.cookies_file, args.config) - for file_ in file_check: - if file_ is not None and not os.path.exists(file_): - sys.exit("No such file or directory: '%s'" % file_) - - if args.config: - config = Config(app, args.config) - # apply settings - for setting in config.settings: - setattr(args, config.settings[setting]['mapping'], config.property(setting)) - - split_check = ( - (args.proxy, 'proxy'), - ) - for arg, name in split_check: - if arg: - item = arg.split(':') - if len(item) < 2 or not len(item[1]): - p.print_help() - sys.exit(1) - setattr(args, name, item) - - do_action('ParseArgs', args) - - if args.debug: - debug(args.debug) - - # verbose flag got changed on us, so we reload the flag - if messageHandler.verbose != args.verbose: - messageHandler.verbose = args.verbose - - if args.script is None: - p.print_help() - sys.exit(1) - - if not os.path.exists(args.script): - sys.exit("No such file or directory: '%s'" % args.script) - - return args - - -def main(): - app = QApplication(sys.argv) +def main(arguments): + app = QApplication([sys.argv[0]] + arguments) app.setWindowIcon(QIcon(':/resources/pyphantomjs-icon.png')) app.setApplicationName('PyPhantomJS') @@ -136,7 +58,7 @@ app.setOrganizationDomain('www.umaclan.com') app.setApplicationVersion(__version__) - args = parseArgs(app, sys.argv[1:]) + args = parseArgs(app, arguments) phantom = Phantom(app, args) @@ -151,4 +73,4 @@ if __name__ == '__main__': - sys.exit(main()) + sys.exit(main(sys.argv[1:])) diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/resources.py phantomjs-1.4.0+dfsg/python/pyphantomjs/resources.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/resources.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/resources.py 2011-12-27 13:46:57.000000000 +0000 @@ -2,15 +2,15 @@ # Resource object code # -# Created: Tue Sep 13 14:48:39 2011 -# by: The Resource Compiler for PyQt (Qt v4.7.2) +# Created: Fri Dec 2 16:03:48 2011 +# by: The Resource Compiler for PyQt (Qt v4.7.3) # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore qt_resource_data = "\ -\x00\x00\x09\x75\ +\x00\x00\x09\xbc\ \x2f\ \x2a\x6a\x73\x6c\x69\x6e\x74\x20\x73\x6c\x6f\x70\x70\x79\x3a\x20\ \x74\x72\x75\x65\x2c\x20\x6e\x6f\x6d\x65\x6e\x3a\x20\x74\x72\x75\ @@ -134,36 +134,40 @@ \x74\x73\x3b\x0a\x0a\x20\x20\x20\x20\x69\x66\x20\x28\x6e\x61\x6d\ \x65\x20\x3d\x3d\x3d\x20\x27\x77\x65\x62\x70\x61\x67\x65\x27\x20\ \x7c\x7c\x20\x6e\x61\x6d\x65\x20\x3d\x3d\x3d\x20\x27\x66\x73\x27\ -\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x63\x6f\x64\x65\ -\x20\x3d\x20\x70\x68\x61\x6e\x74\x6f\x6d\x2e\x6c\x6f\x61\x64\x4d\ -\x6f\x64\x75\x6c\x65\x53\x6f\x75\x72\x63\x65\x28\x6e\x61\x6d\x65\ -\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x66\x75\x6e\x63\x20\ -\x3d\x20\x6e\x65\x77\x20\x46\x75\x6e\x63\x74\x69\x6f\x6e\x28\x22\ -\x65\x78\x70\x6f\x72\x74\x73\x22\x2c\x20\x22\x77\x69\x6e\x64\x6f\ -\x77\x22\x2c\x20\x63\x6f\x64\x65\x29\x3b\x0a\x20\x20\x20\x20\x20\ -\x20\x20\x20\x65\x78\x70\x6f\x72\x74\x73\x20\x3d\x20\x7b\x7d\x3b\ -\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x69\x66\x20\x28\x6e\x61\x6d\ -\x65\x20\x3d\x3d\x3d\x20\x27\x66\x73\x27\x29\x20\x7b\x0a\x20\x20\ -\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x65\x78\x70\x6f\x72\x74\ -\x73\x20\x3d\x20\x70\x68\x61\x6e\x74\x6f\x6d\x2e\x63\x72\x65\x61\ -\x74\x65\x46\x69\x6c\x65\x73\x79\x73\x74\x65\x6d\x28\x29\x3b\x0a\ -\x20\x20\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x20\x20\ -\x20\x20\x66\x75\x6e\x63\x2e\x63\x61\x6c\x6c\x28\x7b\x7d\x2c\x20\ -\x65\x78\x70\x6f\x72\x74\x73\x2c\x20\x7b\x7d\x29\x3b\x0a\x20\x20\ -\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x65\x78\x70\ -\x6f\x72\x74\x73\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x0a\x20\x20\x20\ -\x20\x69\x66\x20\x28\x74\x79\x70\x65\x6f\x66\x20\x65\x78\x70\x6f\ -\x72\x74\x73\x20\x3d\x3d\x3d\x20\x27\x75\x6e\x64\x65\x66\x69\x6e\ -\x65\x64\x27\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x74\ -\x68\x72\x6f\x77\x20\x27\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x6d\x6f\ -\x64\x75\x6c\x65\x20\x27\x20\x2b\x20\x6e\x61\x6d\x65\x20\x2b\x20\ -\x27\x20\x66\x6f\x72\x20\x72\x65\x71\x75\x69\x72\x65\x28\x29\x27\ -\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x7d\x0a\x0a\x2f\x2f\x20\x4c\x65\ -\x67\x61\x63\x79\x20\x77\x61\x79\x20\x74\x6f\x20\x75\x73\x65\x20\ -\x57\x65\x62\x50\x61\x67\x65\x0a\x77\x69\x6e\x64\x6f\x77\x2e\x57\ -\x65\x62\x50\x61\x67\x65\x20\x3d\x20\x72\x65\x71\x75\x69\x72\x65\ -\x28\x27\x77\x65\x62\x70\x61\x67\x65\x27\x29\x2e\x63\x72\x65\x61\ -\x74\x65\x3b\x0a\ +\x20\x7c\x7c\x20\x6e\x61\x6d\x65\x20\x3d\x3d\x20\x27\x77\x65\x62\ +\x73\x65\x72\x76\x65\x72\x27\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\ +\x20\x20\x20\x63\x6f\x64\x65\x20\x3d\x20\x70\x68\x61\x6e\x74\x6f\ +\x6d\x2e\x6c\x6f\x61\x64\x4d\x6f\x64\x75\x6c\x65\x53\x6f\x75\x72\ +\x63\x65\x28\x6e\x61\x6d\x65\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x66\x75\x6e\x63\x20\x3d\x20\x6e\x65\x77\x20\x46\x75\x6e\ +\x63\x74\x69\x6f\x6e\x28\x22\x65\x78\x70\x6f\x72\x74\x73\x22\x2c\ +\x20\x22\x77\x69\x6e\x64\x6f\x77\x22\x2c\x20\x63\x6f\x64\x65\x29\ +\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x65\x78\x70\x6f\x72\x74\ +\x73\x20\x3d\x20\x7b\x7d\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x69\x66\x20\x28\x6e\x61\x6d\x65\x20\x3d\x3d\x3d\x20\x27\x66\x73\ +\x27\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x65\x78\x70\x6f\x72\x74\x73\x20\x3d\x20\x70\x68\x61\x6e\x74\ +\x6f\x6d\x2e\x63\x72\x65\x61\x74\x65\x46\x69\x6c\x65\x73\x79\x73\ +\x74\x65\x6d\x28\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7d\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x66\x75\x6e\x63\x2e\x63\x61\ +\x6c\x6c\x28\x7b\x7d\x2c\x20\x65\x78\x70\x6f\x72\x74\x73\x2c\x20\ +\x7b\x7d\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x72\x65\x74\ +\x75\x72\x6e\x20\x65\x78\x70\x6f\x72\x74\x73\x3b\x0a\x20\x20\x20\ +\x20\x7d\x0a\x0a\x20\x20\x20\x20\x69\x66\x20\x28\x74\x79\x70\x65\ +\x6f\x66\x20\x65\x78\x70\x6f\x72\x74\x73\x20\x3d\x3d\x3d\x20\x27\ +\x75\x6e\x64\x65\x66\x69\x6e\x65\x64\x27\x29\x20\x7b\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x20\x74\x68\x72\x6f\x77\x20\x27\x55\x6e\x6b\ +\x6e\x6f\x77\x6e\x20\x6d\x6f\x64\x75\x6c\x65\x20\x27\x20\x2b\x20\ +\x6e\x61\x6d\x65\x20\x2b\x20\x27\x20\x66\x6f\x72\x20\x72\x65\x71\ +\x75\x69\x72\x65\x28\x29\x27\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x7d\ +\x0a\x0a\x2f\x2f\x20\x4c\x65\x67\x61\x63\x79\x20\x77\x61\x79\x20\ +\x74\x6f\x20\x75\x73\x65\x20\x57\x65\x62\x50\x61\x67\x65\x0a\x77\ +\x69\x6e\x64\x6f\x77\x2e\x57\x65\x62\x50\x61\x67\x65\x20\x3d\x20\ +\x72\x65\x71\x75\x69\x72\x65\x28\x27\x77\x65\x62\x70\x61\x67\x65\ +\x27\x29\x2e\x63\x72\x65\x61\x74\x65\x3b\x0a\x77\x69\x6e\x64\x6f\ +\x77\x2e\x57\x65\x62\x53\x65\x72\x76\x65\x72\x20\x3d\x20\x72\x65\ +\x71\x75\x69\x72\x65\x28\x27\x77\x65\x62\x73\x65\x72\x76\x65\x72\ +\x27\x29\x2e\x63\x72\x65\x61\x74\x65\x3b\x0a\ \x00\x00\x01\x1a\ \x28\ \x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x28\x6f\x70\x74\x73\x29\x20\ @@ -4357,6 +4361,295 @@ \x40\x1a\x41\xe2\x10\xf1\xb9\x99\x34\x59\x2e\x08\x20\xc2\x77\x60\ \xad\x8e\x88\xa1\x8d\x43\x29\x4f\x8b\x89\x01\x6b\xfc\xff\x01\x4e\ \x6c\xf6\xa7\ +\x00\x00\x11\xe9\ +\x2f\ +\x2a\x6a\x73\x6c\x69\x6e\x74\x20\x73\x6c\x6f\x70\x70\x79\x3a\x20\ +\x74\x72\x75\x65\x2c\x20\x6e\x6f\x6d\x65\x6e\x3a\x20\x74\x72\x75\ +\x65\x20\x2a\x2f\x0a\x2f\x2a\x67\x6c\x6f\x62\x61\x6c\x20\x65\x78\ +\x70\x6f\x72\x74\x73\x3a\x74\x72\x75\x65\x2c\x70\x68\x61\x6e\x74\ +\x6f\x6d\x3a\x74\x72\x75\x65\x20\x2a\x2f\x0a\x0a\x2f\x2a\x0a\x20\ +\x20\x54\x68\x69\x73\x20\x66\x69\x6c\x65\x20\x69\x73\x20\x70\x61\ +\x72\x74\x20\x6f\x66\x20\x74\x68\x65\x20\x50\x68\x61\x6e\x74\x6f\ +\x6d\x4a\x53\x20\x70\x72\x6f\x6a\x65\x63\x74\x20\x66\x72\x6f\x6d\ +\x20\x4f\x66\x69\x20\x4c\x61\x62\x73\x2e\x0a\x0a\x20\x20\x43\x6f\ +\x70\x79\x72\x69\x67\x68\x74\x20\x28\x43\x29\x20\x32\x30\x31\x31\ +\x20\x41\x72\x69\x79\x61\x20\x48\x69\x64\x61\x79\x61\x74\x20\x3c\ +\x61\x72\x69\x79\x61\x2e\x68\x69\x64\x61\x79\x61\x74\x40\x67\x6d\ +\x61\x69\x6c\x2e\x63\x6f\x6d\x3e\x0a\x20\x20\x43\x6f\x70\x79\x72\ +\x69\x67\x68\x74\x20\x28\x43\x29\x20\x32\x30\x31\x31\x20\x49\x76\ +\x61\x6e\x20\x44\x65\x20\x4d\x61\x72\x69\x6e\x6f\x20\x3c\x69\x76\ +\x61\x6e\x2e\x64\x65\x2e\x6d\x61\x72\x69\x6e\x6f\x40\x67\x6d\x61\ +\x69\x6c\x2e\x63\x6f\x6d\x3e\x0a\x20\x20\x43\x6f\x70\x79\x72\x69\ +\x67\x68\x74\x20\x28\x43\x29\x20\x32\x30\x31\x31\x20\x4a\x61\x6d\ +\x65\x73\x20\x52\x6f\x65\x20\x3c\x72\x6f\x65\x6a\x61\x6d\x65\x73\ +\x31\x32\x40\x68\x6f\x74\x6d\x61\x69\x6c\x2e\x63\x6f\x6d\x3e\x0a\ +\x20\x20\x43\x6f\x70\x79\x72\x69\x67\x68\x74\x20\x28\x43\x29\x20\ +\x32\x30\x31\x31\x20\x65\x78\x65\x63\x6a\x6f\x73\x68\x2c\x20\x68\ +\x74\x74\x70\x3a\x2f\x2f\x65\x78\x65\x63\x6a\x6f\x73\x68\x2e\x62\ +\x6c\x6f\x67\x73\x70\x6f\x74\x2e\x63\x6f\x6d\x0a\x20\x20\x43\x6f\ +\x70\x79\x72\x69\x67\x68\x74\x20\x28\x43\x29\x20\x32\x30\x31\x31\ +\x20\x4b\x6c\x61\x72\xc3\xa4\x6c\x76\x64\x61\x6c\x65\x6e\x73\x20\ +\x44\x61\x74\x61\x6b\x6f\x6e\x73\x75\x6c\x74\x20\x41\x42\x2c\x20\ +\x61\x20\x4b\x44\x41\x42\x20\x47\x72\x6f\x75\x70\x20\x63\x6f\x6d\ +\x70\x61\x6e\x79\x2c\x20\x69\x6e\x66\x6f\x40\x6b\x64\x61\x62\x2e\ +\x63\x6f\x6d\x0a\x20\x20\x41\x75\x74\x68\x6f\x72\x3a\x20\x4d\x69\ +\x6c\x69\x61\x6e\x20\x57\x6f\x6c\x66\x66\x20\x3c\x6d\x69\x6c\x69\ +\x61\x6e\x2e\x77\x6f\x6c\x66\x66\x40\x6b\x64\x61\x62\x2e\x63\x6f\ +\x6d\x3e\x0a\x0a\x20\x20\x52\x65\x64\x69\x73\x74\x72\x69\x62\x75\ +\x74\x69\x6f\x6e\x20\x61\x6e\x64\x20\x75\x73\x65\x20\x69\x6e\x20\ +\x73\x6f\x75\x72\x63\x65\x20\x61\x6e\x64\x20\x62\x69\x6e\x61\x72\ +\x79\x20\x66\x6f\x72\x6d\x73\x2c\x20\x77\x69\x74\x68\x20\x6f\x72\ +\x20\x77\x69\x74\x68\x6f\x75\x74\x0a\x20\x20\x6d\x6f\x64\x69\x66\ +\x69\x63\x61\x74\x69\x6f\x6e\x2c\x20\x61\x72\x65\x20\x70\x65\x72\ +\x6d\x69\x74\x74\x65\x64\x20\x70\x72\x6f\x76\x69\x64\x65\x64\x20\ +\x74\x68\x61\x74\x20\x74\x68\x65\x20\x66\x6f\x6c\x6c\x6f\x77\x69\ +\x6e\x67\x20\x63\x6f\x6e\x64\x69\x74\x69\x6f\x6e\x73\x20\x61\x72\ +\x65\x20\x6d\x65\x74\x3a\x0a\x0a\x20\x20\x20\x20\x2a\x20\x52\x65\ +\x64\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x73\x20\x6f\x66\ +\x20\x73\x6f\x75\x72\x63\x65\x20\x63\x6f\x64\x65\x20\x6d\x75\x73\ +\x74\x20\x72\x65\x74\x61\x69\x6e\x20\x74\x68\x65\x20\x61\x62\x6f\ +\x76\x65\x20\x63\x6f\x70\x79\x72\x69\x67\x68\x74\x0a\x20\x20\x20\ +\x20\x20\x20\x6e\x6f\x74\x69\x63\x65\x2c\x20\x74\x68\x69\x73\x20\ +\x6c\x69\x73\x74\x20\x6f\x66\x20\x63\x6f\x6e\x64\x69\x74\x69\x6f\ +\x6e\x73\x20\x61\x6e\x64\x20\x74\x68\x65\x20\x66\x6f\x6c\x6c\x6f\ +\x77\x69\x6e\x67\x20\x64\x69\x73\x63\x6c\x61\x69\x6d\x65\x72\x2e\ +\x0a\x20\x20\x20\x20\x2a\x20\x52\x65\x64\x69\x73\x74\x72\x69\x62\ +\x75\x74\x69\x6f\x6e\x73\x20\x69\x6e\x20\x62\x69\x6e\x61\x72\x79\ +\x20\x66\x6f\x72\x6d\x20\x6d\x75\x73\x74\x20\x72\x65\x70\x72\x6f\ +\x64\x75\x63\x65\x20\x74\x68\x65\x20\x61\x62\x6f\x76\x65\x20\x63\ +\x6f\x70\x79\x72\x69\x67\x68\x74\x0a\x20\x20\x20\x20\x20\x20\x6e\ +\x6f\x74\x69\x63\x65\x2c\x20\x74\x68\x69\x73\x20\x6c\x69\x73\x74\ +\x20\x6f\x66\x20\x63\x6f\x6e\x64\x69\x74\x69\x6f\x6e\x73\x20\x61\ +\x6e\x64\x20\x74\x68\x65\x20\x66\x6f\x6c\x6c\x6f\x77\x69\x6e\x67\ +\x20\x64\x69\x73\x63\x6c\x61\x69\x6d\x65\x72\x20\x69\x6e\x20\x74\ +\x68\x65\x0a\x20\x20\x20\x20\x20\x20\x64\x6f\x63\x75\x6d\x65\x6e\ +\x74\x61\x74\x69\x6f\x6e\x20\x61\x6e\x64\x2f\x6f\x72\x20\x6f\x74\ +\x68\x65\x72\x20\x6d\x61\x74\x65\x72\x69\x61\x6c\x73\x20\x70\x72\ +\x6f\x76\x69\x64\x65\x64\x20\x77\x69\x74\x68\x20\x74\x68\x65\x20\ +\x64\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x2e\x0a\x20\x20\ +\x20\x20\x2a\x20\x4e\x65\x69\x74\x68\x65\x72\x20\x74\x68\x65\x20\ +\x6e\x61\x6d\x65\x20\x6f\x66\x20\x74\x68\x65\x20\x3c\x6f\x72\x67\ +\x61\x6e\x69\x7a\x61\x74\x69\x6f\x6e\x3e\x20\x6e\x6f\x72\x20\x74\ +\x68\x65\x0a\x20\x20\x20\x20\x20\x20\x6e\x61\x6d\x65\x73\x20\x6f\ +\x66\x20\x69\x74\x73\x20\x63\x6f\x6e\x74\x72\x69\x62\x75\x74\x6f\ +\x72\x73\x20\x6d\x61\x79\x20\x62\x65\x20\x75\x73\x65\x64\x20\x74\ +\x6f\x20\x65\x6e\x64\x6f\x72\x73\x65\x20\x6f\x72\x20\x70\x72\x6f\ +\x6d\x6f\x74\x65\x20\x70\x72\x6f\x64\x75\x63\x74\x73\x0a\x20\x20\ +\x20\x20\x20\x20\x64\x65\x72\x69\x76\x65\x64\x20\x66\x72\x6f\x6d\ +\x20\x74\x68\x69\x73\x20\x73\x6f\x66\x74\x77\x61\x72\x65\x20\x77\ +\x69\x74\x68\x6f\x75\x74\x20\x73\x70\x65\x63\x69\x66\x69\x63\x20\ +\x70\x72\x69\x6f\x72\x20\x77\x72\x69\x74\x74\x65\x6e\x20\x70\x65\ +\x72\x6d\x69\x73\x73\x69\x6f\x6e\x2e\x0a\x0a\x20\x20\x54\x48\x49\ +\x53\x20\x53\x4f\x46\x54\x57\x41\x52\x45\x20\x49\x53\x20\x50\x52\ +\x4f\x56\x49\x44\x45\x44\x20\x42\x59\x20\x54\x48\x45\x20\x43\x4f\ +\x50\x59\x52\x49\x47\x48\x54\x20\x48\x4f\x4c\x44\x45\x52\x53\x20\ +\x41\x4e\x44\x20\x43\x4f\x4e\x54\x52\x49\x42\x55\x54\x4f\x52\x53\ +\x20\x22\x41\x53\x20\x49\x53\x22\x0a\x20\x20\x41\x4e\x44\x20\x41\ +\x4e\x59\x20\x45\x58\x50\x52\x45\x53\x53\x20\x4f\x52\x20\x49\x4d\ +\x50\x4c\x49\x45\x44\x20\x57\x41\x52\x52\x41\x4e\x54\x49\x45\x53\ +\x2c\x20\x49\x4e\x43\x4c\x55\x44\x49\x4e\x47\x2c\x20\x42\x55\x54\ +\x20\x4e\x4f\x54\x20\x4c\x49\x4d\x49\x54\x45\x44\x20\x54\x4f\x2c\ +\x20\x54\x48\x45\x0a\x20\x20\x49\x4d\x50\x4c\x49\x45\x44\x20\x57\ +\x41\x52\x52\x41\x4e\x54\x49\x45\x53\x20\x4f\x46\x20\x4d\x45\x52\ +\x43\x48\x41\x4e\x54\x41\x42\x49\x4c\x49\x54\x59\x20\x41\x4e\x44\ +\x20\x46\x49\x54\x4e\x45\x53\x53\x20\x46\x4f\x52\x20\x41\x20\x50\ +\x41\x52\x54\x49\x43\x55\x4c\x41\x52\x20\x50\x55\x52\x50\x4f\x53\ +\x45\x0a\x20\x20\x41\x52\x45\x20\x44\x49\x53\x43\x4c\x41\x49\x4d\ +\x45\x44\x2e\x20\x49\x4e\x20\x4e\x4f\x20\x45\x56\x45\x4e\x54\x20\ +\x53\x48\x41\x4c\x4c\x20\x3c\x43\x4f\x50\x59\x52\x49\x47\x48\x54\ +\x20\x48\x4f\x4c\x44\x45\x52\x3e\x20\x42\x45\x20\x4c\x49\x41\x42\ +\x4c\x45\x20\x46\x4f\x52\x20\x41\x4e\x59\x0a\x20\x20\x44\x49\x52\ +\x45\x43\x54\x2c\x20\x49\x4e\x44\x49\x52\x45\x43\x54\x2c\x20\x49\ +\x4e\x43\x49\x44\x45\x4e\x54\x41\x4c\x2c\x20\x53\x50\x45\x43\x49\ +\x41\x4c\x2c\x20\x45\x58\x45\x4d\x50\x4c\x41\x52\x59\x2c\x20\x4f\ +\x52\x20\x43\x4f\x4e\x53\x45\x51\x55\x45\x4e\x54\x49\x41\x4c\x20\ +\x44\x41\x4d\x41\x47\x45\x53\x0a\x20\x20\x28\x49\x4e\x43\x4c\x55\ +\x44\x49\x4e\x47\x2c\x20\x42\x55\x54\x20\x4e\x4f\x54\x20\x4c\x49\ +\x4d\x49\x54\x45\x44\x20\x54\x4f\x2c\x20\x50\x52\x4f\x43\x55\x52\ +\x45\x4d\x45\x4e\x54\x20\x4f\x46\x20\x53\x55\x42\x53\x54\x49\x54\ +\x55\x54\x45\x20\x47\x4f\x4f\x44\x53\x20\x4f\x52\x20\x53\x45\x52\ +\x56\x49\x43\x45\x53\x3b\x0a\x20\x20\x4c\x4f\x53\x53\x20\x4f\x46\ +\x20\x55\x53\x45\x2c\x20\x44\x41\x54\x41\x2c\x20\x4f\x52\x20\x50\ +\x52\x4f\x46\x49\x54\x53\x3b\x20\x4f\x52\x20\x42\x55\x53\x49\x4e\ +\x45\x53\x53\x20\x49\x4e\x54\x45\x52\x52\x55\x50\x54\x49\x4f\x4e\ +\x29\x20\x48\x4f\x57\x45\x56\x45\x52\x20\x43\x41\x55\x53\x45\x44\ +\x20\x41\x4e\x44\x0a\x20\x20\x4f\x4e\x20\x41\x4e\x59\x20\x54\x48\ +\x45\x4f\x52\x59\x20\x4f\x46\x20\x4c\x49\x41\x42\x49\x4c\x49\x54\ +\x59\x2c\x20\x57\x48\x45\x54\x48\x45\x52\x20\x49\x4e\x20\x43\x4f\ +\x4e\x54\x52\x41\x43\x54\x2c\x20\x53\x54\x52\x49\x43\x54\x20\x4c\ +\x49\x41\x42\x49\x4c\x49\x54\x59\x2c\x20\x4f\x52\x20\x54\x4f\x52\ +\x54\x0a\x20\x20\x28\x49\x4e\x43\x4c\x55\x44\x49\x4e\x47\x20\x4e\ +\x45\x47\x4c\x49\x47\x45\x4e\x43\x45\x20\x4f\x52\x20\x4f\x54\x48\ +\x45\x52\x57\x49\x53\x45\x29\x20\x41\x52\x49\x53\x49\x4e\x47\x20\ +\x49\x4e\x20\x41\x4e\x59\x20\x57\x41\x59\x20\x4f\x55\x54\x20\x4f\ +\x46\x20\x54\x48\x45\x20\x55\x53\x45\x20\x4f\x46\x0a\x20\x20\x54\ +\x48\x49\x53\x20\x53\x4f\x46\x54\x57\x41\x52\x45\x2c\x20\x45\x56\ +\x45\x4e\x20\x49\x46\x20\x41\x44\x56\x49\x53\x45\x44\x20\x4f\x46\ +\x20\x54\x48\x45\x20\x50\x4f\x53\x53\x49\x42\x49\x4c\x49\x54\x59\ +\x20\x4f\x46\x20\x53\x55\x43\x48\x20\x44\x41\x4d\x41\x47\x45\x2e\ +\x0a\x2a\x2f\x0a\x0a\x65\x78\x70\x6f\x72\x74\x73\x2e\x63\x72\x65\ +\x61\x74\x65\x20\x3d\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x28\ +\x6f\x70\x74\x73\x29\x20\x7b\x0a\x20\x20\x20\x20\x76\x61\x72\x20\ +\x73\x65\x72\x76\x65\x72\x20\x3d\x20\x70\x68\x61\x6e\x74\x6f\x6d\ +\x2e\x63\x72\x65\x61\x74\x65\x57\x65\x62\x53\x65\x72\x76\x65\x72\ +\x28\x29\x3b\x0a\x20\x20\x20\x20\x76\x61\x72\x20\x68\x61\x6e\x64\ +\x6c\x65\x72\x73\x20\x3d\x20\x7b\x7d\x3b\x0a\x0a\x20\x20\x20\x20\ +\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x63\x68\x65\x63\x6b\x54\x79\ +\x70\x65\x28\x6f\x2c\x20\x74\x79\x70\x65\x29\x20\x7b\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x74\x79\x70\ +\x65\x6f\x66\x20\x6f\x20\x3d\x3d\x3d\x20\x74\x79\x70\x65\x3b\x0a\ +\x20\x20\x20\x20\x7d\x0a\x0a\x20\x20\x20\x20\x66\x75\x6e\x63\x74\ +\x69\x6f\x6e\x20\x69\x73\x4f\x62\x6a\x65\x63\x74\x28\x6f\x29\x20\ +\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\ +\x20\x63\x68\x65\x63\x6b\x54\x79\x70\x65\x28\x6f\x2c\x20\x27\x6f\ +\x62\x6a\x65\x63\x74\x27\x29\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x0a\ +\x20\x20\x20\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x69\x73\x55\ +\x6e\x64\x65\x66\x69\x6e\x65\x64\x28\x6f\x29\x20\x7b\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x63\x68\x65\ +\x63\x6b\x54\x79\x70\x65\x28\x6f\x2c\x20\x27\x75\x6e\x64\x65\x66\ +\x69\x6e\x65\x64\x27\x29\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x0a\x20\ +\x20\x20\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x69\x73\x55\x6e\ +\x64\x65\x66\x69\x6e\x65\x64\x4f\x72\x4e\x75\x6c\x6c\x28\x6f\x29\ +\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\ +\x6e\x20\x69\x73\x55\x6e\x64\x65\x66\x69\x6e\x65\x64\x28\x6f\x29\ +\x20\x7c\x7c\x20\x6e\x75\x6c\x6c\x20\x3d\x3d\x3d\x20\x6f\x3b\x0a\ +\x20\x20\x20\x20\x7d\x0a\x0a\x20\x20\x20\x20\x66\x75\x6e\x63\x74\ +\x69\x6f\x6e\x20\x63\x6f\x70\x79\x49\x6e\x74\x6f\x28\x74\x61\x72\ +\x67\x65\x74\x2c\x20\x73\x6f\x75\x72\x63\x65\x29\x20\x7b\x0a\x20\ +\x20\x20\x20\x20\x20\x20\x20\x69\x66\x20\x28\x74\x61\x72\x67\x65\ +\x74\x20\x3d\x3d\x3d\x20\x73\x6f\x75\x72\x63\x65\x20\x7c\x7c\x20\ +\x69\x73\x55\x6e\x64\x65\x66\x69\x6e\x65\x64\x4f\x72\x4e\x75\x6c\ +\x6c\x28\x73\x6f\x75\x72\x63\x65\x29\x29\x20\x7b\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\ +\x74\x61\x72\x67\x65\x74\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x7d\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x74\x61\x72\x67\x65\ +\x74\x20\x3d\x20\x74\x61\x72\x67\x65\x74\x20\x7c\x7c\x20\x7b\x7d\ +\x3b\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x2f\x2f\x20\x43\x6f\ +\x70\x79\x20\x69\x6e\x74\x6f\x20\x6f\x62\x6a\x65\x63\x74\x73\x20\ +\x6f\x6e\x6c\x79\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x69\x66\x20\ +\x28\x69\x73\x4f\x62\x6a\x65\x63\x74\x28\x74\x61\x72\x67\x65\x74\ +\x29\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x2f\x2f\x20\x4d\x61\x6b\x65\x20\x73\x75\x72\x65\x20\x73\x6f\ +\x75\x72\x63\x65\x20\x65\x78\x69\x73\x74\x73\x0a\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x73\x6f\x75\x72\x63\x65\x20\x3d\ +\x20\x73\x6f\x75\x72\x63\x65\x20\x7c\x7c\x20\x7b\x7d\x3b\x0a\x0a\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x69\x66\x20\x28\ +\x69\x73\x4f\x62\x6a\x65\x63\x74\x28\x73\x6f\x75\x72\x63\x65\x29\ +\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x76\x61\x72\x20\x69\x2c\x20\x6e\x65\x77\x54\x61\ +\x72\x67\x65\x74\x2c\x20\x6e\x65\x77\x53\x6f\x75\x72\x63\x65\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x66\x6f\x72\x20\x28\x69\x20\x69\x6e\x20\x73\x6f\x75\x72\x63\ +\x65\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x69\x66\x20\x28\x73\x6f\x75\ +\x72\x63\x65\x2e\x68\x61\x73\x4f\x77\x6e\x50\x72\x6f\x70\x65\x72\ +\x74\x79\x28\x69\x29\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x6e\x65\x77\x54\x61\x72\x67\x65\x74\x20\x3d\x20\x74\x61\x72\ +\x67\x65\x74\x5b\x69\x5d\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x6e\x65\x77\x53\x6f\x75\x72\x63\x65\x20\x3d\x20\x73\x6f\x75\x72\ +\x63\x65\x5b\x69\x5d\x3b\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x69\x66\x20\x28\x6e\x65\x77\x54\x61\x72\x67\x65\x74\x20\x26\x26\ +\x20\x69\x73\x4f\x62\x6a\x65\x63\x74\x28\x6e\x65\x77\x53\x6f\x75\ +\x72\x63\x65\x29\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x2f\x2f\x20\x44\x65\x65\x70\x20\x63\x6f\x70\x79\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x6e\x65\x77\ +\x54\x61\x72\x67\x65\x74\x20\x3d\x20\x63\x6f\x70\x79\x49\x6e\x74\ +\x6f\x28\x74\x61\x72\x67\x65\x74\x5b\x69\x5d\x2c\x20\x6e\x65\x77\ +\x53\x6f\x75\x72\x63\x65\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x7d\x20\x65\x6c\x73\x65\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x6e\x65\x77\x54\x61\x72\x67\x65\x74\x20\ +\x3d\x20\x6e\x65\x77\x53\x6f\x75\x72\x63\x65\x3b\x0a\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x7d\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x69\x66\x20\x28\x21\x69\x73\x55\x6e\x64\x65\x66\x69\x6e\x65\x64\ +\x28\x6e\x65\x77\x54\x61\x72\x67\x65\x74\x29\x29\x20\x7b\x0a\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x74\x61\x72\x67\x65\ +\x74\x5b\x69\x5d\x20\x3d\x20\x6e\x65\x77\x54\x61\x72\x67\x65\x74\ +\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x7d\x20\x65\x6c\x73\x65\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x74\x61\x72\x67\x65\x74\x20\ +\x3d\x20\x73\x6f\x75\x72\x63\x65\x3b\x0a\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x7d\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x72\x65\x74\x75\x72\ +\x6e\x20\x74\x61\x72\x67\x65\x74\x3b\x0a\x20\x20\x20\x20\x7d\x0a\ +\x0a\x20\x20\x20\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x64\x65\ +\x66\x69\x6e\x65\x53\x65\x74\x74\x65\x72\x28\x68\x61\x6e\x64\x6c\ +\x65\x72\x4e\x61\x6d\x65\x2c\x20\x73\x69\x67\x6e\x61\x6c\x4e\x61\ +\x6d\x65\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x65\ +\x72\x76\x65\x72\x2e\x5f\x5f\x64\x65\x66\x69\x6e\x65\x53\x65\x74\ +\x74\x65\x72\x5f\x5f\x28\x68\x61\x6e\x64\x6c\x65\x72\x4e\x61\x6d\ +\x65\x2c\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x28\x66\x29\x20\ +\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x69\x66\ +\x20\x28\x68\x61\x6e\x64\x6c\x65\x72\x73\x20\x26\x26\x20\x74\x79\ +\x70\x65\x6f\x66\x20\x68\x61\x6e\x64\x6c\x65\x72\x73\x5b\x73\x69\ +\x67\x6e\x61\x6c\x4e\x61\x6d\x65\x5d\x20\x3d\x3d\x3d\x20\x27\x66\ +\x75\x6e\x63\x74\x69\x6f\x6e\x27\x29\x20\x7b\x0a\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x74\x72\x79\x20\ +\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x74\x68\x69\x73\x5b\x73\x69\x67\x6e\x61\ +\x6c\x4e\x61\x6d\x65\x5d\x2e\x64\x69\x73\x63\x6f\x6e\x6e\x65\x63\ +\x74\x28\x68\x61\x6e\x64\x6c\x65\x72\x73\x5b\x73\x69\x67\x6e\x61\ +\x6c\x4e\x61\x6d\x65\x5d\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x7d\x20\x63\x61\x74\x63\x68\ +\x20\x28\x65\x29\x20\x7b\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x68\x61\x6e\x64\x6c\x65\x72\x73\x5b\x73\x69\x67\x6e\x61\ +\x6c\x4e\x61\x6d\x65\x5d\x20\x3d\x20\x66\x3b\x0a\x20\x20\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x74\x68\x69\x73\x5b\x73\x69\x67\ +\x6e\x61\x6c\x4e\x61\x6d\x65\x5d\x2e\x63\x6f\x6e\x6e\x65\x63\x74\ +\x28\x68\x61\x6e\x64\x6c\x65\x72\x73\x5b\x73\x69\x67\x6e\x61\x6c\ +\x4e\x61\x6d\x65\x5d\x29\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\ +\x7d\x29\x3b\x0a\x20\x20\x20\x20\x7d\x0a\x0a\x20\x20\x20\x20\x2f\ +\x2f\x20\x64\x65\x65\x70\x20\x63\x6f\x70\x79\x0a\x20\x20\x20\x20\ +\x2f\x2f\x54\x4f\x44\x4f\x3a\x20\x75\x73\x65\x20\x74\x68\x69\x73\ +\x3f\x0a\x2f\x2f\x20\x20\x20\x20\x20\x73\x65\x72\x76\x65\x72\x2e\ +\x73\x65\x74\x74\x69\x6e\x67\x73\x20\x3d\x20\x4a\x53\x4f\x4e\x2e\ +\x70\x61\x72\x73\x65\x28\x4a\x53\x4f\x4e\x2e\x73\x74\x72\x69\x6e\ +\x67\x69\x66\x79\x28\x70\x68\x61\x6e\x74\x6f\x6d\x2e\x64\x65\x66\ +\x61\x75\x6c\x74\x53\x65\x72\x76\x65\x72\x53\x65\x74\x74\x69\x6e\ +\x67\x73\x29\x29\x3b\x0a\x0a\x20\x20\x20\x20\x64\x65\x66\x69\x6e\ +\x65\x53\x65\x74\x74\x65\x72\x28\x22\x6f\x6e\x4e\x65\x77\x52\x65\ +\x71\x75\x65\x73\x74\x22\x2c\x20\x22\x6e\x65\x77\x52\x65\x71\x75\ +\x65\x73\x74\x22\x29\x3b\x0a\x0a\x20\x20\x20\x20\x73\x65\x72\x76\ +\x65\x72\x2e\x6c\x69\x73\x74\x65\x6e\x20\x3d\x20\x66\x75\x6e\x63\ +\x74\x69\x6f\x6e\x20\x28\x70\x6f\x72\x74\x2c\x20\x68\x61\x6e\x64\ +\x6c\x65\x72\x29\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x69\ +\x66\x20\x28\x61\x72\x67\x75\x6d\x65\x6e\x74\x73\x2e\x6c\x65\x6e\ +\x67\x74\x68\x20\x3d\x3d\x3d\x20\x32\x20\x26\x26\x20\x74\x79\x70\ +\x65\x6f\x66\x20\x68\x61\x6e\x64\x6c\x65\x72\x20\x3d\x3d\x3d\x20\ +\x27\x66\x75\x6e\x63\x74\x69\x6f\x6e\x27\x29\x20\x7b\x0a\x20\x20\ +\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x74\x68\x69\x73\x2e\x6f\ +\x6e\x4e\x65\x77\x52\x65\x71\x75\x65\x73\x74\x20\x3d\x20\x68\x61\ +\x6e\x64\x6c\x65\x72\x3b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x20\x20\x2f\x2f\x54\x4f\x44\x4f\x3a\x20\x73\x65\x74\x74\x69\ +\x6e\x67\x73\x3f\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\ +\x20\x72\x65\x74\x75\x72\x6e\x20\x74\x68\x69\x73\x2e\x6c\x69\x73\ +\x74\x65\x6e\x4f\x6e\x50\x6f\x72\x74\x28\x70\x6f\x72\x74\x29\x3b\ +\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x20\ +\x20\x20\x20\x74\x68\x72\x6f\x77\x20\x22\x57\x72\x6f\x6e\x67\x20\ +\x75\x73\x65\x20\x6f\x66\x20\x57\x65\x62\x53\x65\x72\x76\x65\x72\ +\x23\x6c\x69\x73\x74\x65\x6e\x22\x3b\x0a\x20\x20\x20\x20\x7d\x3b\ +\x0a\x0a\x20\x20\x20\x20\x2f\x2f\x20\x43\x6f\x70\x79\x20\x6f\x70\ +\x74\x69\x6f\x6e\x73\x20\x69\x6e\x74\x6f\x20\x73\x65\x72\x76\x65\ +\x72\x0a\x20\x20\x20\x20\x69\x66\x20\x28\x6f\x70\x74\x73\x29\x20\ +\x7b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x65\x72\x76\x65\x72\ +\x20\x3d\x20\x63\x6f\x70\x79\x49\x6e\x74\x6f\x28\x73\x65\x72\x76\ +\x65\x72\x2c\x20\x6f\x70\x74\x73\x29\x3b\x0a\x20\x20\x20\x20\x7d\ +\x0a\x0a\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x73\x65\x72\ +\x76\x65\x72\x3b\x0a\x7d\x3b\x0a\ \x00\x00\x18\x67\ \x2f\ \x2a\x6a\x73\x6c\x69\x6e\x74\x20\x73\x6c\x6f\x70\x70\x79\x3a\x20\ @@ -5097,6 +5390,10 @@ \x00\x7d\x71\x73\ \x00\x63\ \x00\x6f\x00\x66\x00\x66\x00\x65\x00\x65\x00\x2d\x00\x73\x00\x63\x00\x72\x00\x69\x00\x70\x00\x74\x00\x2e\x00\x6a\x00\x73\ +\x00\x0c\ +\x06\x7b\x66\x93\ +\x00\x77\ +\x00\x65\x00\x62\x00\x73\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x6a\x00\x73\ \x00\x0a\ \x06\x72\xf2\x33\ \x00\x77\ @@ -5109,14 +5406,15 @@ qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x01\ -\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x09\x79\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x07\ +\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x09\xc0\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x07\ \x00\x00\x00\x14\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x56\x00\x02\x00\x00\x00\x02\x00\x00\x00\x05\ -\x00\x00\x00\x9c\x00\x01\x00\x00\x00\x01\x00\x00\x60\xc2\ -\x00\x00\x00\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x97\ -\x00\x00\x00\xdc\x00\x00\x00\x00\x00\x01\x00\x01\x27\x85\ -\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x01\x00\x01\x0f\x1a\ +\x00\x00\x00\x9c\x00\x01\x00\x00\x00\x01\x00\x00\x61\x09\ +\x00\x00\x00\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xde\ +\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x01\x00\x01\x39\xb9\ +\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x01\x00\x01\x21\x4e\ +\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x01\x00\x01\x0f\x61\ " def qInitResources(): diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/resources.qrc phantomjs-1.4.0+dfsg/python/pyphantomjs/resources.qrc --- phantomjs-1.3.0+dfsg/python/pyphantomjs/resources.qrc 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/resources.qrc 2011-12-27 13:46:57.000000000 +0000 @@ -4,6 +4,7 @@ configurator.js modules/fs.js modules/webpage.js + modules/webserver.js resources/coffee-script.js resources/pyphantomjs-icon.png
diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/tools/build_binary.py phantomjs-1.4.0+dfsg/python/pyphantomjs/tools/build_binary.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/tools/build_binary.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/tools/build_binary.py 2011-12-27 13:46:57.000000000 +0000 @@ -11,11 +11,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import os diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/tools/pyphantomjs phantomjs-1.4.0+dfsg/python/pyphantomjs/tools/pyphantomjs --- phantomjs-1.3.0+dfsg/python/pyphantomjs/tools/pyphantomjs 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/tools/pyphantomjs 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -#!/usr/bin/env python -''' - This file is part of the PyPhantomJS project. - - Copyright (C) 2011 James Roe - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' - -from runpy import run_module - -# automatically convert Qt types by using api 2 -import sip -for item in ('QDate', 'QDateTime', 'QString', 'QTextStream', 'QTime' - 'QUrl', 'QVariant'): - sip.setapi(item, 2) - -if __name__ == '__main__': - run_module('pyphantomjs.pyphantomjs', run_name='__main__') diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/utils.py phantomjs-1.4.0+dfsg/python/pyphantomjs/utils.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/utils.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/utils.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,163 +10,81 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' -import argparse -import codecs -import os import sys -from PyQt4.QtCore import (QByteArray, QDateTime, qDebug, QFile, Qt, +from PyQt4.QtCore import (QByteArray, QDateTime, QFile, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QtWarningMsg) -from __init__ import __version__ -from plugincontroller import do_action +def debug(debug_type): + def excepthook(type_, value, tb): + import traceback + + # print the exception... + traceback.print_exception(type_, value, tb) + print + # ...then start the debugger in post-mortem mode + pdb.pm() + + # we are NOT in interactive mode + if not hasattr(sys, 'ps1') or sys.stderr.target.isatty(): + import pdb + + from PyQt4.QtCore import pyqtRemoveInputHook + pyqtRemoveInputHook() + + if debug_type == 'exception': + sys.excepthook = excepthook + elif debug_type == 'program': + pdb.set_trace() + + +class CaseInsensitiveDict(dict): + def __delitem__(self, key): + for dictKey in self: + if self.sameKey(key, dictKey): + super(CaseInsensitiveDict, self).__delitem__(dictKey) + return + + raise KeyError(key) + + def __contains__(self, key): + for dictKey in self: + if self.sameKey(key, dictKey): + return True + return False -license = ''' - PyPhantomJS Version %s - - Copyright (C) 2011 James Roe - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -''' % __version__ - + def __getitem__(self, key): + for dictKey, dictValue in self.items(): + if self.sameKey(key, dictKey): + return dictValue + + raise KeyError(key) + + def __setitem__(self, key, value): + for dictKey in self: + if self.sameKey(key, dictKey): + super(CaseInsensitiveDict, self).__setitem__(dictKey, value) + return + + super(CaseInsensitiveDict, self).__setitem__(key, value) + + def sameKey(self, key, dictKey): + if hasattr(key, 'lower'): + key = key.lower() + if hasattr(dictKey, 'lower'): + dictKey = dictKey.lower() -def argParser(): - class YesOrNoAction(argparse.Action): - '''Converts yes or no arguments to True/False respectively''' - def __call__(self, parser, namespace, value, option_string=None): - answer = False if value == 'no' else True - setattr(namespace, self.dest, answer) - - parser = argparse.ArgumentParser( - description='Minimalistic headless WebKit-based JavaScript-driven tool', - usage='%(prog)s [options] script.[js|coffee] [script argument [script argument ...]]', - formatter_class=argparse.RawTextHelpFormatter - ) - - parser.add_argument('script', metavar='script.[js|coffee]', nargs='?', - help='The script to execute, and any args to pass to it' - ) - parser.add_argument('-v', '--version', - action='version', version=license, - help="show this program's version and license" - ) - - program = parser.add_argument_group('program options') - script = parser.add_argument_group('script options') - debug = parser.add_argument_group('debug options') - - program.add_argument('--config', metavar='/path/to/config', - help='Specifies path to a JSON-formatted config file' - ) - program.add_argument('--disk-cache', default=False, action=YesOrNoAction, - choices=['yes', 'no'], - help='Enable disk cache (default: no)' - ) - program.add_argument('--ignore-ssl-errors', default=False, action=YesOrNoAction, - choices=['yes', 'no'], - help='Ignore SSL errors (default: no)' - ) - program.add_argument('--max-disk-cache-size', default=-1, metavar='size', type=int, - help='Limits the size of disk cache (in KB)' - ) - program.add_argument('--output-encoding', default='System', metavar='encoding', - help='Sets the encoding used for terminal output (default: %(default)s)' - ) - program.add_argument('--proxy', metavar='address:port', - help='Set the network proxy' - ) - program.add_argument('--script-encoding', default='utf-8', metavar='encoding', - help='Sets the encoding used for scripts (default: %(default)s)' - ) - - script.add_argument('--cookies-file', metavar='/path/to/cookies.txt', - help='Sets the file name to store the persistent cookies' - ) - script.add_argument('--load-images', default=True, action=YesOrNoAction, - choices=['yes', 'no'], - help='Load all inlined images (default: yes)' - ) - script.add_argument('--load-plugins', default=False, action=YesOrNoAction, - choices=['yes', 'no'], - help='Load all plugins (i.e. Flash, Silverlight, ...) (default: no)' - ) - script.add_argument('--local-to-remote-url-access', default=False, action=YesOrNoAction, - choices=['yes', 'no'], - help='Local content can access remote URL (default: no)' - ) - - debug.add_argument('--debug', choices=['exception', 'program'], metavar='option', - help=('Debug the program with pdb\n' - ' exception : Start debugger when program hits exception\n' - ' program : Start the program with the debugger enabled') - ) - debug.add_argument('--verbose', action='store_true', - help='Show verbose debug messages' - ) - - do_action('ArgParser') - - return parser - - -CSConverter = None -def coffee2js(script): - global CSConverter - if not CSConverter: - from csconverter import CSConverter - return CSConverter().convert(script) - - -def injectJsInFrame(filePath, scriptEncoding, libraryPath, targetFrame, startingScript=False): - try: - # if file doesn't exist in the CWD, use the lookup - if not os.path.exists(filePath): - filePath = os.path.join(libraryPath, filePath) - - try: - with codecs.open(filePath, encoding=scriptEncoding) as f: - script = f.read() - except UnicodeDecodeError as e: - sys.exit("%s in '%s'" % (e, filePath)) - - if script.startswith('#!') and not filePath.lower().endswith('.coffee'): - script = '//' + script - - if filePath.lower().endswith('.coffee'): - result = coffee2js(script) - if not result[0]: - if startingScript: - sys.exit("%s: '%s'" % (result[1], filePath)) - else: - qDebug("%s: '%s'" % (result[1], filePath)) - script = '' - else: - script = result[1] - - targetFrame.evaluateJavaScript(script) - return True - except IOError as (t, e): - qDebug("%s: '%s'" % (e, filePath)) + if key == dictKey: + return True return False diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/webpage.py phantomjs-1.4.0+dfsg/python/pyphantomjs/webpage.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/webpage.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/webpage.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,28 +10,71 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' +import codecs +import os +import sys +from cStringIO import StringIO from math import ceil, floor import sip -from PyQt4.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QByteArray, - QDir, QEvent, QEventLoop, QFileInfo, QObject, - QPoint, QRect, QSize, QSizeF, Qt, QUrl) +from PyQt4.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QBuffer, + QByteArray, QDir, QEvent, QEventLoop, QFileInfo, + QObject, QPoint, QRect, QSize, QSizeF, Qt, QUrl, + qDebug) from PyQt4.QtGui import (QApplication, QDesktopServices, QImage, QMouseEvent, QPainter, QPalette, QPrinter, QRegion, qRgba) from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt4.QtWebKit import QWebPage, QWebSettings +try: + from PIL import Image +except ImportError: + qDebug('PIL not found! Saving to gif files will be disabled.') + +from csconverter import CSConverter from networkaccessmanager import NetworkAccessManager from plugincontroller import do_action -from utils import injectJsInFrame + + +def injectJsInFrame(filePath, scriptEncoding, libraryPath, targetFrame, startingScript=False): + try: + # if file doesn't exist in the CWD, use the lookup + if not os.path.exists(filePath): + filePath = os.path.join(libraryPath, filePath) + + try: + with codecs.open(filePath, encoding=scriptEncoding) as f: + script = f.read() + except UnicodeError as e: + sys.exit("%s in '%s'" % (e, filePath)) + + if script.startswith('#!') and not filePath.lower().endswith('.coffee'): + script = '//' + script + + if filePath.lower().endswith('.coffee'): + result = CSConverter().convert(script) + if not result[0]: + if startingScript: + sys.exit("%s: '%s'" % (result[1], filePath)) + else: + qDebug("%s: '%s'" % (result[1], filePath)) + script = '' + else: + script = result[1] + + targetFrame.evaluateJavaScript(script) + return True + except IOError as (_, e): + qDebug("%s: '%s'" % (e, filePath)) + return False class CustomPage(QWebPage): @@ -67,8 +110,8 @@ initialized = pyqtSignal() javaScriptAlertSent = pyqtSignal(str) javaScriptConsoleMessageSent = pyqtSignal(str, int, str) - loadStarted = pyqtSignal() loadFinished = pyqtSignal(str) + loadStarted = pyqtSignal() resourceReceived = pyqtSignal('QVariantMap') resourceRequested = pyqtSignal('QVariantMap') @@ -86,10 +129,11 @@ self.setObjectName('WebPage') self.m_webPage = CustomPage(self) self.m_mainFrame = self.m_webPage.mainFrame() + self.m_webPage.mainFrame().setHtml(self.blankHtml) self.m_mainFrame.javaScriptWindowObjectCleared.connect(self.initialized) - self.m_webPage.loadStarted.connect(self.loadStarted) - self.m_webPage.loadFinished.connect(self.finish) + self.m_webPage.loadStarted.connect(self.loadStarted, Qt.QueuedConnection) + self.m_webPage.loadFinished.connect(self.finish, Qt.QueuedConnection) # Start with transparent background palette = self.m_webPage.palette() @@ -109,9 +153,6 @@ self.m_webPage.settings().setAttribute(QWebSettings.LocalStorageEnabled, True) self.m_webPage.settings().setLocalStoragePath(QDesktopServices.storageLocation(QDesktopServices.DataLocation)) - # Ensure we have a document.body. - self.m_webPage.mainFrame().setHtml(self.blankHtml) - # Custom network access manager to allow traffic monitoring self.m_networkAccessManager = NetworkAccessManager(self.parent(), args) self.m_webPage.setNetworkAccessManager(self.m_networkAccessManager) @@ -147,6 +188,32 @@ def mainFrame(self): return self.m_mainFrame + def renderGif(self, image, fileName): + try: + Image + except NameError: + return False + + buffer_ = QBuffer() + buffer_.open(QBuffer.ReadWrite) + image.save(buffer_, 'PNG') + + stream = StringIO() + stream.write(buffer_.data()) + buffer_.close() + stream.seek(0) + pilimg = Image.open(stream) + + # use the adaptive quantizer instead of the web quantizer; eases off of grainy images + pilimg = pilimg.convert('RGB').convert('P', palette=Image.ADAPTIVE) + + try: + pilimg.save(fileName) + return True + except IOError as (_, e): + qDebug("WebPage.renderGif - %s: '%s'" % (e, fileName)) + return False + def renderImage(self): contentsSize = self.m_mainFrame.contentsSize() contentsSize -= QSize(self.m_scrollPosition.x(), self.m_scrollPosition.y()) @@ -337,6 +404,14 @@ def injectJs(self, filePath): return injectJsInFrame(filePath, self.parent().m_scriptEncoding.encoding, self.m_libraryPath, self.m_mainFrame) + @pyqtProperty(str) + def libraryPath(self): + return self.m_libraryPath + + @libraryPath.setter + def libraryPath(self, dirPath): + self.m_libraryPath = dirPath + @pyqtSlot(str, str, 'QVariantMap') @pyqtSlot(str, 'QVariantMap', 'QVariantMap') def openUrl(self, address, op, settings): @@ -401,17 +476,11 @@ return self.renderPdf(fileName) image = self.renderImage() + if fileName.lower().endswith('.gif'): + return self.renderGif(image, fileName) return image.save(fileName) - @pyqtProperty(str) - def libraryPath(self): - return self.m_libraryPath - - @libraryPath.setter - def libraryPath(self, dirPath): - self.m_libraryPath = dirPath - @pyqtSlot(str, 'QVariant', 'QVariant') def sendEvent(self, type_, arg1, arg2): type_ = type_.lower() diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/webpage-shim.js phantomjs-1.4.0+dfsg/python/pyphantomjs/webpage-shim.js --- phantomjs-1.3.0+dfsg/python/pyphantomjs/webpage-shim.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/webpage-shim.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -/*jslint sloppy: true, nomen: true */ -/*global window:true,phantom:true,fs:true */ - -/* - This file is part of the PhantomJS project from Ofi Labs. - - Copyright (C) 2011 Ariya Hidayat - Copyright (C) 2011 Ivan De Marino - Copyright (C) 2011 James Roe - Copyright (C) 2011 execjosh, http://execjosh.blogspot.com - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - diff -Nru phantomjs-1.3.0+dfsg/python/pyphantomjs/webserver.py phantomjs-1.4.0+dfsg/python/pyphantomjs/webserver.py --- phantomjs-1.3.0+dfsg/python/pyphantomjs/webserver.py 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/pyphantomjs/webserver.py 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,312 @@ +''' + This file is part of the PyPhantomJS project. + + Copyright (C) 2011 James Roe + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +import socket +from BaseHTTPServer import (__version__ as __server_version__, + BaseHTTPRequestHandler, HTTPServer) +from cStringIO import StringIO +from threading import Thread +from SocketServer import ThreadingMixIn +from urlparse import urlparse + +import sip +from PyQt4.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, Q_ARG, + qDebug, QMetaObject, QObject, Qt, QThread) + +from __init__ import __version__ +from plugincontroller import do_action +from utils import CaseInsensitiveDict + + +servers = [] + + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + pass + + +class WebServer(QObject): + newRequest = pyqtSignal(QObject, QObject) + + def __init__(self, parent): + super(WebServer, self).__init__(parent) + + self.setObjectName('WebServer') + + self.m_port = 0 + self.httpd = None + + servers.append(self) + + do_action('WebServerInit') + + def __del__(self): + self.close() + + @pyqtSlot() + def close(self): + if self.httpd is not None: + self.httpd.shutdown() + self.m_port = 0 + + @pyqtSlot(int, result=bool) + def listenOnPort(self, port): + try: + self.httpd = ThreadingHTTPServer(('localhost', port), WebServerHandler) + Thread(target=self.httpd.serve_forever).start() + except socket.error as (_, e): + qDebug('WebServer.listenOnPort - %s' % e) + self.m_port = 0 + return False + else: + self.m_port = port + return True + + @pyqtProperty(int) + def port(self): + return self.m_port + + @pyqtSlot() + def release(self): + self.close() + servers.remove(self) + self.parent().m_servers.remove(self) + sip.delete(self) + + do_action('WebServer') + + +class WebServerHandler(BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + server_version = 'BaseHTTP/%s PyPhantomJS/%s' % (__server_version__, __version__) + + def handle_one_request(self): + '''Handle a single HTTP request. + + A patch to the request handling: + + * This removes the functionality which calls do_X(), + and simply calls our handleRequest() directly. + * If do_X() didn't exist, a 501 unsupported + method error would be returned. Since we patched + that out as well, the user can actually handle when + a request is or isn't valid through the code. + * Then we handle the request and response. + ''' + try: + self.raw_requestline = self.rfile.readline(65537) + if len(self.raw_requestline) > 65536: + self.requestline = '' + self.request_version = '' + self.command = '' + self.send_error(414) + return + if not self.raw_requestline: + self.close_connection = 1 + return + if not self.parse_request(): + # An error code has been sent, just exit + return + + # these variables must get (re)set on every request + # for handleRequest(), due to the HTTP/1.1 nature of + # keepalive, which seems to keep a constant running + # instance of this class + self.m_handleResponse = True + self.m_headers = CaseInsensitiveDict() + self.m_statusCode = 200 + self.m_wfile = StringIO() + + self.handleRequest() + + # some methods handle the response on their own instead + if self.m_handleResponse: + self.handleResponse() + + self.wfile.flush() #actually send the response if not already done. + except socket.timeout, e: + #a read or a write timed out. Discard this connection + self.log_error("Request timed out: %r", e) + self.close_connection = 1 + return + + def handleResponse(self): + # send response code with response text (ex. 200 OK) + self.send_response(self.m_statusCode, self.responses[self.m_statusCode][0]) + + # send all headers + for name, value in self.m_headers.items(): + self.send_header(name, value) + + # send content-length if not already sent + if self.protocol_version == 'HTTP/1.1': + if 'content-length' not in self.m_headers: + if self.m_statusCode >= 200 and self.m_statusCode not in (204, 304): + self.send_header('Content-Length', self.m_wfile.tell()) + + self.end_headers() #finish sending the headers + + # check if body should be written to response + if self.command != 'HEAD' and self.m_statusCode >= 200 and self.m_statusCode not in (204, 304): + self.wfile.write(self.m_wfile.getvalue()) #write body to page + + def handleRequest(self): + request = WebServerRequest(self) + response = WebServerResponse(self) + + for server in servers: + # verify which server this request is for + if self.server == server.httpd: + connectionType = Qt.BlockingQueuedConnection + if QThread.currentThread() == server.thread(): + connectionType = Qt.DirectConnection + + QMetaObject.invokeMethod(server, 'newRequest', connectionType, + Q_ARG(WebServerRequest, request), + Q_ARG(WebServerResponse, response)) + break + + def log_message(self, format, *args): + qDebug("%s - - %s" % (self.address_string(), + format % args)) + + do_action('WebServerHandler') + + +class WebServerResponse(QObject): + def __init__(self, conn): + super(WebServerResponse, self).__init__() + + self.setObjectName('WebServerResponse') + + self.m_conn = conn + + do_action('WebServerResponseInit') + + @pyqtSlot(str, result=str) + def header(self, name): + return self.m_conn.m_headers.get(name, '') + + @pyqtProperty('QVariantMap') + def headers(self): + return self.m_conn.m_headers + + @headers.setter + def headers(self, headers): + self.m_conn.m_headers = CaseInsensitiveDict(headers) + + @pyqtSlot(int) + @pyqtSlot(int, str) + def sendError(self, code, message=None): + self.m_conn.send_error(code, message) + self.m_conn.m_handleResponse = False + + @pyqtSlot(str, str) + def setHeader(self, name, value): + self.m_conn.m_headers[name] = value + + @pyqtProperty(int) + def statusCode(self): + return self.m_conn.m_statusCode + + @statusCode.setter + def statusCode(self, code): + self.m_conn.m_statusCode = code + + @pyqtSlot(str) + def write(self, body): + self.m_conn.m_wfile.write(body) + + @pyqtSlot(int, 'QVariantMap') + def writeHead(self, code, headers): + self.m_conn.m_statusCode = code + self.m_conn.m_headers = CaseInsensitiveDict(headers) + + do_action('WebServerResponse') + + +class WebServerRequest(QObject): + def __init__(self, request): + super(WebServerRequest, self).__init__() + + self.setObjectName('WebServerRequest') + + self.m_request = request + self.m_headerNames = list(request.headers) + self.m_headers = CaseInsensitiveDict(request.headers) + self.m_numHeaders = len(request.headers) + + do_action('WebServerRequestInit') + + @pyqtSlot(int, result=str) + def headerName(self, header): + if header <= self.m_numHeaders: + return self.m_headerNames[header] + return '' + + @pyqtProperty(int) + def headers(self): + return self.m_numHeaders + + @pyqtSlot(int, result=str) + def headerValue(self, header): + if header <= self.m_numHeaders: + return self.m_request.headers[self.m_headerNames[header]] + return '' + + @pyqtProperty(str) + def httpVersion(self): + return self.m_request.request_version[-3:] + + @pyqtProperty(int) + def isSSL(self): + return 0 + + @pyqtProperty(str) + def method(self): + return self.m_request.command + + @pyqtProperty(str) + def remoteIP(self): + return self.m_request.client_address[0] + + @pyqtProperty(int) + def remotePort(self): + return self.m_request.client_address[1] + + @pyqtProperty(str) + def remoteUser(self): + try: + return self.m_headers['REMOTE_USER'] + except KeyError: + return '' + + #@pyqtProperty(int) + #def statusCode(self): + # return self.m_request.status_code + + @pyqtProperty(str) + def url(self): + return self.m_request.path + + @pyqtProperty(str) + def queryString(self): + return urlparse(self.m_request.path).query + + do_action('WebServerRequest') diff -Nru phantomjs-1.3.0+dfsg/python/scripts/pyphantomjs phantomjs-1.4.0+dfsg/python/scripts/pyphantomjs --- phantomjs-1.3.0+dfsg/python/scripts/pyphantomjs 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/scripts/pyphantomjs 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env python +''' + This file is part of the PyPhantomJS project. + + Copyright (C) 2011 James Roe + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' + +from runpy import run_module + +# automatically convert Qt types by using api 2 +import sip +for item in ('QDate', 'QDateTime', 'QString', 'QTextStream', 'QTime' + 'QUrl', 'QVariant'): + sip.setapi(item, 2) + +if __name__ == '__main__': + run_module('pyphantomjs.pyphantomjs', run_name='__main__') diff -Nru phantomjs-1.3.0+dfsg/python/setup.py phantomjs-1.4.0+dfsg/python/setup.py --- phantomjs-1.3.0+dfsg/python/setup.py 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/python/setup.py 2011-12-27 13:46:57.000000000 +0000 @@ -10,11 +10,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . ''' import os @@ -36,11 +36,17 @@ else: README += INSTALL -try: - import PyQt4 -except ImportError: - print ('\nWARNING: PyPhantomJS requires PyQt4, which was not found!\n' - ' Refer to the README or INSTALL file for installation information.\n') +mainWarningPrinted = False +mainWarning = ('\nWARNING: PyPhantomJS requires additional libraries, which were not found!\n' + ' Refer to the README or INSTALL file for installation information.\n') +for module in ('PyQt4', 'PIL'): + try: + __import__(module) + except ImportError: + if not mainWarningPrinted: + print mainWarning + mainWarningPrinted = True + print "* '%s' library not found" % module setup( @@ -55,7 +61,7 @@ packages=find_packages(), include_package_data=True, install_requires=['argparse'], - scripts = ['pyphantomjs/tools/pyphantomjs'], + scripts = ['scripts/pyphantomjs'], classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', diff -Nru phantomjs-1.3.0+dfsg/README.md phantomjs-1.4.0+dfsg/README.md --- phantomjs-1.3.0+dfsg/README.md 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/README.md 2011-12-27 13:46:57.000000000 +0000 @@ -28,6 +28,6 @@ PhantomJS is based on [Qt](http://qt.nokia.com). There are two implementations, using C++ and Python. -The last [stable release](http://code.google.com/p/phantomjs/wiki/ReleaseNotes) is version 1.3 ("Water Lily"). +The latest [stable release](http://code.google.com/p/phantomjs/wiki/ReleaseNotes) is version 1.4 ("Glory of the Snow"). If you want to contribute, please read the [Contribution Guide](http://code.google.com/p/phantomjs/wiki/ContributionGuide). diff -Nru phantomjs-1.3.0+dfsg/src/bootstrap.js phantomjs-1.4.0+dfsg/src/bootstrap.js --- phantomjs-1.3.0+dfsg/src/bootstrap.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/bootstrap.js 2011-12-27 13:46:57.000000000 +0000 @@ -37,7 +37,7 @@ var code, func, exports; - if (name === 'webpage' || name === 'fs') { + if (name === 'webpage' || name === 'fs' || name === 'webserver') { code = phantom.loadModuleSource(name); func = new Function("exports", "window", code); exports = {}; diff -Nru phantomjs-1.3.0+dfsg/src/config.cpp phantomjs-1.4.0+dfsg/src/config.cpp --- phantomjs-1.3.0+dfsg/src/config.cpp 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/config.cpp 2011-12-27 13:46:57.000000000 +0000 @@ -33,8 +33,10 @@ #include #include #include +#include #include "terminal.h" +#include "utils.h" // public: Config::Config(QObject *parent) @@ -104,6 +106,10 @@ setLocalToRemoteUrlAccessEnabled(true); continue; } + if (arg == "--proxy-type=") { + setProxyType(arg.mid(13).trimmed()); + continue; + } if (arg.startsWith("--proxy=")) { setProxy(arg.mid(8).trimmed()); continue; @@ -125,6 +131,11 @@ loadJsonFile(configPath); continue; } + if (arg.startsWith("--remote-debugger-port=")) { + setDebug(true); + setRemoteDebugPort(arg.mid(23).trimmed().toInt()); + continue; + } if (arg.startsWith("--")) { setUnknownOption(QString("Unknown option '%1'").arg(arg)); return; @@ -146,54 +157,35 @@ return path.isEmpty() ? path : QDir::fromNativeSeparators(path); } -// THIS METHOD ASSUMES THAT content IS *NEVER* NULL! -static bool readFile(const QString &path, QString *const content) -{ - // Ensure empty content - content->clear(); - - // Check existence and try to open as text - QFile file(path); - if (!file.exists() || !file.open(QFile::ReadOnly | QFile::Text)) { - return false; - } - - content->append(QString::fromUtf8(file.readAll()).trimmed()); - - file.close(); - - return true; -} - void Config::loadJsonFile(const QString &filePath) { QString jsonConfig; - if (!readFile(normalizePath(filePath), &jsonConfig)) { + QFile f(filePath); + + // Check file exists and is readable + if (!f.exists() || !f.open(QFile::ReadOnly | QFile::Text)) { Terminal::instance()->cerr("Unable to open config: \"" + filePath + "\""); return; - } else if (jsonConfig.isEmpty()) { - return; - } else if (!jsonConfig.startsWith('{') || !jsonConfig.endsWith('}')) { + } + + // Read content + jsonConfig = QString::fromUtf8(f.readAll().trimmed()); + f.close(); + + // Check it's a valid JSON format + if (jsonConfig.isEmpty() || !jsonConfig.startsWith('{') || !jsonConfig.endsWith('}')) { Terminal::instance()->cerr("Config file MUST be in JSON format!"); return; } // Load configurator - QString configurator; - if (!readFile(":/configurator.js", &configurator)) { - Terminal::instance()->cerr("Unable to load JSON configurator!"); - return; - } else if (configurator.isEmpty()) { - Terminal::instance()->cerr("Unable to set-up JSON configurator!"); - return; - } + QString configurator = Utils::readResourceFileUtf8(":/configurator.js"); + // Use a temporary QWebPage to load the JSON configuration in this Object using the 'configurator' above QWebPage webPage; - - // Add the config object + // Add this object to the global scope webPage.mainFrame()->addToJavaScriptWindowObject("config", this); - - // Apply the settings + // Apply the JSON config settings to this very object webPage.mainFrame()->evaluateJavaScript(configurator.arg(jsonConfig)); } @@ -281,6 +273,16 @@ m_pluginsEnabled = value; } +QString Config::proxyType() const +{ + return m_proxyType; +} + +void Config::setProxyType(const QString value) +{ + m_proxyType = value; +} + QString Config::proxy() const { return proxyHost() + ":" + proxyPort(); @@ -373,6 +375,26 @@ m_versionFlag = value; } +bool Config::debug() const +{ + return m_debug; +} + +void Config::setDebug(const bool value) +{ + m_debug = value; +} + +int Config::remoteDebugPort() const +{ + return m_remoteDebugPort; +} + +void Config::setRemoteDebugPort(const int port) +{ + m_remoteDebugPort = port; +} + // private: void Config::resetToDefaults() { @@ -384,6 +406,7 @@ m_localToRemoteUrlAccessEnabled = false; m_outputEncoding = "UTF-8"; m_pluginsEnabled = false; + m_proxyType = "http"; m_proxyHost.clear(); m_proxyPort = 1080; m_scriptArgs.clear(); @@ -391,6 +414,8 @@ m_scriptFile.clear(); m_unknownOption.clear(); m_versionFlag = false; + m_debug = false; + m_remoteDebugPort = -1; } void Config::setProxyHost(const QString &value) diff -Nru phantomjs-1.3.0+dfsg/src/config.h phantomjs-1.4.0+dfsg/src/config.h --- phantomjs-1.3.0+dfsg/src/config.h 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/config.h 2011-12-27 13:46:57.000000000 +0000 @@ -33,6 +33,7 @@ #include #include +#include class Config: QObject { @@ -44,6 +45,7 @@ Q_PROPERTY(bool localToRemoteUrlAccessEnabled READ localToRemoteUrlAccessEnabled WRITE setLocalToRemoteUrlAccessEnabled) Q_PROPERTY(QString outputEncoding READ outputEncoding WRITE setOutputEncoding) Q_PROPERTY(bool pluginsEnabled READ pluginsEnabled WRITE setPluginsEnabled) + Q_PROPERTY(QString proxyType READ proxyType WRITE setProxyType) Q_PROPERTY(QString proxy READ proxy WRITE setProxy) Q_PROPERTY(QString scriptEncoding READ scriptEncoding WRITE setScriptEncoding) @@ -78,6 +80,9 @@ bool pluginsEnabled() const; void setPluginsEnabled(const bool value); + QString proxyType() const; + void setProxyType(const QString value); + QString proxy() const; void setProxy(const QString &value); QString proxyHost() const; @@ -98,6 +103,11 @@ bool versionFlag() const; void setVersionFlag(const bool value); + void setDebug(const bool value); + bool debug() const; + + void setRemoteDebugPort(const int port); + int remoteDebugPort() const; private: void resetToDefaults(); void setProxyHost(const QString &value); @@ -113,6 +123,7 @@ bool m_localToRemoteUrlAccessEnabled; QString m_outputEncoding; bool m_pluginsEnabled; + QString m_proxyType; QString m_proxyHost; int m_proxyPort; QStringList m_scriptArgs; @@ -122,6 +133,8 @@ bool m_versionFlag; QString m_authUser; QString m_authPass; + bool m_debug; + int m_remoteDebugPort; }; #endif // CONFIG_H diff -Nru phantomjs-1.3.0+dfsg/src/consts.h phantomjs-1.4.0+dfsg/src/consts.h --- phantomjs-1.3.0+dfsg/src/consts.h 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/consts.h 2011-12-27 13:46:57.000000000 +0000 @@ -32,11 +32,12 @@ #ifndef CONSTS_H #define CONSTS_H -// Current Version: 1.3.0 (unstable) +// Current Version: 1.4.0 (unstable) #define PHANTOMJS_VERSION_MAJOR 1 -#define PHANTOMJS_VERSION_MINOR 3 +#define PHANTOMJS_VERSION_MINOR 4 #define PHANTOMJS_VERSION_PATCH 0 -#define PHANTOMJS_VERSION_STRING "1.3.0" +#define PHANTOMJS_VERSION_STRING "1.4.0" + #define COFFEE_SCRIPT_EXTENSION ".coffee" #define JS_ELEMENT_CLICK "(function (el) { " \ diff -Nru phantomjs-1.3.0+dfsg/src/csconverter.cpp phantomjs-1.4.0+dfsg/src/csconverter.cpp --- phantomjs-1.3.0+dfsg/src/csconverter.cpp 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/csconverter.cpp 2011-12-27 13:46:57.000000000 +0000 @@ -30,9 +30,9 @@ #include "csconverter.h" #include -#include #include +#include "utils.h" #include "terminal.h" static CSConverter *csconverter_instance = 0; @@ -48,14 +48,7 @@ CSConverter::CSConverter() : QObject(QCoreApplication::instance()) { - QFile file(":/coffee-script.js"); - if (!file.open(QFile::ReadOnly)) { - Terminal::instance()->cerr("CoffeeScript compiler is not available!"); - exit(1); - } - QString script = QString::fromUtf8(file.readAll()); - file.close(); - m_webPage.mainFrame()->evaluateJavaScript(script); + m_webPage.mainFrame()->evaluateJavaScript(Utils::readResourceFileUtf8(":/coffee-script.js")); m_webPage.mainFrame()->addToJavaScriptWindowObject("converter", this); } diff -Nru phantomjs-1.3.0+dfsg/src/debug_harness.html phantomjs-1.4.0+dfsg/src/debug_harness.html --- phantomjs-1.3.0+dfsg/src/debug_harness.html 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/debug_harness.html 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru phantomjs-1.3.0+dfsg/src/debug_wrapper.js phantomjs-1.4.0+dfsg/src/debug_wrapper.js --- phantomjs-1.3.0+dfsg/src/debug_wrapper.js 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/debug_wrapper.js 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,12 @@ +// This is a wrapper for the script to be executed. +// When remote debugging, there's no way to reload the page +// to be able to run the script being debugged, because there's +// no user-facing way to interact with phantomjs. +// This provides a function wrapper around the script in order to +// make the whole script callable from the console on the remote debugger. + +var __run = function() { + +%1 + +}; \ No newline at end of file diff -Nru phantomjs-1.3.0+dfsg/src/filesystem-shim.js phantomjs-1.4.0+dfsg/src/filesystem-shim.js --- phantomjs-1.3.0+dfsg/src/filesystem-shim.js 2011-10-29 09:20:23.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/filesystem-shim.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,163 +0,0 @@ -/*jslint sloppy: true, nomen: true */ -/*global window:true,phantom:true,fs:true */ - -/* - This file is part of the PhantomJS project from Ofi Labs. - - Copyright (C) 2011 Ivan De Marino - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// window.fs -// JavaScript "shim" to throw exceptions in case a critical operation fails. - -/** Open and return a "file" object. - * It will throw exception if it fails. - * - * @param path Path of the file to open - * @param mode Open Mode. A string made of 'r', 'w', 'a/+' characters. - * @return "file" object - */ -window.fs.open = function (path, mode) { - var file = window.fs._open(path, mode); - if (file) { - return file; - } - throw "Unable to open file '" + path + "'"; -}; - -/** Open, read and return content of a file. - * It will throw an exception if it fails. - * - * @param path Path of the file to read from - * @return file content - */ -window.fs.read = function (path) { - var f = fs.open(path, 'r'), - content = f.read(); - - f.close(); - return content; -}; - -/** Open and write content to a file - * It will throw an exception if it fails. - * - * @param path Path of the file to read from - * @param content Content to write to the file - * @param mode Open Mode. A string made of 'w' or 'a / +' characters. - */ -window.fs.write = function (path, content, mode) { - var f = fs.open(path, mode); - - f.write(content); - f.close(); -}; - -/** Return the size of a file, in bytes. - * It will throw an exception if it fails. - * - * @param path Path of the file to read the size of - * @return File size in bytes - */ -window.fs.size = function (path) { - var size = fs._size(path); - if (size !== -1) { - return size; - } - throw "Unable to read file '" + path + "' size"; -}; - -/** Copy a file. - * It will throw an exception if it fails. - * - * @param source Path of the source file - * @param destination Path of the destination file - */ -window.fs.copy = function (source, destination) { - if (!fs._copy(source, destination)) { - throw "Unable to copy file '" + source + "' at '" + destination + "'"; - } -}; - -/** Copy a directory tree. - * It will throw an exception if it fails. - * - * @param source Path of the source directory tree - * @param destination Path of the destination directory tree - */ -window.fs.copyTree = function (source, destination) { - if (!fs._copyTree(source, destination)) { - throw "Unable to copy directory tree '" + source + "' at '" + destination + "'"; - } -}; - -/** Move a file. - * It will throw an exception if it fails. - * - * @param source Path of the source file - * @param destination Path of the destination file - */ -window.fs.move = function (source, destination) { - fs.copy(source, destination); - fs.remove(source); -}; - -/** Removes a file. - * It will throw an exception if it fails. - * - * @param path Path of the file to remove - */ -window.fs.remove = function (path) { - if (!fs._remove(path)) { - throw "Unable to remove file '" + path + "'"; - } -}; - -/** Removes a directory. - * It will throw an exception if it fails. - * - * @param path Path of the directory to remove - */ -window.fs.removeDirectory = function (path) { - if (!fs._removeDirectory(path)) { - throw "Unable to remove directory '" + path + "'"; - } -}; - -/** Removes a directory tree. - * It will throw an exception if it fails. - * - * @param path Path of the directory tree to remove - */ -window.fs.removeTree = function (path) { - if (!fs._removeTree(path)) { - throw "Unable to remove directory tree '" + path + "'"; - } -}; - -window.fs.touch = function (path) { - fs.write(path, "", 'a'); -}; \ No newline at end of file diff -Nru phantomjs-1.3.0+dfsg/src/modules/webserver.js phantomjs-1.4.0+dfsg/src/modules/webserver.js --- phantomjs-1.3.0+dfsg/src/modules/webserver.js 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/modules/webserver.js 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,130 @@ +/*jslint sloppy: true, nomen: true */ +/*global exports:true,phantom:true */ + +/* + This file is part of the PhantomJS project from Ofi Labs. + + Copyright (C) 2011 Ariya Hidayat + Copyright (C) 2011 Ivan De Marino + Copyright (C) 2011 James Roe + Copyright (C) 2011 execjosh, http://execjosh.blogspot.com + Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Milian Wolff + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +exports.create = function (opts) { + var server = phantom.createWebServer(), + handlers = {}; + + function checkType(o, type) { + return typeof o === type; + } + + function isObject(o) { + return checkType(o, 'object'); + } + + function isUndefined(o) { + return checkType(o, 'undefined'); + } + + function isUndefinedOrNull(o) { + return isUndefined(o) || null === o; + } + + function copyInto(target, source) { + if (target === source || isUndefinedOrNull(source)) { + return target; + } + + target = target || {}; + + // Copy into objects only + if (isObject(target)) { + // Make sure source exists + source = source || {}; + + if (isObject(source)) { + var i, newTarget, newSource; + for (i in source) { + if (source.hasOwnProperty(i)) { + newTarget = target[i]; + newSource = source[i]; + + if (newTarget && isObject(newSource)) { + // Deep copy + newTarget = copyInto(target[i], newSource); + } else { + newTarget = newSource; + } + + if (!isUndefined(newTarget)) { + target[i] = newTarget; + } + } + } + } else { + target = source; + } + } + + return target; + } + + function defineSetter(handlerName, signalName) { + server.__defineSetter__(handlerName, function (f) { + if (handlers && typeof handlers[signalName] === 'function') { + try { + this[signalName].disconnect(handlers[signalName]); + } catch (e) {} + } + handlers[signalName] = f; + this[signalName].connect(handlers[signalName]); + }); + } + + // deep copy + //TODO: use this? +// server.settings = JSON.parse(JSON.stringify(phantom.defaultServerSettings)); + + defineSetter("onNewRequest", "newRequest"); + + server.listen = function (port, handler) { + if (arguments.length === 2 && typeof handler === 'function') { + this.onNewRequest = handler; + //TODO: settings? + return this.listenOnPort(port); + } + throw "Wrong use of WebServer#listen"; + }; + + // Copy options into server + if (opts) { + server = copyInto(server, opts); + } + + return server; +}; diff -Nru phantomjs-1.3.0+dfsg/src/mongoose/mongoose.c phantomjs-1.4.0+dfsg/src/mongoose/mongoose.c --- phantomjs-1.3.0+dfsg/src/mongoose/mongoose.c 1970-01-01 00:00:00.000000000 +0000 +++ phantomjs-1.4.0+dfsg/src/mongoose/mongoose.c 2011-12-27 13:46:57.000000000 +0000 @@ -0,0 +1,4154 @@ +// Copyright (c) 2004-2010 Sergey Lyubka +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if defined(_WIN32) +#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 +#else +#define _XOPEN_SOURCE 600 // For flockfile() on Linux +#define _LARGEFILE_SOURCE // Enable 64-bit file offsets +#define __STDC_FORMAT_MACROS // wants this for C++ +#endif + +#if defined(__SYMBIAN32__) +#define NO_SSL // SSL is not supported +#define NO_CGI // CGI is not supported +#define PATH_MAX FILENAME_MAX +#endif // __SYMBIAN32__ + +#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE +#include +#include +#include +#include +#include +#endif // !_WIN32_WCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific +#define _WIN32_WINNT 0x0400 // To make it link in VS2005 +#include + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +#ifndef _WIN32_WCE +#include +#include +#include +#else // _WIN32_WCE +#include +#define NO_CGI // WinCE has no pipes + +typedef long off_t; +#define BUFSIZ 4096 + +#define errno GetLastError() +#define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10) +#endif // _WIN32_WCE + +#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \ + ((uint64_t)((uint32_t)(hi))) << 32)) +#define RATE_DIFF 10000000 // 100 nsecs +#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de) +#define SYS2UNIX_TIME(lo, hi) \ + (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF) + +// Visual Studio 6 does not know __func__ or __FUNCTION__ +// The rest of MS compilers use __FUNCTION__, not C99 __func__ +// Also use _strtoui64 on modern M$ compilers +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define STRX(x) #x +#define STR(x) STRX(x) +#define __func__ "line " STR(__LINE__) +#define strtoull(x, y, z) strtoul(x, y, z) +#define strtoll(x, y, z) strtol(x, y, z) +#else +#define __func__ __FUNCTION__ +#define strtoull(x, y, z) _strtoui64(x, y, z) +#define strtoll(x, y, z) _strtoi64(x, y, z) +#endif // _MSC_VER + +#define ERRNO GetLastError() +#define NO_SOCKLEN_T +#define SSL_LIB "ssleay32.dll" +#define CRYPTO_LIB "libeay32.dll" +#define DIRSEP '\\' +#define IS_DIRSEP_CHAR(c) ((c) == '/' || (c) == '\\') +#define O_NONBLOCK 0 +#if !defined(EWOULDBLOCK) +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif // !EWOULDBLOCK +#define _POSIX_ +#define INT64_FMT "I64d" + +#define WINCDECL __cdecl +#define SHUT_WR 1 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define sleep(x) Sleep((x) * 1000) + +#define pipe(x) _pipe(x, BUFSIZ, _O_BINARY) +#define popen(x, y) _popen(x, y) +#define pclose(x) _pclose(x) +#define close(x) _close(x) +#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y)) +#define RTLD_LAZY 0 +#define fseeko(x, y, z) fseek((x), (y), (z)) +#define fdopen(x, y) _fdopen((x), (y)) +#define write(x, y, z) _write((x), (y), (unsigned) z) +#define read(x, y, z) _read((x), (y), (unsigned) z) +#define flockfile(x) (void) 0 +#define funlockfile(x) (void) 0 + +#if !defined(fileno) +#define fileno(x) _fileno(x) +#endif // !fileno MINGW #defines fileno + +typedef HANDLE pthread_mutex_t; +typedef struct {HANDLE signal, broadcast;} pthread_cond_t; +typedef DWORD pthread_t; +#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here. + +struct timespec { + long tv_nsec; + long tv_sec; +}; + +static int pthread_mutex_lock(pthread_mutex_t *); +static int pthread_mutex_unlock(pthread_mutex_t *); +static FILE *mg_fopen(const char *path, const char *mode); + +#if defined(HAVE_STDINT) +#include +#else +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef unsigned __int64 uint64_t; +typedef __int64 int64_t; +#define INT64_MAX 9223372036854775807 +#endif // HAVE_STDINT + +// POSIX dirent interface +struct dirent { + char d_name[PATH_MAX]; +}; + +typedef struct DIR { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +#else // UNIX specific +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#if !defined(NO_SSL_DL) && !defined(NO_SSL) +#include +#endif +#include +#if defined(__MACH__) +#define SSL_LIB "libssl.dylib" +#define CRYPTO_LIB "libcrypto.dylib" +#else +#if !defined(SSL_LIB) +#define SSL_LIB "libssl.so" +#endif +#if !defined(CRYPTO_LIB) +#define CRYPTO_LIB "libcrypto.so" +#endif +#endif +#define DIRSEP '/' +#define IS_DIRSEP_CHAR(c) ((c) == '/') +#ifndef O_BINARY +#define O_BINARY 0 +#endif // O_BINARY +#define closesocket(a) close(a) +#define mg_fopen(x, y) fopen(x, y) +#define mg_mkdir(x, y) mkdir(x, y) +#define mg_remove(x) remove(x) +#define mg_rename(x, y) rename(x, y) +#define ERRNO errno +#define INVALID_SOCKET (-1) +#define INT64_FMT PRId64 +typedef int SOCKET; +#define WINCDECL + +#endif // End of Windows and UNIX specific includes + +#include "mongoose.h" + +#define MONGOOSE_VERSION "3.1" +#define PASSWORDS_FILE_NAME ".htpasswd" +#define CGI_ENVIRONMENT_SIZE 4096 +#define MAX_CGI_ENVIR_VARS 64 +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +#if defined(DEBUG) +#define DEBUG_TRACE(x) do { \ + flockfile(stdout); \ + printf("*** %lu.%p.%s.%d: ", \ + (unsigned long) time(NULL), (void *) pthread_self(), \ + __func__, __LINE__); \ + printf x; \ + putchar('\n'); \ + fflush(stdout); \ + funlockfile(stdout); \ +} while (0) +#else +#define DEBUG_TRACE(x) +#endif // DEBUG + +// Darwin prior to 7.0 and Win32 do not have socklen_t +#ifdef NO_SOCKLEN_T +typedef int socklen_t; +#endif // NO_SOCKLEN_T + +typedef void * (*mg_thread_func_t)(void *); + +static const char *http_500_error = "Internal Server Error"; + +// Snatched from OpenSSL includes. I put the prototypes here to be independent +// from the OpenSSL source installation. Having this, mongoose + SSL can be +// built on any system with binary SSL libraries installed. +typedef struct ssl_st SSL; +typedef struct ssl_method_st SSL_METHOD; +typedef struct ssl_ctx_st SSL_CTX; + +#define SSL_ERROR_WANT_READ 2 +#define SSL_ERROR_WANT_WRITE 3 +#define SSL_FILETYPE_PEM 1 +#define CRYPTO_LOCK 1 + +#if defined(NO_SSL_DL) +extern void SSL_free(SSL *); +extern int SSL_accept(SSL *); +extern int SSL_connect(SSL *); +extern int SSL_read(SSL *, void *, int); +extern int SSL_write(SSL *, const void *, int); +extern int SSL_get_error(const SSL *, int); +extern int SSL_set_fd(SSL *, int); +extern SSL *SSL_new(SSL_CTX *); +extern SSL_CTX *SSL_CTX_new(SSL_METHOD *); +extern SSL_METHOD *SSLv23_server_method(void); +extern int SSL_library_init(void); +extern void SSL_load_error_strings(void); +extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); +extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); +extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); +extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t); +extern void SSL_CTX_free(SSL_CTX *); +extern unsigned long ERR_get_error(void); +extern char *ERR_error_string(unsigned long, char *); +extern int CRYPTO_num_locks(void); +extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int)); +extern void CRYPTO_set_id_callback(unsigned long (*)(void)); +#else +// Dynamically loaded SSL functionality +struct ssl_func { + const char *name; // SSL function name + void (*ptr)(void); // Function pointer +}; + +#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr) +#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr) +#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr) +#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr) +#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr) +#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr) +#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr) +#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr) +#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr) +#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr) +#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr) +#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \ + const char *, int)) ssl_sw[11].ptr) +#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \ + const char *, int)) ssl_sw[12].ptr) +#define SSL_CTX_set_default_passwd_cb \ + (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr) +#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr) +#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr) +#define SSL_CTX_use_certificate_chain_file \ + (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr) + +#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr) +#define CRYPTO_set_locking_callback \ + (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr) +#define CRYPTO_set_id_callback \ + (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr) +#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr) +#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr) + +// set_ssl_option() function updates this array. +// It loads SSL library dynamically and changes NULLs to the actual addresses +// of respective functions. The macros above (like SSL_connect()) are really +// just calling these functions indirectly via the pointer. +static struct ssl_func ssl_sw[] = { + {"SSL_free", NULL}, + {"SSL_accept", NULL}, + {"SSL_connect", NULL}, + {"SSL_read", NULL}, + {"SSL_write", NULL}, + {"SSL_get_error", NULL}, + {"SSL_set_fd", NULL}, + {"SSL_new", NULL}, + {"SSL_CTX_new", NULL}, + {"SSLv23_server_method", NULL}, + {"SSL_library_init", NULL}, + {"SSL_CTX_use_PrivateKey_file", NULL}, + {"SSL_CTX_use_certificate_file",NULL}, + {"SSL_CTX_set_default_passwd_cb",NULL}, + {"SSL_CTX_free", NULL}, + {"SSL_load_error_strings", NULL}, + {"SSL_CTX_use_certificate_chain_file", NULL}, + {NULL, NULL} +}; + +// Similar array as ssl_sw. These functions could be located in different lib. +static struct ssl_func crypto_sw[] = { + {"CRYPTO_num_locks", NULL}, + {"CRYPTO_set_locking_callback", NULL}, + {"CRYPTO_set_id_callback", NULL}, + {"ERR_get_error", NULL}, + {"ERR_error_string", NULL}, + {NULL, NULL} +}; +#endif // NO_SSL_DL + +static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +// Unified socket address. For IPv6 support, add IPv6 address structure +// in the union u. +struct usa { + socklen_t len; + union { + struct sockaddr sa; + struct sockaddr_in sin; + } u; +}; + +// Describes a string (chunk of memory). +struct vec { + const char *ptr; + size_t len; +}; + +// Structure used by mg_stat() function. Uses 64 bit file length. +struct mgstat { + int is_directory; // Directory marker + int64_t size; // File size + time_t mtime; // Modification time +}; + +// Describes listening socket, or socket which was accept()-ed by the master +// thread and queued for future handling by the worker thread. +struct socket { + struct socket *next; // Linkage + SOCKET sock; // Listening socket + struct usa lsa; // Local socket address + struct usa rsa; // Remote socket address + int is_ssl; // Is socket SSL-ed + int is_proxy; +}; + +enum { + CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER, + PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE, + SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE, + GLOBAL_PASSWORDS_FILE, INDEX_FILES, + ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE, + EXTRA_MIME_TYPES, LISTENING_PORTS, + DOCUMENT_ROOT, SSL_CERTIFICATE, NUM_THREADS, RUN_AS_USER, + NUM_OPTIONS +}; + +static const char *config_options[] = { + "C", "cgi_extensions", ".cgi,.pl,.php", + "E", "cgi_environment", NULL, + "G", "put_delete_passwords_file", NULL, + "I", "cgi_interpreter", NULL, + "P", "protect_uri", NULL, + "R", "authentication_domain", "mydomain.com", + "S", "ssi_extensions", ".shtml,.shtm", + "a", "access_log_file", NULL, + "c", "ssl_chain_file", NULL, + "d", "enable_directory_listing", "yes", + "e", "error_log_file", NULL, + "g", "global_passwords_file", NULL, + "i", "index_files", "index.html,index.htm,index.cgi", + "k", "enable_keep_alive", "no", + "l", "access_control_list", NULL, + "M", "max_request_size", "16384", + "m", "extra_mime_types", NULL, + "p", "listening_ports", "8080", + "r", "document_root", ".", + "s", "ssl_certificate", NULL, + "t", "num_threads", "10", + "u", "run_as_user", NULL, + NULL +}; +#define ENTRIES_PER_CONFIG_OPTION 3 + +struct mg_context { + volatile int stop_flag; // Should we stop event loop + SSL_CTX *ssl_ctx; // SSL context + char *config[NUM_OPTIONS]; // Mongoose configuration parameters + mg_callback_t user_callback; // User-defined callback function + void *user_data; // User-defined data + + struct socket *listening_sockets; + + volatile int num_threads; // Number of threads + pthread_mutex_t mutex; // Protects (max|num)_threads + pthread_cond_t cond; // Condvar for tracking workers terminations + + struct socket queue[20]; // Accepted sockets + volatile int sq_head; // Head of the socket queue + volatile int sq_tail; // Tail of the socket queue + pthread_cond_t sq_full; // Singaled when socket is produced + pthread_cond_t sq_empty; // Signaled when socket is consumed +}; + +struct mg_connection { + struct mg_connection *peer; // Remote target in proxy mode + struct mg_request_info request_info; + struct mg_context *ctx; + SSL *ssl; // SSL descriptor + struct socket client; // Connected client + time_t birth_time; // Time connection was accepted + int64_t num_bytes_sent; // Total bytes sent to client + int64_t content_len; // Content-Length header value + int64_t consumed_content; // How many bytes of content is already read + char *buf; // Buffer for received data + int buf_size; // Buffer size + int request_len; // Size of the request + headers in a buffer + int data_len; // Total size of data in a buffer +}; + +const char **mg_get_valid_option_names(void) { + return config_options; +} + +static void *call_user(struct mg_connection *conn, enum mg_event event) { + conn->request_info.user_data = conn->ctx->user_data; + return conn->ctx->user_callback == NULL ? NULL : + conn->ctx->user_callback(event, conn, &conn->request_info); +} + +static int get_option_index(const char *name) { + int i; + + for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_CONFIG_OPTION) { + if (strcmp(config_options[i], name) == 0 || + strcmp(config_options[i + 1], name) == 0) { + return i / ENTRIES_PER_CONFIG_OPTION; + } + } + return -1; +} + +const char *mg_get_option(const struct mg_context *ctx, const char *name) { + int i; + if ((i = get_option_index(name)) == -1) { + return NULL; + } else if (ctx->config[i] == NULL) { + return ""; + } else { + return ctx->config[i]; + } +} + +// Print error message to the opened error log stream. +static void cry(struct mg_connection *conn, const char *fmt, ...) { + char buf[BUFSIZ]; + va_list ap; + FILE *fp; + time_t timestamp; + + va_start(ap, fmt); + (void) vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + // Do not lock when getting the callback value, here and below. + // I suppose this is fine, since function cannot disappear in the + // same way string option can. + conn->request_info.log_message = buf; + if (call_user(conn, MG_EVENT_LOG) == NULL) { + fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : + mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); + + if (fp != NULL) { + flockfile(fp); + timestamp = time(NULL); + + (void) fprintf(fp, + "[%010lu] [error] [client %s] ", + (unsigned long) timestamp, + inet_ntoa(conn->client.rsa.u.sin.sin_addr)); + + if (conn->request_info.request_method != NULL) { + (void) fprintf(fp, "%s %s: ", + conn->request_info.request_method, + conn->request_info.uri); + } + + (void) fprintf(fp, "%s", buf); + fputc('\n', fp); + funlockfile(fp); + if (fp != stderr) { + fclose(fp); + } + } + } + conn->request_info.log_message = NULL; +} + +// Return OpenSSL error message +static const char *ssl_error(void) { + unsigned long err; + err = ERR_get_error(); + return err == 0 ? "" : ERR_error_string(err, NULL); +} + +// Return fake connection structure. Used for logging, if connection +// is not applicable at the moment of logging. +static struct mg_connection *fc(struct mg_context *ctx) { + static struct mg_connection fake_connection; + fake_connection.ctx = ctx; + return &fake_connection; +} + +const char *mg_version(void) { + return MONGOOSE_VERSION; +} + +static void mg_strlcpy(register char *dst, register const char *src, size_t n) { + for (; *src != '\0' && n > 1; n--) { + *dst++ = *src++; + } + *dst = '\0'; +} + +static int lowercase(const char *s) { + return tolower(* (const unsigned char *) s); +} + +static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + + if (len > 0) + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + + return diff; +} + +static int mg_strcasecmp(const char *s1, const char *s2) { + int diff; + + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0'); + + return diff; +} + +static char * mg_strndup(const char *ptr, size_t len) { + char *p; + + if ((p = (char *) malloc(len + 1)) != NULL) { + mg_strlcpy(p, ptr, len + 1); + } + + return p; +} + +static char * mg_strdup(const char *str) { + return mg_strndup(str, strlen(str)); +} + +// Like snprintf(), but never returns negative value, or the value +// that is larger than a supplied buffer. +// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability +// in his audit report. +static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen, + const char *fmt, va_list ap) { + int n; + + if (buflen == 0) + return 0; + + n = vsnprintf(buf, buflen, fmt, ap); + + if (n < 0) { + cry(conn, "vsnprintf error"); + n = 0; + } else if (n >= (int) buflen) { + cry(conn, "truncating vsnprintf buffer: [%.*s]", + n > 200 ? 200 : n, buf); + n = (int) buflen - 1; + } + buf[n] = '\0'; + + return n; +} + +static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, + const char *fmt, ...) { + va_list ap; + int n; + + va_start(ap, fmt); + n = mg_vsnprintf(conn, buf, buflen, fmt, ap); + va_end(ap); + + return n; +} + +// Skip the characters until one of the delimiters characters found. +// 0-terminate resulting word. Skip the delimiter and following whitespaces if any. +// Advance pointer to buffer to the next word. Return found 0-terminated word. +// Delimiters can be quoted with quotechar. +static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) { + char *p, *begin_word, *end_word, *end_whitespace; + + begin_word = *buf; + end_word = begin_word + strcspn(begin_word, delimiters); + + /* Check for quotechar */ + if (end_word > begin_word) { + p = end_word - 1; + while (*p == quotechar) { + /* If there is anything beyond end_word, copy it */ + if (*end_word == '\0') { + *p = '\0'; + break; + } else { + size_t end_off = strcspn(end_word + 1, delimiters); + memmove (p, end_word, end_off + 1); + p += end_off; /* p must correspond to end_word - 1 */ + end_word += end_off + 1; + } + } + for (p++; p < end_word; p++) { + *p = '\0'; + } + } + + if (*end_word == '\0') { + *buf = end_word; + } else { + end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); + + for (p = end_word; p < end_whitespace; p++) { + *p = '\0'; + } + + *buf = end_whitespace; + } + + return begin_word; +} + +// Simplified version of skip_quoted without quote char +// and whitespace == delimiters +static char *skip(char **buf, const char *delimiters) { + return skip_quoted(buf, delimiters, delimiters, 0); +} + + +// Return HTTP header value, or NULL if not found. +static const char *get_header(const struct mg_request_info *ri, + const char *name) { + int i; + + for (i = 0; i < ri->num_headers; i++) + if (!mg_strcasecmp(name, ri->http_headers[i].name)) + return ri->http_headers[i].value; + + return NULL; +} + +const char *mg_get_header(const struct mg_connection *conn, const char *name) { + return get_header(&conn->request_info, name); +} + +// A helper function for traversing comma separated list of values. +// It returns a list pointer shifted to the next value, of NULL if the end +// of the list found. +// Value is stored in val vector. If value has form "x=y", then eq_val +// vector is initialized to point to the "y" part, and val vector length +// is adjusted to point only to "x". +static const char *next_option(const char *list, struct vec *val, + struct vec *eq_val) { + if (list == NULL || *list == '\0') { + /* End of the list */ + list = NULL; + } else { + val->ptr = list; + if ((list = strchr(val->ptr, ',')) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val->len = list - val->ptr; + list++; + } else { + /* This value is the last one */ + list = val->ptr + strlen(val->ptr); + val->len = list - val->ptr; + } + + if (eq_val != NULL) { + /* + * Value has form "x=y", adjust pointers and lengths + * so that val points to "x", and eq_val points to "y". + */ + eq_val->len = 0; + eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); + if (eq_val->ptr != NULL) { + eq_val->ptr++; /* Skip over '=' character */ + eq_val->len = val->ptr + val->len - eq_val->ptr; + val->len = (eq_val->ptr - val->ptr) - 1; + } + } + } + + return list; +} + +#if !defined(NO_CGI) +static int match_extension(const char *path, const char *ext_list) { + struct vec ext_vec; + size_t path_len; + + path_len = strlen(path); + + while ((ext_list = next_option(ext_list, &ext_vec, NULL)) != NULL) + if (ext_vec.len < path_len && + mg_strncasecmp(path + path_len - ext_vec.len, + ext_vec.ptr, ext_vec.len) == 0) + return 1; + + return 0; +} +#endif // !NO_CGI + +// HTTP 1.1 assumes keep alive if "Connection:" header is not set +// This function must tolerate situations when connection info is not +// set up, for example if request parsing failed. +static int should_keep_alive(const struct mg_connection *conn) { + const char *http_version = conn->request_info.http_version; + const char *header = mg_get_header(conn, "Connection"); + return (header == NULL && http_version && !strcmp(http_version, "1.1")) || + (header != NULL && !mg_strcasecmp(header, "keep-alive")); +} + +static const char *suggest_connection_header(const struct mg_connection *conn) { + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + +static void send_http_error(struct mg_connection *conn, int status, + const char *reason, const char *fmt, ...) { + char buf[BUFSIZ]; + va_list ap; + int len; + + conn->request_info.status_code = status; + + if (call_user(conn, MG_HTTP_ERROR) == NULL) { + buf[0] = '\0'; + len = 0; + + /* Errors 1xx, 204 and 304 MUST NOT send a body */ + if (status > 199 && status != 204 && status != 304) { + len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); + cry(conn, "%s", buf); + buf[len++] = '\n'; + + va_start(ap, fmt); + len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap); + va_end(ap); + } + DEBUG_TRACE(("[%s]", buf)); + + mg_printf(conn, "HTTP/1.1 %d %s\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %d\r\n" + "Connection: %s\r\n\r\n", status, reason, len, + suggest_connection_header(conn)); + conn->num_bytes_sent += mg_printf(conn, "%s", buf); + } +} + +#if defined(_WIN32) && !defined(__SYMBIAN32__) +static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) { + unused = NULL; + *mutex = CreateMutex(NULL, FALSE, NULL); + return *mutex == NULL ? -1 : 0; +} + +static int pthread_mutex_destroy(pthread_mutex_t *mutex) { + return CloseHandle(*mutex) == 0 ? -1 : 0; +} + +static int pthread_mutex_lock(pthread_mutex_t *mutex) { + return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; +} + +static int pthread_mutex_unlock(pthread_mutex_t *mutex) { + return ReleaseMutex(*mutex) == 0 ? -1 : 0; +} + +static int pthread_cond_init(pthread_cond_t *cv, const void *unused) { + unused = NULL; + cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL); + cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL); + return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1; +} + +static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) { + HANDLE handles[] = {cv->signal, cv->broadcast}; + ReleaseMutex(*mutex); + WaitForMultipleObjects(2, handles, FALSE, INFINITE); + return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; +} + +static int pthread_cond_signal(pthread_cond_t *cv) { + return SetEvent(cv->signal) == 0 ? -1 : 0; +} + +static int pthread_cond_broadcast(pthread_cond_t *cv) { + // Implementation with PulseEvent() has race condition, see + // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + // return PulseEvent(cv->broadcast) == 0 ? -1 : 0; + + // [KirillJacobson] PulseEvent causes ms_stop() function to hang when the + // process runs in the VisualStudio debugger (observed on Windows XP SP3, + // VS 2008) + // + // MSDN states that PulseEvent() "function is unreliable and should + // not be used. It exists mainly for backward compatibility" + // http://msdn.microsoft.com/en-us/library/ms684914%28v=vs.85%29.aspx + return SetEvent(cv->broadcast) == 0 ? -1 : 0; +} + +static int pthread_cond_destroy(pthread_cond_t *cv) { + return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1; +} + +static pthread_t pthread_self(void) { + return GetCurrentThreadId(); +} + +// For Windows, change all slashes to backslashes in path names. +static void change_slashes_to_backslashes(char *path) { + int i; + + for (i = 0; path[i] != '\0'; i++) { + if (path[i] == '/') + path[i] = '\\'; + // i > 0 check is to preserve UNC paths, like \\server\file.txt + if (path[i] == '\\' && i > 0) + while (path[i + 1] == '\\' || path[i + 1] == '/') + (void) memmove(path + i + 1, + path + i + 2, strlen(path + i + 1)); + } +} + +// Encode 'path' which is assumed UTF-8 string, into UNICODE string. +// wbuf and wbuf_len is a target buffer and its length. +static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) { + char buf[PATH_MAX], *p; + + mg_strlcpy(buf, path, sizeof(buf)); + change_slashes_to_backslashes(buf); + + // Point p to the end of the file name + p = buf + strlen(buf) - 1; + + // Trim trailing backslash character + while (p > buf && *p == '\\' && p[-1] != ':') { + *p-- = '\0'; + } + + // Protect from CGI code disclosure. + // This is very nasty hole. Windows happily opens files with + // some garbage in the end of file name. So fopen("a.cgi ", "r") + // actually opens "a.cgi", and does not return an error! + if (*p == 0x20 || // No space at the end + (*p == 0x2e && p > buf) || // No '.' but allow '.' as full path + *p == 0x2b || // No '+' + (*p & ~0x7f)) { // And generally no non-ascii chars + (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf); + buf[0] = '\0'; + } + + (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); +} + +#if defined(_WIN32_WCE) +static time_t time(time_t *ptime) { + time_t t; + SYSTEMTIME st; + FILETIME ft; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); + t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime); + + if (ptime != NULL) { + *ptime = t; + } + + return t; +} + +static time_t mktime(struct tm *ptm) { + SYSTEMTIME st; + FILETIME ft, lft; + + st.wYear = ptm->tm_year + 1900; + st.wMonth = ptm->tm_mon + 1; + st.wDay = ptm->tm_mday; + st.wHour = ptm->tm_hour; + st.wMinute = ptm->tm_min; + st.wSecond = ptm->tm_sec; + st.wMilliseconds = 0; + + SystemTimeToFileTime(&st, &ft); + LocalFileTimeToFileTime(&ft, &lft); + return (time_t) ((MAKEUQUAD(lft.dwLowDateTime, lft.dwHighDateTime) - + EPOCH_DIFF) / RATE_DIFF); +} + +static struct tm *localtime(const time_t *ptime, struct tm *ptm) { + int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF; + FILETIME ft, lft; + SYSTEMTIME st; + TIME_ZONE_INFORMATION tzinfo; + + if (ptm == NULL) { + return NULL; + } + + * (int64_t *) &ft = t; + FileTimeToLocalFileTime(&ft, &lft); + FileTimeToSystemTime(&lft, &st); + ptm->tm_year = st.wYear - 1900; + ptm->tm_mon = st.wMonth - 1; + ptm->tm_wday = st.wDayOfWeek; + ptm->tm_mday = st.wDay; + ptm->tm_hour = st.wHour; + ptm->tm_min = st.wMinute; + ptm->tm_sec = st.wSecond; + ptm->tm_yday = 0; // hope nobody uses this + ptm->tm_isdst = + GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0; + + return ptm; +} + +static size_t strftime(char *dst, size_t dst_size, const char *fmt, + const struct tm *tm) { + (void) snprintf(dst, dst_size, "implement strftime() for WinCE"); + return 0; +} +#endif + +static int mg_rename(const char* oldname, const char* newname) { + wchar_t woldbuf[PATH_MAX]; + wchar_t wnewbuf[PATH_MAX]; + + to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf)); + to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf)); + + return MoveFileW(woldbuf, wnewbuf) ? 0 : -1; +} + + +static FILE *mg_fopen(const char *path, const char *mode) { + wchar_t wbuf[PATH_MAX], wmode[20]; + + to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode)); + + return _wfopen(wbuf, wmode); +} + +static int mg_stat(const char *path, struct mgstat *stp) { + int ok = -1; // Error + wchar_t wbuf[PATH_MAX]; + WIN32_FILE_ATTRIBUTE_DATA info; + + to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); + + if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) { + stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh); + stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime); + stp->is_directory = + info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + ok = 0; // Success + } + + return ok; +} + +static int mg_remove(const char *path) { + wchar_t wbuf[PATH_MAX]; + to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); + return DeleteFileW(wbuf) ? 0 : -1; +} + +static int mg_mkdir(const char *path, int mode) { + char buf[PATH_MAX]; + wchar_t wbuf[PATH_MAX]; + + mode = 0; // Unused + mg_strlcpy(buf, path, sizeof(buf)); + change_slashes_to_backslashes(buf); + + (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); + + return CreateDirectoryW(wbuf, NULL) ? 0 : -1; +} + +// Implementation of POSIX opendir/closedir/readdir for Windows. +static DIR * opendir(const char *name) { + DIR *dir = NULL; + wchar_t wpath[PATH_MAX]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_unicode(name, wpath, ARRAY_SIZE(wpath)); + attrs = GetFileAttributesW(wpath); + if (attrs != 0xFFFFFFFF && + ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + dir->handle = FindFirstFileW(wpath, &dir->info); + dir->result.d_name[0] = '\0'; + } else { + free(dir); + dir = NULL; + } + } + + return dir; +} + +static int closedir(DIR *dir) { + int result = 0; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + + free(dir); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + +struct dirent * readdir(DIR *dir) { + struct dirent *result = 0; + + if (dir) { + if (dir->handle != INVALID_HANDLE_VALUE) { + result = &dir->result; + (void) WideCharToMultiByte(CP_UTF8, 0, + dir->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + + if (!FindNextFileW(dir->handle, &dir->info)) { + (void) FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + +#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows + +static int start_thread(struct mg_context *ctx, mg_thread_func_t func, + void *param) { + HANDLE hThread; + ctx = NULL; // Unused + + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) func, param, 0, + NULL); + if (hThread != NULL) { + (void) CloseHandle(hThread); + } + + return hThread == NULL ? -1 : 0; +} + +static HANDLE dlopen(const char *dll_name, int flags) { + wchar_t wbuf[PATH_MAX]; + flags = 0; // Unused + to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf)); + return LoadLibraryW(wbuf); +} + +#if !defined(NO_CGI) +#define SIGKILL 0 +static int kill(pid_t pid, int sig_num) { + (void) TerminateProcess(pid, sig_num); + (void) CloseHandle(pid); + return 0; +} + +static pid_t spawn_process(struct mg_connection *conn, const char *prog, + char *envblk, char *envp[], int fd_stdin, + int fd_stdout, const char *dir) { + HANDLE me; + char *p, *interp, cmdline[PATH_MAX], buf[PATH_MAX]; + FILE *fp; + STARTUPINFOA si; + PROCESS_INFORMATION pi; + + envp = NULL; // Unused + + (void) memset(&si, 0, sizeof(si)); + (void) memset(&pi, 0, sizeof(pi)); + + // TODO(lsm): redirect CGI errors to the error log file + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + me = GetCurrentProcess(); + (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me, + &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS); + (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me, + &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); + + // If CGI file is a script, try to read the interpreter line + interp = conn->ctx->config[CGI_INTERPRETER]; + if (interp == NULL) { + buf[2] = '\0'; + if ((fp = fopen(cmdline, "r")) != NULL) { + (void) fgets(buf, sizeof(buf), fp); + if (buf[0] != '#' || buf[1] != '!') { + // First line does not start with "#!". Do not set interpreter. + buf[2] = '\0'; + } else { + // Trim whitespaces in interpreter name + for (p = &buf[strlen(buf) - 1]; p > buf && isspace(*p); p--) { + *p = '\0'; + } + } + (void) fclose(fp); + } + interp = buf + 2; + } + + (void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s%c%s", + interp, interp[0] == '\0' ? "" : " ", dir, DIRSEP, prog); + + DEBUG_TRACE(("Running [%s]", cmdline)); + if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, + CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) { + cry(conn, "%s: CreateProcess(%s): %d", + __func__, cmdline, ERRNO); + pi.hProcess = (pid_t) -1; + } else { + (void) close(fd_stdin); + (void) close(fd_stdout); + } + + (void) CloseHandle(si.hStdOutput); + (void) CloseHandle(si.hStdInput); + (void) CloseHandle(pi.hThread); + + return (pid_t) pi.hProcess; +} +#endif /* !NO_CGI */ + +static int set_non_blocking_mode(SOCKET sock) { + unsigned long on = 1; + return ioctlsocket(sock, FIONBIO, &on); +} + +#else +static int mg_stat(const char *path, struct mgstat *stp) { + struct stat st; + int ok; + + if (stat(path, &st) == 0) { + ok = 0; + stp->size = st.st_size; + stp->mtime = st.st_mtime; + stp->is_directory = S_ISDIR(st.st_mode); + } else { + ok = -1; + } + + return ok; +} + +static void set_close_on_exec(int fd) { + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +static int start_thread(struct mg_context *ctx, mg_thread_func_t func, + void *param) { + pthread_t thread_id; + pthread_attr_t attr; + int retval; + + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled + // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5); + + if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) { + cry(fc(ctx), "%s: %s", __func__, strerror(retval)); + } + + return retval; +} + +#ifndef NO_CGI +static pid_t spawn_process(struct mg_connection *conn, const char *prog, + char *envblk, char *envp[], int fd_stdin, + int fd_stdout, const char *dir) { + pid_t pid; + const char *interp; + + envblk = NULL; // Unused + + if ((pid = fork()) == -1) { + // Parent + send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO)); + } else if (pid == 0) { + // Child + if (chdir(dir) != 0) { + cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO)); + } else if (dup2(fd_stdin, 0) == -1) { + cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO)); + } else if (dup2(fd_stdout, 1) == -1) { + cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO)); + } else { + (void) dup2(fd_stdout, 2); + (void) close(fd_stdin); + (void) close(fd_stdout); + + // Execute CGI program. No need to lock: new process + interp = conn->ctx->config[CGI_INTERPRETER]; + if (interp == NULL) { + (void) execle(prog, prog, NULL, envp); + cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); + } else { + (void) execle(interp, interp, prog, NULL, envp); + cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog, + strerror(ERRNO)); + } + } + exit(EXIT_FAILURE); + } else { + // Parent. Close stdio descriptors + (void) close(fd_stdin); + (void) close(fd_stdout); + } + + return pid; +} +#endif // !NO_CGI + +static int set_non_blocking_mode(SOCKET sock) { + int flags; + + flags = fcntl(sock, F_GETFL, 0); + (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK); + + return 0; +} +#endif // _WIN32 + +// Write data to the IO channel - opened file descriptor, socket or SSL +// descriptor. Return number of bytes written. +static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, + int64_t len) { + int64_t sent; + int n, k; + + sent = 0; + while (sent < len) { + + /* How many bytes we send in this iteration */ + k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); + + if (ssl != NULL) { + n = SSL_write(ssl, buf + sent, k); + } else if (fp != NULL) { + n = fwrite(buf + sent, 1, (size_t)k, fp); + if (ferror(fp)) + n = -1; + } else { + n = send(sock, buf + sent, (size_t)k, 0); + } + + if (n < 0) + break; + + sent += n; + } + + return sent; +} + +// Read from IO channel - opened file descriptor, socket, or SSL descriptor. +// Return number of bytes read. +static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) { + int nread; + + if (ssl != NULL) { + nread = SSL_read(ssl, buf, len); + } else if (fp != NULL) { + // Use read() instead of fread(), because if we're reading from the CGI + // pipe, fread() may block until IO buffer is filled up. We cannot afford + // to block and must pass all read bytes immediately to the client. + nread = read(fileno(fp), buf, (size_t) len); + if (ferror(fp)) + nread = -1; + } else { + nread = recv(sock, buf, (size_t) len, 0); + } + + return nread; +} + +int mg_read(struct mg_connection *conn, void *buf, size_t len) { + int n, buffered_len, nread; + const char *buffered; + + assert((conn->content_len == -1 && conn->consumed_content == 0) || + conn->consumed_content <= conn->content_len); + DEBUG_TRACE(("%p %zu %lld %lld", buf, len, + conn->content_len, conn->consumed_content)); + nread = 0; + if (conn->consumed_content < conn->content_len) { + + // Adjust number of bytes to read. + int64_t to_read = conn->content_len - conn->consumed_content; + if (to_read < (int64_t) len) { + len = (int) to_read; + } + + // How many bytes of data we have buffered in the request buffer? + buffered = conn->buf + conn->request_len + conn->consumed_content; + buffered_len = conn->data_len - conn->request_len; + assert(buffered_len >= 0); + + // Return buffered data back if we haven't done that yet. + if (conn->consumed_content < (int64_t) buffered_len) { + buffered_len -= (int) conn->consumed_content; + if (len < (size_t) buffered_len) { + buffered_len = len; + } + memcpy(buf, buffered, (size_t)buffered_len); + len -= buffered_len; + buf = (char *) buf + buffered_len; + conn->consumed_content += buffered_len; + nread = buffered_len; + } + + // We have returned all buffered data. Read new data from the remote socket. + while (len > 0) { + n = pull(NULL, conn->client.sock, conn->ssl, (char *) buf, (int) len); + if (n <= 0) { + break; + } + buf = (char *) buf + n; + conn->consumed_content += n; + nread += n; + len -= n; + } + } + return nread; +} + +int mg_write(struct mg_connection *conn, const void *buf, size_t len) { + return (int) push(NULL, conn->client.sock, conn->ssl, + (const char *) buf, (int64_t) len); +} + +int mg_printf(struct mg_connection *conn, const char *fmt, ...) { + char buf[BUFSIZ]; + int len; + va_list ap; + + va_start(ap, fmt); + len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap); + va_end(ap); + + return mg_write(conn, buf, (size_t)len); +} + +// URL-decode input buffer into destination buffer. +// 0-terminate the destination buffer. Return the length of decoded data. +// form-url-encoded data differs from URI encoding in a way that it +// uses '+' as character for space, see RFC 1866 section 8.2.1 +// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt +static size_t url_decode(const char *src, size_t src_len, char *dst, + size_t dst_len, int is_form_url_encoded) { + size_t i, j; + int a, b; +#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') + + for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { + if (src[i] == '%' && + isxdigit(* (const unsigned char *) (src + i + 1)) && + isxdigit(* (const unsigned char *) (src + i + 2))) { + a = tolower(* (const unsigned char *) (src + i + 1)); + b = tolower(* (const unsigned char *) (src + i + 2)); + dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); + i += 2; + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + + dst[j] = '\0'; /* Null-terminate the destination */ + + return j; +} + +// Scan given buffer and fetch the value of the given variable. +// It can be specified in query string, or in the POST data. +// Return NULL if the variable not found, or allocated 0-terminated value. +// It is caller's responsibility to free the returned value. +int mg_get_var(const char *buf, size_t buf_len, const char *name, + char *dst, size_t dst_len) { + const char *p, *e, *s; + size_t name_len, len; + + name_len = strlen(name); + e = buf + buf_len; + len = -1; + dst[0] = '\0'; + + // buf is "var1=val1&var2=val2...". Find variable first + for (p = buf; p != NULL && p + name_len < e; p++) { + if ((p == buf || p[-1] == '&') && p[name_len] == '=' && + !mg_strncasecmp(name, p, name_len)) { + + // Point p to variable value + p += name_len + 1; + + // Point s to the end of the value + s = (const char *) memchr(p, '&', (size_t)(e - p)); + if (s == NULL) { + s = e; + } + assert(s >= p); + + // Decode variable into destination buffer + if ((size_t) (s - p) < dst_len) { + len = url_decode(p, (size_t)(s - p), dst, dst_len, 1); + } + break; + } + } + + return len; +} + +int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name, + char *dst, size_t dst_size) { + const char *s, *p, *end; + int name_len, len = -1; + + dst[0] = '\0'; + if ((s = mg_get_header(conn, "Cookie")) == NULL) { + return 0; + } + + name_len = strlen(cookie_name); + end = s + strlen(s); + + for (; (s = strstr(s, cookie_name)) != NULL; s += name_len) + if (s[name_len] == '=') { + s += name_len + 1; + if ((p = strchr(s, ' ')) == NULL) + p = end; + if (p[-1] == ';') + p--; + if (*s == '"' && p[-1] == '"' && p > s + 1) { + s++; + p--; + } + if ((size_t) (p - s) < dst_size) { + len = (p - s) + 1; + mg_strlcpy(dst, s, (size_t)len); + } + break; + } + + return len; +} + +// Mongoose allows to specify multiple directories to serve, +// like /var/www,/~bob=/home/bob. That means that root directory depends on URI. +// This function returns root dir for given URI. +static int get_document_root(const struct mg_connection *conn, + struct vec *document_root) { + const char *root, *uri; + int len_of_matched_uri; + struct vec uri_vec, path_vec; + + uri = conn->request_info.uri; + len_of_matched_uri = 0; + root = next_option(conn->ctx->config[DOCUMENT_ROOT], document_root, NULL); + + while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) { + if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) { + *document_root = path_vec; + len_of_matched_uri = uri_vec.len; + break; + } + } + + return len_of_matched_uri; +} + +static void convert_uri_to_file_name(struct mg_connection *conn, + const char *uri, char *buf, + size_t buf_len) { + struct vec vec; + int match_len; + + match_len = get_document_root(conn, &vec); + mg_snprintf(conn, buf, buf_len, "%.*s%s", vec.len, vec.ptr, uri + match_len); + +#if defined(_WIN32) && !defined(__SYMBIAN32__) + change_slashes_to_backslashes(buf); +#endif /* _WIN32 */ + + DEBUG_TRACE(("[%s] -> [%s], [%.*s]", uri, buf, (int) vec.len, vec.ptr)); +} + +static int sslize(struct mg_connection *conn, int (*func)(SSL *)) { + return (conn->ssl = SSL_new(conn->ctx->ssl_ctx)) != NULL && + SSL_set_fd(conn->ssl, conn->client.sock) == 1 && + func(conn->ssl) == 1; +} + +static struct mg_connection *mg_connect(struct mg_connection *conn, + const char *host, int port, int use_ssl) { + struct mg_connection *newconn = NULL; + struct sockaddr_in sin; + struct hostent *he; + int sock; + + if (conn->ctx->ssl_ctx == NULL && use_ssl) { + cry(conn, "%s: SSL is not initialized", __func__); + } else if ((he = gethostbyname(host)) == NULL) { + cry(conn, "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO)); + } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + cry(conn, "%s: socket: %s", __func__, strerror(ERRNO)); + } else { + sin.sin_family = AF_INET; + sin.sin_port = htons((uint16_t) port); + sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; + if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) { + cry(conn, "%s: connect(%s:%d): %s", __func__, host, port, + strerror(ERRNO)); + closesocket(sock); + } else if ((newconn = (struct mg_connection *) + calloc(1, sizeof(*newconn))) == NULL) { + cry(conn, "%s: calloc: %s", __func__, strerror(ERRNO)); + closesocket(sock); + } else { + newconn->client.sock = sock; + newconn->client.rsa.u.sin = sin; + if (use_ssl) { + sslize(newconn, SSL_connect); + } + } + } + + return newconn; +} + +// Check whether full request is buffered. Return: +// -1 if request is malformed +// 0 if request is not yet fully buffered +// >0 actual request length, including last \r\n\r\n +static int get_request_len(const char *buf, int buflen) { + const char *s, *e; + int len = 0; + + DEBUG_TRACE(("buf: %p, len: %d", buf, buflen)); + for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++) + // Control characters are not allowed but >=128 is. + if (!isprint(* (const unsigned char *) s) && *s != '\r' && + *s != '\n' && * (const unsigned char *) s < 128) { + len = -1; + } else if (s[0] == '\n' && s[1] == '\n') { + len = (int) (s - buf) + 2; + } else if (s[0] == '\n' && &s[1] < e && + s[1] == '\r' && s[2] == '\n') { + len = (int) (s - buf) + 3; + } + + return len; +} + +// Convert month to the month number. Return -1 on error, or month number +static int month_number_to_month_name(const char *s) { + size_t i; + + for (i = 0; i < ARRAY_SIZE(month_names); i++) + if (!strcmp(s, month_names[i])) + return (int) i; + + return -1; +} + +// Parse date-time string, and return the corresponding time_t value +static time_t parse_date_string(const char *s) { + time_t current_time; + struct tm tm, *tmp; + char mon[32]; + int sec, min, hour, mday, month, year; + + (void) memset(&tm, 0, sizeof(tm)); + sec = min = hour = mday = month = year = 0; + + if (((sscanf(s, "%d/%3s/%d %d:%d:%d", + &mday, mon, &year, &hour, &min, &sec) == 6) || + (sscanf(s, "%d %3s %d %d:%d:%d", + &mday, mon, &year, &hour, &min, &sec) == 6) || + (sscanf(s, "%*3s, %d %3s %d %d:%d:%d", + &mday, mon, &year, &hour, &min, &sec) == 6) || + (sscanf(s, "%d-%3s-%d %d:%d:%d", + &mday, mon, &year, &hour, &min, &sec) == 6)) && + (month = month_number_to_month_name(mon)) != -1) { + tm.tm_mday = mday; + tm.tm_mon = month; + tm.tm_year = year; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + } + + if (tm.tm_year > 1900) { + tm.tm_year -= 1900; + } else if (tm.tm_year < 70) { + tm.tm_year += 100; + } + + // Set Daylight Saving Time field + current_time = time(NULL); + tmp = localtime(¤t_time); + tm.tm_isdst = tmp->tm_isdst; + + return mktime(&tm); +} + +// Protect against directory disclosure attack by removing '..', +// excessive '/' and '\' characters +static void remove_double_dots_and_double_slashes(char *s) { + char *p = s; + + while (*s != '\0') { + *p++ = *s++; + if (s[-1] == '/' || s[-1] == '\\') { + // Skip all following slashes and backslashes + while (*s == '/' || *s == '\\') { + s++; + } + + // Skip all double-dots + while (*s == '.' && s[1] == '.') { + s += 2; + } + } + } + *p = '\0'; +} + +static const struct { + const char *extension; + size_t ext_len; + const char *mime_type; + size_t mime_type_len; +} builtin_mime_types[] = { + {".html", 5, "text/html", 9}, + {".htm", 4, "text/html", 9}, + {".shtm", 5, "text/html", 9}, + {".shtml", 6, "text/html", 9}, + {".css", 4, "text/css", 8}, + {".js", 3, "application/x-javascript", 24}, + {".ico", 4, "image/x-icon", 12}, + {".gif", 4, "image/gif", 9}, + {".jpg", 4, "image/jpeg", 10}, + {".jpeg", 5, "image/jpeg", 10}, + {".png", 4, "image/png", 9}, + {".svg", 4, "image/svg+xml", 13}, + {".torrent", 8, "application/x-bittorrent", 24}, + {".wav", 4, "audio/x-wav", 11}, + {".mp3", 4, "audio/x-mp3", 11}, + {".mid", 4, "audio/mid", 9}, + {".m3u", 4, "audio/x-mpegurl", 15}, + {".ram", 4, "audio/x-pn-realaudio", 20}, + {".xml", 4, "text/xml", 8}, + {".xslt", 5, "application/xml", 15}, + {".ra", 3, "audio/x-pn-realaudio", 20}, + {".doc", 4, "application/msword", 19}, + {".exe", 4, "application/octet-stream", 24}, + {".zip", 4, "application/x-zip-compressed", 28}, + {".xls", 4, "application/excel", 17}, + {".tgz", 4, "application/x-tar-gz", 20}, + {".tar", 4, "application/x-tar", 17}, + {".gz", 3, "application/x-gunzip", 20}, + {".arj", 4, "application/x-arj-compressed", 28}, + {".rar", 4, "application/x-arj-compressed", 28}, + {".rtf", 4, "application/rtf", 15}, + {".pdf", 4, "application/pdf", 15}, + {".swf", 4, "application/x-shockwave-flash",29}, + {".mpg", 4, "video/mpeg", 10}, + {".mpeg", 5, "video/mpeg", 10}, + {".asf", 4, "video/x-ms-asf", 14}, + {".avi", 4, "video/x-msvideo", 15}, + {".bmp", 4, "image/bmp", 9}, + {NULL, 0, NULL, 0} +}; + +// Look at the "path" extension and figure what mime type it has. +// Store mime type in the vector. +static void get_mime_type(struct mg_context *ctx, const char *path, + struct vec *vec) { + struct vec ext_vec, mime_vec; + const char *list, *ext; + size_t i, path_len; + + path_len = strlen(path); + + // Scan user-defined mime types first, in case user wants to + // override default mime types. + list = ctx->config[EXTRA_MIME_TYPES]; + while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { + // ext now points to the path suffix + ext = path + path_len - ext_vec.len; + if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { + *vec = mime_vec; + return; + } + } + + // Now scan built-in mime types + for (i = 0; builtin_mime_types[i].extension != NULL; i++) { + ext = path + (path_len - builtin_mime_types[i].ext_len); + if (path_len > builtin_mime_types[i].ext_len && + mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) { + vec->ptr = builtin_mime_types[i].mime_type; + vec->len = builtin_mime_types[i].mime_type_len; + return; + } + } + + // Nothing found. Fall back to "text/plain" + vec->ptr = "text/plain"; + vec->len = 10; +} + +#ifndef HAVE_MD5 +typedef struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} MD5_CTX; + +#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234) +#define byteReverse(buf, len) // Do nothing +#else +static void byteReverse(unsigned char *buf, unsigned longs) { + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious +// initialization constants. +static void MD5Init(MD5_CTX *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) { + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) { + uint32_t t; + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + memcpy(ctx->in, buf, len); +} + +static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) { + unsigned count; + unsigned char *p; + + count = (ctx->bits[0] >> 3) & 0x3F; + + p = ctx->in + count; + *p++ = 0x80; + count = 64 - 1 - count; + if (count < 8) { + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + memset(ctx->in, 0, 56); + } else { + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(*ctx)); +} +#endif // !HAVE_MD5 + +// Stringify binary data. Output buffer must be twice as big as input, +// because each byte takes 2 bytes in string representation +static void bin2str(char *to, const unsigned char *p, size_t len) { + static const char *hex = "0123456789abcdef"; + + for (; len--; p++) { + *to++ = hex[p[0] >> 4]; + *to++ = hex[p[0] & 0x0f]; + } + *to = '\0'; +} + +// Return stringified MD5 hash for list of vectors. Buffer must be 33 bytes. +void mg_md5(char *buf, ...) { + unsigned char hash[16]; + const char *p; + va_list ap; + MD5_CTX ctx; + + MD5Init(&ctx); + + va_start(ap, buf); + while ((p = va_arg(ap, const char *)) != NULL) { + MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p)); + } + va_end(ap); + + MD5Final(hash, &ctx); + bin2str(buf, hash, sizeof(hash)); +} + +// Check the user's password, return 1 if OK +static int check_password(const char *method, const char *ha1, const char *uri, + const char *nonce, const char *nc, const char *cnonce, + const char *qop, const char *response) { + char ha2[32 + 1], expected_response[32 + 1]; + + // Some of the parameters may be NULL + if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL || + qop == NULL || response == NULL) { + return 0; + } + + // NOTE(lsm): due to a bug in MSIE, we do not compare the URI + // TODO(lsm): check for authentication timeout + if (// strcmp(dig->uri, c->ouri) != 0 || + strlen(response) != 32 + // || now - strtoul(dig->nonce, NULL, 10) > 3600 + ) { + return 0; + } + + mg_md5(ha2, method, ":", uri, NULL); + mg_md5(expected_response, ha1, ":", nonce, ":", nc, + ":", cnonce, ":", qop, ":", ha2, NULL); + + return mg_strcasecmp(response, expected_response) == 0; +} + +// Use the global passwords file, if specified by auth_gpass option, +// or search for .htpasswd in the requested directory. +static FILE *open_auth_file(struct mg_connection *conn, const char *path) { + struct mg_context *ctx = conn->ctx; + char name[PATH_MAX]; + const char *p, *e; + struct mgstat st; + FILE *fp; + + if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) { + // Use global passwords file + fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r"); + if (fp == NULL) + cry(fc(ctx), "fopen(%s): %s", + ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO)); + } else if (!mg_stat(path, &st) && st.is_directory) { + (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s", + path, DIRSEP, PASSWORDS_FILE_NAME); + fp = mg_fopen(name, "r"); + } else { + // Try to find .htpasswd in requested directory. + for (p = path, e = p + strlen(p) - 1; e > p; e--) + if (IS_DIRSEP_CHAR(*e)) + break; + (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", + (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME); + fp = mg_fopen(name, "r"); + } + + return fp; +} + +// Parsed Authorization header +struct ah { + char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; +}; + +static int parse_auth_header(struct mg_connection *conn, char *buf, + size_t buf_size, struct ah *ah) { + char *name, *value, *s; + const char *auth_header; + + if ((auth_header = mg_get_header(conn, "Authorization")) == NULL || + mg_strncasecmp(auth_header, "Digest ", 7) != 0) { + return 0; + } + + // Make modifiable copy of the auth header + (void) mg_strlcpy(buf, auth_header + 7, buf_size); + + s = buf; + (void) memset(ah, 0, sizeof(*ah)); + + // Parse authorization header + for (;;) { + // Gobble initial spaces + while (isspace(* (unsigned char *) s)) { + s++; + } + name = skip_quoted(&s, "=", " ", 0); + /* Value is either quote-delimited, or ends at first comma or space. */ + if (s[0] == '\"') { + s++; + value = skip_quoted(&s, "\"", " ", '\\'); + if (s[0] == ',') { + s++; + } + } else { + value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces + } + if (*name == '\0') { + break; + } + + if (!strcmp(name, "username")) { + ah->user = value; + } else if (!strcmp(name, "cnonce")) { + ah->cnonce = value; + } else if (!strcmp(name, "response")) { + ah->response = value; + } else if (!strcmp(name, "uri")) { + ah->uri = value; + } else if (!strcmp(name, "qop")) { + ah->qop = value; + } else if (!strcmp(name, "nc")) { + ah->nc = value; + } else if (!strcmp(name, "nonce")) { + ah->nonce = value; + } + } + + // CGI needs it as REMOTE_USER + if (ah->user != NULL) { + conn->request_info.remote_user = mg_strdup(ah->user); + } else { + return 0; + } + + return 1; +} + +// Authorize against the opened passwords file. Return 1 if authorized. +static int authorize(struct mg_connection *conn, FILE *fp) { + struct ah ah; + char line[256], f_user[256], ha1[256], f_domain[256], buf[BUFSIZ]; + + if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { + return 0; + } + + // Loop over passwords file + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) { + continue; + } + + if (!strcmp(ah.user, f_user) && + !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain)) + return check_password( + conn->request_info.request_method, + ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop, + ah.response); + } + + return 0; +} + +// Return 1 if request is authorised, 0 otherwise. +static int check_authorization(struct mg_connection *conn, const char *path) { + FILE *fp; + char fname[PATH_MAX]; + struct vec uri_vec, filename_vec; + const char *list; + int authorized; + + fp = NULL; + authorized = 1; + + list = conn->ctx->config[PROTECT_URI]; + while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { + if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { + (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s", + filename_vec.len, filename_vec.ptr); + if ((fp = mg_fopen(fname, "r")) == NULL) { + cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); + } + break; + } + } + + if (fp == NULL) { + fp = open_auth_file(conn, path); + } + + if (fp != NULL) { + authorized = authorize(conn, fp); + (void) fclose(fp); + } + + return authorized; +} + +static void send_authorization_request(struct mg_connection *conn) { + conn->request_info.status_code = 401; + (void) mg_printf(conn, + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Digest qop=\"auth\", " + "realm=\"%s\", nonce=\"%lu\"\r\n\r\n", + conn->ctx->config[AUTHENTICATION_DOMAIN], + (unsigned long) time(NULL)); +} + +static int is_authorized_for_put(struct mg_connection *conn) { + FILE *fp; + int ret = 0; + + fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL : + mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r"); + + if (fp != NULL) { + ret = authorize(conn, fp); + (void) fclose(fp); + } + + return ret; +} + +int mg_modify_passwords_file(const char *fname, const char *domain, + const char *user, const char *pass) { + int found; + char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; + FILE *fp, *fp2; + + found = 0; + fp = fp2 = NULL; + + // Regard empty password as no password - remove user record. + if (pass[0] == '\0') { + pass = NULL; + } + + (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); + + // Create the file if does not exist + if ((fp = mg_fopen(fname, "a+")) != NULL) { + (void) fclose(fp); + } + + // Open the given file and temporary file + if ((fp = mg_fopen(fname, "r")) == NULL) { + return 0; + } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) { + fclose(fp); + return 0; + } + + // Copy the stuff to temporary file + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { + continue; + } + + if (!strcmp(u, user) && !strcmp(d, domain)) { + found++; + if (pass != NULL) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + } else { + (void) fprintf(fp2, "%s", line); + } + } + + // If new user, just add it + if (!found && pass != NULL) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + + // Close files + (void) fclose(fp); + (void) fclose(fp2); + + // Put the temp file in place of real file + (void) mg_remove(fname); + (void) mg_rename(tmp, fname); + + return 1; +} + +struct de { + struct mg_connection *conn; + char *file_name; + struct mgstat st; +}; + +static void url_encode(const char *src, char *dst, size_t dst_len) { + static const char *dont_escape = "._-$,;~()"; + static const char *hex = "0123456789abcdef"; + const char *end = dst + dst_len - 1; + + for (; *src != '\0' && dst < end; src++, dst++) { + if (isalnum(*(const unsigned char *) src) || + strchr(dont_escape, * (const unsigned char *) src) != NULL) { + *dst = *src; + } else if (dst + 2 < end) { + dst[0] = '%'; + dst[1] = hex[(* (const unsigned char *) src) >> 4]; + dst[2] = hex[(* (const unsigned char *) src) & 0xf]; + dst += 2; + } + } + + *dst = '\0'; +} + +static void print_dir_entry(struct de *de) { + char size[64], mod[64], href[PATH_MAX]; + + if (de->st.is_directory) { + (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); + } else { + // We use (signed) cast below because MSVC 6 compiler cannot + // convert unsigned __int64 to double. Sigh. + if (de->st.size < 1024) { + (void) mg_snprintf(de->conn, size, sizeof(size), + "%lu", (unsigned long) de->st.size); + } else if (de->st.size < 1024 * 1024) { + (void) mg_snprintf(de->conn, size, sizeof(size), + "%.1fk", (double) de->st.size / 1024.0); + } else if (de->st.size < 1024 * 1024 * 1024) { + (void) mg_snprintf(de->conn, size, sizeof(size), + "%.1fM", (double) de->st.size / 1048576); + } else { + (void) mg_snprintf(de->conn, size, sizeof(size), + "%.1fG", (double) de->st.size / 1073741824); + } + } + (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime)); + url_encode(de->file_name, href, sizeof(href)); + de->conn->num_bytes_sent += mg_printf(de->conn, + "%s%s" + " %s  %s\n", + de->conn->request_info.uri, href, de->st.is_directory ? "/" : "", + de->file_name, de->st.is_directory ? "/" : "", mod, size); +} + +// This function is called from send_directory() and used for +// sorting directory entries by size, or name, or modification time. +// On windows, __cdecl specification is needed in case if project is built +// with __stdcall convention. qsort always requires __cdels callback. +static int WINCDECL compare_dir_entries(const void *p1, const void *p2) { + const struct de *a = (const struct de *) p1, *b = (const struct de *) p2; + const char *query_string = a->conn->request_info.query_string; + int cmp_result = 0; + + if (query_string == NULL) { + query_string = "na"; + } + + if (a->st.is_directory && !b->st.is_directory) { + return -1; // Always put directories on top + } else if (!a->st.is_directory && b->st.is_directory) { + return 1; // Always put directories on top + } else if (*query_string == 'n') { + cmp_result = strcmp(a->file_name, b->file_name); + } else if (*query_string == 's') { + cmp_result = a->st.size == b->st.size ? 0 : + a->st.size > b->st.size ? 1 : -1; + } else if (*query_string == 'd') { + cmp_result = a->st.mtime == b->st.mtime ? 0 : + a->st.mtime > b->st.mtime ? 1 : -1; + } + + return query_string[1] == 'd' ? -cmp_result : cmp_result; +} + +static void handle_directory_request(struct mg_connection *conn, + const char *dir) { + struct dirent *dp; + DIR *dirp; + struct de *entries = NULL; + char path[PATH_MAX]; + int i, sort_direction, num_entries = 0, arr_size = 128; + + if ((dirp = opendir(dir)) == NULL) { + send_http_error(conn, 500, "Cannot open directory", + "Error: opendir(%s): %s", path, strerror(ERRNO)); + return; + } + + (void) mg_printf(conn, "%s", + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: text/html; charset=utf-8\r\n\r\n"); + + sort_direction = conn->request_info.query_string != NULL && + conn->request_info.query_string[1] == 'd' ? 'a' : 'd'; + + while ((dp = readdir(dirp)) != NULL) { + + // Do not show current dir and passwords file + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..") || + !strcmp(dp->d_name, PASSWORDS_FILE_NAME)) + continue; + + if (entries == NULL || num_entries >= arr_size) { + arr_size *= 2; + entries = (struct de *) realloc(entries, + arr_size * sizeof(entries[0])); + } + + if (entries == NULL) { + closedir(dirp); + send_http_error(conn, 500, "Cannot open directory", + "%s", "Error: cannot allocate memory"); + return; + } + + mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name); + + // If we don't memset stat structure to zero, mtime will have + // garbage and strftime() will segfault later on in + // print_dir_entry(). memset is required only if mg_stat() + // fails. For more details, see + // http://code.google.com/p/mongoose/issues/detail?id=79 + if (mg_stat(path, &entries[num_entries].st) != 0) { + memset(&entries[num_entries].st, 0, sizeof(entries[num_entries].st)); + } + + entries[num_entries].conn = conn; + entries[num_entries].file_name = mg_strdup(dp->d_name); + num_entries++; + } + (void) closedir(dirp); + + conn->num_bytes_sent += mg_printf(conn, + "Index of %s" + "" + "

Index of %s

"
+      ""
+      ""
+      ""
+      "",
+      conn->request_info.uri, conn->request_info.uri,
+      sort_direction, sort_direction, sort_direction);
+
+  // Print first entry - link to a parent directory
+  conn->num_bytes_sent += mg_printf(conn,
+      ""
+      "\n",
+      conn->request_info.uri, "..", "Parent directory", "-", "-");
+
+  // Sort and print directory entries
+  qsort(entries, (size_t)num_entries, sizeof(entries[0]), compare_dir_entries);
+  for (i = 0; i < num_entries; i++) {
+    print_dir_entry(&entries[i]);
+    free(entries[i].file_name);
+  }
+  free(entries);
+
+  conn->num_bytes_sent += mg_printf(conn, "%s", "
NameModifiedSize

%s %s  %s
"); + conn->request_info.status_code = 200; +} + +// Send len bytes from the opened file to the client. +static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) { + char buf[BUFSIZ]; + int to_read, num_read, num_written; + + while (len > 0) { + // Calculate how much to read from the file in the buffer + to_read = sizeof(buf); + if ((int64_t) to_read > len) + to_read = (int) len; + + // Read from file, exit the loop on error + if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0) + break; + + // Send read bytes to the client, exit the loop on error + if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read) + break; + + // Both read and were successful, adjust counters + conn->num_bytes_sent += num_written; + len -= num_written; + } +} + +static int parse_range_header(const char *header, int64_t *a, int64_t *b) { + return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); +} + +static void handle_file_request(struct mg_connection *conn, const char *path, + struct mgstat *stp) { + char date[64], lm[64], etag[64], range[64]; + const char *fmt = "%a, %d %b %Y %H:%M:%S %Z", *msg = "OK", *hdr; + time_t curtime = time(NULL); + int64_t cl, r1, r2; + struct vec mime_vec; + FILE *fp; + int n; + + get_mime_type(conn->ctx, path, &mime_vec); + cl = stp->size; + conn->request_info.status_code = 200; + range[0] = '\0'; + + if ((fp = mg_fopen(path, "rb")) == NULL) { + send_http_error(conn, 500, http_500_error, + "fopen(%s): %s", path, strerror(ERRNO)); + return; + } + set_close_on_exec(fileno(fp)); + + // If Range: header specified, act accordingly + r1 = r2 = 0; + hdr = mg_get_header(conn, "Range"); + if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) { + conn->request_info.status_code = 206; + (void) fseeko(fp, (off_t) r1, SEEK_SET); + cl = n == 2 ? r2 - r1 + 1: cl - r1; + (void) mg_snprintf(conn, range, sizeof(range), + "Content-Range: bytes " + "%" INT64_FMT "-%" + INT64_FMT "/%" INT64_FMT "\r\n", + r1, r1 + cl - 1, stp->size); + msg = "Partial Content"; + } + + // Prepare Etag, Date, Last-Modified headers + (void) strftime(date, sizeof(date), fmt, localtime(&curtime)); + (void) strftime(lm, sizeof(lm), fmt, localtime(&stp->mtime)); + (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx", + (unsigned long) stp->mtime, (unsigned long) stp->size); + + (void) mg_printf(conn, + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "Etag: \"%s\"\r\n" + "Content-Type: %.*s\r\n" + "Content-Length: %" INT64_FMT "\r\n" + "Connection: %s\r\n" + "Accept-Ranges: bytes\r\n" + "%s\r\n", + conn->request_info.status_code, msg, date, lm, etag, + mime_vec.len, mime_vec.ptr, cl, suggest_connection_header(conn), range); + + if (strcmp(conn->request_info.request_method, "HEAD") != 0) { + send_file_data(conn, fp, cl); + } + (void) fclose(fp); +} + +// Parse HTTP headers from the given buffer, advance buffer to the point +// where parsing stopped. +static void parse_http_headers(char **buf, struct mg_request_info *ri) { + int i; + + for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) { + ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0); + ri->http_headers[i].value = skip(buf, "\r\n"); + if (ri->http_headers[i].name[0] == '\0') + break; + ri->num_headers = i + 1; + } +} + +static int is_valid_http_method(const char *method) { + return !strcmp(method, "GET") || !strcmp(method, "POST") || + !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") || + !strcmp(method, "PUT") || !strcmp(method, "DELETE"); +} + +// Parse HTTP request, fill in mg_request_info structure. +static int parse_http_request(char *buf, struct mg_request_info *ri) { + int status = 0; + + // RFC says that all initial whitespaces should be ingored + while (*buf != '\0' && isspace(* (unsigned char *) buf)) { + buf++; + } + + ri->request_method = skip(&buf, " "); + ri->uri = skip(&buf, " "); + ri->http_version = skip(&buf, "\r\n"); + + if (is_valid_http_method(ri->request_method) && + strncmp(ri->http_version, "HTTP/", 5) == 0) { + ri->http_version += 5; /* Skip "HTTP/" */ + parse_http_headers(&buf, ri); + status = 1; + } + + return status; +} + +// Keep reading the input (either opened file descriptor fd, or socket sock, +// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the +// buffer (which marks the end of HTTP request). Buffer buf may already +// have some data. The length of the data is stored in nread. +// Upon every read operation, increase nread by the number of bytes read. +static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, + int *nread) { + int n, request_len; + + request_len = 0; + while (*nread < bufsiz && request_len == 0) { + n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread); + if (n <= 0) { + break; + } else { + *nread += n; + request_len = get_request_len(buf, *nread); + } + } + + return request_len; +} + +// For given directory path, substitute it to valid index file. +// Return 0 if index file has been found, -1 if not found. +// If the file is found, it's stats is returned in stp. +static int substitute_index_file(struct mg_connection *conn, char *path, + size_t path_len, struct mgstat *stp) { + const char *list = conn->ctx->config[INDEX_FILES]; + struct mgstat st; + struct vec filename_vec; + size_t n = strlen(path); + int found = 0; + + // The 'path' given to us points to the directory. Remove all trailing + // directory separator characters from the end of the path, and + // then append single directory separator character. + while (n > 0 && IS_DIRSEP_CHAR(path[n - 1])) { + n--; + } + path[n] = DIRSEP; + + // Traverse index files list. For each entry, append it to the given + // path and see if the file exists. If it exists, break the loop + while ((list = next_option(list, &filename_vec, NULL)) != NULL) { + + // Ignore too long entries that may overflow path buffer + if (filename_vec.len > path_len - n) + continue; + + // Prepare full path to the index file + (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); + + // Does it exist? + if (mg_stat(path, &st) == 0) { + // Yes it does, break the loop + *stp = st; + found = 1; + break; + } + } + + // If no index file exists, restore directory path + if (!found) { + path[n] = '\0'; + } + + return found; +} + +// Return True if we should reply 304 Not Modified. +static int is_not_modified(const struct mg_connection *conn, + const struct mgstat *stp) { + const char *ims = mg_get_header(conn, "If-Modified-Since"); + return ims != NULL && stp->mtime <= parse_date_string(ims); +} + +static int forward_body_data(struct mg_connection *conn, FILE *fp, + SOCKET sock, SSL *ssl) { + const char *expect, *buffered; + char buf[BUFSIZ]; + int to_read, nread, buffered_len, success = 0; + + expect = mg_get_header(conn, "Expect"); + assert(fp != NULL); + + if (conn->content_len == -1) { + send_http_error(conn, 411, "Length Required", ""); + } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) { + send_http_error(conn, 417, "Expectation Failed", ""); + } else { + if (expect != NULL) { + (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); + } + + buffered = conn->buf + conn->request_len; + buffered_len = conn->data_len - conn->request_len; + assert(buffered_len >= 0); + assert(conn->consumed_content == 0); + + if (buffered_len > 0) { + if ((int64_t) buffered_len > conn->content_len) { + buffered_len = (int) conn->content_len; + } + push(fp, sock, ssl, buffered, (int64_t) buffered_len); + conn->consumed_content += buffered_len; + } + + while (conn->consumed_content < conn->content_len) { + to_read = sizeof(buf); + if ((int64_t) to_read > conn->content_len - conn->consumed_content) { + to_read = (int) (conn->content_len - conn->consumed_content); + } + nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read); + if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) { + break; + } + conn->consumed_content += nread; + } + + if (conn->consumed_content == conn->content_len) { + success = 1; + } + + // Each error code path in this function must send an error + if (!success) { + send_http_error(conn, 577, http_500_error, ""); + } + } + + return success; +} + +#if !defined(NO_CGI) +// This structure helps to create an environment for the spawned CGI program. +// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, +// last element must be NULL. +// However, on Windows there is a requirement that all these VARIABLE=VALUE\0 +// strings must reside in a contiguous buffer. The end of the buffer is +// marked by two '\0' characters. +// We satisfy both worlds: we create an envp array (which is vars), all +// entries are actually pointers inside buf. +struct cgi_env_block { + struct mg_connection *conn; + char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer + int len; // Space taken + char *vars[MAX_CGI_ENVIR_VARS]; // char **envp + int nvars; // Number of variables +}; + +// Append VARIABLE=VALUE\0 string to the buffer, and add a respective +// pointer into the vars array. +static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { + int n, space; + char *added; + va_list ap; + + // Calculate how much space is left in the buffer + space = sizeof(block->buf) - block->len - 2; + assert(space >= 0); + + // Make a pointer to the free space int the buffer + added = block->buf + block->len; + + // Copy VARIABLE=VALUE\0 string into the free space + va_start(ap, fmt); + n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap); + va_end(ap); + + // Make sure we do not overflow buffer and the envp array + if (n > 0 && n < space && + block->nvars < (int) ARRAY_SIZE(block->vars) - 2) { + // Append a pointer to the added string into the envp array + block->vars[block->nvars++] = block->buf + block->len; + // Bump up used length counter. Include \0 terminator + block->len += n + 1; + } + + return added; +} + +static void prepare_cgi_environment(struct mg_connection *conn, + const char *prog, + struct cgi_env_block *blk) { + const char *s, *slash; + struct vec var_vec, root; + char *p; + int i; + + blk->len = blk->nvars = 0; + blk->conn = conn; + + get_document_root(conn, &root); + + addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]); + addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr); + addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr); + + // Prepare the environment block + addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP + addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.u.sin.sin_port)); + addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method); + addenv(blk, "REMOTE_ADDR=%s", + inet_ntoa(conn->client.rsa.u.sin.sin_addr)); + addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port); + addenv(blk, "REQUEST_URI=%s", conn->request_info.uri); + + // SCRIPT_NAME + assert(conn->request_info.uri[0] == '/'); + slash = strrchr(conn->request_info.uri, '/'); + if ((s = strrchr(prog, '/')) == NULL) + s = prog; + addenv(blk, "SCRIPT_NAME=%.*s%s", slash - conn->request_info.uri, + conn->request_info.uri, s); + + addenv(blk, "SCRIPT_FILENAME=%s", prog); + addenv(blk, "PATH_TRANSLATED=%s", prog); + addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on"); + + if ((s = mg_get_header(conn, "Content-Type")) != NULL) + addenv(blk, "CONTENT_TYPE=%s", s); + + if (conn->request_info.query_string != NULL) + addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string); + + if ((s = mg_get_header(conn, "Content-Length")) != NULL) + addenv(blk, "CONTENT_LENGTH=%s", s); + + if ((s = getenv("PATH")) != NULL) + addenv(blk, "PATH=%s", s); + +#if defined(_WIN32) + if ((s = getenv("COMSPEC")) != NULL) + addenv(blk, "COMSPEC=%s", s); + if ((s = getenv("SYSTEMROOT")) != NULL) + addenv(blk, "SYSTEMROOT=%s", s); +#else + if ((s = getenv("LD_LIBRARY_PATH")) != NULL) + addenv(blk, "LD_LIBRARY_PATH=%s", s); +#endif /* _WIN32 */ + + if ((s = getenv("PERLLIB")) != NULL) + addenv(blk, "PERLLIB=%s", s); + + if (conn->request_info.remote_user != NULL) { + addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user); + addenv(blk, "%s", "AUTH_TYPE=Digest"); + } + + // Add all headers as HTTP_* variables + for (i = 0; i < conn->request_info.num_headers; i++) { + p = addenv(blk, "HTTP_%s=%s", + conn->request_info.http_headers[i].name, + conn->request_info.http_headers[i].value); + + // Convert variable name into uppercase, and change - to _ + for (; *p != '=' && *p != '\0'; p++) { + if (*p == '-') + *p = '_'; + *p = (char) toupper(* (unsigned char *) p); + } + } + + // Add user-specified variables + s = conn->ctx->config[CGI_ENVIRONMENT]; + while ((s = next_option(s, &var_vec, NULL)) != NULL) { + addenv(blk, "%.*s", var_vec.len, var_vec.ptr); + } + + blk->vars[blk->nvars++] = NULL; + blk->buf[blk->len++] = '\0'; + + assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); + assert(blk->len > 0); + assert(blk->len < (int) sizeof(blk->buf)); +} + +static void handle_cgi_request(struct mg_connection *conn, const char *prog) { + int headers_len, data_len, i, fd_stdin[2], fd_stdout[2]; + const char *status; + char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p; + struct mg_request_info ri; + struct cgi_env_block blk; + FILE *in, *out; + pid_t pid; + + prepare_cgi_environment(conn, prog, &blk); + + // CGI must be executed in its own directory. 'dir' must point to the + // directory containing executable program, 'p' must point to the + // executable program name relative to 'dir'. + (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog); + if ((p = strrchr(dir, DIRSEP)) != NULL) { + *p++ = '\0'; + } else { + dir[0] = '.', dir[1] = '\0'; + p = (char *) prog; + } + + pid = (pid_t) -1; + fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1; + in = out = NULL; + + if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) { + send_http_error(conn, 500, http_500_error, + "Cannot create CGI pipe: %s", strerror(ERRNO)); + goto done; + } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars, + fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) { + goto done; + } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL || + (out = fdopen(fd_stdout[0], "rb")) == NULL) { + send_http_error(conn, 500, http_500_error, + "fopen: %s", strerror(ERRNO)); + goto done; + } + + setbuf(in, NULL); + setbuf(out, NULL); + + // spawn_process() must close those! + // If we don't mark them as closed, close() attempt before + // return from this function throws an exception on Windows. + // Windows does not like when closed descriptor is closed again. + fd_stdin[0] = fd_stdout[1] = -1; + + // Send POST data to the CGI process if needed + if (!strcmp(conn->request_info.request_method, "POST") && + !forward_body_data(conn, in, INVALID_SOCKET, NULL)) { + goto done; + } + + // Now read CGI reply into a buffer. We need to set correct + // status code, thus we need to see all HTTP headers first. + // Do not send anything back to client, until we buffer in all + // HTTP headers. + data_len = 0; + headers_len = read_request(out, INVALID_SOCKET, NULL, + buf, sizeof(buf), &data_len); + if (headers_len <= 0) { + send_http_error(conn, 500, http_500_error, + "CGI program sent malformed HTTP headers: [%.*s]", + data_len, buf); + goto done; + } + pbuf = buf; + buf[headers_len - 1] = '\0'; + parse_http_headers(&pbuf, &ri); + + // Make up and send the status line + status = get_header(&ri, "Status"); + conn->request_info.status_code = status == NULL ? 200 : atoi(status); + (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n", conn->request_info.status_code); + + // Send headers + for (i = 0; i < ri.num_headers; i++) { + mg_printf(conn, "%s: %s\r\n", + ri.http_headers[i].name, ri.http_headers[i].value); + } + (void) mg_write(conn, "\r\n", 2); + + // Send chunk of data that may be read after the headers + conn->num_bytes_sent += mg_write(conn, buf + headers_len, + (size_t)(data_len - headers_len)); + + // Read the rest of CGI output and send to the client + send_file_data(conn, out, INT64_MAX); + +done: + if (pid != (pid_t) -1) { + kill(pid, SIGKILL); +#if !defined(_WIN32) + do {} while (waitpid(-1, &i, WNOHANG) > 0); +#endif + } + if (fd_stdin[0] != -1) { + (void) close(fd_stdin[0]); + } + if (fd_stdout[1] != -1) { + (void) close(fd_stdout[1]); + } + + if (in != NULL) { + (void) fclose(in); + } else if (fd_stdin[1] != -1) { + (void) close(fd_stdin[1]); + } + + if (out != NULL) { + (void) fclose(out); + } else if (fd_stdout[0] != -1) { + (void) close(fd_stdout[0]); + } +} +#endif // !NO_CGI + +// For a given PUT path, create all intermediate subdirectories +// for given path. Return 0 if the path itself is a directory, +// or -1 on error, 1 if OK. +static int put_dir(const char *path) { + char buf[PATH_MAX]; + const char *s, *p; + struct mgstat st; + size_t len; + + for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { + len = p - path; + assert(len < sizeof(buf)); + (void) memcpy(buf, path, len); + buf[len] = '\0'; + + // Try to create intermediate directory + if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) { + return -1; + } + + // Is path itself a directory? + if (p[1] == '\0') { + return 0; + } + } + + return 1; +} + +static void put_file(struct mg_connection *conn, const char *path) { + struct mgstat st; + const char *range; + int64_t r1, r2; + FILE *fp; + int rc; + + conn->request_info.status_code = mg_stat(path, &st) == 0 ? 200 : 201; + + if ((rc = put_dir(path)) == 0) { + mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->request_info.status_code); + } else if (rc == -1) { + send_http_error(conn, 500, http_500_error, + "put_dir(%s): %s", path, strerror(ERRNO)); + } else if ((fp = mg_fopen(path, "wb+")) == NULL) { + send_http_error(conn, 500, http_500_error, + "fopen(%s): %s", path, strerror(ERRNO)); + } else { + set_close_on_exec(fileno(fp)); + range = mg_get_header(conn, "Content-Range"); + r1 = r2 = 0; + if (range != NULL && parse_range_header(range, &r1, &r2) > 0) { + conn->request_info.status_code = 206; + // TODO(lsm): handle seek error + (void) fseeko(fp, (off_t) r1, SEEK_SET); + } + if (forward_body_data(conn, fp, INVALID_SOCKET, NULL)) + (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", + conn->request_info.status_code); + (void) fclose(fp); + } +} + +static void send_ssi_file(struct mg_connection *, const char *, FILE *, int); + +static void do_ssi_include(struct mg_connection *conn, const char *ssi, + char *tag, int include_level) { + char file_name[BUFSIZ], path[PATH_MAX], *p; + struct vec root; + int is_ssi; + FILE *fp; + + get_document_root(conn, &root); + + // sscanf() is safe here, since send_ssi_file() also uses buffer + // of size BUFSIZ to get the tag. So strlen(tag) is always < BUFSIZ. + if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { + // File name is relative to the webserver root + (void) mg_snprintf(conn, path, sizeof(path), "%.*s%c%s", + root.len, root.ptr, DIRSEP, file_name); + } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) { + // File name is relative to the webserver working directory + // or it is absolute system path + (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name); + } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) { + // File name is relative to the currect document + (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi); + if ((p = strrchr(path, DIRSEP)) != NULL) { + p[1] = '\0'; + } + (void) mg_snprintf(conn, path + strlen(path), + sizeof(path) - strlen(path), "%s", file_name); + } else { + cry(conn, "Bad SSI #include: [%s]", tag); + return; + } + + if ((fp = mg_fopen(path, "rb")) == NULL) { + cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s", + tag, path, strerror(ERRNO)); + } else { + set_close_on_exec(fileno(fp)); + is_ssi = match_extension(path, conn->ctx->config[SSI_EXTENSIONS]); + if (is_ssi) { + send_ssi_file(conn, path, fp, include_level + 1); + } else { + send_file_data(conn, fp, INT64_MAX); + } + (void) fclose(fp); + } +} + +#if !defined(NO_POPEN) +static void do_ssi_exec(struct mg_connection *conn, char *tag) { + char cmd[BUFSIZ]; + FILE *fp; + + if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { + cry(conn, "Bad SSI #exec: [%s]", tag); + } else if ((fp = popen(cmd, "r")) == NULL) { + cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO)); + } else { + send_file_data(conn, fp, INT64_MAX); + (void) pclose(fp); + } +} +#endif // !NO_POPEN + +static void send_ssi_file(struct mg_connection *conn, const char *path, + FILE *fp, int include_level) { + char buf[BUFSIZ]; + int ch, len, in_ssi_tag; + + if (include_level > 10) { + cry(conn, "SSI #include level is too deep (%s)", path); + return; + } + + in_ssi_tag = 0; + len = 0; + + while ((ch = fgetc(fp)) != EOF) { + if (in_ssi_tag && ch == '>') { + in_ssi_tag = 0; + buf[len++] = (char) ch; + buf[len] = '\0'; + assert(len <= (int) sizeof(buf)); + if (len < 6 || memcmp(buf, "