#!/usr/bin/python

import os, sys, httplib, locale, datetime, hashlib, hmac, base64, re

def s3request(accesskey, secretkey, httpverb, host, filename, body=None, host_has_bucket=False, ssl=True, port=None):
    if host_has_bucket:
        bucket_and_file = "/" + host.split(".", 1)[0] + filename
    else:
        bucket_and_file = filename

    locale.setlocale(locale.LC_TIME, "C")
    date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")

    string_to_sign = (httpverb + "\n" +
                      "\n" + # content-md5
                      "\n" + # content-type
                      date + "\n" +
                      bucket_and_file)
    sha1_hmac = hmac.new(secretkey, string_to_sign, hashlib.sha1);
    auth = "AWS " + accesskey + ":" + base64.b64encode(sha1_hmac.digest())

    if ssl:
       http = httplib.HTTPSConnection(host, port)
    else:
       http = httplib.HTTPConnection(host, port)

    http.request(httpverb, filename, body,
                 {"Date": date, "Authorization": auth})

    return http.getresponse()

def usage():
    sys.exit(("Usage:\n\n" +
              "s3file.py [-e] [-s] [-p<port>] <GET|HEAD|PUT|DELETE>\n" +
              "          <host> /bucket/path/to/file [<local file>]\n\n" +
              "s3file.py [-e] [-s] [-p<port>] -b <GET|HEAD|PUT|DELETE>\n" +
              "          <bucket.host> /path/to/file [<local file>]\n"))


host_has_bucket = False
ssl = True
port = None
show_etag = False

arg_pos = 1
while arg_pos < len(sys.argv) and sys.argv[arg_pos].startswith("-"):
    if sys.argv[arg_pos] == "-b":
        host_has_bucket = True
    elif sys.argv[arg_pos] == "-s":
        ssl = False
    elif sys.argv[arg_pos] == "-e":
        show_etag = True
    elif not re.match(r"-p\d+$", sys.argv[arg_pos]) is None:
        port = int(sys.argv[arg_pos][2:])
    else:
        usage()
    arg_pos += 1

if (len(sys.argv) - arg_pos) < 3 or (len(sys.argv) - arg_pos) > 4:
    usage()

httpverb = sys.argv[arg_pos]
arg_pos += 1
host = sys.argv[arg_pos]
arg_pos += 1
filename = sys.argv[arg_pos]
arg_pos += 1
if len(sys.argv) == (arg_pos + 1):
    local_file = sys.argv[arg_pos]
else:
    local_file = None

accesskey = os.getenv("S3ACCESSKEY")
if accesskey is None:
    sys.exit("no S3ACCESSKEY environment variable")

secretkey = os.getenv("S3SECRETKEY")
if secretkey is None:
    sys.exit("no S3SECRETKEY environment variable")

if httpverb == "GET":
    body = None
    expected_response = 200
elif httpverb == "HEAD":
    if not local_file is None:
        sys.exit("HEAD and local file are mutual exclusive")
    body = None
    output = sys.stdout
    expected_response = 200
elif httpverb == "PUT":
    if local_file is None:
        body = sys.stdin
    else:
        body = open(local_file)
    output = sys.stdout
    expected_response = 200
elif httpverb == "DELETE":
    if not local_file is None:
        sys.exit("DELETE and local file are mutual exclusive")
    body = None
    output = sys.stdout
    expected_response = 204
else:
    usage()

response = s3request(accesskey, secretkey, httpverb, host, filename, body, host_has_bucket, ssl, port)
if response.status != expected_response:
    sys.exit(str(response.status) + " " + response.reason)

content_length = response.getheader("Content-Length")
if content_length is None:
    sys.stderr.write("warning: no Content-Length\n")

if show_etag:
    etag = response.getheader("ETag")
    if etag is None:
        sys.stderr.write("warning: no ETag header\n")
    else:
        sys.stderr.write("ETag: " + etag + "\n")

if httpverb == "GET":
    if local_file is None:
        output = sys.stdout
    else:
        output = open(local_file, "w")

CHUNK_LENGTH = 10 * 1024 * 1024
read_bytes = 0

while True:
    chunk = response.read(CHUNK_LENGTH)
    output.write(chunk)
    read_bytes += len(chunk)
    if len(chunk) != CHUNK_LENGTH:
        break

if not content_length is None and str(read_bytes) != content_length:
    sys.exit("expected %sB, got %i" % (content_length, read_bytes))
