<commit_before>package akashic.storage.service

import java.net.URLEncoder

import akka.http.scaladsl.model.{StatusCodes, HttpRequest}
import akka.http.scaladsl.server.Directives._
import java.nio.file.Path

import akashic.storage.{files, server}
import akashic.storage.patch.{Commit, Patch, Version, Data}
import com.google.common.hash.Hashing
import com.google.common.io.BaseEncoding
import org.apache.commons.codec.binary.Hex
import org.apache.commons.io.FileUtils
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import scala.xml.{XML, NodeSeq}
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._

object CompleteMultipartUpload {
  val matcher =
    post &
    extractObject &
    parameter("uploadId") &
    entity(as[String]) &
    extractRequest

  val route = matcher.as(t)(_.run)

  case class t(bucketName: String, keyName: String,
               uploadId: String,
               data: String,
               req: HttpRequest) extends AuthorizedAPI {
    def name = "Complete Multipart Upload"
    def resource = Resource.forObject(bucketName, keyName)
    def runOnce = {
      case class Part(partNumber: Int, eTag: String)
      val bucket = findBucket(server.tree, bucketName)
      val key = findKey(bucket, keyName, Error.NoSuchUpload())
      val upload = findUpload(key, uploadId)

      val parts = Try {
        val xml = XML.loadString(data)
        (xml \ "Part").map { a =>
          Part(
            (a \ "PartNumber").text.toInt,
            (a \ "ETag").text.replace("\"", "")
          )
        }
      } match {
        case Success(a) => a
        case Failure(_) => failWith(Error.MalformedXML())
      }

      // parts must be sorted
      if (parts != parts.sortBy(_.partNumber))
        failWith(Error.InvalidPartOrder())

      val lastPartNumber = parts.last.partNumber

      for (Part(partNumber, eTag) <- parts) {
        val uploadedPart: Path = upload.findPart(partNumber).map(_.unwrap.filePath) match {
          case Some(a) => a
          case None => failWith(Error.InvalidPart())
        }
        val computedETag = Hex.encodeHexString(files.computeMD5(uploadedPart))
        if (computedETag != eTag) {
          failWith(Error.InvalidPart())
        }
        // Each part must be at least 5MB in size, expect the last part.
        if (partNumber < lastPartNumber && files.fileSize(uploadedPart) < (5 << 20)) {
          failWith(Error.EntityTooSmall())
        }
      }

      // http://stackoverflow.com/questions/12186993/what-is-the-algorithm-to-compute-the-amazon-s3-etag-for-a-file-larger-than-5gb<commit_msg>CompleteMultipartUpload: size should be greater than 0<commit_after>package akashic.storage.service

import java.net.URLEncoder

import akka.http.scaladsl.model.{StatusCodes, HttpRequest}
import akka.http.scaladsl.server.Directives._
import java.nio.file.Path

import akashic.storage.{files, server}
import akashic.storage.patch.{Commit, Patch, Version, Data}
import com.google.common.hash.Hashing
import com.google.common.io.BaseEncoding
import org.apache.commons.codec.binary.Hex
import org.apache.commons.io.FileUtils
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import scala.xml.{XML, NodeSeq}
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._

object CompleteMultipartUpload {
  val matcher =
    post &
    extractObject &
    parameter("uploadId") &
    entity(as[String]) &
    extractRequest

  val route = matcher.as(t)(_.run)

  case class t(bucketName: String, keyName: String,
               uploadId: String,
               data: String,
               req: HttpRequest) extends AuthorizedAPI {
    def name = "Complete Multipart Upload"
    def resource = Resource.forObject(bucketName, keyName)
    def runOnce = {
      case class Part(partNumber: Int, eTag: String)
      val bucket = findBucket(server.tree, bucketName)
      val key = findKey(bucket, keyName, Error.NoSuchUpload())
      val upload = findUpload(key, uploadId)

      val parts = Try {
        val xml = XML.loadString(data)
        (xml \ "Part").map { a =>
          Part(
            (a \ "PartNumber").text.toInt,
            (a \ "ETag").text.replace("\"", "")
          )
        }
      } match {
        case Success(a) => a
        case Failure(_) => failWith(Error.MalformedXML())
      }

      if (parts.size == 0)
        failWith(Error.MalformedXML())

      // parts must be sorted
      if (parts != parts.sortBy(_.partNumber))
        failWith(Error.InvalidPartOrder())

      val lastPartNumber = parts.last.partNumber

      for (Part(partNumber, eTag) <- parts) {
        val uploadedPart: Path = upload.findPart(partNumber).map(_.unwrap.filePath) match {
          case Some(a) => a
          case None => failWith(Error.InvalidPart())
        }
        val computedETag = Hex.encodeHexString(files.computeMD5(uploadedPart))
        if (computedETag != eTag) {
          failWith(Error.InvalidPart())
        }
        // Each part must be at least 5MB in size, expect the last part.
        if (partNumber < lastPartNumber && files.fileSize(uploadedPart) < (5 << 20)) {
          failWith(Error.EntityTooSmall())
        }
      }

      // http://stackoverflow.com/questions/12186993/what-is-the-algorithm-to-compute-the-amazon-s3-etag-for-a-file-larger-than-5gb
{'fraction_non_alphanumeric': 0.11776631035753778, 'fraction_numerical': 0.005897530409141172, 'mean_word_length': 2.7661346287300486, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3523624', 'n_tokens_mistral': 1685, 'n_tokens_neox': 1647, 'n_words': 351}