private void handleUpload()

in api_dev/src/main/java/com/google/appengine/api/blobstore/dev/ee10/UploadBlobServlet.java [162:449]


  private void handleUpload(final HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    String sessionId = getSessionId(req);
    BlobUploadSession session = uploadSessionStorage.loadSession(sessionId);

    if (session == null) {
      resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No upload session: " + sessionId);
      return;
    }

    Map<String, List<String>> blobKeys = new HashMap<String, List<String>>();
    Map<String, List<Map<String, String>>> blobInfos =
          new HashMap<String, List<Map<String, String>>>();
    final Map<String, List<String>> otherParams = new HashMap<String, List<String>>();
    try {
      MimeMultipart multipart = MultipartMimeUtils.parseMultipartRequest(req);
      int parts = multipart.getCount();

      // Check blob sizes upfront so we don't need to worry about rolling back
      // partial uploads.
      if (session.hasMaxUploadSizeBytes() || session.hasMaxUploadSizeBytesPerBlob()) {
        int totalSize = 0;
        int largestBlobSize = 0;
        for (int i = 0; i < parts; i++) {
          BodyPart part = multipart.getBodyPart(i);
          if (part.getFileName() != null && !part.getFileName().isEmpty()) {
            int size = part.getSize();
            if (size != -1) {
              totalSize += size;
              largestBlobSize = Math.max(size, largestBlobSize);
            } else {
              logger.log(Level.WARNING,
                         "Unable to determine size of upload part named " +
                         part.getFileName() + "." +
                         " Upload limit checks may not be accurate.");
            }
          }
        }
        if (session.hasMaxUploadSizeBytesPerBlob() &&
                session.getMaxUploadSizeBytesPerBlob() < largestBlobSize) {
          resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE,
              UPLOAD_BLOB_TOO_LARGE_RESPONSE);
          return;
        }
        if (session.hasMaxUploadSizeBytes() &&
                session.getMaxUploadSizeBytes() < totalSize) {
          resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE,
              UPLOAD_TOTAL_TOO_LARGE_RESPONSE);
          return;
        }
      }

      for (int i = 0; i < parts; i++) {
        BodyPart part = multipart.getBodyPart(i);
        String fieldName = MultipartMimeUtils.getFieldName(part);
        if (part.getFileName() != null) {
          if (part.getFileName().length() > 0) {
            BlobKey blobKey = assignBlobKey(session);
            List<String> keys = blobKeys.get(fieldName);
            if (keys == null) {
              keys = new ArrayList<String>();
              blobKeys.put(fieldName, keys);
            }
            keys.add(blobKey.getKeyString());

            MessageDigest digest = MessageDigest.getInstance("MD5");
            boolean swallowDueToThrow = true;
            OutputStream outStream = getBlobStorage().storeBlob(blobKey);
            try {
              InputStream inStream = part.getInputStream();
              try {
                final int bufferSize = (1 << 16);
                byte [] buffer = new byte[bufferSize];
                while (true) {
                  int bytesRead = inStream.read(buffer);
                  if (bytesRead == -1) {
                    break;
                  }
                  outStream.write(buffer, 0, bytesRead);
                  digest.update(buffer, 0, bytesRead);
                }
                outStream.close();
                byte[] hash = digest.digest();

                StringBuilder hashString = new StringBuilder();
                for (int j = 0; j < hash.length; j++) {
                  String hexValue = Integer.toHexString(0xFF & hash[j]);
                  if (hexValue.length() == 1) {
                    hashString.append("0");
                  }
                  hashString.append(hexValue);
                }

                String originalContentType = part.getContentType();
                String newContentType = createContentType(blobKey);
                DataSource dataSource = MultipartMimeUtils.createDataSource(
                    newContentType, new byte[0]);
                part.setDataHandler(new DataHandler(dataSource));
                part.addHeader("Content-type", newContentType);
                Clock clock = apiProxyLocal.getClock();
                blobInfoStorage.saveBlobInfo(new BlobInfo(
                    blobKey,
                    originalContentType,
                    new Date(clock.getCurrentTime()),
                    part.getFileName(),
                    part.getSize(),
                    hashString.toString()));
                swallowDueToThrow = false;
              } finally {
                Closeables.close(inStream, swallowDueToThrow);
              }
            } finally {
              Closeables.close(outStream, swallowDueToThrow);
            }

            // This codes must be run after the BlobInfo is persisted locally.
            List<Map<String, String>> infos = blobInfos.get(fieldName);
            if (infos == null) {
              infos = new ArrayList<Map<String, String>>();
              blobInfos.put(fieldName, infos);
            }
            infos.add(getInfoFromStorage(blobKey, session));
          }
        } else {
          List<String> values = otherParams.get(fieldName);
          if (values == null) {
            values = new ArrayList<String>();
            otherParams.put(fieldName, values);
          }
          values.add(MultipartMimeUtils.getTextContent(part));
        }
      }
      req.setAttribute(UPLOADED_BLOBKEY_ATTR, blobKeys);
      req.setAttribute(UPLOADED_BLOBINFO_ATTR, blobInfos);

      uploadSessionStorage.deleteSession(sessionId);

      ByteArrayOutputStream modifiedRequest = new ByteArrayOutputStream();
      String oldValue = System.setProperty("mail.mime.foldtext", "false");
      try {
        multipart.writeTo(modifiedRequest);
      } finally {
        if (oldValue == null) {
          System.clearProperty("mail.mime.foldtext");
        } else {
          System.setProperty("mail.mime.foldtext", oldValue);
        }
      }

      final byte[] modifiedRequestBytes = modifiedRequest.toByteArray();
      final ByteArrayInputStream modifiedRequestStream =
          new ByteArrayInputStream(modifiedRequestBytes);
      final BufferedReader modifiedReader =
          new BufferedReader(new InputStreamReader(modifiedRequestStream));

      HttpServletRequest wrappedRequest =
          new HttpServletRequestWrapper(req) {
            @Override
            public String getHeader(String name) {
              if (Ascii.equalsIgnoreCase(name, UPLOAD_HEADER)) {
                return "true";
              } else if (Ascii.equalsIgnoreCase(name, "Content-Length")) {
                return String.valueOf(modifiedRequestBytes.length);
              } else {
                return super.getHeader(name);
              }
            }

            @Override
            public Enumeration<String> getHeaderNames() {
              List<String> headers = Collections.list(super.getHeaderNames());
              headers.add(UPLOAD_HEADER);
              return Collections.enumeration(headers);
            }

            @Override
            public Enumeration<String> getHeaders(String name) {
              if (Ascii.equalsIgnoreCase(name, UPLOAD_HEADER)) {
                return Collections.enumeration(ImmutableList.of("true"));
              } else if (Ascii.equalsIgnoreCase(name, "Content-Length")) {
                return Collections.enumeration(
                    ImmutableList.of(String.valueOf(modifiedRequestBytes.length)));
              } else {
                return super.getHeaders(name);
              }
            }

            @Override
            public int getIntHeader(String name) {
              if (Ascii.equalsIgnoreCase(name, UPLOAD_HEADER)) {
                throw new NumberFormatException(UPLOAD_HEADER + "does not have an integer value");
              } else if (Ascii.equalsIgnoreCase(name, "Content-Length")) {
                return modifiedRequestBytes.length;
              } else {
                return super.getIntHeader(name);
              }
            }

            @Override
            public ServletInputStream getInputStream() {
              return new ServletInputStream() {
                @Override
                public int read() {
                  return modifiedRequestStream.read();
                }

                @Override
                public void close() throws IOException {
                  modifiedRequestStream.close();
                }

                @Override
                public boolean isFinished() {
                  return true;
                }

                @Override
                public boolean isReady() {
                  return true;
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                  throw new UnsupportedOperationException();
                }
              };
            }

            @Override
            public BufferedReader getReader() {
              return modifiedReader;
            }

            @Override
            public Map<String, String[]> getParameterMap() {
              Map<String, String[]> parameters = super.getParameterMap();
              if (otherParams.isEmpty()) {
                return parameters;
              } else {
                // HttpServlet.getParameterMap() result is immutable so we need to take a copy.
                Map<String, String[]> map = new HashMap<>(parameters);
                for (Map.Entry<String, List<String>> entry : otherParams.entrySet()) {
                  map.put(entry.getKey(), entry.getValue().toArray(new String[0]));
                }
                // Maintain the semantic of ServletRequestWrapper by returning
                // an immutable map.
                return Collections.unmodifiableMap(map);
              }
            }

            @Override
            public Enumeration<String> getParameterNames() {
              List<String> allNames = new ArrayList<>();

              Enumeration<String> names = super.getParameterNames();
              while (names.hasMoreElements()) {
                allNames.add(names.nextElement());
              }
              allNames.addAll(otherParams.keySet());
              return Collections.enumeration(allNames);
            }

            @Override
            public String[] getParameterValues(String name) {
              if (otherParams.containsKey(name)) {
                return otherParams.get(name).toArray(new String[0]);
              } else {
                return super.getParameterValues(name);
              }
            }

            @Override
            public String getParameter(String name) {
              if (otherParams.containsKey(name)) {
                return otherParams.get(name).get(0);
              } else {
                return super.getParameter(name);
              }
            }
          };

      String successPath = session.getSuccessPath();
      getServletContext().getRequestDispatcher(successPath).forward(wrappedRequest,
                                                                    resp);
    } catch (MessagingException | NoSuchAlgorithmException ex) {
      throw new ServletException(ex);
    }
  }