Skip to content

Commit

Permalink
Merge pull request #12127 from jetty/fix/jetty-12.0.x/12104/Connectio…
Browse files Browse the repository at this point in the history
…nHeader

Fix #12104 Empty Connection Header
  • Loading branch information
joakime authored Aug 2, 2024
2 parents fa143fa + 385d943 commit 22a8685
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,41 @@ public boolean is(String name)
return _name.equalsIgnoreCase(name);
}

/**
* Return a {@link HttpField} without a given value (case-insensitive)
* @param value The value to remove
* @return A new {@link HttpField} if the value was removed, but others remain; this {@link HttpField} if it
* did not contain the value; or {@code null} if the value was the only value.
*/
public HttpField withoutValue(String value)
{
if (_value.length() < value.length())
return this;

if (_value.equalsIgnoreCase(value))
return null;

if (_value.length() == value.length())
return this;

QuotedCSV csv = new QuotedCSV(false, _value);
boolean removed = false;
for (Iterator<String> i = csv.iterator(); i.hasNext();)
{
String element = i.next();
if (element.equalsIgnoreCase(value))
{
removed = true;
i.remove();
}
}

if (!removed)
return this;

return new HttpField(_header, _name, csv.asString());
}

private int nameHashCode()
{
int h = this._hash;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.BufferUtil;
Expand Down Expand Up @@ -627,23 +625,58 @@ else if (contentLength != field.getLongValue())

case CONNECTION:
{
boolean keepAlive = field.contains(HttpHeaderValue.KEEP_ALIVE.asString());
if (keepAlive && _info.getHttpVersion() == HttpVersion.HTTP_1_0 && _persistent == null)
String value = field.getValue();

// Handle simple case of close value only
if (HttpHeaderValue.CLOSE.is(value))
{
_persistent = true;
if (!close)
header.put(CONNECTION_CLOSE);
close = true;
_persistent = false;
}
if (field.contains(HttpHeaderValue.CLOSE.asString()))
// Handle close with other values
else if (field.contains(HttpHeaderValue.CLOSE.asString()))
{
close = true;
_persistent = false;

// Add the field, but without keep-alive
putTo(field.withoutValue(HttpHeaderValue.KEEP_ALIVE.asString()), header);
}
if (keepAlive && _persistent == Boolean.FALSE)
// Handle Keep-Alive value only
else if (HttpHeaderValue.KEEP_ALIVE.is(value))
{
field = new HttpField(HttpHeader.CONNECTION,
Stream.of(field.getValues()).filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s))
.collect(Collectors.joining(", ")));
// If we can persist for HTTP/1.0
if (_persistent != Boolean.FALSE && _info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
// then do so
_persistent = true;
header.put(CONNECTION_KEEP_ALIVE);
}
// otherwise we just ignore the keep-alive
}
// Handle Keep-Alive with other values, but no close
else if (field.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
{
// If we can persist for HTTP/1.0
if (_persistent != Boolean.FALSE && _info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
// then do so
_persistent = true;
putTo(field, header);
}
else
{
// otherwise we add the field, but without keep-alive
putTo(field.withoutValue(HttpHeaderValue.KEEP_ALIVE.asString()), header);
}
}
// Handle connection header without either close nor keep-alive
else
{
putTo(field, header);
}
putTo(field, header);
break;
}

Expand Down Expand Up @@ -799,6 +832,7 @@ public String toString()
private static final byte[] LAST_CHUNK = {(byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n'};
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\r\n");
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\r\n");
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\r\n");
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1 + " ");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\r\n");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ public Iterator<String> iterator()
return _values.iterator();
}

public String asString()
{
if (_values.isEmpty())
return null;
if (_values.size() == 1)
return _values.get(0);

StringBuilder builder = new StringBuilder();
join(builder, _values);
return builder.toString();
}

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -203,4 +206,19 @@ public void testGetValueParameters()
assertThat(map.get("p2"), is("v2"));
assertThat(map.get("p3"), is(" v ; 3=three"));
}

@Test
public void testWithoutValue()
{
HttpField field = new HttpField("name", "value");
assertThat(field.withoutValue("value"), nullValue());
assertThat(field.withoutValue("other"), sameInstance(field));
assertThat(field.withoutValue("val"), sameInstance(field));


field = new HttpField("name", "list, of, values");
assertThat(field.withoutValue("value"), sameInstance(field));
assertThat(field.withoutValue("often"), sameInstance(field));
assertThat(field.withoutValue("of"), equalTo(new HttpField("name", "list, values")));
}
}
Loading

0 comments on commit 22a8685

Please sign in to comment.