package io.lumify.web.routes.vertex; import com.google.inject.Inject; import io.lumify.core.config.Configuration; import io.lumify.core.exception.LumifyException; import io.lumify.core.model.properties.LumifyProperties; import io.lumify.core.model.properties.MediaLumifyProperties; import io.lumify.core.model.user.UserRepository; import io.lumify.core.model.workspace.WorkspaceRepository; import io.lumify.core.user.User; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import io.lumify.miniweb.HandlerChain; import io.lumify.miniweb.utils.UrlUtils; import io.lumify.web.BaseRequestHandler; import org.apache.commons.io.IOUtils; import org.securegraph.Authorizations; import org.securegraph.Graph; import org.securegraph.Vertex; import org.securegraph.property.StreamingPropertyValue; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; public class VertexRaw extends BaseRequestHandler { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(VertexRaw.class); private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=([0-9]*)-([0-9]*)"); private final Graph graph; @Inject public VertexRaw( final Graph graph, final UserRepository userRepository, final WorkspaceRepository workspaceRepository, final Configuration configuration) { super(userRepository, workspaceRepository, configuration); this.graph = graph; } @Override public void handle(HttpServletRequest request, HttpServletResponse response, HandlerChain chain) throws Exception { boolean download = getOptionalParameter(request, "download") != null; boolean playback = getOptionalParameter(request, "playback") != null; User user = getUser(request); Authorizations authorizations = getAuthorizations(request, user); String graphVertexId = UrlUtils.urlDecode(getAttributeString(request, "graphVertexId")); Vertex artifactVertex = graph.getVertex(graphVertexId, authorizations); if (artifactVertex == null) { respondWithNotFound(response); return; } String fileName = LumifyProperties.FILE_NAME.getPropertyValue(artifactVertex); if (fileName == null) { fileName = LumifyProperties.TITLE.getPropertyValue(artifactVertex); } if (playback) { handlePartialPlayback(request, response, artifactVertex, fileName); } else { String mimeType = getMimeType(artifactVertex); response.setContentType(mimeType); setMaxAge(response, EXPIRES_1_HOUR); String fileNameWithoutQuotes = fileName.replace('"', '\''); if (download) { response.addHeader("Content-Disposition", "attachment; filename=\"" + fileNameWithoutQuotes + "\""); } else { response.addHeader("Content-Disposition", "inline; filename=\"" + fileNameWithoutQuotes + "\""); } StreamingPropertyValue rawValue = LumifyProperties.RAW.getPropertyValue(artifactVertex); if (rawValue == null) { LOGGER.warn("Could not find raw on artifact: %s", artifactVertex.getId()); respondWithNotFound(response); return; } try (InputStream in = rawValue.getInputStream()) { IOUtils.copy(in, response.getOutputStream()); } } chain.next(request, response); } private void handlePartialPlayback(HttpServletRequest request, HttpServletResponse response, Vertex artifactVertex, String fileName) throws IOException { String type = getRequiredParameter(request, "type"); InputStream in; Long totalLength; long partialStart = 0; Long partialEnd = null; String range = request.getHeader("Range"); if (range != null) { response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); Matcher m = RANGE_PATTERN.matcher(range); if (m.matches()) { partialStart = Long.parseLong(m.group(1)); if (m.group(2).length() > 0) { partialEnd = Long.parseLong(m.group(2)); } } } response.setCharacterEncoding(null); response.setContentType(type); response.addHeader("Content-Disposition", "attachment; filename=" + fileName); StreamingPropertyValue mediaPropertyValue = getStreamingPropertyValue(artifactVertex, type); totalLength = mediaPropertyValue.getLength(); in = mediaPropertyValue.getInputStream(); if (partialEnd == null) { partialEnd = totalLength; } // Ensure that the last byte position is less than the instance-length partialEnd = Math.min(partialEnd, totalLength - 1); long partialLength = totalLength; if (range != null) { partialLength = partialEnd - partialStart + 1; response.addHeader("Content-Range", "bytes " + partialStart + "-" + partialEnd + "/" + totalLength); if (partialStart > 0) { in.skip(partialStart); } } response.addHeader("Content-Length", "" + partialLength); OutputStream out = response.getOutputStream(); copy(in, out, partialLength); response.flushBuffer(); } private StreamingPropertyValue getStreamingPropertyValue(Vertex artifactVertex, String type) { StreamingPropertyValue mediaPropertyValue; if (MediaLumifyProperties.MIME_TYPE_AUDIO_MP4.equals(type)) { mediaPropertyValue = MediaLumifyProperties.AUDIO_MP4.getPropertyValue(artifactVertex); checkNotNull(mediaPropertyValue, String.format("Could not find %s property on artifact %s", MediaLumifyProperties.MIME_TYPE_AUDIO_MP4, artifactVertex.getId())); } else if (MediaLumifyProperties.MIME_TYPE_AUDIO_OGG.equals(type)) { mediaPropertyValue = MediaLumifyProperties.AUDIO_OGG.getPropertyValue(artifactVertex); checkNotNull(mediaPropertyValue, String.format("Could not find %s property on artifact %s", MediaLumifyProperties.MIME_TYPE_AUDIO_OGG, artifactVertex.getId())); } else if (MediaLumifyProperties.MIME_TYPE_VIDEO_MP4.equals(type)) { mediaPropertyValue = MediaLumifyProperties.VIDEO_MP4.getPropertyValue(artifactVertex); checkNotNull(mediaPropertyValue, String.format("Could not find %s property on artifact %s", MediaLumifyProperties.MIME_TYPE_VIDEO_MP4, artifactVertex.getId())); } else if (MediaLumifyProperties.MIME_TYPE_VIDEO_WEBM.equals(type)) { mediaPropertyValue = MediaLumifyProperties.VIDEO_WEBM.getPropertyValue(artifactVertex); checkNotNull(mediaPropertyValue, String.format("Could not find %s property on artifact %s", MediaLumifyProperties.MIME_TYPE_VIDEO_WEBM, artifactVertex.getId())); } else { throw new LumifyException("Invalid video type: " + type); } return mediaPropertyValue; } private void copy(InputStream in, OutputStream out, Long length) throws IOException { byte[] buffer = new byte[1024]; int read; while (length > 0 && (read = in.read(buffer, 0, (int) Math.min(length, buffer.length))) > 0) { out.write(buffer, 0, read); length -= read; } } private String getMimeType(Vertex artifactVertex) { String mimeType = LumifyProperties.MIME_TYPE.getPropertyValue(artifactVertex); if (mimeType == null || mimeType.isEmpty()) { mimeType = "application/octet-stream"; } return mimeType; } }