Search code examples
scalaakkaurlencodeakka-http

How to prevent percent decoding of URIs in Akka Http redirect requests?


I am using Akka Http to handle the redirection of a signed url for a text resource stored in GCS (Google Cloud Storage). Basically I have some files in GCS that I want to deliver with my API when requested. Unfortunately, the path to this resource has a subdirectory with a = character in it, something like: https://storage.googleapis.com/path_start/more_path/format=avro/still_more_path/filename.avro.

Google storage's signUrl method percent encodes this = character as %3D in the signed url. I have verified that using this signed url allows access to the files in GCS (hitting it starts a download). The problem is when Akka Http redirects this signed url on request, it decodes it back to the equals sign. This results in a SignatureDoesNotMatch error from GCS:


    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message>

I attempt to generate the signed url and redirect it in the following code snippet:

          val signedUrl = cloudStorageService.getSignedUrl(bucketId, filePath)
          logger.info(s"signedUrl: $signedUrl")
          redirect(signedUrl, TemporaryRedirect)

and my cloudStorageService.getSignedUrl method is defined separately here:

      def getSignedUrl(bucket: String, path: String, expiration: Option[Int] = None): String = {
        val maxLifetime = 60 * 60 * 24 * 7
        val lifetime = expiration.getOrElse(maxLifetime)
        val cappedExpiration = Math.min(lifetime, maxLifetime)
    
        val blobInfo = BlobInfo.newBuilder(BlobId.of(bucket, path)).build
        storage.signUrl(blobInfo, cappedExpiration, TimeUnit.SECONDS, Storage.SignUrlOption.withV4Signature).toString
      }

The logging statement in the first snippet shows me that the signed URL is generated with the %3D, but the redirected URL has been percent decoded back to =, resulting in the SignatureDoesNotMatch error.

This behavior is standard for Akka Http. According to the documentation for Uri class, all members represent percent decoded elements. There is an option to construct the uri with a raw query string, but in my case the encoded character happens in the uri path itself, not the query.

So my question is, is there any way to get Akka Http to redirect this signed url without percent decoding the path? Or am I mistaken, and there's another way to approach this problem?


Solution

  • You might want to create the redirect response 'manually' instead of using the 'redirect' directive. The directive implementation is:

    HttpResponse(
          status = redirectionType,
          headers = headers.Location(uri) :: Nil,
          entity = redirectionType.htmlTemplate match {
            case ""       => HttpEntity.Empty
            case template => HttpEntity(ContentTypes.`text/html(UTF-8)`, template format uri)
          }
    

    Instead of headers.Location(uri) you could introduce your own RawHeader to build up a Location header. You can probably leave the entity empty in your scenario.

    (relevant documentation: https://doc.akka.io//docs/akka-http/current/routing-dsl/directives/route-directives/redirect.html)