docs/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java

824 lines
31 KiB
Java
Raw Normal View History

2013-07-27 18:33:20 +02:00
package com.sismics.docs.rest.resource;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
2013-08-20 21:51:07 +02:00
import com.google.common.io.ByteStreams;
2015-05-09 14:44:19 +02:00
import com.sismics.docs.core.constant.PermType;
2018-03-29 17:59:47 +02:00
import com.sismics.docs.core.dao.AclDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.dao.FileDao;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.dao.dto.DocumentDto;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
2018-03-29 11:34:25 +02:00
import com.sismics.docs.core.event.FileUpdatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
2013-07-27 18:33:20 +02:00
import com.sismics.docs.core.model.jpa.File;
2013-08-20 21:51:07 +02:00
import com.sismics.docs.core.model.jpa.User;
2017-11-23 15:32:20 +01:00
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.EncryptionUtil;
import com.sismics.docs.core.util.FileUtil;
2013-07-27 18:33:20 +02:00
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.RestUtil;
2013-07-27 18:33:20 +02:00
import com.sismics.rest.util.ValidationUtil;
2017-11-23 15:32:20 +01:00
import com.sismics.util.HttpUtil;
2018-02-02 12:37:56 +01:00
import com.sismics.util.JsonUtil;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.mime.MimeType;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
2017-11-23 15:32:20 +01:00
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
2017-11-23 15:32:20 +01:00
import java.io.IOException;
import java.io.InputStream;
2016-11-20 18:41:42 +01:00
import java.net.URISyntaxException;
import java.net.URLDecoder;
2022-03-21 11:36:25 +01:00
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
2013-07-27 18:33:20 +02:00
/**
* File REST resources.
*
* @author bgamard
*/
@Path("/file")
public class FileResource extends BaseResource {
/**
* Add a file (with or without a document).
*
* @api {put} /file Add a file
* @apiDescription A file can be added without associated document, and will go in a temporary storage waiting for one.
* This resource accepts only multipart/form-data.
* @apiName PutFile
* @apiGroup File
* @apiParam {String} id Document ID
2018-11-23 14:54:11 +01:00
* @apiParam {String} previousFileId ID of the file to replace by this new version
* @apiParam {String} file File data
* @apiSuccess {String} status Status OK
* @apiSuccess {String} id File ID
* @apiSuccess {Number} size File size (in bytes)
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) NotFound Document not found
* @apiError (server) StreamError Error reading the input file
* @apiError (server) ErrorGuessMime Error guessing mime type
* @apiError (client) QuotaReached Quota limit reached
* @apiError (server) FileError Error adding a file
* @apiPermission user
* @apiVersion 1.5.0
*
2013-08-13 20:20:28 +02:00
* @param documentId Document ID
2013-07-27 18:33:20 +02:00
* @param fileBodyPart File to add
* @return Response
*/
@PUT
@Consumes("multipart/form-data")
public Response add(
@FormDataParam("id") String documentId,
2018-11-23 14:54:11 +01:00
@FormDataParam("previousFileId") String previousFileId,
2015-09-07 21:51:13 +02:00
@FormDataParam("file") FormDataBodyPart fileBodyPart) {
2013-07-27 18:33:20 +02:00
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
ValidationUtil.validateRequired(fileBodyPart, "file");
// Get the document
2016-03-14 01:39:29 +01:00
DocumentDto documentDto = null;
if (Strings.isNullOrEmpty(documentId)) {
documentId = null;
} else {
DocumentDao documentDao = new DocumentDao();
2016-03-15 22:44:50 +01:00
documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
2016-03-14 01:39:29 +01:00
if (documentDto == null) {
2016-05-06 00:36:54 +02:00
throw new NotFoundException();
}
2013-07-27 18:33:20 +02:00
}
// Keep unencrypted data temporary on disk
2022-05-16 19:22:54 +02:00
String name = fileBodyPart.getContentDisposition() != null ?
URLDecoder.decode(fileBodyPart.getContentDisposition().getFileName(), StandardCharsets.UTF_8) : null;
java.nio.file.Path unencryptedFile;
long fileSize;
2013-08-20 21:51:07 +02:00
try {
2022-05-16 19:22:54 +02:00
unencryptedFile = AppContext.getInstance().getFileService().createTemporaryFile(name);
Files.copy(fileBodyPart.getValueAs(InputStream.class), unencryptedFile, StandardCopyOption.REPLACE_EXISTING);
fileSize = Files.size(unencryptedFile);
2013-08-20 21:51:07 +02:00
} catch (IOException e) {
throw new ServerException("StreamError", "Error reading the input file", e);
}
try {
2018-11-23 14:54:11 +01:00
String fileId = FileUtil.createFile(name, previousFileId, unencryptedFile, fileSize, documentDto == null ?
null : documentDto.getLanguage(), principal.getId(), documentId);
2013-07-27 18:33:20 +02:00
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok")
.add("id", fileId)
.add("size", fileSize);
2015-09-07 21:51:13 +02:00
return Response.ok().entity(response.build()).build();
} catch (IOException e) {
throw new ClientException(e.getMessage(), e.getMessage(), e);
2013-07-27 18:33:20 +02:00
} catch (Exception e) {
throw new ServerException("FileError", "Error adding a file", e);
}
}
/**
* Attach a file to a document.
*
2018-03-23 12:52:42 +01:00
* @api {post} /file/:fileId/attach Attach a file to a document
* @apiName PostFileAttach
* @apiGroup File
* @apiParam {String} fileId File ID
* @apiParam {String} id Document ID
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) IllegalFile File not orphan
* @apiError (server) AttachError Error attaching file to document
* @apiPermission user
* @apiVersion 1.5.0
*
* @param id File ID
* @return Response
*/
@POST
2018-03-23 12:52:42 +01:00
@Path("{id: [a-z0-9\\-]+}/attach")
public Response attach(
@PathParam("id") String id,
2015-09-07 21:51:13 +02:00
@FormParam("id") String documentId) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
2018-03-29 11:34:25 +02:00
ValidationUtil.validateRequired(documentId, "documentId");
// Get the current user
UserDao userDao = new UserDao();
User user = userDao.getById(principal.getId());
// Get the document and the file
DocumentDao documentDao = new DocumentDao();
FileDao fileDao = new FileDao();
2015-11-16 02:22:51 +01:00
File file = fileDao.getFile(id, principal.getId());
2016-03-15 22:44:50 +01:00
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
2016-03-14 01:39:29 +01:00
if (file == null || documentDto == null) {
2016-05-06 00:36:54 +02:00
throw new NotFoundException();
}
// Check that the file is orphan
if (file.getDocumentId() != null) {
throw new ClientException("IllegalFile", MessageFormat.format("File not orphan: {0}", id));
}
// Update the file
file.setDocumentId(documentId);
file.setOrder(fileDao.getByDocumentId(principal.getId(), documentId).size());
2015-03-06 21:13:09 +01:00
fileDao.update(file);
2018-03-29 11:34:25 +02:00
// Raise a new file updated event and document updated event (it wasn't sent during file creation)
try {
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
java.nio.file.Path unencryptedFile = EncryptionUtil.decryptFile(storedFile, user.getPrivateKey());
2018-03-13 23:32:05 +01:00
FileUtil.startProcessingFile(id);
2018-03-29 11:34:25 +02:00
FileUpdatedAsyncEvent fileUpdatedAsyncEvent = new FileUpdatedAsyncEvent();
fileUpdatedAsyncEvent.setUserId(principal.getId());
fileUpdatedAsyncEvent.setLanguage(documentDto.getLanguage());
fileUpdatedAsyncEvent.setFileId(file.getId());
2018-03-29 11:34:25 +02:00
fileUpdatedAsyncEvent.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(fileUpdatedAsyncEvent);
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
2016-03-14 01:39:29 +01:00
documentUpdatedAsyncEvent.setDocumentId(documentId);
ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent);
} catch (Exception e) {
throw new ServerException("AttachError", "Error attaching file to document", e);
}
2018-03-29 11:34:25 +02:00
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
2018-03-23 12:52:42 +01:00
/**
* Update a file.
*
* @api {post} /file/:id Update a file
* @apiName PostFile
* @apiGroup File
* @apiParam {String} id File ID
* @apiParam {String} name Name
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiPermission user
* @apiVersion 1.6.0
*
* @param id File ID
* @return Response
*/
@POST
@Path("{id: [a-z0-9\\-]+}")
public Response update(@PathParam("id") String id,
@FormParam("name") String name) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the file
File file = findFile(id, null);
// Validate input data
name = ValidationUtil.validateLength(name, "name", 1, 200, false);
// Update the file
FileDao fileDao = new FileDao();
file.setName(name);
fileDao.update(file);
// Always return OK
2018-03-29 11:34:25 +02:00
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Process a file manually.
*
* @api {post} /file/:id/process Process a file manually
* @apiName PostFileProcess
* @apiGroup File
* @apiParam {String} id File ID
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (server) ProcessingError Processing error
* @apiPermission user
* @apiVersion 1.6.0
*
* @param id File ID
* @return Response
*/
@POST
@Path("{id: [a-z0-9\\-]+}/process")
public Response process(@PathParam("id") String id) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the document and the file
DocumentDao documentDao = new DocumentDao();
FileDao fileDao = new FileDao();
File file = fileDao.getFile(id);
2018-11-28 13:09:20 +01:00
if (file == null || file.getDocumentId() == null) {
2018-03-29 11:34:25 +02:00
throw new NotFoundException();
}
DocumentDto documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null));
if (documentDto == null) {
throw new NotFoundException();
}
// Get the creating user
UserDao userDao = new UserDao();
User user = userDao.getById(file.getUserId());
2018-03-29 11:34:25 +02:00
// Start the processing asynchronously
try {
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
java.nio.file.Path unencryptedFile = EncryptionUtil.decryptFile(storedFile, user.getPrivateKey());
FileUtil.startProcessingFile(id);
FileUpdatedAsyncEvent event = new FileUpdatedAsyncEvent();
event.setUserId(principal.getId());
event.setLanguage(documentDto.getLanguage());
event.setFileId(file.getId());
event.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(event);
2018-03-29 11:34:25 +02:00
} catch (Exception e) {
throw new ServerException("ProcessingError", "Error processing this file", e);
}
// Always return OK
2018-03-23 12:52:42 +01:00
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
2013-08-03 20:19:02 +02:00
/**
* Reorder files.
*
* @api {post} /file/:reorder Reorder files
* @apiName PostFileReorder
* @apiGroup File
* @apiParam {String} id Document ID
* @apiParam {String[]} order List of files ID
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) NotFound Document not found
* @apiPermission user
* @apiVersion 1.5.0
*
2013-08-13 20:20:28 +02:00
* @param documentId Document ID
* @param idList List of files ID in the new order
2013-08-03 20:19:02 +02:00
* @return Response
*/
@POST
@Path("reorder")
public Response reorder(
@FormParam("id") String documentId,
2015-09-07 21:51:13 +02:00
@FormParam("order") List<String> idList) {
2013-08-03 20:19:02 +02:00
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
ValidationUtil.validateRequired(documentId, "id");
ValidationUtil.validateRequired(idList, "order");
2013-08-03 21:51:15 +02:00
// Get the document
2016-05-08 12:14:06 +02:00
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.WRITE, getTargetIdList(null))) {
2016-05-06 00:36:54 +02:00
throw new NotFoundException();
2013-08-03 21:51:15 +02:00
}
2013-08-03 20:19:02 +02:00
// Reorder files
FileDao fileDao = new FileDao();
for (File file : fileDao.getByDocumentId(principal.getId(), documentId)) {
2013-08-03 20:19:02 +02:00
int order = idList.lastIndexOf(file.getId());
if (order != -1) {
file.setOrder(order);
}
}
// Raise a document updated event
DocumentUpdatedAsyncEvent event = new DocumentUpdatedAsyncEvent();
event.setUserId(principal.getId());
event.setDocumentId(documentId);
ThreadLocalContext.get().addAsyncEvent(event);
2013-08-03 20:19:02 +02:00
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
2013-08-03 20:19:02 +02:00
}
2013-07-27 18:33:20 +02:00
/**
* Returns files linked to a document or not linked to any document.
*
2018-10-18 11:32:42 +02:00
* @api {get} /file/list Get files
* @apiName GetFileList
* @apiGroup File
* @apiParam {String} id Document ID
* @apiParam {String} share Share ID
* @apiSuccess {Object[]} files List of files
* @apiSuccess {String} files.id ID
2019-01-30 21:14:07 +01:00
* @apiSuccess {String} files.processing True if the file is currently processing
* @apiSuccess {String} files.name File name
2018-11-23 14:54:11 +01:00
* @apiSuccess {String} files.version Zero-based version number
2019-01-30 21:14:07 +01:00
* @apiSuccess {String} files.mimetype MIME type
* @apiSuccess {String} files.document_id Document ID
* @apiSuccess {String} files.create_date Create date (timestamp)
* @apiSuccess {String} files.size File size (in bytes)
* @apiError (client) ForbiddenError Access denied
* @apiError (client) NotFound Document not found
* @apiError (server) FileError Unable to get the size of a file
* @apiPermission none
* @apiVersion 1.5.0
*
2013-08-13 20:20:28 +02:00
* @param documentId Document ID
* @param shareId Sharing ID
2013-07-27 18:33:20 +02:00
* @return Response
*/
@GET
@Path("list")
public Response list(
@QueryParam("id") String documentId,
2015-09-07 21:51:13 +02:00
@QueryParam("share") String shareId) {
2015-03-06 21:23:50 +01:00
boolean authenticated = authenticate();
// Check document visibility
2015-03-06 21:23:50 +01:00
if (documentId != null) {
AclDao aclDao = new AclDao();
2016-03-15 22:44:50 +01:00
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) {
2016-05-06 00:36:54 +02:00
throw new NotFoundException();
}
2015-03-06 21:23:50 +01:00
} else if (!authenticated) {
throw new ForbiddenClientException();
2013-07-27 18:33:20 +02:00
}
FileDao fileDao = new FileDao();
2015-09-07 21:51:13 +02:00
JsonArrayBuilder files = Json.createArrayBuilder();
for (File fileDb : fileDao.getByDocumentId(principal.getId(), documentId)) {
files.add(RestUtil.fileToJsonObjectBuilder(fileDb));
2013-07-27 18:33:20 +02:00
}
2015-09-07 21:51:13 +02:00
JsonObjectBuilder response = Json.createObjectBuilder()
.add("files", files);
2015-09-07 21:51:13 +02:00
return Response.ok().entity(response.build()).build();
2013-07-27 18:33:20 +02:00
}
2019-01-30 21:14:07 +01:00
/**
* List all versions of a file.
*
* @api {get} /file/id/versions Get versions of a file
* @apiName GetFileVersions
* @apiGroup File
* @apiParam {String} id File ID
* @apiSuccess {Object[]} files List of files
* @apiSuccess {String} files.id ID
* @apiSuccess {String} files.name File name
* @apiSuccess {String} files.version Zero-based version number
* @apiSuccess {String} files.mimetype MIME type
* @apiSuccess {String} files.create_date Create date (timestamp)
* @apiError (client) ForbiddenError Access denied
* @apiError (client) NotFound File not found
* @apiPermission user
* @apiVersion 1.5.0
*
* @param id File ID
* @return Response
*/
@GET
@Path("{id: [a-z0-9\\-]+}/versions")
public Response versions(@PathParam("id") String id) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get versions
File file = findFile(id, null);
FileDao fileDao = new FileDao();
List<File> fileList = Lists.newArrayList(file);
if (file.getVersionId() != null) {
fileList = fileDao.getByVersionId(file.getVersionId());
}
JsonArrayBuilder files = Json.createArrayBuilder();
for (File fileDb : fileList) {
files.add(Json.createObjectBuilder()
.add("id", fileDb.getId())
.add("name", JsonUtil.nullable(fileDb.getName()))
.add("version", fileDb.getVersion())
.add("mimetype", fileDb.getMimeType())
.add("create_date", fileDb.getCreateDate().getTime()));
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("files", files);
return Response.ok().entity(response.build()).build();
}
2013-07-27 18:33:20 +02:00
/**
* Deletes a file.
*
* @api {delete} /file/:id Delete a file
* @apiName DeleteFile
* @apiGroup File
* @apiParam {String} id File ID
* @apiParam {String} share Share ID
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) NotFound File or document not found
* @apiPermission user
* @apiVersion 1.5.0
*
2013-07-27 18:33:20 +02:00
* @param id File ID
* @return Response
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
public Response delete(
2015-09-07 21:51:13 +02:00
@PathParam("id") String id) {
2013-07-27 18:33:20 +02:00
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the file
2018-03-23 12:52:42 +01:00
File file = findFile(id, null);
// Delete the file
2018-03-23 12:52:42 +01:00
FileDao fileDao = new FileDao();
fileDao.delete(file.getId(), principal.getId());
// Update the user quota
UserDao userDao = new UserDao();
User user = userDao.getById(principal.getId());
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
try {
user.setStorageCurrent(user.getStorageCurrent() - Files.size(storedFile));
userDao.updateQuota(user);
} catch (IOException e) {
// The file doesn't exists on disk, which is weird, but not fatal
}
// Raise a new file deleted event
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFileId(file.getId());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
2016-05-08 12:14:06 +02:00
if (file.getDocumentId() != null) {
// Raise a new document updated
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
2016-05-08 12:14:06 +02:00
documentUpdatedAsyncEvent.setDocumentId(file.getDocumentId());
ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent);
}
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
2013-07-27 18:33:20 +02:00
}
/**
* Returns a file.
*
2018-10-18 11:32:42 +02:00
* @api {get} /file/:id/data Get a file data
* @apiName GetFile
* @apiGroup File
* @apiParam {String} id File ID
* @apiParam {String} share Share ID
* @apiParam {String="web","thumb","content"} [size] Size variation
* @apiSuccess {Object} file The file data is the whole response
* @apiError (client) SizeError Size must be web or thumb
* @apiError (client) ForbiddenError Access denied or document not visible
* @apiError (client) NotFound File not found
* @apiError (server) ServiceUnavailable Error reading the file
* @apiPermission none
* @apiVersion 1.5.0
*
2013-08-14 00:47:58 +02:00
* @param fileId File ID
* @return Response
*/
@GET
@Path("{id: [a-z0-9\\-]+}/data")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response data(
2013-08-14 00:47:58 +02:00
@PathParam("id") final String fileId,
@QueryParam("share") String shareId,
2015-09-07 21:51:13 +02:00
@QueryParam("size") String size) {
authenticate();
if (size != null && !Lists.newArrayList("web", "thumb", "content").contains(size)) {
throw new ClientException("SizeError", "Size must be web, thumb or content");
}
// Get the file
2018-03-23 12:52:42 +01:00
File file = findFile(fileId, shareId);
2013-07-28 18:29:03 +02:00
// Get the stored file
2018-03-23 12:52:42 +01:00
UserDao userDao = new UserDao();
java.nio.file.Path storedFile;
String mimeType;
2016-05-06 00:36:54 +02:00
boolean decrypt;
if (size != null) {
if (size.equals("content")) {
return Response.ok(Strings.nullToEmpty(file.getContent()))
2019-05-22 15:36:50 +02:00
.header(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8")
.build();
}
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_" + size);
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
2013-08-20 21:51:07 +02:00
decrypt = true; // Thumbnails are encrypted
if (!Files.exists(storedFile)) {
2016-11-20 18:41:42 +01:00
try {
2018-03-14 20:39:32 +01:00
storedFile = Paths.get(getClass().getResource("/image/file-" + size + ".png").toURI());
2016-11-20 18:41:42 +01:00
} catch (URISyntaxException e) {
// Ignore
}
mimeType = MimeType.IMAGE_PNG;
2013-08-21 01:05:59 +02:00
decrypt = false;
}
2013-07-28 18:29:03 +02:00
} else {
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
mimeType = file.getMimeType();
2013-08-20 21:51:07 +02:00
decrypt = true; // Original files are encrypted
}
// Stream the output and decrypt it if necessary
StreamingOutput stream;
2015-05-09 14:44:19 +02:00
// A file is always encrypted by the creator of it
User user = userDao.getById(file.getUserId());
// Write the decrypted file to the output
2013-08-20 21:51:07 +02:00
try {
InputStream fileInputStream = Files.newInputStream(storedFile);
2013-08-20 21:51:07 +02:00
final InputStream responseInputStream = decrypt ?
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
stream = outputStream -> {
try {
ByteStreams.copy(responseInputStream, outputStream);
} finally {
2013-08-20 21:51:07 +02:00
try {
responseInputStream.close();
outputStream.close();
} catch (IOException e) {
// Ignore
2013-08-20 21:51:07 +02:00
}
}
};
} catch (Exception e) {
2015-05-23 19:16:38 +02:00
return Response.status(Status.SERVICE_UNAVAILABLE).build();
2013-07-28 18:29:03 +02:00
}
Response.ResponseBuilder builder = Response.ok(stream)
2018-03-26 17:15:36 +02:00
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + file.getFullName("data") + "\"")
.header(HttpHeaders.CONTENT_TYPE, mimeType);
if (decrypt) {
// Cache real files
builder.header(HttpHeaders.CACHE_CONTROL, "private")
.header(HttpHeaders.EXPIRES, HttpUtil.buildExpiresHeader(3_600_000L * 24L * 365L));
} else {
// Do not cache the temporary thumbnail
builder.header(HttpHeaders.CACHE_CONTROL, "no-store, must-revalidate")
.header(HttpHeaders.EXPIRES, "0");
}
return builder.build();
}
2018-03-23 12:52:42 +01:00
/**
* Returns all files from a document, zipped.
*
* @api {get} /file/zip Returns all files from a document, zipped.
* @apiName GetFileZip
* @apiGroup File
* @apiParam {String} id Document ID
* @apiParam {String} share Share ID
* @apiSuccess {Object} file The ZIP file is the whole response
* @apiError (client) NotFoundException Document not found
* @apiError (server) InternalServerError Error creating the ZIP file
* @apiPermission none
* @apiVersion 1.5.0
*
* @param documentId Document ID
* @param shareId Share ID
* @return Response
*/
@GET
@Path("zip")
@Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN})
public Response zip(
@QueryParam("id") String documentId,
2015-09-07 21:51:13 +02:00
@QueryParam("share") String shareId) {
authenticate();
// Get the document
DocumentDao documentDao = new DocumentDao();
2016-03-15 22:44:50 +01:00
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
2015-11-16 02:22:51 +01:00
if (documentDto == null) {
2016-05-06 00:36:54 +02:00
throw new NotFoundException();
}
// Get files associated with this document
FileDao fileDao = new FileDao();
final List<File> fileList = fileDao.getByDocumentId(principal.getId(), documentId);
String zipFileName = documentDto.getTitle().replaceAll("\\W+", "_");
return sendZippedFiles(zipFileName, fileList);
}
/**
* Returns a list of files, zipped
*
* @api {post} /file/zip Returns a list of files, zipped
* @apiName GetFilesZip
* @apiGroup File
* @apiParam {String[]} files IDs
* @apiSuccess {Object} file The ZIP file is the whole response
* @apiError (client) NotFoundException Files not found
* @apiError (server) InternalServerError Error creating the ZIP file
* @apiPermission none
* @apiVersion 1.11.0
*
* @param filesIdsList Files IDs
* @return Response
*/
@POST
@Path("zip")
@Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN})
public Response zip(
@FormParam("files") List<String> filesIdsList) {
authenticate();
List<File> fileList = findFiles(filesIdsList);
return sendZippedFiles("files", fileList);
}
/**
* Sent the content of a list of files.
*/
private Response sendZippedFiles(String zipFileName, List<File> fileList) {
final UserDao userDao = new UserDao();
// Create the ZIP stream
StreamingOutput stream = outputStream -> {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
// Add each file to the ZIP stream
int index = 0;
for (File file : fileList) {
java.nio.file.Path storedfile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
InputStream fileInputStream = Files.newInputStream(storedfile);
// Add the decrypted file to the ZIP stream
// Files are encrypted by the creator of them
User user = userDao.getById(file.getUserId());
try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) {
ZipEntry zipEntry = new ZipEntry(index + "-" + file.getFullName(Integer.toString(index)));
zipOutputStream.putNextEntry(zipEntry);
ByteStreams.copy(decryptedStream, zipOutputStream);
zipOutputStream.closeEntry();
} catch (Exception e) {
throw new WebApplicationException(e);
}
index++;
}
}
outputStream.close();
};
// Write to the output
return Response.ok(stream)
.header("Content-Type", "application/zip")
.header("Content-Disposition", "attachment; filename=\"" + zipFileName + ".zip\"")
.build();
}
2018-03-23 12:52:42 +01:00
/**
* Find a file with access rights checking.
*
* @param fileId File ID
* @param shareId Share ID
* @return File
*/
private File findFile(String fileId, String shareId) {
FileDao fileDao = new FileDao();
File file = fileDao.getFile(fileId);
if (file == null) {
throw new NotFoundException();
}
checkFileAccessible(shareId, file);
return file;
}
2018-03-23 12:52:42 +01:00
/**
* Find a list of files with access rights checking.
*
* @param filesIds Files IDs
* @return List<File>
*/
private List<File> findFiles(List<String> filesIds) {
FileDao fileDao = new FileDao();
List<File> files = fileDao.getFiles(filesIds);
for (File file : files) {
checkFileAccessible(null, file);
}
return files;
}
/**
* Check if a file is accessible to the current user
* @param shareId Share ID
* @param file
*/
private void checkFileAccessible(String shareId, File file) {
2018-03-23 12:52:42 +01:00
if (file.getDocumentId() == null) {
// It's an orphan file
if (!file.getUserId().equals(principal.getId())) {
// But not ours
throw new ForbiddenClientException();
}
} else {
// Check document accessibility
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, getTargetIdList(shareId))) {
throw new ForbiddenClientException();
}
}
}
2013-07-27 18:33:20 +02:00
}