com.google.apphosting.runtime.jetty.ee8.ParseBlobUploadHandler Maven / Gradle / Ivy
/*
* Copyright 2021 Google LLC
*
* Licensed 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
*
* https://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.
*/
package com.google.apphosting.runtime.jetty.ee8;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.apphosting.utils.servlet.MultipartMimeUtils;
import com.google.common.collect.Maps;
import com.google.common.flogger.GoogleLogger;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee8.nested.HandlerWrapper;
/**
* {@code ParseBlobUploadHandler} is responsible for the parsing multipart/form-data or
* multipart/mixed requests used to make Blob upload callbacks, and storing a set of string-encoded
* blob keys as a servlet request attribute. This allows the {@code
* BlobstoreService.getUploadedBlobs()} method to return the appropriate {@code BlobKey} objects.
*
* This listener automatically runs on all dynamic requests in the production environment. In the
* DevAppServer, the equivalent work is subsumed by {@code UploadBlobServlet}.
*
*/
public class ParseBlobUploadHandler extends HandlerWrapper {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
/**
* An arbitrary HTTP header that is set on all blob upload
* callbacks.
*/
static final String UPLOAD_HEADER = "X-AppEngine-BlobUpload";
static final String UPLOADED_BLOBKEY_ATTR = "com.google.appengine.api.blobstore.upload.blobkeys";
static final String UPLOADED_BLOBINFO_ATTR =
"com.google.appengine.api.blobstore.upload.blobinfos";
// This field has to be the same as X_APPENGINE_CLOUD_STORAGE_OBJECT in http_proto.cc.
// This header will have the creation date in the format YYYY-MM-DD HH:mm:ss.SSS.
static final String UPLOAD_CREATION_HEADER = "X-AppEngine-Upload-Creation";
// This field has to be the same as X_APPENGINE_CLOUD_STORAGE_OBJECT in http_proto.cc.
// This header will have the filename of created the object in Cloud Storage when appropriate.
static final String CLOUD_STORAGE_OBJECT_HEADER = "X-AppEngine-Cloud-Storage-Object";
static final String CONTENT_LENGTH_HEADER = "Content-Length";
@Override
public void handle(String target, org.eclipse.jetty.ee8.nested.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (request.getDispatcherType() == DispatcherType.REQUEST
&& request.getHeader(UPLOAD_HEADER) != null) {
Map> blobKeys = new HashMap<>();
Map>> blobInfos = new HashMap<>();
Map> otherParams = new HashMap<>();
try {
MimeMultipart multipart = MultipartMimeUtils.parseMultipartRequest(request);
int parts = multipart.getCount();
for (int i = 0; i < parts; i++) {
BodyPart part = multipart.getBodyPart(i);
String fieldName = MultipartMimeUtils.getFieldName(part);
if (part.getFileName() != null) {
ContentType contentType = new ContentType(part.getContentType());
if ("message/external-body".equals(contentType.getBaseType())) {
String blobKeyString = contentType.getParameter("blob-key");
List keys = blobKeys.computeIfAbsent(fieldName, k -> new ArrayList<>());
keys.add(blobKeyString);
List