Skip to content

Commit

Permalink
Implement containsLast in HttpFields (#10340)
Browse files Browse the repository at this point in the history
Fully implement list iterator so that we can efficiently check for the last item in a multi header list.

---------

Signed-off-by: gregw <gregw@webtide.com>
  • Loading branch information
gregw authored Aug 26, 2023
1 parent 053d44e commit 8ed56b3
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,20 @@ public void beforeDecoding(Response response)
HttpResponse httpResponse = (HttpResponse)response;
httpResponse.headers(headers ->
{
ListIterator<HttpField> iterator = headers.listIterator();
while (iterator.hasNext())
boolean seenContentEncoding = false;
for (ListIterator<HttpField> iterator = headers.listIterator(headers.size()); iterator.hasPrevious();)
{
HttpField field = iterator.next();
HttpField field = iterator.previous();
HttpHeader header = field.getHeader();
if (header == HttpHeader.CONTENT_LENGTH)
{
// Content-Length is not valid anymore while we are decoding.
iterator.remove();
}
else if (header == HttpHeader.CONTENT_ENCODING)
else if (header == HttpHeader.CONTENT_ENCODING && !seenContentEncoding)
{
// Content-Encoding should be removed/modified as the content will be decoded.
// Last Content-Encoding should be removed/modified as the content will be decoded.
seenContentEncoding = true;
String value = field.getValue();
int comma = value.lastIndexOf(",");
if (comma < 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,13 @@ protected void responseHeaders(HttpExchange exchange)
{
// Content-Encoding may have multiple values in the order they
// are applied, but we only support one decoding pass, the last one.
String contentEncoding = responseHeaders.get(HttpHeader.CONTENT_ENCODING);
String contentEncoding = responseHeaders.getLast(HttpHeader.CONTENT_ENCODING);
if (contentEncoding != null)
{
int comma = contentEncoding.indexOf(",");
if (comma > 0)
{
QuotedCSV parser = new QuotedCSV(false);
parser.addValue(contentEncoding);
List<String> values = parser.getValues();
List<String> values = new QuotedCSV(false, contentEncoding).getValues();
contentEncoding = values.get(values.size() - 1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,56 @@ else if (c != '0' && c != '.')
return param != ZERO_QUALITY.length() && match == search.length();
}

/**
* Look for a value as the last value in a possible multivalued field
* Parameters and specifically quality parameters are not considered.
* @param search Values to search for (case-insensitive)
* @return True iff the value is contained in the field value entirely or
* as the last element of a quoted comma separated list.
*/
public boolean containsLast(String search)
{
return containsLast(getValue(), search);
}

/**
* Look for the last value in a possible multivalued field
* Parameters and specifically quality parameters are not considered.
* @param value The field value to search in.
* @param search Values to search for (case-insensitive)
* @return True iff the value is contained in the field value entirely or
* as the last element of a quoted comma separated list.
*/
public static boolean containsLast(String value, String search)
{
if (search == null)
return value == null;
if (search.isEmpty())
return false;
if (value == null)
return false;
if (search.equalsIgnoreCase(value))
return true;

if (value.endsWith(search))
{
int i = value.length() - search.length() - 1;
while (i >= 0)
{
char c = value.charAt(i--);
if (c == ',')
return true;
if (c != ' ')
return false;
}
return true;
}

QuotedCSV csv = new QuotedCSV(false, value);
List<String> values = csv.getValues();
return !values.isEmpty() && search.equalsIgnoreCase(values.get(values.size() - 1));
}

@Override
public int hashCode()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,27 @@ default HttpFields get()
return this;
}

@Override
default Iterator<HttpField> iterator()
{
return listIterator();
}

/**
* @return an iterator over the {@link HttpField}s in this {@code HttpFields}.
* @see #listIterator(int)
*/
default ListIterator<HttpField> listIterator()
{
return listIterator(0);
}

/**
* @return an iterator over the {@link HttpField}s in this {@code HttpFields} starting at the given index.
* @see #listIterator()
*/
ListIterator<HttpField> listIterator(int index);

/**
* <p>Returns an immutable copy of this {@link HttpFields} instance.</p>
*
Expand Down Expand Up @@ -245,6 +266,27 @@ default boolean contains(HttpHeader header, String value)
return false;
}

/**
* Look for a value as the last value in a possible multivalued field.
* Parameters and specifically quality parameters are not considered.
* @param header The {@link HttpHeader} type to search for.
* @param value The value to search for (case-insensitive)
* @return True iff the value is contained in the field value entirely or
* as the last element of a quoted comma separated list.
* @see HttpField#containsLast(String)
*/
default boolean containsLast(HttpHeader header, String value)
{
for (ListIterator<HttpField> i = listIterator(size()); i.hasPrevious();)
{
HttpField f = i.previous();

if (f.getHeader() == header)
return f.containsLast(value);
}
return false;
}

/**
* <p>Returns whether this instance contains the given field name
* with the given value.</p>
Expand Down Expand Up @@ -340,6 +382,28 @@ default String get(HttpHeader header)
return null;
}

/**
* <p>Returns the encoded value of the last field with the given field name,
* or {@code null} if no such header is present.</p>
* <p>In case of multi-valued fields, the returned value is the encoded
* value, including commas and quotes, as returned by {@link HttpField#getValue()}.</p>
*
* @param header the field name to search for
* @return the raw value of the last field with the given field name,
* or {@code null} if no such header is present
* @see HttpField#getValue()
*/
default String getLast(HttpHeader header)
{
for (ListIterator<HttpField> i = listIterator(size()); i.hasPrevious();)
{
HttpField f = i.previous();
if (f.getHeader() == header)
return f.getValue();
}
return null;
}

/**
* <p>Returns the encoded value of the first field with the given field name,
* or {@code null} if no such field is present.</p>
Expand Down Expand Up @@ -904,11 +968,7 @@ default Mutable add(HttpHeader header, long value)
*/
default Mutable add(HttpField field)
{
ListIterator<HttpField> i = listIterator();
while (i.hasNext())
{
i.next();
}
ListIterator<HttpField> i = listIterator(size());
i.add(field);
return this;
}
Expand Down Expand Up @@ -1041,20 +1101,6 @@ default void ensureField(HttpField field)
}
}

/**
* @return an {@link Iterator} over the {@link HttpField}s of this instance
*/
@Override
default Iterator<HttpField> iterator()
{
return listIterator();
}

/**
* @return a {@link ListIterator} over the {@link HttpField}s of this instance
*/
ListIterator<HttpField> listIterator();

/**
* <p>Puts the given {@link HttpField} into this instance.</p>
* <p>If a fields with the same name is present, the given field
Expand Down Expand Up @@ -1111,7 +1157,7 @@ default Mutable put(String name, String value)
* <p>This method behaves like {@link #remove(HttpHeader)} when
* the given {@code value} is {@code null}, otherwise behaves
* like {@link #put(HttpField)}.</p>
*
*
* @param header the name of the field
* @param value the value of the field; if {@code null} the field is removed
* @return this instance
Expand Down Expand Up @@ -1590,9 +1636,9 @@ public Mutable clear()
}

@Override
public ListIterator<HttpField> listIterator()
public ListIterator<HttpField> listIterator(int index)
{
ListIterator<HttpField> i = _fields.listIterator();
ListIterator<HttpField> i = _fields.listIterator(index);
return new ListIterator<>()
{
HttpField last;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
package org.eclipse.jetty.http;

import java.util.Arrays;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Stream;
Expand Down Expand Up @@ -134,24 +134,9 @@ public HttpField getField(int index)
}

@Override
public Iterator<HttpField> iterator()
public ListIterator<HttpField> listIterator(int index)
{
return new Iterator<>()
{
int _index = 0;

@Override
public boolean hasNext()
{
return _index < _size;
}

@Override
public HttpField next()
{
return _fields[_index++];
}
};
return new Listerator(index);
}

@Override
Expand All @@ -171,4 +156,77 @@ public String toString()
{
return asString();
}

private class Listerator implements ListIterator<HttpField>
{
private int _index;
private int _last = -1;

Listerator(int index)
{
if (index < 0 || index > _size)
throw new NoSuchElementException(Integer.toString(index));
_index = index;
}

@Override
public void add(HttpField field)
{
throw new UnsupportedOperationException();
}

@Override
public boolean hasNext()
{
return _index < _size;
}

@Override
public boolean hasPrevious()
{
return _index > 0;
}

@Override
public HttpField next()
{
if (_index >= _size)
throw new NoSuchElementException(Integer.toString(_index));
_last = _index++;
return _fields[_last];
}

@Override
public int nextIndex()
{
return _index + 1;
}

@Override
public HttpField previous()
{
if (_index <= 0)
throw new NoSuchElementException(Integer.toString(_index - 1));
_last = --_index;
return _fields[_last];
}

@Override
public int previousIndex()
{
return _index - 1;
}

@Override
public void remove()
{
throw new UnsupportedOperationException();
}

@Override
public void set(HttpField field)
{
throw new UnsupportedOperationException();
}
}
}
Loading

0 comments on commit 8ed56b3

Please sign in to comment.