in app/helpers/S3Signer.scala [163:213]
protected def calculateCanonicalString(httpMethod:String, uriPath: String, uriQueryParams:Map[String,String],
headers:Map[String,String], payloadHash:Option[String]) = {
val checksummer = MessageDigest.getInstance("SHA-256")
logger.debug(s"uriPath is $uriPath ${uriPath.length}")
val canonicalUrl = if(uriPath.length<=1) {
"/"
} else {
//Note that the spec at https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html demands that spaces should
//be double-encoded for all services with the EXCEPTION of s3. So we encode once and make sure that + (how the java urlencoder does a space)
//gets replaced with %20 which is what AWS wants
uriPath
.replaceAll("\\+","%20") //java encoder often encodes spaces as '+' instead of %20
.replaceAll(":", "%3A") //: character is not being automatically encoded (%3A)
}
logger.debug(s"encodedUrl: $canonicalUrl")
val canonicalQueryString = uriQueryParams
.map(entry=>URLEncoder.encode(entry._1,"UTF-8")+"=" + entry._2)
.toList.sorted
.mkString("&")
logger.debug(s"canonicalQueryString: $canonicalQueryString")
val updatedHeaders = if(headers.keys.exists(_=="x-amz-content-sha256")){
headers
} else {
headers + ("x-amz-content-sha256"->checksummer.digest("".getBytes("UTF-8")).map("%02x".format(_)).mkString)
}
checksummer.reset()
val canonicalHeaders = updatedHeaders.keys.toList.sorted.map(header=>{
header.toLowerCase + ":" + headers(header).trim
}).mkString("\n") + "\n"
logger.debug(s"canonicalHeaders: $canonicalHeaders")
val signedHeaders = headers.keys.map(_.toLowerCase).toList.sorted.mkString(";")
logger.debug(s"signedHeaders: $signedHeaders")
val hashedPayload = payloadHash match {
case Some(hexDigest)=>hexDigest
case None=>checksummer.digest("".getBytes("UTF-8")).map("%02x".format(_)).mkString
}
logger.debug(s"hashedPayload: $hashedPayload")
s"""$httpMethod
|$canonicalUrl
|$canonicalQueryString
|$canonicalHeaders
|$signedHeaders
|$hashedPayload""".stripMargin
}