diff --git a/modules/cert.cpp b/modules/cert.cpp
index 7584068a..d8549e24 100644
--- a/modules/cert.cpp
+++ b/modules/cert.cpp
@@ -79,7 +79,8 @@ class CCertMod : public CModule {
} else if (sPageName == "update") {
CFile fPemFile(PemFile());
- if (fPemFile.Open(O_WRONLY | O_TRUNC | O_CREAT)) {
+ if (WebSock.IsPost() &&
+ fPemFile.Open(O_WRONLY | O_TRUNC | O_CREAT)) {
fPemFile.Write(WebSock.GetParam("cert", true, ""));
fPemFile.Close();
}
@@ -87,7 +88,9 @@ class CCertMod : public CModule {
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "delete") {
- CFile::Delete(PemFile());
+ if (WebSock.IsPost()) {
+ CFile::Delete(PemFile());
+ }
WebSock.Redirect(GetWebPath());
return true;
}
diff --git a/modules/certauth.cpp b/modules/certauth.cpp
index e8756575..6fb3bc75 100644
--- a/modules/certauth.cpp
+++ b/modules/certauth.cpp
@@ -375,7 +375,9 @@ class CSSLClientCertMod : public CModule {
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "delete") {
- DelKey(*pUser, WebSock.GetParam("key", false));
+ if (WebSock.IsPost()) {
+ DelKey(*pUser, WebSock.GetParam("key"));
+ }
WebSock.Redirect(GetWebPath());
return true;
}
diff --git a/modules/data/cert/tmpl/index.tmpl b/modules/data/cert/tmpl/index.tmpl
index d3a12cee..d38527c1 100644
--- a/modules/data/cert/tmpl/index.tmpl
+++ b/modules/data/cert/tmpl/index.tmpl
@@ -2,8 +2,11 @@
INC Header.tmpl ?>
IF Cert ?>
- SETBLOCK DELLINK ?> FORMAT "here" "TRANSLATORS: this text is inserted into `click here` in the other string"?> ENDSETBLOCK ?>
-
FORMAT "You already have a certificate set, use the form below to overwrite the current certificate. Alternatively click {1} to delete your certificate." "DELLINK ESC=" "TRANSLATORS: {1} is `here`, translateable in the other string" ?>
+
ELSE ?>
FORMAT "You do not have a certificate yet." ?>
ENDIF ?>
diff --git a/modules/data/certauth/tmpl/index.tmpl b/modules/data/certauth/tmpl/index.tmpl
index 3b0d69ab..815c2871 100644
--- a/modules/data/certauth/tmpl/index.tmpl
+++ b/modules/data/certauth/tmpl/index.tmpl
@@ -33,7 +33,11 @@
LOOP KeyLoop ?>
- | [ FORMAT "del" ?>] |
+ |
VAR Key ?> |
ENDLOOP ?>
diff --git a/modules/data/notes/tmpl/index.tmpl b/modules/data/notes/tmpl/index.tmpl
index e4087ca3..eea3dd10 100644
--- a/modules/data/notes/tmpl/index.tmpl
+++ b/modules/data/notes/tmpl/index.tmpl
@@ -38,7 +38,11 @@
LOOP NotesLoop ?>
- " /> |
+ |
VAR Key ?> |
VAR Note ?> |
diff --git a/modules/data/webadmin/tmpl/del_chan.tmpl b/modules/data/webadmin/tmpl/del_chan.tmpl
new file mode 100644
index 00000000..a54dd8fb
--- /dev/null
+++ b/modules/data/webadmin/tmpl/del_chan.tmpl
@@ -0,0 +1,27 @@
+ I18N znc-webadmin ?>
+ INC Header.tmpl ?>
+
+
+
FORMAT "Confirm Channel Deletion" ?>
+
+
+
+ FORMAT "Are you sure you want to delete channel “{3}” of network “{2}” of user “{1}”?" Username Network Channel ?>
+
+
+
+
+
+
+
+ INC Footer.tmpl ?>
diff --git a/modules/notes.cpp b/modules/notes.cpp
index de7d02d0..ad6c237d 100644
--- a/modules/notes.cpp
+++ b/modules/notes.cpp
@@ -203,7 +203,9 @@ class CNotesMod : public CModule {
return true;
} else if (sPageName == "delnote") {
- DelNote(WebSock.GetParam("key", false));
+ if (WebSock.IsPost()) {
+ DelNote(WebSock.GetParam("key"));
+ }
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "addnote") {
diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp
index 666db814..162df4fb 100644
--- a/modules/webadmin.cpp
+++ b/modules/webadmin.cpp
@@ -568,7 +568,7 @@ class CWebAdminMod : public CModule {
}
if (pNetwork) {
- return DelChan(WebSock, pNetwork);
+ return DelChan(WebSock, pNetwork, Tmpl);
}
WebSock.PrintErrorPage(t_s("No such user or network"));
@@ -1306,8 +1306,11 @@ class CWebAdminMod : public CModule {
return false;
}
- bool DelChan(CWebSock& WebSock, CIRCNetwork* pNetwork) {
- CString sChan = WebSock.GetParam("name", false);
+ bool DelChan(CWebSock& WebSock, CIRCNetwork* pNetwork, CTemplate& Tmpl) {
+ CString sChan = WebSock.GetParam("name");
+ if (sChan.empty() && !WebSock.IsPost()) {
+ sChan = WebSock.GetParam("name", false);
+ }
if (sChan.empty()) {
WebSock.PrintErrorPage(
@@ -1315,6 +1318,16 @@ class CWebAdminMod : public CModule {
return true;
}
+ if (!WebSock.IsPost()) {
+ // Show the "Are you sure?" page:
+
+ Tmpl.SetFile("del_chan.tmpl");
+ Tmpl["Username"] = pNetwork->GetUser()->GetUsername();
+ Tmpl["Network"] = pNetwork->GetName();
+ Tmpl["Channel"] = sChan;
+ return true;
+ }
+
pNetwork->DelChan(sChan);
pNetwork->PutIRC("PART " + sChan);
diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp
index bc278c3b..2f7a9574 100644
--- a/test/integration/tests/modules.cpp
+++ b/test/integration/tests/modules.cpp
@@ -443,6 +443,62 @@ TEST_F(ZNCTest, ModuleCSRFOverride) {
EXPECT_THAT(reply, HasSubstr("ipsum"));
}
+TEST_F(ZNCTest, WebDeleteNeedsPost) {
+ int port = PickPortNumber();
+ auto znc = Run();
+ auto ircd = ConnectIRCd();
+ auto client = LoginClient();
+ client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8());
+ client.ReadUntil(":Port added");
+ client.Write("znc loadmod notes");
+ client.ReadUntil("Loaded module");
+ client.Write("PRIVMSG *notes :Add secretkey lorem");
+ client.ReadUntil("Added note");
+
+ auto url = [&](const QString& path) {
+ QNetworkRequest request(
+ QUrl(QStringLiteral("http://127.0.0.1:%1%2").arg(port).arg(path)));
+ request.setRawHeader("Authorization",
+ "Basic " + QByteArray("user:hunter2").toBase64());
+ return request;
+ };
+ HttpPost(url("/mods/global/webadmin/addchan"), {
+ {"user", "user"},
+ {"network", "test"},
+ {"submitted", "1"},
+ {"name", "#znc"},
+ {"enabled", "1"},
+ });
+ const QString sEditNetwork =
+ "/mods/global/webadmin/editnetwork?user=user&network=test";
+ const std::string sChanRow = "name=\"channel\" value=\"#znc\"";
+ EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
+ HasSubstr(sChanRow));
+
+ // A plain GET, e.g. from an
on another page, must not delete
+ EXPECT_THAT(HttpGet(url("/mods/global/webadmin/"
+ "delchan?user=user&network=test&name=%23znc"))
+ ->readAll()
+ .toStdString(),
+ HasSubstr("Confirm Channel Deletion"));
+ EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
+ HasSubstr(sChanRow));
+ HttpGet(url("/mods/user/notes/delnote?key=secretkey"));
+ EXPECT_THAT(HttpGet(url("/mods/user/notes/"))->readAll().toStdString(),
+ HasSubstr("secretkey"));
+
+ HttpPost(url("/mods/global/webadmin/delchan"), {
+ {"user", "user"},
+ {"network", "test"},
+ {"name", "#znc"},
+ });
+ EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
+ Not(HasSubstr(sChanRow)));
+ HttpPost(url("/mods/user/notes/delnote"), {{"key", "secretkey"}});
+ EXPECT_THAT(HttpGet(url("/mods/user/notes/"))->readAll().toStdString(),
+ Not(HasSubstr("secretkey")));
+}
+
class SaslModuleTest : public ZNCTest,
public testing::WithParamInterface<
std::pair>> {