ssvm: fix post request header case mismatch (#7445)

Fixes #7442

With d74f64a2e16 key for the host header was changed to lowercase which is causing host be null while processing upload in SSVM

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: João Jandre <48719461+JoaoJandre@users.noreply.github.com>
This commit is contained in:
Abhishek Kumar 2023-04-25 16:00:21 +05:30 committed by GitHub
parent 5885045873
commit e166f96094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 41 deletions

View File

@ -18,16 +18,15 @@ package org.apache.cloudstack.storage.resource;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.handler.codec.DecoderException;
import org.apache.cloudstack.storage.template.UploadEntity;
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.commons.lang3.StringUtils;
@ -41,6 +40,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
@ -79,16 +79,46 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
private boolean requestProcessed = false;
private static final String HEADER_SIGNATURE = "x-signature";
enum UploadHeader {
SIGNATURE("x-signature"),
METADATA("x-metadata"),
EXPIRES("x-expires"),
HOST("x-forwarded-host"),
CONTENT_LENGTH("content-length");
private static final String HEADER_METADATA = "x-metadata";
private static final String HEADER_EXPIRES = "x-expires";
private static final String HEADER_HOST = "x-forwarded-host";
private final String name;
UploadHeader(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public static UploadHeader fromName(String name) {
for (UploadHeader type : values()) {
if (type.getName().equalsIgnoreCase(name)) {
return type;
}
}
return null;
}
}
private static long processTimeout;
protected Map<UploadHeader, String> getUploadRequestUsefulHeaders(HttpHeaders headers) {
Map<UploadHeader, String> headerMap = new HashMap<>();
for (Entry<String, String> entry : headers) {
UploadHeader headerType = UploadHeader.fromName(entry.getKey());
if (headerType != null) {
headerMap.put(headerType, entry.getValue());
}
}
for (UploadHeader type : UploadHeader.values()) {
logger.info(String.format("HEADER: %s=%s", type, headerMap.get(type)));
}
return headerMap;
}
public HttpUploadServerHandler(NfsSecondaryStorageResource storageResource) {
this.storageResource = storageResource;
}
@ -123,36 +153,8 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
URI uri = new URI(request.getUri());
String signature = null;
String expires = null;
String metadata = null;
String hostname = null;
long contentLength = 0;
for (Entry<String, String> entry : request.headers()) {
switch (entry.getKey()) {
case HEADER_SIGNATURE:
signature = entry.getValue();
break;
case HEADER_METADATA:
metadata = entry.getValue();
break;
case HEADER_EXPIRES:
expires = entry.getValue();
break;
case HEADER_HOST:
hostname = entry.getValue();
break;
case HttpHeaders.Names.CONTENT_LENGTH:
contentLength = Long.parseLong(entry.getValue());
break;
}
}
logger.info("HEADER: signature=" + signature);
logger.info("HEADER: metadata=" + metadata);
logger.info("HEADER: expires=" + expires);
logger.info("HEADER: hostname=" + hostname);
logger.info("HEADER: content-length=" + contentLength);
Map<UploadHeader, String> headers = getUploadRequestUsefulHeaders(request.headers());
long contentLength = headers.get(UploadHeader.CONTENT_LENGTH) != null ? Long.parseLong(headers.get(UploadHeader.CONTENT_LENGTH)) : 0;
QueryStringDecoder decoderQuery = new QueryStringDecoder(uri);
Map<String, List<String>> uriAttributes = decoderQuery.parameters();
uuid = uriAttributes.get("uuid").get(0);
@ -160,9 +162,10 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
UploadEntity uploadEntity = null;
try {
// Validate the request here
storageResource.validatePostUploadRequest(signature, metadata, expires, hostname, contentLength, uuid);
storageResource.validatePostUploadRequest(headers.get(UploadHeader.SIGNATURE), headers.get(UploadHeader.METADATA),
headers.get(UploadHeader.EXPIRES), headers.get(UploadHeader.HOST), contentLength, uuid);
//create an upload entity. This will fail if entity already exists.
uploadEntity = storageResource.createUploadEntity(uuid, metadata, contentLength);
uploadEntity = storageResource.createUploadEntity(uuid, headers.get(UploadHeader.METADATA), contentLength);
} catch (InvalidParameterValueException ex) {
logger.error("post request validation failed", ex);
responseContent.append(ex.getMessage());
@ -280,7 +283,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
if (!close) {
// There's no need to add 'content-length' header if this is the last response.
response.headers().set(CONTENT_LENGTH, buf.readableBytes());
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, buf.readableBytes());
}
// Write the response.
ChannelFuture future = channel.writeAndFlush(response);

View File

@ -0,0 +1,74 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.resource;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;
@RunWith(MockitoJUnitRunner.class)
public class HttpUploadServerHandlerTest {
@Spy
@InjectMocks
HttpUploadServerHandler httpUploadServerHandler = new HttpUploadServerHandler(Mockito.mock(NfsSecondaryStorageResource.class));
private void runGetUploadRequestUsefulHeadersTestForHost(String hostHeaderKey, String hostHeaderValue) {
HttpHeaders request = new DefaultHttpHeaders();
request.add(hostHeaderKey, hostHeaderValue);
Map<HttpUploadServerHandler.UploadHeader, String> headers = httpUploadServerHandler.getUploadRequestUsefulHeaders(request);
Assert.assertEquals(hostHeaderValue, headers.get(HttpUploadServerHandler.UploadHeader.HOST));
}
@Test
public void testGetUploadRequestUsefulHeadersHeaderKeyDifferentCase() {
String host = "SomeHost";
runGetUploadRequestUsefulHeadersTestForHost(HttpUploadServerHandler.UploadHeader.HOST.getName(), host);
runGetUploadRequestUsefulHeadersTestForHost(HttpUploadServerHandler.UploadHeader.HOST.getName().toUpperCase(), host);
runGetUploadRequestUsefulHeadersTestForHost("X-Forwarded-Host", host);
}
@Test
public void testGetUploadRequestUsefulHeadersAllKeys() {
HttpHeaders request = new DefaultHttpHeaders();
String sign = "Sign";
String metadata = "met";
String expires = "ex";
String host = "SomeHost";
long contentLength = 100L;
request.add("x-Signature", sign);
request.add("X-metadata", metadata);
request.add("X-EXPIRES", expires);
request.add("X-Forwarded-Host", host);
request.add(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(contentLength));
Map<HttpUploadServerHandler.UploadHeader, String> headers = httpUploadServerHandler.getUploadRequestUsefulHeaders(request);
Assert.assertEquals(sign, headers.get(HttpUploadServerHandler.UploadHeader.SIGNATURE));
Assert.assertEquals(metadata, headers.get(HttpUploadServerHandler.UploadHeader.METADATA));
Assert.assertEquals(expires, headers.get(HttpUploadServerHandler.UploadHeader.EXPIRES));
Assert.assertEquals(host, headers.get(HttpUploadServerHandler.UploadHeader.HOST));
Assert.assertEquals(String.valueOf(contentLength), headers.get(HttpUploadServerHandler.UploadHeader.CONTENT_LENGTH));
}
}