Skip to content

Commit 6569ddf

Browse files
[JENKINS-76335] Allow redirect on GET and HEAD only (#229)
* [JENKINS-76335] Allow redirect on GET and HEAD only In Apache http client 5, the behavior of the DefaultRedirectStrategy has changed. With httpclient4 a redirect was only done on GET and HEAD requests, using the same method. In httpclient 5 a redirect is done for all methods and all redirect status codes. But a POST request is converted to a GET for 301 and 302 codes. Also a redirect is only done when the url is on the same server:port or when no cookies and authorization headers are included. This change implements a new RedirectStrategy, that limits redirects to GET and HEAD as in client 4 but also keeps the enhanced security of not following redirects then the server/port changes and cookies/authorization is included. * Remove unused import * add test --------- Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
1 parent 3159efa commit 6569ddf

3 files changed

Lines changed: 128 additions & 3 deletions

File tree

src/main/java/jenkins/plugins/http_request/HttpRequestExecution.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import jenkins.plugins.http_request.auth.CertificateAuthentication;
7777
import jenkins.plugins.http_request.auth.CredentialBasicAuthentication;
7878
import jenkins.plugins.http_request.auth.CredentialNtlmAuthentication;
79+
import jenkins.plugins.http_request.util.BackWardCompatibleRedirectStrategy;
7980
import jenkins.plugins.http_request.util.HttpClientUtil;
8081
import jenkins.plugins.http_request.util.HttpRequestFormDataPart;
8182
import jenkins.plugins.http_request.util.HttpRequestNameValuePair;
@@ -310,7 +311,7 @@ private ResponseContentSupplier authAndRequest()
310311
try {
311312
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
312313
clientBuilder.disableAutomaticRetries();
313-
clientBuilder.disableRedirectHandling();
314+
clientBuilder.setRedirectStrategy(new BackWardCompatibleRedirectStrategy());
314315

315316
if (useSystemProperties) {
316317
clientBuilder.useSystemProperties();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package jenkins.plugins.http_request.util;
2+
3+
import org.apache.hc.client5.http.classic.methods.HttpGet;
4+
import org.apache.hc.client5.http.classic.methods.HttpHead;
5+
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
6+
import org.apache.hc.core5.http.Header;
7+
import org.apache.hc.core5.http.HttpHeaders;
8+
import org.apache.hc.core5.http.HttpRequest;
9+
import org.apache.hc.core5.http.HttpResponse;
10+
import org.apache.hc.core5.http.HttpStatus;
11+
import org.apache.hc.core5.http.protocol.HttpContext;
12+
13+
public class BackWardCompatibleRedirectStrategy extends DefaultRedirectStrategy {
14+
15+
private static final String[] REDIRECT_METHODS = new String[]{HttpGet.METHOD_NAME, HttpHead.METHOD_NAME};
16+
17+
@Override
18+
public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) {
19+
if (!response.containsHeader(HttpHeaders.LOCATION)) {
20+
return false;
21+
}
22+
23+
final int statusCode = response.getCode();
24+
final String method = request.getMethod();
25+
final Header locationHeader = response.getFirstHeader("location");
26+
return switch (statusCode) {
27+
case HttpStatus.SC_MOVED_TEMPORARILY ->
28+
isRedirectable(method) && locationHeader != null;
29+
case HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_TEMPORARY_REDIRECT,
30+
HttpStatus.SC_PERMANENT_REDIRECT -> isRedirectable(method);
31+
case HttpStatus.SC_SEE_OTHER -> true;
32+
default -> false;
33+
};
34+
}
35+
36+
protected boolean isRedirectable(final String method) {
37+
for (final String m : REDIRECT_METHODS) {
38+
if (m.equalsIgnoreCase(method)) {
39+
return true;
40+
}
41+
}
42+
return false;
43+
}
44+
}

src/test/java/jenkins/plugins/http_request/HttpRequestTest.java

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static jenkins.plugins.http_request.Registers.registerTimeout;
1818
import static jenkins.plugins.http_request.Registers.registerUnwrappedPutFileUpload;
1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
2021
import static org.junit.jupiter.api.Assertions.assertTrue;
2122

2223
import java.io.File;
@@ -31,16 +32,17 @@
3132

3233
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
3334
import org.apache.hc.client5.http.impl.classic.HttpResponseAdapter;
34-
import org.apache.hc.core5.http.HttpResponse;
3535
import org.apache.hc.core5.http.io.entity.StringEntity;
3636
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
37-
import org.apache.hc.core5.http.message.BasicHttpResponse;
3837
import org.eclipse.jetty.http.HttpCookie;
3938
import org.eclipse.jetty.http.HttpStatus;
4039
import org.eclipse.jetty.server.Request;
4140
import org.eclipse.jetty.server.Response;
4241
import org.eclipse.jetty.util.Callback;
4342
import org.eclipse.jetty.util.Fields;
43+
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
44+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
45+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
4446
import org.junit.jupiter.api.Assumptions;
4547
import org.junit.jupiter.api.Test;
4648
import org.junit.jupiter.api.io.TempDir;
@@ -1089,4 +1091,82 @@ private static File newFolder(File root, String... subDirs) throws IOException {
10891091
return result;
10901092
}
10911093

1094+
private static class RedirectStatus {
1095+
boolean called = false;
1096+
boolean redirected = false;
1097+
};
1098+
1099+
@Test
1100+
void noRedirectOnPOSTOn302() throws Exception {
1101+
final RedirectStatus redirectStatus = new RedirectStatus();
1102+
1103+
registerHandler("/redirected", HttpMode.GET, new SimpleHandler() {
1104+
@Override
1105+
boolean doHandle(Request request, Response response, Callback callback) {
1106+
redirectStatus.called = true;
1107+
return okAllIsWell(response, callback);
1108+
}
1109+
});
1110+
registerHandler("/redirectPOST", HttpMode.POST, new SimpleHandler() {
1111+
@Override
1112+
boolean doHandle(Request request, Response response, Callback callback) {
1113+
redirectStatus.redirected = true;
1114+
Response.sendRedirect(request, response, callback, 302, "/redirected", false);
1115+
return true;
1116+
}
1117+
});
1118+
String body = "send-body-workflow";
1119+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "postBody");
1120+
proj.setDefinition(new CpsFlowDefinition(
1121+
"def response = httpRequest" +
1122+
" httpMode: 'POST'," +
1123+
" requestBody: '" + body + "'," +
1124+
" url: '" + baseURL() + "/redirectPOST'\n" +
1125+
"println('Response: ' + response.content)\n",
1126+
true));
1127+
1128+
WorkflowRun run = proj.scheduleBuild2(0).get();
1129+
1130+
// Check expectations
1131+
j.assertBuildStatusSuccess(run);
1132+
assertTrue(redirectStatus.redirected);
1133+
assertFalse(redirectStatus.called);
1134+
}
1135+
1136+
@Test
1137+
void redirectOnGETOn302() throws Exception {
1138+
final RedirectStatus redirectStatus = new RedirectStatus();
1139+
1140+
registerHandler("/redirected", HttpMode.GET, new SimpleHandler() {
1141+
@Override
1142+
boolean doHandle(Request request, Response response, Callback callback) {
1143+
redirectStatus.called = true;
1144+
return okAllIsWell(response, callback);
1145+
}
1146+
});
1147+
registerHandler("/redirectGET", HttpMode.GET, new SimpleHandler() {
1148+
@Override
1149+
boolean doHandle(Request request, Response response, Callback callback) {
1150+
redirectStatus.redirected = true;
1151+
Response.sendRedirect(request, response, callback, 302, "/redirected", false);
1152+
return true;
1153+
}
1154+
});
1155+
String body = "send-body-workflow";
1156+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "postBody");
1157+
proj.setDefinition(new CpsFlowDefinition(
1158+
"def response = httpRequest" +
1159+
" httpMode: 'GET'," +
1160+
" requestBody: '" + body + "'," +
1161+
" url: '" + baseURL() + "/redirectGET'\n" +
1162+
"println('Response: ' + response.content)\n",
1163+
true));
1164+
1165+
WorkflowRun run = proj.scheduleBuild2(0).get();
1166+
1167+
// Check expectations
1168+
j.assertBuildStatusSuccess(run);
1169+
assertTrue(redirectStatus.redirected);
1170+
assertTrue(redirectStatus.called);
1171+
}
10921172
}

0 commit comments

Comments
 (0)