diff --git a/webaccess/CMakeLists.txt b/webaccess/CMakeLists.txt index 00ad7e6171..edce970de3 100644 --- a/webaccess/CMakeLists.txt +++ b/webaccess/CMakeLists.txt @@ -2,3 +2,6 @@ project(webaccess) add_subdirectory(src) add_subdirectory(res) +if(NOT ANDROID AND NOT IOS) + add_subdirectory(test) +endif() diff --git a/webaccess/src/CMakeLists.txt b/webaccess/src/CMakeLists.txt index 331837f6e2..8611cf3317 100644 --- a/webaccess/src/CMakeLists.txt +++ b/webaccess/src/CMakeLists.txt @@ -22,6 +22,7 @@ else() endif() set(WEBACCESS_SOURCES + commonjscss.cpp commonjscss.h qhttpserver/http_parser.c qhttpserver/http_parser.h qhttpserver/qhttpconnection.cpp qhttpserver/qhttpconnection.h @@ -33,6 +34,7 @@ set(WEBACCESS_SOURCES webaccessauth.cpp webaccessauth.h webaccessconfiguration.cpp webaccessconfiguration.h webaccesssimpledesk.cpp webaccesssimpledesk.h + webaccessupload.cpp webaccessupload.h ${QM_FILES} ) diff --git a/webaccess/src/commonjscss.cpp b/webaccess/src/commonjscss.cpp new file mode 100644 index 0000000000..5eb871c3ce --- /dev/null +++ b/webaccess/src/commonjscss.cpp @@ -0,0 +1,75 @@ +/* + Q Light Controller Plus + commonjscss.cpp + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "commonjscss.h" + +QString webAccessJsStringEscaped(const QString &text) +{ + QString escaped; + escaped.reserve(text.length() * 2); + + for (const QChar &ch : text) + { + const char16_t code = ch.unicode(); + + switch (code) + { + case '\\': + escaped += "\\\\"; + break; + case '\'': + escaped += "\\'"; + break; + case '"': + escaped += "\\\""; + break; + case '\b': + escaped += "\\b"; + break; + case '\f': + escaped += "\\f"; + break; + case '\n': + escaped += "\\n"; + break; + case '\r': + escaped += "\\r"; + break; + case '\t': + escaped += "\\t"; + break; + case '<': + escaped += "\\x3C"; + break; + default: + if (code < 0x20 || code == 0x2028 || code == 0x2029) + { + escaped += "\\u"; + escaped += QString::number(code, 16).rightJustified(4, '0'); + } + else + { + escaped += ch; + } + break; + } + } + + return escaped; +} diff --git a/webaccess/src/commonjscss.h b/webaccess/src/commonjscss.h index 05d1bc9c55..f643986b70 100644 --- a/webaccess/src/commonjscss.h +++ b/webaccess/src/commonjscss.h @@ -20,6 +20,10 @@ #ifndef COMMONJSCSS_H #define COMMONJSCSS_H +#include + +QString webAccessJsStringEscaped(const QString &text); + #define HTML_HEADER \ "\n" \ "\n" \ diff --git a/webaccess/src/webaccess-qml.cpp b/webaccess/src/webaccess-qml.cpp index 5c5987fb4c..7269d5cf8c 100644 --- a/webaccess/src/webaccess-qml.cpp +++ b/webaccess/src/webaccess-qml.cpp @@ -774,12 +774,6 @@ void WebAccessQml::slotHandleWebSocketRequest(QHttpConnection *conn, QString dat m_sd->setAbsoluteChannelValue(absAddress, uchar(value)); return; } - else if (cmdList[0] == "GM_VALUE") - { - uchar value = cmdList[1].toInt(); - m_doc->inputOutputMap()->setGrandMasterValue(value); - return; - } else if (cmdList[0] == "POLL") return; diff --git a/webaccess/src/webaccess.cpp b/webaccess/src/webaccess.cpp index 776bf15953..bea0b16d47 100644 --- a/webaccess/src/webaccess.cpp +++ b/webaccess/src/webaccess.cpp @@ -352,6 +352,9 @@ void WebAccess::slotHandleWebSocketRequest(QHttpConnection *conn, QString data) quint32 wID = cmdList[2].toUInt(); VCWidget *widget = m_vc->widget(wID); + if (widget == nullptr) + return; + switch(widget->type()) { case VCWidget::AnimationWidget: @@ -449,12 +452,6 @@ void WebAccess::slotHandleWebSocketRequest(QHttpConnection *conn, QString data) return; } - else if (cmdList[0] == "GM_VALUE") - { - uchar value = cmdList[1].toInt(); - m_doc->inputOutputMap()->setGrandMasterValue(value); - return; - } else if (cmdList[0] == "POLL") return; @@ -514,30 +511,36 @@ void WebAccess::slotHandleWebSocketRequest(QHttpConnection *conn, QString data) cue->slotPreviousCue(); else if (cmdList[1] == "NEXT") cue->slotNextCue(); - else if (cmdList[1] == "STEP") + else if (cmdList[1] == "STEP" && cmdList.count() > 2) cue->slotCurrentStepChanged(cmdList[2].toInt()); - else if (cmdList[1] == "CUE_STEP_NOTE") + else if (cmdList[1] == "CUE_STEP_NOTE" && cmdList.count() > 3) cue->slotStepNoteChanged(cmdList[2].toInt(), cmdList[3]); - else if (cmdList[1] == "CUE_SHOWPANEL") + else if (cmdList[1] == "CUE_SHOWPANEL" && cmdList.count() > 2) cue->slotSideFaderButtonChecked(cmdList[2] == "1" ? false : true); - else if (cmdList[1] == "CUE_SIDECHANGE") + else if (cmdList[1] == "CUE_SIDECHANGE" && cmdList.count() > 2) cue->slotSetSideFaderValue((cmdList[2]).toInt()); } break; case VCWidget::FrameWidget: case VCWidget::SoloFrameWidget: { + if (cmdList.count() < 2) + return; + VCFrame *frame = qobject_cast(widget); if (cmdList[1] == "NEXT_PG") frame->slotNextPage(); else if (cmdList[1] == "PREV_PG") frame->slotPreviousPage(); - else if (cmdList[1] == "FRAME_DISABLE") + else if (cmdList[1] == "FRAME_DISABLE" && cmdList.count() > 2) frame->setDisableState(cmdList[2] == "1" ? false : true); } break; case VCWidget::ClockWidget: { + if (cmdList.count() < 2) + return; + VCClock *clock = qobject_cast(widget); if (cmdList[1] == "S") clock->playPauseTimer(); @@ -547,24 +550,27 @@ void WebAccess::slotHandleWebSocketRequest(QHttpConnection *conn, QString data) break; case VCWidget::AnimationWidget: { + if (cmdList.count() < 2) + return; + VCMatrix *matrix = qobject_cast(widget); - if (cmdList[1] == "MATRIX_SLIDER_CHANGE") + if (cmdList[1] == "MATRIX_SLIDER_CHANGE" && cmdList.count() > 2) matrix->slotSetSliderValue(cmdList[2].toInt()); - if (cmdList[1] == "MATRIX_COMBO_CHANGE") + if (cmdList[1] == "MATRIX_COMBO_CHANGE" && cmdList.count() > 2) matrix->slotSetAnimationValue(cmdList[2]); - if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList[2] == "COLOR_1") + if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList.count() > 3 && cmdList[2] == "COLOR_1") matrix->slotColor1Changed(cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList[2] == "COLOR_2") + if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList.count() > 3 && cmdList[2] == "COLOR_2") matrix->slotColor2Changed(cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList[2] == "COLOR_3") + if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList.count() > 3 && cmdList[2] == "COLOR_3") matrix->slotColor3Changed(cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList[2] == "COLOR_4") + if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList.count() > 3 && cmdList[2] == "COLOR_4") matrix->slotColor4Changed(cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList[2] == "COLOR_5") + if (cmdList[1] == "MATRIX_COLOR_CHANGE" && cmdList.count() > 3 && cmdList[2] == "COLOR_5") matrix->slotColor5Changed(cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_KNOB") + if (cmdList[1] == "MATRIX_KNOB" && cmdList.count() > 3) matrix->slotMatrixControlKnobValueChanged(cmdList[2].toInt(), cmdList[3].toInt()); - if (cmdList[1] == "MATRIX_PUSHBUTTON") + if (cmdList[1] == "MATRIX_PUSHBUTTON" && cmdList.count() > 2) matrix->slotMatrixControlPushButtonClicked(cmdList[2].toInt()); } break; @@ -704,7 +710,7 @@ QString WebAccess::getFrameHTML(const VCFrame *frame) for (const VCFramePageShortcut* shortcut : shortcuts) { m_JScode += "framesPageNames[" + QString::number(frame->id()) + "][" + QString::number(index) + "] = \"" + - QString(shortcut->name()).replace("\\", "\\\\").replace("\"", "\\\"") + "\";\n"; + webAccessJsStringEscaped(shortcut->name()) + "\";\n"; index++; } currentPageName = QString(shortcuts[frame->currentPage()]->name()); @@ -728,7 +734,7 @@ QString WebAccess::getFrameHTML(const VCFrame *frame) str += "\n"; m_JScode += "frameCaption[" + QString::number(frame->id()) + "] = \"" + - QString(frame->caption()).replace("\\", "\\\\").replace("\"", "\\\"") + "\";\n"; + webAccessJsStringEscaped(frame->caption()) + "\";\n"; if (frame->isEnableButtonVisible()) { str += "id()) + "\" " + @@ -752,7 +758,8 @@ QString WebAccess::getFrameHTML(const VCFrame *frame) ""; str += "
id()) + "\" style=\"width: " + QString::number(frame->isCollapsed() ? 60 : 100)+"px;\">" + - "
id()) + "Page\">" + currentPageName + "
\n"; + "
id()) + "Page\">" + + currentPageName.toHtmlEscaped() + "
\n"; str += "id()) + "\" href=\"javascript:frameNextPage(" + QString::number(frame->id()) + ");\" style=\"display: " + QString(!frame->isCollapsed() ? "block" : "none") + ";\">" + @@ -813,7 +820,7 @@ QString WebAccess::getSoloFrameHTML(const VCSoloFrame *frame) for (const VCFramePageShortcut* shortcut : shortcuts) { m_JScode += "framesPageNames[" + QString::number(frame->id()) + "][" + QString::number(index) + "] = \"" + - QString(shortcut->name()).replace("\\", "\\\\").replace("\"", "\\\"") + "\";\n"; + webAccessJsStringEscaped(shortcut->name()) + "\";\n"; index++; } currentPageName = QString(shortcuts[frame->currentPage()]->name()); @@ -837,7 +844,7 @@ QString WebAccess::getSoloFrameHTML(const VCSoloFrame *frame) str += "\n"; m_JScode += "frameCaption[" + QString::number(frame->id()) + "] = \"" + - QString(frame->caption()).replace("\\", "\\\\").replace("\"", "\\\"") + "\";\n"; + webAccessJsStringEscaped(frame->caption()) + "\";\n"; if (frame->isEnableButtonVisible()) { str += "id()) + "\" " + @@ -861,7 +868,8 @@ QString WebAccess::getSoloFrameHTML(const VCSoloFrame *frame) ""; str += "
id()) + "\" style=\"width: " + QString::number(frame->isCollapsed() ? 60 : 100) + "px;\">" + - "
id()) + "Page\">" + currentPageName + "
\n"; + "
id()) + "Page\">" + + currentPageName.toHtmlEscaped() + "
\n"; str += "id()) + "\" href=\"javascript:frameNextPage(" + QString::number(frame->id()) + ");\" style=\"display: " + QString(!frame->isCollapsed() ? "block" : "none") + ";\">" + diff --git a/webaccess/src/webaccessbase.cpp b/webaccess/src/webaccessbase.cpp index 61f41a29d1..b14b20625e 100644 --- a/webaccess/src/webaccessbase.cpp +++ b/webaccess/src/webaccessbase.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -31,6 +30,7 @@ #include "webaccessconfiguration.h" #include "webaccesssimpledesk.h" #include "webaccessnetwork.h" +#include "webaccessupload.h" #include "commonjscss.h" #include "qlcconfig.h" #include "qlcfile.h" @@ -49,13 +49,6 @@ namespace { -QString sanitizeUploadedFileName(const QString &rawName) -{ - QString normalizedName = rawName; - normalizedName.replace('\\', '/'); - return QFileInfo(normalizedName).fileName(); -} - bool extractMultipartFilePayload(const QHttpRequest *req, QByteArray &payload, QString *fileName = nullptr) { payload.clear(); @@ -117,7 +110,16 @@ bool extractMultipartFilePayload(const QHttpRequest *req, QByteArray &payload, Q QRegularExpression re("filename=\"([^\"]*)\""); QRegularExpressionMatch match = re.match(QString::fromUtf8(partHeaders)); if (match.hasMatch()) - *fileName = sanitizeUploadedFileName(match.captured(1)); + { + const QString rawName = match.captured(1); + if (!isPlainUploadedFileName(rawName)) + { + qWarning() << Q_FUNC_INFO << "Rejected fixture upload filename" << rawName; + return false; + } + + *fileName = rawName; + } } const int payloadStart = headersEnd + payloadSeparatorSize; @@ -629,6 +631,18 @@ bool WebAccessBase::handleCommonWebSocketCommand(QHttpConnection *conn, const We return true; } + else if (cmdList[0] == "GM_VALUE") + { + if (m_auth && user && user->level < SIMPLE_DESK_AND_VC_LEVEL) + return true; + + if (cmdList.count() < 2) + return true; + + uchar value = cmdList[1].toInt(); + m_doc->inputOutputMap()->setGrandMasterValue(value); + return true; + } else if (cmdList[0] == "QLC+AUTH") { if (!m_auth) @@ -642,6 +656,9 @@ bool WebAccessBase::handleCommonWebSocketCommand(QHttpConnection *conn, const We if (cmdList.at(1) == "ADD_USER") { + if (cmdList.count() < 5) + return true; + QString username = cmdList.at(2); QString password = cmdList.at(3); int level = cmdList.at(4).toInt(); @@ -664,12 +681,18 @@ bool WebAccessBase::handleCommonWebSocketCommand(QHttpConnection *conn, const We } else if (cmdList.at(1) == "DEL_USER") { + if (cmdList.count() < 3) + return true; + QString username = cmdList.at(2); if (!username.isEmpty()) m_auth->deleteUser(username); } else if (cmdList.at(1) == "SET_USER_LEVEL") { + if (cmdList.count() < 4) + return true; + QString username = cmdList.at(2); int level = cmdList.at(3).toInt(); if (username.isEmpty()) @@ -716,7 +739,15 @@ bool WebAccessBase::handleCommonWebSocketCommand(QHttpConnection *conn, const We if (cmdList.at(1) == "NETWORK" && m_netConfig != nullptr) { QString wsMessage; - if (m_netConfig->updateNetworkSettings(cmdList)) + // QLC+SYS|NETWORK|dev|mode|ip|netmask|gateway|ssid|wpapsk + if (cmdList.count() < 9) + { + wsMessage = QString("ALERT|" + tr("Invalid network configuration request.")); + if (conn) + conn->webSocketWrite(wsMessage); + return true; + } + else if (m_netConfig->updateNetworkSettings(cmdList)) wsMessage = QString("ALERT|" + tr("Network configuration changed. Reboot to apply the changes.")); else wsMessage = QString("ALERT|" + tr("An error occurred while updating the network configuration.")); diff --git a/webaccess/src/webaccessconfiguration.cpp b/webaccess/src/webaccessconfiguration.cpp index c37820ec35..4192472674 100644 --- a/webaccess/src/webaccessconfiguration.cpp +++ b/webaccess/src/webaccessconfiguration.cpp @@ -86,7 +86,8 @@ QString WebAccessConfiguration::getIOConfigHTML(const Doc *doc) quint32 currentFeedback = (fp == NULL)?QLCChannel::invalid():fp->output(); QString currentProfileName = (ip == NULL)?KInputNone:ip->profileName(); - html += "" + uniName + "\n"; + html += "" + + uniName.toHtmlEscaped() + "\n"; html += "\n"; html += "\n"; html += "\n"; html += "\n"; html += "\n" @@ -226,14 +234,16 @@ QString WebAccessConfiguration::getPasswordsConfigHTML(const WebAccessAuth *auth foreach (WebAccessUser user, auth->getUsers()) { QString username = user.username; + QString usernameAttr = username.toHtmlEscaped(); + QString usernameJsAttr = webAccessJsStringEscaped(username).toHtmlEscaped(); int level = user.level; - html += ""; - html += "" + username + ""; - html += ""; + html += ""; + html += "" + usernameAttr + ""; + html += ""; html += ""; - html += ""; html += "\n"; + bodyHTML += "\n"; } bodyHTML += "\n"; bodyHTML += "\n"; diff --git a/webaccess/src/webaccessupload.cpp b/webaccess/src/webaccessupload.cpp new file mode 100644 index 0000000000..0b95469b3f --- /dev/null +++ b/webaccess/src/webaccessupload.cpp @@ -0,0 +1,39 @@ +/* + Q Light Controller Plus + webaccessupload.cpp + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include "webaccessupload.h" + +bool isPlainUploadedFileName(const QString &rawName) +{ + if (rawName.isEmpty() || rawName.length() > 255 || + rawName == "." || rawName == "..") + return false; + + if (rawName.contains('/') || rawName.contains('\\')) + return false; + + if (!rawName.endsWith(".qxf", Qt::CaseInsensitive) && + !rawName.endsWith(".d4", Qt::CaseInsensitive)) + return false; + + // Backstop for any path component Qt normalizes internally. + return QFileInfo(rawName).fileName() == rawName; +} diff --git a/webaccess/src/webaccessupload.h b/webaccess/src/webaccessupload.h new file mode 100644 index 0000000000..4ff9f7339c --- /dev/null +++ b/webaccess/src/webaccessupload.h @@ -0,0 +1,27 @@ +/* + Q Light Controller Plus + webaccessupload.h + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef WEBACCESSUPLOAD_H +#define WEBACCESSUPLOAD_H + +#include + +bool isPlainUploadedFileName(const QString &rawName); + +#endif // WEBACCESSUPLOAD_H diff --git a/webaccess/test/CMakeLists.txt b/webaccess/test/CMakeLists.txt new file mode 100644 index 0000000000..4e3c53d1a1 --- /dev/null +++ b/webaccess/test/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable(webaccessescaping_test WIN32 MACOSX_BUNDLE + ../src/commonjscss.cpp + webaccessescaping_test.cpp webaccessescaping_test.h +) +target_include_directories(webaccessescaping_test PRIVATE + ../src +) +target_link_libraries(webaccessescaping_test PRIVATE + Qt${QT_MAJOR_VERSION}::Core + Qt${QT_MAJOR_VERSION}::Test +) + +add_executable(webaccessupload_test WIN32 MACOSX_BUNDLE + ../src/webaccessupload.cpp + webaccessupload_test.cpp webaccessupload_test.h +) +target_include_directories(webaccessupload_test PRIVATE + ../src +) +target_link_libraries(webaccessupload_test PRIVATE + Qt${QT_MAJOR_VERSION}::Core + Qt${QT_MAJOR_VERSION}::Test +) diff --git a/webaccess/test/webaccessescaping_test.cpp b/webaccess/test/webaccessescaping_test.cpp new file mode 100644 index 0000000000..ff126ba2e3 --- /dev/null +++ b/webaccess/test/webaccessescaping_test.cpp @@ -0,0 +1,64 @@ +/* + Q Light Controller Plus + webaccessescaping_test.cpp + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include "commonjscss.h" +#include "webaccessescaping_test.h" + +void WebAccessEscaping_Test::leavesPlainAsciiUnchanged() +{ + QCOMPARE(webAccessJsStringEscaped("QLC+ fixture 123"), + QString("QLC+ fixture 123")); +} + +void WebAccessEscaping_Test::escapesJavaScriptStringDelimiters() +{ + QCOMPARE(webAccessJsStringEscaped("'\"\\"), + QString("\\'\\\"\\\\")); +} + +void WebAccessEscaping_Test::escapesScriptBreakingCharacters() +{ + QString text; + text.append('<'); + text.append('>'); + text.append('&'); + text.append(QChar(0x2028)); + text.append(QChar(0x2029)); + + QCOMPARE(webAccessJsStringEscaped(text), + QString("\\x3C>&\\u2028\\u2029")); +} + +void WebAccessEscaping_Test::escapesControlCharacters() +{ + QString text; + text.append(QChar(0x0000)); + text.append('\b'); + text.append('\f'); + text.append('\n'); + text.append('\r'); + text.append('\t'); + + QCOMPARE(webAccessJsStringEscaped(text), + QString("\\u0000\\b\\f\\n\\r\\t")); +} + +QTEST_MAIN(WebAccessEscaping_Test) diff --git a/webaccess/test/webaccessescaping_test.h b/webaccess/test/webaccessescaping_test.h new file mode 100644 index 0000000000..ed6fc74c6e --- /dev/null +++ b/webaccess/test/webaccessescaping_test.h @@ -0,0 +1,36 @@ +/* + Q Light Controller Plus + webaccessescaping_test.h + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef WEBACCESSESCAPING_TEST_H +#define WEBACCESSESCAPING_TEST_H + +#include + +class WebAccessEscaping_Test final : public QObject +{ + Q_OBJECT + +private slots: + void leavesPlainAsciiUnchanged(); + void escapesJavaScriptStringDelimiters(); + void escapesScriptBreakingCharacters(); + void escapesControlCharacters(); +}; + +#endif // WEBACCESSESCAPING_TEST_H diff --git a/webaccess/test/webaccessupload_test.cpp b/webaccess/test/webaccessupload_test.cpp new file mode 100644 index 0000000000..d6780a9010 --- /dev/null +++ b/webaccess/test/webaccessupload_test.cpp @@ -0,0 +1,59 @@ +/* + Q Light Controller Plus + webaccessupload_test.cpp + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include "webaccessupload.h" +#include "webaccessupload_test.h" + +void WebAccessUpload_Test::acceptsFixtureFileNames() +{ + QVERIFY(isPlainUploadedFileName("fixture.qxf")); + QVERIFY(isPlainUploadedFileName("fixture.D4")); +} + +void WebAccessUpload_Test::rejectsPathComponents() +{ + QVERIFY(isPlainUploadedFileName("../fixture.qxf") == false); + QVERIFY(isPlainUploadedFileName("..\\fixture.qxf") == false); + QVERIFY(isPlainUploadedFileName("/tmp/fixture.qxf") == false); + QVERIFY(isPlainUploadedFileName("dir/fixture.qxf") == false); +} + +void WebAccessUpload_Test::rejectsReservedAndEmptyNames() +{ + QVERIFY(isPlainUploadedFileName("") == false); + QVERIFY(isPlainUploadedFileName(".") == false); + QVERIFY(isPlainUploadedFileName("..") == false); +} + +void WebAccessUpload_Test::rejectsUnexpectedExtensions() +{ + QVERIFY(isPlainUploadedFileName("fixture.php") == false); + QVERIFY(isPlainUploadedFileName("fixture.qxf.php") == false); + QVERIFY(isPlainUploadedFileName("fixture") == false); +} + +void WebAccessUpload_Test::rejectsOverlongNames() +{ + QVERIFY(isPlainUploadedFileName(QString(251, 'a') + ".qxf")); + QVERIFY(isPlainUploadedFileName(QString(252, 'a') + ".qxf") == false); +} + +QTEST_MAIN(WebAccessUpload_Test) diff --git a/webaccess/test/webaccessupload_test.h b/webaccess/test/webaccessupload_test.h new file mode 100644 index 0000000000..885316d4e6 --- /dev/null +++ b/webaccess/test/webaccessupload_test.h @@ -0,0 +1,37 @@ +/* + Q Light Controller Plus + webaccessupload_test.h + + Copyright (c) Q Light Controller Plus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef WEBACCESSUPLOAD_TEST_H +#define WEBACCESSUPLOAD_TEST_H + +#include + +class WebAccessUpload_Test final : public QObject +{ + Q_OBJECT + +private slots: + void acceptsFixtureFileNames(); + void rejectsPathComponents(); + void rejectsReservedAndEmptyNames(); + void rejectsUnexpectedExtensions(); + void rejectsOverlongNames(); +}; + +#endif // WEBACCESSUPLOAD_TEST_H