/*
* Copyright 2009-2014 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*/
package com.eucalyptus.objectstorage.pipeline.handlers;
import java.util.Map;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.objectstorage.ObjectStorage;
import com.eucalyptus.objectstorage.exceptions.s3.InternalErrorException;
import com.eucalyptus.objectstorage.exceptions.s3.MissingContentLengthException;
import com.eucalyptus.objectstorage.util.OSGUtil;
import com.eucalyptus.objectstorage.util.ObjectStorageProperties;
import com.eucalyptus.util.LogUtil;
import com.eucalyptus.ws.handlers.MessageStackHandler;
/**
* Populates the form field map in the message based on the content body Subsequent stages/handlers can use the map exclusively
*/
public class FormPOSTMultipartDecoder extends MessageStackHandler {
private static Logger LOG = Logger.getLogger(FormPOSTMultipartDecoder.class);
private static final int S3_FORM_BUFFER_SIZE = 25 * 1024; // 20KB buffer for S3, give a bit of room extra to be safe here
private HttpThresholdBufferingAggregator aggregator = new HttpThresholdBufferingAggregator(S3_FORM_BUFFER_SIZE);
@Override
public void handleUpstream(final ChannelHandlerContext channelHandlerContext, final ChannelEvent channelEvent) throws Exception {
LOG.trace(LogUtil.dumpObject(channelEvent));
try {
if (channelEvent instanceof MessageEvent) {
final MessageEvent msgEvent = (MessageEvent) channelEvent;
this.incomingMessage(channelHandlerContext, msgEvent);
} else {
channelHandlerContext.sendUpstream(channelEvent);
}
} catch (Exception e) {
LOG.warn("Caught exception in POST form authentication.", e);
Channels.fireExceptionCaught(channelHandlerContext, e);
}
}
@Override
public void incomingMessage(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
MappingHttpRequest upstreamRequest = null;
ChannelBuffer content = null;
MessageEvent upstreamEvent = event;
if (aggregator.offer(event)) {
HttpThresholdBufferingAggregator.AggregatedMessageEvent result = aggregator.poll();
if (result == null) {
return;
} else {
// process the aggregated result
aggregator.hardReset(); // ensure no further polls return this result.
if (result.getMessageEvent() == null || result.getMessageEvent().getMessage() == null) {
throw new InternalErrorException(null, "Unexpected state in message stack");
}
upstreamRequest = (MappingHttpRequest) (result.getMessageEvent().getMessage());
content = result.getAggregatedContentBuffer();
upstreamEvent = result.getMessageEvent();
}
} else {
if (event.getMessage() instanceof MappingHttpRequest) {
upstreamRequest = (MappingHttpRequest) event.getMessage();
content = upstreamRequest.getContent();
}
}
if (upstreamRequest != null && content != null) {
this.handleFormMessage(upstreamRequest, content);
}
ctx.sendUpstream(upstreamEvent);
}
/**
* Parses and populates the form fields of the httpRequest based on the given content buffer. The contentBuffer may just be the httpRequest's
* content, but the caller must set it as such
*
* @param httpRequest
* @param contentBuffer
* @throws Exception
*/
protected void handleFormMessage(MappingHttpRequest httpRequest, ChannelBuffer contentBuffer) throws Exception {
Map formFields = httpRequest.getFormFields();
long contentLength;
try {
contentLength = Long.parseLong(httpRequest.getHeader(HttpHeaders.Names.CONTENT_LENGTH));
} catch (NumberFormatException e) {
throw new MissingContentLengthException(httpRequest.getHeader(HttpHeaders.Names.CONTENT_LENGTH));
}
// Populate the form map with the bucket, content, and field map
formFields.put(ObjectStorageProperties.FormField.bucket.toString(), getBucketName(httpRequest));
// add this as it's needed for filtering the body later in the pipeline.
formFields.putAll(MultipartFormPartParser.parseForm(httpRequest.getHeader(HttpHeaders.Names.CONTENT_TYPE), contentLength, contentBuffer));
}
protected static String getBucketName(MappingHttpRequest httpRequest) {
// Populate the bucket field from the HOST header or uri path
String operationPath = httpRequest.getServicePath().replaceFirst(ComponentIds.lookup(ObjectStorage.class).getServicePath().toLowerCase(), "");
String[] target = OSGUtil.getTarget(operationPath);
String bucket = null;
if (target != null && target.length >= 1) {
bucket = target[0];
} else {
// Look in HOST header
bucket = httpRequest.getHeader(HttpHeaders.Names.HOST).split(".objectstorage", 2)[0];
}
return bucket;
}
}