Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #12429 - case-insensitive headers for websocket #12441

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,24 @@ default int size()
return size;
}

/**
* @param fields the {@link HttpFields} to convert to a {@link Map}.
* @return an unmodifiable {@link Map} representing the contents of the {@link HttpFields}.
*/
static Map<String, List<String>> asMap(HttpFields fields)
{
return new HttpFieldsMap.Immutable(fields);
}

/**
* @param fields the {@link HttpFields} to convert to a {@link Map}.
* @return a {@link Map} where changes to the contents will be reflected in the supplied {@link HttpFields}.
*/
static Map<String, List<String>> asMutableMap(HttpFields.Mutable fields)
{
return new HttpFieldsMap.Mutable(fields);
}

gregw marked this conversation as resolved.
Show resolved Hide resolved
/**
* @return a sequential stream of the {@link HttpField}s in this instance
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.http;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.eclipse.jetty.util.StringUtil;

/**
* <p>A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.</p>
* @see HttpFieldsMap.Mutable
* @see HttpFieldsMap.Immutable
*/
abstract class HttpFieldsMap extends AbstractMap<String, List<String>>
{
/**
* <p>A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.</p>
* <p>Any changes to the {@link java.util.Map} will be reflected in the underlying instance of {@link HttpFields.Mutable}.</p>
*/
public static class Mutable extends HttpFieldsMap
{
private final HttpFields.Mutable httpFields;

public Mutable(HttpFields.Mutable httpFields)
{
this.httpFields = httpFields;
}

@Override
public List<String> get(Object key)
{
if (key instanceof String s)
return httpFields.getValuesList(s);
return null;
}

@Override
public List<String> put(String key, List<String> value)
{
List<String> oldValue = get(key);
httpFields.put(key, value);
return oldValue;
}

@Override
public List<String> remove(Object key)
{
if (key instanceof String s)
{
List<String> oldValue = get(s);
httpFields.remove(s);
return oldValue;
}
return null;
}

@Override
public Set<Entry<String, List<String>>> entrySet()
{
return new AbstractSet<>()
{
@Override
public Iterator<Entry<String, List<String>>> iterator()
{
return new Iterator<>()
{
private final Iterator<String> iterator = httpFields.getFieldNamesCollection().iterator();
private String name = null;

@Override
public boolean hasNext()
{
return iterator.hasNext();
}

@Override
public Entry<String, List<String>> next()
{
name = iterator.next();
return new HttpFieldsEntry(name);
}

@Override
public void remove()
{
if (name != null)
{
Mutable.this.remove(name);
name = null;
}
}
};
}

@Override
public int size()
{
return httpFields.getFieldNamesCollection().size();
}
};
}
}

/**
* <p>A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.</p>
* <p>Any attempt to modify the map will throw {@link UnsupportedOperationException}.</p>
*/
public static class Immutable extends HttpFieldsMap
{
private final HttpFields httpFields;

public Immutable(HttpFields httpFields)
{
this.httpFields = httpFields;
}

@Override
public List<String> get(Object key)
{
if (key instanceof String s)
return httpFields.getValuesList(s);
return null;
}

@Override
public List<String> put(String key, List<String> value)
{
throw new UnsupportedOperationException();
}

@Override
public List<String> remove(Object key)
{
throw new UnsupportedOperationException();
}

@Override
public Set<Entry<String, List<String>>> entrySet()
{
return new AbstractSet<>()
{
@Override
public Iterator<Entry<String, List<String>>> iterator()
{
return new Iterator<>()
{
private final Iterator<String> iterator = httpFields.getFieldNamesCollection().iterator();

@Override
public boolean hasNext()
{
return iterator.hasNext();
}

@Override
public Entry<String, List<String>> next()
{
return new HttpFieldsEntry(iterator.next());
}

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

@Override
public int size()
{
return httpFields.getFieldNamesCollection().size();
}
};
}
}

private class HttpFieldsEntry implements Entry<String, List<String>>
{
private final String _name;

public HttpFieldsEntry(String name)
{
_name = name;
}

@Override
public String getKey()
{
return _name;
}

@Override
public List<String> getValue()
{
return HttpFieldsMap.this.get(_name);
}

@Override
public List<String> setValue(List<String> value)
{
return HttpFieldsMap.this.put(_name, value);
}

@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o instanceof HttpFieldsEntry other)
return StringUtil.asciiEqualsIgnoreCase(_name, other.getKey());
return false;
}

@Override
public int hashCode()
{
return Objects.hash(StringUtil.asciiToLowerCase(_name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
Expand Down Expand Up @@ -59,7 +61,7 @@ public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeRespons
}
catch (Throwable t)
{
callback.failed(t);
Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, t.getMessage());
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

Expand All @@ -68,7 +70,7 @@ public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeRespons

FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, request, response);
if (frameHandler == null)
callback.failed(new IllegalStateException("No WebSocket FrameHandler was created"));
Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, "No WebSocket FrameHandler was created");
return frameHandler;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.stream.Collectors;

import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.EndPoint;
Expand Down Expand Up @@ -78,7 +79,7 @@ public List<String> getHeaders(String name)
@Override
public Map<String, List<String>> getHeaders()
{
return null;
return HttpFields.asMap(delegate.getHeaders());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@

package org.eclipse.jetty.websocket.client.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.websocket.api.ExtensionConfig;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
Expand Down Expand Up @@ -65,9 +65,7 @@ public List<String> getHeaders(String name)
@Override
public Map<String, List<String>> getHeaders()
{
Map<String, List<String>> headers = getHeaderNames().stream()
.collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name))));
return Collections.unmodifiableMap(headers);
return HttpFields.asMap(delegate.getHeaders());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;

import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
Expand Down Expand Up @@ -359,14 +360,12 @@ private org.eclipse.jetty.websocket.core.server.WebSocketCreator newWebSocketCre
{
try
{
Object webSocket = creator.createWebSocket(new ServerUpgradeRequestDelegate(rq), new ServerUpgradeResponseDelegate(rq, rs), cb);
if (webSocket == null)
cb.succeeded();
return webSocket;
return creator.createWebSocket(new ServerUpgradeRequestDelegate(rq), new ServerUpgradeResponseDelegate(rq, rs), cb);
}
catch (Throwable x)
{
cb.failed(x);
LOG.warn("Could not create WebSocket endpoint");
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
Response.writeError(rq, rs, cb, HttpStatus.INTERNAL_SERVER_ERROR_500, "Could not create WebSocket endpoint");
return null;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
Expand Down Expand Up @@ -73,14 +72,7 @@ public int getHeaderInt(String name)
@Override
public Map<String, List<String>> getHeaders()
{
Map<String, List<String>> result = new LinkedHashMap<>();
HttpFields headers = request.getHeaders();
for (HttpField header : headers)
{
String name = header.getName();
result.put(name, headers.getValuesList(name));
}
return result;
return HttpFields.asMap(request.getHeaders());
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand Down
Loading
Loading