scala-news-reader

rss/atom news reader in scala

git clone https://9o.is/git/scala-news-reader.git

AmazonS3.scala

(5489B)


      1 package com.joereader.lib.aws
      2 
      3 import java.net.URLEncoder._
      4 import java.lang.Long
      5 import com.ning.http.client.RequestBuilder
      6 import com.ning.http.util.Base64
      7 import dispatch._
      8 
      9 
     10 protected object AmazonS3 {
     11 
     12   import javax.crypto
     13 
     14   import java.util.{Date, Locale, SimpleTimeZone}
     15   import java.text.SimpleDateFormat
     16 
     17   val UTF_8 = "UTF-8"
     18 
     19   val Root = "s3.amazonaws.com"
     20 
     21   object rfc822DateParser extends SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US) {
     22     this.setTimeZone(new SimpleTimeZone(0, "GMT"))
     23   }
     24 
     25   def trim(s: String): String = s.dropWhile(_ == ' ').reverse.dropWhile(_ == ' ').reverse.toString
     26 
     27   def md5(bytes: Array[Byte]) = {
     28     import java.security.MessageDigest
     29 
     30     val r = MessageDigest.getInstance("MD5")
     31     r.reset()
     32     r.update(bytes)
     33     Base64.encode(r.digest)
     34   }
     35 
     36   def md5(stream: java.io.InputStream) = {
     37     import java.security.MessageDigest
     38 
     39     val buffer = new Array[Byte](1024)
     40     val r = MessageDigest.getInstance("MD5")
     41     var numRead: Int = 0
     42     do {
     43       numRead = stream.read(buffer)
     44       if (numRead > 0) {
     45         r.update(buffer, 0, numRead)
     46       }
     47     } while (numRead != -1)
     48 
     49     stream.close()
     50     Base64.encode(r.digest())
     51   }
     52 
     53   def sign(method: String, path: String, secretKey: String, date: Date,
     54            contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String, Set[String]]): String = {
     55     sign(method, path, secretKey, Left(date), contentType, contentMd5, amzHeaders)
     56   }
     57 
     58   def sign(method: String, path: String, secretKey: String, dateOrExpires: Either[Date, Long],
     59            contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String, Set[String]]) = {
     60     val SHA1 = "HmacSHA1"
     61     val message = canonicalString(method, path, dateOrExpires, contentType, contentMd5, amzHeaders)
     62     val sig = {
     63       val mac = crypto.Mac.getInstance(SHA1)
     64       val key = new crypto.spec.SecretKeySpec(bytes(secretKey), SHA1)
     65       mac.init(key)
     66       Base64.encode(mac.doFinal(bytes(message)))
     67     }
     68     sig
     69   }
     70 
     71   def signedUri(accessKey: String, secretKey: String, method: String, path: String, amzHeaders: Map[String, Set[String]],
     72                 expires: Long = defaultExpiryTime,
     73                 contentType: Option[String] = None, contentMd5: Option[String] = None) = {
     74     val signed = encode(sign(method, path, secretKey, Right(expires), contentType, contentMd5, amzHeaders), "UTF-8")
     75     "%s?Signature=%s&Expires=%s&AWSAccessKeyId=%s".format(path, signed, expires, accessKey)
     76   }
     77 
     78   def defaultExpiryTime = System.currentTimeMillis() / 1000 + 600
     79 
     80   /**
     81    * @return the canonical request string that needs to be signed for authentication
     82    */
     83   def canonicalString(method: String, path: String, dateOrExpires: Either[Date, Long], contentType: Option[String],
     84                       contentMd5: Option[String], amzHeaders: Map[String, Set[String]]) = {
     85     val amzString = amzHeaders.toList.sortWith(_._1.toLowerCase < _._1.toLowerCase).map {
     86       case (k, v) => "%s:%s".format(k.toLowerCase, v.map(trim).mkString(","))
     87     }
     88     val dateExpiresString = dateOrExpires match {
     89       case Left(date) => rfc822DateParser.format(date)
     90       case Right(expires) => expires.toString
     91     }
     92     (method :: contentMd5.getOrElse("") :: contentType.getOrElse("") :: dateExpiresString :: Nil) ++ amzString ++ List(path) mkString "\n"
     93   }
     94 
     95   def bytes(s: String) = s.getBytes(UTF_8)
     96 
     97   implicit def Request2S3RequestSigner(r: RequestBuilder) = new S3RequestSigner(r)
     98 
     99   implicit def Request2S3RequestSigner(r: String) = new S3RequestSigner(new RequestBuilder().setUrl(r))
    100 
    101   class S3RequestSigner(r: RequestBuilder) {
    102 
    103     import scala.collection.JavaConverters._
    104 
    105     protected def path = RawUri(r.build.getUrl).path.getOrElse("")
    106 
    107     def <@(accessKey: String, secretKey: String) = {
    108       val req = r.build
    109       val contentStream = req.getStreamData
    110       val contentMd5 = if (req.getContentLength <= 0) None else Some(md5(contentStream))
    111 
    112       for (cmd5 <- contentMd5)
    113         r.addHeader("Content-MD5", cmd5)
    114 
    115       val headers = req.getHeaders
    116       val contentType = headers.keySet.asScala.find {
    117         _.toLowerCase == "content-type"
    118       }.map {
    119         headers.get(_).asScala.head
    120       }
    121 
    122       val d = new Date
    123       r.addHeader("Authorization", "AWS %s:%s".format(accessKey, sign(req.getMethod, path, secretKey, d, contentType, contentMd5, amazonHeaders)))
    124       r.addHeader("Date", AmazonS3.rfc822DateParser.format(d))
    125       r
    126     }
    127 
    128     def signed(accessKey: String, secretKey: String, expires: Long = defaultExpiryTime,
    129                contentType: Option[String] = None, contentMd5: Option[String] = None): RequestBuilder = {
    130       val req = r.build
    131       val path = RawUri(req.getUrl).path.getOrElse("")
    132       val uri = signedUri(accessKey, secretKey, req.getMethod, path, amazonHeaders, expires, contentType, contentMd5)
    133       val requestHeaders = for {
    134         (key, Some(value)) <- Map("Content-Type" -> contentType, "Content-Md5" -> contentMd5)
    135       } yield key -> value
    136       (:/(AmazonS3.Root) / uri.substring(1)).setMethod(req.getMethod).setHeaders(req.getHeaders).secure <:< requestHeaders
    137     }
    138 
    139     private def amazonHeaders = {
    140       val headers = r.build.getHeaders
    141       headers.keySet.asScala.filter {
    142         _.toLowerCase.startsWith("x-amz")
    143       }
    144         .map(name => name -> headers.get(name).asScala.toSet).toMap
    145     }
    146   }
    147 
    148 }
    149 
    150 object Bucket extends (String => RequestBuilder) {
    151   def apply(name: String) = :/(AmazonS3.Root) / name
    152 }