Issue #10309 - Duplicate `X-Powered-By` headers (#10310)

* Make HttpChannelState responsible for X-Powered-By / Server headers
* Remove X-Powered-By handling in HttpGenerator
This commit is contained in:
Joakim Erdfelt 2023-08-21 17:00:21 -05:00 committed by GitHub
parent 57d46fe0f7
commit 2178dc2913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 90 deletions

View File

@ -86,33 +86,17 @@ public class HttpGenerator
private boolean _noContentResponse = false;
private Boolean _persistent = null;
private final int _send;
private static final int SEND_SERVER = 0x01;
private static final int SEND_XPOWEREDBY = 0x02;
private static final Index<Boolean> ASSUMED_CONTENT_METHODS = new Index.Builder<Boolean>()
.caseSensitive(false)
.with(HttpMethod.POST.asString(), Boolean.TRUE)
.with(HttpMethod.PUT.asString(), Boolean.TRUE)
.build();
public static void setJettyVersion(String serverVersion)
{
SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\r\n");
SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\r\n");
SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\r\nX-Powered-By: " + serverVersion + "\r\n");
}
// data
private boolean _needCRLF = false;
public HttpGenerator()
{
this(false, false);
}
public HttpGenerator(boolean sendServerVersion, boolean sendXPoweredBy)
{
_send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWEREDBY : 0);
}
public void reset()
@ -595,7 +579,6 @@ public class HttpGenerator
}
// default field values
int send = _send;
HttpField transferEncoding = null;
boolean http11 = _info.getHttpVersion() == HttpVersion.HTTP_1_1;
boolean close = false;
@ -669,13 +652,6 @@ public class HttpGenerator
break;
}
case SERVER:
{
send = send & ~SEND_SERVER;
putTo(field, header);
break;
}
default:
putTo(field, header);
}
@ -792,11 +768,6 @@ public class HttpGenerator
}
}
// Send server?
int status = response != null ? response.getStatus() : -1;
if (status > 199)
header.put(SEND[send]);
// end the header.
header.put(HttpTokens.CRLF);
}
@ -839,9 +810,9 @@ public class HttpGenerator
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\r\n");
private static final byte[][] SEND = new byte[][]{
new byte[0],
StringUtil.getBytes("Server: Jetty(10.x.x)\r\n"),
StringUtil.getBytes("X-Powered-By: Jetty(10.x.x)\r\n"),
StringUtil.getBytes("Server: Jetty(10.x.x)\r\nX-Powered-By: Jetty(10.x.x)\r\n")
StringUtil.getBytes("Server: Jetty(12.x.x)\r\n"),
StringUtil.getBytes("X-Powered-By: Jetty(12.x.x)\r\n"),
StringUtil.getBytes("Server: Jetty(12.x.x)\r\nX-Powered-By: Jetty(12.x.x)\r\n")
};
// Build cache of response lines for status

View File

@ -213,55 +213,6 @@ public class HttpGeneratorServerTest
assertThat(response, containsString("\r\n0123456789"));
}
@Test
public void testSendServerXPoweredBy() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
HttpFields.Mutable fields1 = HttpFields.build();
MetaData.Response info = new MetaData.Response(200, null, HttpVersion.HTTP_1_1, fields1);
HttpFields.Mutable fields2 = HttpFields.build();
fields2.add(HttpHeader.SERVER, "SomeServer");
fields2.add(HttpHeader.X_POWERED_BY, "SomePower");
MetaData.Response infoF = new MetaData.Response(200, null, HttpVersion.HTTP_1_1, fields2);
String head;
HttpGenerator gen = new HttpGenerator(true, true);
gen.generateResponse(info, false, header, null, null, true);
head = BufferUtil.toString(header);
BufferUtil.clear(header);
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, containsString("Server: Jetty(10.x.x)"));
assertThat(head, containsString("X-Powered-By: Jetty(10.x.x)"));
gen.reset();
gen.generateResponse(infoF, false, header, null, null, true);
head = BufferUtil.toString(header);
BufferUtil.clear(header);
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, not(containsString("Server: Jetty(10.x.x)")));
assertThat(head, containsString("Server: SomeServer"));
assertThat(head, containsString("X-Powered-By: Jetty(10.x.x)"));
assertThat(head, containsString("X-Powered-By: SomePower"));
gen.reset();
gen = new HttpGenerator(false, false);
gen.generateResponse(info, false, header, null, null, true);
head = BufferUtil.toString(header);
BufferUtil.clear(header);
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, not(containsString("Server: Jetty(10.x.x)")));
assertThat(head, not(containsString("X-Powered-By: Jetty(10.x.x)")));
gen.reset();
gen.generateResponse(infoF, false, header, null, null, true);
head = BufferUtil.toString(header);
BufferUtil.clear(header);
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, not(containsString("Server: Jetty(10.x.x)")));
assertThat(head, containsString("Server: SomeServer"));
assertThat(head, not(containsString("X-Powered-By: Jetty(10.x.x)")));
assertThat(head, containsString("X-Powered-By: SomePower"));
gen.reset();
}
@Test
public void testResponseIncorrectContentLength() throws Exception
{

View File

@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
@ -527,8 +526,6 @@ public class Server extends Handler.Wrapper implements Attributes
LOG.warn("Download a stable release from https://download.eclipse.org/jetty/");
}
HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
final ExceptionUtil.MultiException multiException = new ExceptionUtil.MultiException();
// Open network connector to ensure ports are available

