/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package com.trsst.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.Source;
import org.apache.abdera.parser.ParseException;
import org.apache.abdera.parser.Parser;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.impl.AbstractCollectionAdapter;
import org.apache.abdera.protocol.server.multipart.MultipartInputStream;
import org.apache.abdera.protocol.server.multipart.MultipartRelatedCollectionInfo;
import org.apache.abdera.util.Constants;
import org.apache.abdera.util.MimeTypeHelper;
import org.apache.commons.codec.binary.Base64;
@SuppressWarnings("unchecked")
public abstract class AbstractMultipartAdapter extends
AbstractCollectionAdapter implements MultipartRelatedCollectionInfo {
private static final String CONTENT_TYPE_HEADER = "content-type";
private static final String CONTENT_ID_HEADER = "content-id";
private static final String START_PARAM = "start";
private static final String TYPE_PARAM = "type";
private static final String BOUNDARY_PARAM = "boundary";
protected Map<String, String> accepts;
public String[] getAccepts(RequestContext request) {
Collection<String> acceptKeys = getAlternateAccepts(request).keySet();
return acceptKeys.toArray(new String[acceptKeys.size()]);
}
protected List<MultipartRelatedPost> getMultipartRelatedData(
RequestContext request, InputStream requestData)
throws IOException, ParseException, MessagingException {
// NOTE: because request.getInputStream() has already been read
// we pass in a seperate input stream containing the request data
MultipartInputStream stream = getMultipartStream(request, requestData);
List<MultipartRelatedPost> result = new LinkedList<MultipartRelatedPost>();
stream.skipBoundary();
String start = request.getContentType().getParameter(START_PARAM);
Document<Source> source = null;
Map<String, String> entryHeaders = new HashMap<String, String>();
InputStream data = null;
Map<String, String> dataHeaders = new HashMap<String, String>();
Map<String, String> headers = getHeaders(stream);
// first part is required to be the feed or entry
if (start == null
|| start.length() == 0
|| (headers.containsKey(CONTENT_ID_HEADER) && start
.equals(headers.get(CONTENT_ID_HEADER)))
|| (headers.containsKey(CONTENT_TYPE_HEADER) && MimeTypeHelper
.isAtom(headers.get(CONTENT_TYPE_HEADER)))) {
source = getEntry(stream, request);
entryHeaders.putAll(headers);
} else {
throw new ParseException("First part was not a feed or entry: "
+ headers);
// data = getDataInputStream(multipart);
// dataHeaders.putAll(headers);
}
try {
while (stream.available() > 0) {
stream.skipBoundary();
headers = getHeaders(stream);
if (start != null
&& (headers.containsKey(CONTENT_ID_HEADER) && start
.equals(headers.get(CONTENT_ID_HEADER)))
&& (headers.containsKey(CONTENT_TYPE_HEADER) && MimeTypeHelper
.isAtom(headers.get(CONTENT_TYPE_HEADER)))) {
throw new ParseException(
"Should not have found a second feed or entry: "
+ headers);
} else {
data = getDataInputStream(stream);
dataHeaders.putAll(headers);
}
checkMultipartContent(source, dataHeaders, request);
result.add(new MultipartRelatedPost(source, data, entryHeaders,
dataHeaders));
}
} catch (IOException ioe) {
log.error("Unexpected error parsing multipart data", ioe);
}
return result;
}
private MultipartInputStream getMultipartStream(RequestContext request,
InputStream inputStream) throws IOException, ParseException,
IllegalArgumentException {
String boundary = request.getContentType().getParameter(BOUNDARY_PARAM);
if (boundary == null) {
throw new IllegalArgumentException(
"multipart/related stream invalid, boundary parameter is missing.");
}
boundary = "--" + boundary;
String type = request.getContentType().getParameter(TYPE_PARAM);
if (!(type != null && MimeTypeHelper.isAtom(type))) {
throw new ParseException(
"multipart/related stream invalid, type parameter should be "
+ Constants.ATOM_MEDIA_TYPE);
}
PushbackInputStream pushBackInput = new PushbackInputStream(
inputStream, 2);
pushBackInput.unread("\r\n".getBytes());
return new MultipartInputStream(pushBackInput, boundary.getBytes());
}
private void checkMultipartContent(Document<Source> entry,
Map<String, String> dataHeaders, RequestContext request)
throws ParseException {
if (entry == null) {
throw new ParseException(
"multipart/related stream invalid, media link entry is missing");
}
if (!dataHeaders.containsKey(CONTENT_TYPE_HEADER)) {
throw new ParseException(
"multipart/related stream invalid, data content-type is missing");
}
if (!isContentTypeAccepted(dataHeaders.get(CONTENT_TYPE_HEADER),
request)) {
throw new ParseException(
"multipart/related stream invalid, content-type "
+ dataHeaders.get(CONTENT_TYPE_HEADER)
+ " not accepted into this multipart file");
}
}
private Map<String, String> getHeaders(MultipartInputStream multipart)
throws IOException, MessagingException {
Map<String, String> mapHeaders = new HashMap<String, String>();
moveToHeaders(multipart);
InternetHeaders headers = new InternetHeaders(multipart);
Enumeration<Header> allHeaders = headers.getAllHeaders();
if (allHeaders != null) {
while (allHeaders.hasMoreElements()) {
Header header = allHeaders.nextElement();
mapHeaders.put(header.getName().toLowerCase(),
header.getValue());
}
}
return mapHeaders;
}
private boolean moveToHeaders(InputStream stream) throws IOException {
boolean dash = false;
boolean cr = false;
int byteReaded;
while ((byteReaded = stream.read()) != -1) {
switch (byteReaded) {
case '\r':
cr = true;
dash = false;
break;
case '\n':
if (cr == true)
return true;
dash = false;
break;
case '-':
if (dash == true) { // two dashes
stream.close();
return false;
}
dash = true;
cr = false;
break;
default:
dash = false;
cr = false;
}
}
return false;
}
private InputStream getDataInputStream(InputStream stream)
throws IOException {
Base64 base64 = new Base64();
ByteArrayOutputStream bo = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int i;
while ((i = stream.read(buffer)) != -1) {
bo.write(buffer, 0, i);
}
return new ByteArrayInputStream(base64.decode(bo.toByteArray()));
}
private <T extends Element> Document<T> getEntry(InputStream stream,
RequestContext request) throws ParseException, IOException {
Parser parser = request.getAbdera().getParser();
if (parser == null)
throw new IllegalArgumentException(
"No Parser implementation was provided");
Document<?> document = parser.parse(stream, request.getResolvedUri()
.toString(), parser.getDefaultParserOptions());
return (Document<T>) document;
}
private boolean isContentTypeAccepted(String contentType,
RequestContext request) {
if (getAlternateAccepts(request) == null) {
return false;
}
for (Map.Entry<String, String> accept : getAlternateAccepts(request)
.entrySet()) {
if (accept.getKey().equalsIgnoreCase(contentType)
&& accept.getValue() != null
&& accept.getValue().equalsIgnoreCase(
Constants.LN_ALTERNATE_MULTIPART_RELATED)) {
return true;
}
}
return false;
}
protected class MultipartRelatedPost {
private final Document<Source> source;
private final InputStream data;
private final Map<String, String> entryHeaders;
private final Map<String, String> dataHeaders;
public MultipartRelatedPost(Document<Source> base, InputStream data,
Map<String, String> entryHeaders,
Map<String, String> dataHeaders) {
this.source = base;
this.data = data;
this.entryHeaders = entryHeaders;
this.dataHeaders = dataHeaders;
}
public Document<Source> getSource() {
return source;
}
public InputStream getData() {
return data;
}
public Map<String, String> getEntryHeaders() {
return entryHeaders;
}
public Map<String, String> getDataHeaders() {
return dataHeaders;
}
}
private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this
.getClass());
}