Skip to content

Commit 8740e9c

Browse files
committed
*Add permanent link to footer.
*Show another CAPTCHA if the supplied one is out of date. This is probably because the user clicked on a link with the CAPTCHA parameters in it. *Make difficulty a parameter of the Java CAPTCHA method.
1 parent 454cd73 commit 8740e9c

File tree

11 files changed

+37
-26
lines changed

11 files changed

+37
-26
lines changed

src/org/wikipedia/servlets/ServletUtils.java

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -173,37 +173,41 @@ public static String generatePagination(String urlbase, int current, int amount,
173173
* Presents a SHA-256 proof of work CAPTCHA. To pass the CAPTCHA, the
174174
* client needs to compute a nonce such that
175175
* sha256(nonce + timestamp + selected concatenated HTTP parameters) begins
176-
* with some quantity of zeros (difficulty is customisable, default 3).
177-
* A CAPTCHA page is shown when the user submits a request. When complete,
178-
* the CAPTCHA page redirects to the expected results. A solved CAPTCHA
179-
* adds URL parameters <code>powans</code> (the solution), <code>powts</code>,
180-
* <code>nonce</code> and <code>powdif</code> (the difficulty). The CAPTCHA
181-
* expires after five minutes.
176+
* with some quantity of zeros (difficulty is customisable, default 3 for
177+
* a runtime of about 0.1 s). A CAPTCHA page is shown when the user submits
178+
* a request. When complete, the CAPTCHA page redirects to the expected
179+
* results. A solved CAPTCHA adds URL parameters <code>powans</code> (the
180+
* solution), <code>powts</code>, <code>nonce</code> and <code>powdif</code>
181+
* (the difficulty). The CAPTCHA expires after five minutes.
182182
*
183183
* @param req a servlet request
184184
* @param response the corresponding response
185185
* @param params the request parameters to concatenate to form the challenge
186186
* string. The challenge string cannot contain new lines.
187187
* @param captcha_string_nonce a nonce, for Content Security Policy purposes,
188188
* to protect an inline script that defines the challenge string only
189+
* @param difficulty the difficulty of the CAPTCHA
189190
* @return whether to continue servlet execution
190191
* @throws IOException if a network error occurs
191192
* @see captcha.js
192193
* @since 0.02
193194
*/
194-
public static boolean showCaptcha(HttpServletRequest req, HttpServletResponse response, List<String> params, String captcha_string_nonce) throws IOException
195+
public static boolean showCaptcha(HttpServletRequest req, HttpServletResponse response, List<String> params, String captcha_string_nonce,
196+
int difficulty) throws IOException
195197
{
196198
// no captcha for the initial input
197199
if (req.getParameterMap().isEmpty())
198200
return true;
199201

200-
// TODO: captcha.js does not propagate POST parameters
202+
// TODO:
203+
// *captcha.js does not propagate POST parameters
204+
// *inject CSP nonce header only when required
201205

202206
PrintWriter out = response.getWriter();
203207
String answer = req.getParameter("powans");
204208
String timestamp = req.getParameter("powts");
205209
String nonce = req.getParameter("nonce");
206-
String difficulty = req.getParameter("powdif");
210+
String reqdifficulty = req.getParameter("powdif");
207211

208212
StringBuilder paramstr = new StringBuilder();
209213
for (String param : params)
@@ -212,7 +216,7 @@ public static boolean showCaptcha(HttpServletRequest req, HttpServletResponse re
212216
String tohash = nonce + timestamp + challenge;
213217

214218
// captcha not attempted, show CAPTCHA screen
215-
if (answer == null && timestamp == null && nonce == null && difficulty == null)
219+
if (answer == null && timestamp == null && nonce == null && reqdifficulty == null)
216220
{
217221
out.println("""
218222
<!doctype html>
@@ -221,6 +225,7 @@ public static boolean showCaptcha(HttpServletRequest req, HttpServletResponse re
221225
<title>CAPTCHA</title>""");
222226
out.println("<script nonce=\"" + captcha_string_nonce + "\">");
223227
out.println(" window.chl = \"" + challenge + "\";");
228+
out.println(" window.difficulty = " + difficulty + ";");
224229
out.println("""
225230
</script>
226231
<script src="captcha.js" defer></script>
@@ -234,19 +239,20 @@ public static boolean showCaptcha(HttpServletRequest req, HttpServletResponse re
234239
return false;
235240
}
236241
// incomplete parameters = fail
237-
else if (answer == null || timestamp == null || nonce == null || difficulty == null)
242+
else if (answer == null || timestamp == null || nonce == null || reqdifficulty == null)
238243
{
239244
response.setStatus(403);
240245
out.println("Incomplete CAPTCHA parameters");
241246
return false;
242247
}
243248

244-
// not recent = fail
249+
// not recent = show another CAPTCHA
245250
OffsetDateTime odt = OffsetDateTime.parse(timestamp);
246251
if (OffsetDateTime.now().minusMinutes(5).isAfter(odt))
247252
{
248-
response.setStatus(403);
249-
out.println("CAPTCHA expired");
253+
response.setStatus(302);
254+
response.setHeader("Location", ServletUtils.getRequestURL(req));
255+
response.getWriter().close();
250256
return false;
251257
}
252258

@@ -255,8 +261,9 @@ else if (answer == null || timestamp == null || nonce == null || difficulty == n
255261
MessageDigest digest = MessageDigest.getInstance("SHA-256");
256262
byte[] hash = digest.digest(tohash.getBytes(StandardCharsets.UTF_8));
257263
String expected = "%064x".formatted(new BigInteger(1, hash));
258-
String prefix = "0".repeat(Integer.parseInt(difficulty));
259-
if (answer.startsWith(prefix) && expected.equals(answer))
264+
int zeroes = Integer.parseInt(reqdifficulty);
265+
String prefix = "0".repeat(zeroes);
266+
if (answer.startsWith(prefix) && expected.equals(answer) && zeroes == difficulty)
260267
return true;
261268

262269
response.setStatus(403);
@@ -282,9 +289,11 @@ else if (answer == null || timestamp == null || nonce == null || difficulty == n
282289
*/
283290
public static String getRequestURL(HttpServletRequest req)
284291
{
292+
Map<String, String[]> params = new LinkedHashMap(req.getParameterMap());
293+
if (params.isEmpty())
294+
return req.getRequestURL().toString();
285295
StringBuilder sb = new StringBuilder(req.getRequestURL());
286296
sb.append("?");
287-
Map<String, String[]> params = new LinkedHashMap(req.getParameterMap());
288297
// CAPTCHA parameters
289298
params.remove("powans");
290299
params.remove("nonce");

src/org/wikipedia/servlets/contributionsurveyor.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<%@ include file="security.jspf" %>
1919
<%@ include file="datevalidate.jspf" %>
2020
<%
21-
if (!ServletUtils.showCaptcha(request, response, List.of("user"), captcha_script_nonce))
21+
if (!ServletUtils.showCaptcha(request, response, List.of("user"), captcha_script_nonce, 3))
2222
throw new SkipPageException();
2323
2424
request.setAttribute("toolname", "Contribution surveyor");

src/org/wikipedia/servlets/extlinkchecker.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-->
99
<%@ include file="security.jspf" %>
1010
<%
11-
if (!ServletUtils.showCaptcha(request, response, List.of("title", "wiki"), captcha_script_nonce))
11+
if (!ServletUtils.showCaptcha(request, response, List.of("title", "wiki"), captcha_script_nonce, 3))
1212
throw new SkipPageException();
1313
request.setAttribute("toolname", "External link checker");
1414

src/org/wikipedia/servlets/footer.jspf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
<br>
3232
<br>
3333
<hr>
34+
<p><a href="<%= ServletUtils.getRequestURL(request) %>">Permanent link</a> to this query.
35+
3436
<p><%= request.getAttribute("toolname") %>: Copyright &copy; MER-C 2007-
3537
<%= OffsetDateTime.now().getYear() %>. This tool is free software:
3638
you can redistribute it and/or modify it under the terms of the

src/org/wikipedia/servlets/imagecci.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<%@ include file="security.jspf" %>
1919
<%@ include file="datevalidate.jspf" %>
2020
<%
21-
if (!ServletUtils.showCaptcha(request, response, List.of("user"), captcha_script_nonce))
21+
if (!ServletUtils.showCaptcha(request, response, List.of("user"), captcha_script_nonce, 3))
2222
throw new SkipPageException();
2323
2424
request.setAttribute("toolname", "Image contribution surveyor");

src/org/wikipedia/servlets/linksearch.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-->
99
<%@ include file="security.jspf" %>
1010
<%
11-
if (!ServletUtils.showCaptcha(request, response, List.of("link"), captcha_script_nonce))
11+
if (!ServletUtils.showCaptcha(request, response, List.of("link"), captcha_script_nonce, 3))
1212
throw new SkipPageException();
1313
request.setAttribute("toolname", "Cross-wiki linksearch");
1414
request.setAttribute("scripts", new String[] { "common.js", "XWikiLinksearch.js" });

src/org/wikipedia/servlets/nppcheck.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<%@ include file="security.jspf" %>
1010
<%@ include file="datevalidate.jspf" %>
1111
<%
12-
if (!ServletUtils.showCaptcha(request, response, List.of("username"), captcha_script_nonce))
12+
if (!ServletUtils.showCaptcha(request, response, List.of("username"), captcha_script_nonce, 3))
1313
throw new SkipPageException();
1414
1515
request.setAttribute("toolname", "NPP/AFC checker");

src/org/wikipedia/servlets/prefixcontribs.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-->
99
<%@ include file="security.jspf" %>
1010
<%
11-
if (!ServletUtils.showCaptcha(request, response, List.of("prefix"), captcha_script_nonce))
11+
if (!ServletUtils.showCaptcha(request, response, List.of("prefix"), captcha_script_nonce, 3))
1212
throw new SkipPageException();
1313
request.setAttribute("toolname", "Prefix contributions");
1414
request.setAttribute("earliest_default", LocalDate.now(ZoneOffset.UTC).minusDays(7));

src/org/wikipedia/servlets/spamarchivesearch.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-->
99
<%@ include file="security.jspf" %>
1010
<%
11-
if (!ServletUtils.showCaptcha(request, response, List.of("query"), captcha_script_nonce))
11+
if (!ServletUtils.showCaptcha(request, response, List.of("query"), captcha_script_nonce, 3))
1212
throw new SkipPageException();
1313
request.setAttribute("toolname", "Spam archive search");
1414
String query = request.getParameter("query");

src/org/wikipedia/servlets/userwatchlist.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-->
99
<%@ include file="security.jspf" %>
1010
<%
11-
if (!ServletUtils.showCaptcha(request, response, List.of("page"), captcha_script_nonce))
11+
if (!ServletUtils.showCaptcha(request, response, List.of("page"), captcha_script_nonce, 3))
1212
throw new SkipPageException();
1313
request.setAttribute("toolname", "User watchlist");
1414
request.setAttribute("earliest_default", LocalDate.now(ZoneOffset.UTC).minusDays(30));

0 commit comments

Comments
 (0)