Skip to content

Commit

Permalink
Read the filename property from multipart request. Refactoring of fil…
Browse files Browse the repository at this point in the history
…e handling classes.

modified:   src/main/java/org/restheart/db/GridFsDAO.java
modified:   src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java
  • Loading branch information
Maurizio Turatti committed Oct 12, 2015
1 parent ff5d7e8 commit fc22576
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 70 deletions.
56 changes: 28 additions & 28 deletions src/main/java/org/restheart/db/GridFsDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
* @author Andrea Di Cesare <[email protected]>
*/
public class GridFsDAO implements GridFsRepository {

private static final String FILENAME = "filename";
private static final String _ID = "_id";

private final MongoClient client;

public GridFsDAO() {
Expand All @@ -42,37 +46,24 @@ public GridFsDAO() {

@Override
public int createFile(
final Database db,
final String dbName,
final String bucketName,
final Object fileId,
final DBObject properties,
final Database db,
final String dbName,
final String bucketName,
final Object fileId,
final DBObject properties,
final File data) throws IOException, DuplicateKeyException {

final String bucket = extractBucketName(bucketName);
GridFS gridfs = new GridFS(db.getDB(dbName), bucket);
GridFSInputFile gfsFile = gridfs.createFile(data);

// remove from the properties the fields that are managed directly by the GridFs
properties.removeField("_id");
Object _fileName = properties.removeField("filename");
properties.removeField("chunkSize");
properties.removeField("uploadDate");
properties.removeField("length");
properties.removeField("md5");

String fileName;
if (_fileName != null && _fileName instanceof String) {
fileName = (String) _fileName;
} else {
fileName = null;
}
String filename = extractFilenameFromProperties(properties);
gfsFile.setFilename(filename);

// add etag
properties.put("_etag", new ObjectId());

gfsFile.setId(fileId);
gfsFile.setFilename(fileName);

properties.toMap().keySet().stream().forEach(k -> gfsFile.put((String) k, properties.get((String) k)));

Expand All @@ -81,24 +72,33 @@ public int createFile(
return HttpStatus.SC_CREATED;
}

private String extractFilenameFromProperties(final DBObject properties) {
String filename = null;
if (properties != null && properties.containsField(FILENAME)) {
filename = (String) properties.get(FILENAME);
}

return filename;
}

@Override
public int deleteFile(
final Database db,
final String dbName,
final String bucketName,
final Object fileId,
final Database db,
final String dbName,
final String bucketName,
final Object fileId,
final ObjectId requestEtag) {

GridFS gridfs = new GridFS(db.getDB(dbName), extractBucketName(bucketName));
GridFSDBFile dbsfile = gridfs.findOne(new BasicDBObject("_id", fileId));
GridFSDBFile dbsfile = gridfs.findOne(new BasicDBObject(_ID, fileId));

if (dbsfile == null) {
return HttpStatus.SC_NOT_FOUND;
} else {
int code = checkEtag(requestEtag, dbsfile);
if (code == HttpStatus.SC_NO_CONTENT) {
// delete file
gridfs.remove(new BasicDBObject("_id", fileId));
gridfs.remove(new BasicDBObject(_ID, fileId));
}

return code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class BodyInjectorHandler extends PipedHttpHandler {

static final Logger LOGGER = LoggerFactory.getLogger(BodyInjectorHandler.class);

private static final String PROPERTIES = "properties";
private static final String _ID = "_id";
private static final String CONTENT_TYPE = "contentType";
private static final String FILENAME = "filename";

private static final String ERROR_INVALID_CONTENTTYPE = "Content-Type must be either: "
+ Representation.HAL_JSON_MEDIA_TYPE
+ " or " + Representation.JSON_MEDIA_TYPE;
Expand Down Expand Up @@ -77,7 +82,7 @@ public BodyInjectorHandler(PipedHttpHandler next) {
* @throws Exception
*/
@Override
public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception {
public void handleRequest(final HttpServerExchange exchange, final RequestContext context) throws Exception {
if (context.getMethod() == RequestContext.METHOD.GET
|| context.getMethod() == RequestContext.METHOD.OPTIONS
|| context.getMethod() == RequestContext.METHOD.DELETE) {
Expand All @@ -88,9 +93,8 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t
// check the content type
HeaderValues contentTypes = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE);

if ((context.getType() == RequestContext.TYPE.FILE && context.getMethod() == RequestContext.METHOD.PUT)
|| (context.getType() == RequestContext.TYPE.FILES_BUCKET && context.getMethod() == RequestContext.METHOD.POST)) {
if (unsupportedContentTypeFiles(contentTypes)) {
if (isPutFileRequest(context) || isPostFilesbucketRequest(context)) {
if (unsupportedContentTypeForFiles(contentTypes)) {
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, ERROR_INVALID_CONTENTTYPE_FILE);
return;
}
Expand All @@ -99,53 +103,59 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t
return;
}

DBObject content = null;
DBObject content;

if (isNotFormData(contentTypes)) { // json or hal+json
final String contentString = ChannelReader.read(exchange.getRequestChannel());

try {
content = (DBObject) JSON.parse(contentString);
} catch (JSONParseException | IllegalArgumentException ex) {
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, "Invalid data", ex);
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, "Invalid data: No Form data found", ex);
return;
}
} else { // multipart form -> file
FormDataParser parser = this.formParserFactory.createParser(exchange);

if (parser == null) {
String errMsg = "This request is not form encoded";
String errMsg = "There is no form parser registered for the request content type";
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, errMsg);
return;
}

FormData data;
FormData formData;

try {
data = parser.parseBlocking();
formData = parser.parseBlocking();
} catch (IOException ioe) {
String errMsg = "Error parsing the multipart form";
String errMsg = "Error parsing the multipart form: data could not be read";
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, errMsg, ioe);
return;
}

try {
content = findProps(data);
content = extractProperties(formData);
} catch (JSONParseException | IllegalArgumentException ex) {
String errMsg = "Invalid data";
String errMsg = "Invalid data: 'properties' field is not a valid JSON";
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, errMsg, ex);
return;
}

final String fileFieldName = findFile(data);
final String fileField = extractFileField(formData);

if (fileFieldName == null) {
String errMsg = "This request does not contain any file";
if (fileField == null) {
String errMsg = "This request does not contain any binary file";
ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, errMsg);
return;
}

File file = data.getFirst(fileFieldName).getFile();
final File file = formData.getFirst(fileField).getFile();

String filename = extractFilename(formData, fileField, file.getName());

content.put(FILENAME, filename);

LOGGER.info("@@@ content = " + content.toString());

context.setFile(file);

Expand All @@ -157,7 +167,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t
} else {
filterJsonContent(content, context);

Object _id = content.get("_id");
Object _id = content.get(_ID);

if (_id != null) {
try {
Expand All @@ -173,24 +183,42 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t
getNext().handleRequest(exchange, context);
}

private String extractFilename(FormData formData, final String fileField, final String defaultFilename) {
String filename = formData.getFirst(fileField).getFileName();
if (filename == null) {
LOGGER.warn("Filename is not present! Using a default");
filename = defaultFilename;
}
return filename;
}

private static boolean isPostFilesbucketRequest(final RequestContext context) {
return context.getType() == RequestContext.TYPE.FILES_BUCKET && context.getMethod() == RequestContext.METHOD.POST;
}

private static boolean isPutFileRequest(final RequestContext context) {
return context.getType() == RequestContext.TYPE.FILE && context.getMethod() == RequestContext.METHOD.PUT;
}

/**
*
* @param contentTypes
* @return false if the content-type is form data
*/
private static boolean isNotFormData(HeaderValues contentTypes) {
private static boolean isNotFormData(final HeaderValues contentTypes) {
return contentTypes.stream()
.noneMatch(ct -> ct.startsWith(Representation.APP_FORM_URLENCODED_TYPE)
|| ct.startsWith(Representation.MULTIPART_FORM_DATA_TYPE));
}

/**
* Clean-up the JSON content, filtering out reserved keys and injecting the request content in the ctx
* Clean-up the JSON content, filtering out reserved keys and injecting the
* request content in the ctx
*
* @param content
* @param ctx
*/
private void filterJsonContent(DBObject content, RequestContext ctx) {
private static void filterJsonContent(final DBObject content, final RequestContext ctx) {
filterOutReservedKeys(content, ctx);
ctx.setContent(content);
}
Expand All @@ -201,10 +229,10 @@ private void filterJsonContent(DBObject content, RequestContext ctx) {
* @param content
* @param context
*/
private void filterOutReservedKeys(DBObject content, RequestContext context) {
private static void filterOutReservedKeys(final DBObject content, final RequestContext context) {
final HashSet<String> keysToRemove = new HashSet<>();
content.keySet().stream()
.filter(key -> key.startsWith("_") && !key.equals("_id"))
.filter(key -> key.startsWith("_") && !key.equals(_ID))
.forEach(key -> {
keysToRemove.add(key);
});
Expand All @@ -213,15 +241,15 @@ private void filterOutReservedKeys(DBObject content, RequestContext context) {
content.removeField(keyToRemove);
return keyToRemove;
}).forEach(keyToRemove -> {
context.addWarning("the reserved field " + keyToRemove + " was filtered out from the request");
context.addWarning("Reserved field " + keyToRemove + " was filtered out from the request");
});
}

private void injectContentTypeFromFile(final DBObject content, final File file) throws IOException {
if (content.get("contentType") == null && file != null) {
private static void injectContentTypeFromFile(final DBObject content, final File file) throws IOException {
if (content.get(CONTENT_TYPE) == null && file != null) {
final String contentType = detectMediaType(file);
if (contentType != null) {
content.put("contentType", contentType);
content.put(CONTENT_TYPE, contentType);
}
}
}
Expand All @@ -232,7 +260,7 @@ private void injectContentTypeFromFile(final DBObject content, final File file)
* @param contentTypes
* @return
*/
private static boolean unsupportedContentType(HeaderValues contentTypes) {
private static boolean unsupportedContentType(final HeaderValues contentTypes) {
return contentTypes == null
|| contentTypes.isEmpty()
|| contentTypes.stream().noneMatch(
Expand All @@ -246,7 +274,7 @@ private static boolean unsupportedContentType(HeaderValues contentTypes) {
* @param contentTypes
* @return
*/
private static boolean unsupportedContentTypeFiles(HeaderValues contentTypes) {
private static boolean unsupportedContentTypeForFiles(final HeaderValues contentTypes) {
return contentTypes == null
|| contentTypes.isEmpty()
|| contentTypes.stream().noneMatch(
Expand All @@ -258,27 +286,30 @@ private static boolean unsupportedContentTypeFiles(HeaderValues contentTypes) {
* Search request for a field named 'properties' which contains JSON
*
* @param data
* @return the parsed DBObject from the form data or an empty DBObject the etag value)
* @return the parsed DBObject from the form data or an empty DBObject the
* etag value)
*/
private DBObject findProps(final FormData data) throws JSONParseException {
DBObject result = new BasicDBObject();
if (data.getFirst("properties") != null) {
String propsString = data.getFirst("properties").getValue();
if (propsString != null) {
result = (DBObject) JSON.parse(propsString);
}
private static DBObject extractProperties(final FormData data) throws JSONParseException {
DBObject properties = new BasicDBObject();

final String propsString = data.getFirst(PROPERTIES) != null
? data.getFirst(PROPERTIES).getValue()
: null;

if (propsString != null) {
properties = (DBObject) JSON.parse(propsString);
}

return result;
return properties;
}

/**
* Find the name of the first file field in this request
*
* @param data
* @return the file field name or null
* @return the first file field name or null
*/
private String findFile(final FormData data) {
private static String extractFileField(final FormData data) {
String fileField = null;
for (String f : data) {
if (data.getFirst(f) != null && data.getFirst(f).isFile()) {
Expand All @@ -289,8 +320,14 @@ private String findFile(final FormData data) {
return fileField;
}

public static String detectMediaType(File data) throws IOException {
Tika tika = new Tika();
return tika.detect(data);
/**
* Uses Apache Tika to detect the file's media type
*
* @param file
* @return
* @throws IOException
*/
public static String detectMediaType(File file) throws IOException {
return new Tika().detect(file);
}
}

0 comments on commit fc22576

Please sign in to comment.