View File

@ -165,7 +165,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
protected HttpGenerator newHttpGenerator()
{
return new HttpGenerator(_configuration.getSendServerVersion(), _configuration.getSendXPoweredBy());
return new HttpGenerator();
}
protected HttpParser newHttpParser(HttpCompliance compliance)

View File

@ -94,7 +94,6 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
private static final String REQUEST1 = REQUEST1_HEADER + REQUEST1_CONTENT.getBytes().length + "\n\n" + REQUEST1_CONTENT;
private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" +
"Server: Jetty(" + Server.getVersion() + ")\n" +
"Content-Type: text/plain;charset=utf-8\n" +
"Content-Length: 5\n" +
"\n" +
@ -124,7 +123,6 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
protected static final String RESPONSE2 =
"HTTP/1.1 200 OK\n" +
"Server: Jetty(" + Server.getVersion() + ")\n" +
"Content-Type: text/xml; charset=ISO-8859-1\n" +
"Content-Length: " + REQUEST2_CONTENT.getBytes().length + "\n" +
"\n" +
@ -728,7 +726,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
}
/*
/**
* Feed the server fragmentary headers and see how it copes with it.
*/
@Test

View File

@ -63,6 +63,7 @@ public class HttpServerTestFixture
_connector = connector;
_httpConfiguration = _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration();
_httpConfiguration.setSendDateHeader(false);
_httpConfiguration.setSendServerVersion(false);
_server.addConnector(_connector);
}

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.server;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.jetty.http.HttpCookie;
@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ResponseTest
@ -194,6 +196,80 @@ public class ResponseTest
assertThat(response.get(HttpHeader.LOCATION), is("/somewhere/else"));
}
@Test
public void testXPoweredByDefault() throws Exception
{
server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class)
.getHttpConfiguration().setSendXPoweredBy(true);
server.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
Content.Sink.write(response, true, "Test", callback);
return true;
}
});
server.start();
String request = """
GET /test HTTP/1.0\r
Host: hostname\r
\r
""";
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request));
assertEquals(HttpStatus.OK_200, response.getStatus());
// ensure there are only 1 entry for each of these headers
List<HttpHeader> expectedHeaders = List.of(HttpHeader.SERVER, HttpHeader.X_POWERED_BY, HttpHeader.DATE, HttpHeader.CONTENT_LENGTH);
for (HttpHeader expectedHeader: expectedHeaders)
{
List<String> actualHeader = response.getValuesList(expectedHeader);
assertThat(expectedHeader + " exists", actualHeader, is(notNullValue()));
assertThat(expectedHeader + " header count", actualHeader.size(), is(1));
}
assertThat(response.get(HttpHeader.CONTENT_LENGTH), is("4"));
assertThat(response.get(HttpHeader.X_POWERED_BY), is(HttpConfiguration.SERVER_VERSION));
}
@Test
public void testXPoweredByOverride() throws Exception
{
server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class)
.getHttpConfiguration().setSendXPoweredBy(true);
server.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
// replace the X-Powered-By value
response.getHeaders().put(HttpHeader.X_POWERED_BY, "SomeServer");
Content.Sink.write(response, true, "Test", callback);
return true;
}
});
server.start();
String request = """
GET /test HTTP/1.0\r
Host: hostname\r
\r
""";
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request));
assertEquals(HttpStatus.OK_200, response.getStatus());
// ensure there are only 1 entry for each of these headers
List<HttpHeader> expectedHeaders = List.of(HttpHeader.SERVER, HttpHeader.X_POWERED_BY, HttpHeader.DATE, HttpHeader.CONTENT_LENGTH);
for (HttpHeader expectedHeader: expectedHeaders)
{
List<String> actualHeader = response.getValuesList(expectedHeader);
assertThat(expectedHeader + " exists", actualHeader, is(notNullValue()));
assertThat(expectedHeader + " header count", actualHeader.size(), is(1));
}
assertThat(response.get(HttpHeader.CONTENT_LENGTH), is("4"));
assertThat(response.get(HttpHeader.X_POWERED_BY), is("SomeServer"));
}
@Test
public void testHttpCookieProcessing() throws Exception
{

View File

@ -93,13 +93,11 @@ public class SSLEngineTest
* The expected response.
*/
private static final String RESPONSE0 = "HTTP/1.1 200 OK\n" +
"Server: Jetty(" + JETTY_VERSION + ")\n" +
"Content-Length: " + HELLO_WORLD.length() + "\n" +
'\n' +
HELLO_WORLD;
private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" +
"Server: Jetty(" + JETTY_VERSION + ")\n" +
"Content-Length: " + HELLO_WORLD.length() + "\n" +
"Connection: close\n" +
'\n' +
@ -121,6 +119,7 @@ public class SSLEngineTest
HttpConnectionFactory http = new HttpConnectionFactory();
http.setInputBufferSize(512);
http.getHttpConfiguration().setRequestHeaderSize(512);
http.getHttpConfiguration().setSendServerVersion(false);
connector = new ServerConnector(server, sslContextFactory, http);
connector.setPort(0);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);