]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-6979 #resolve #comment mod_http_cache: added base-domain config to s3 profiles...
authorChris Rienzo <chris.rienzo@grasshopper.com>
Thu, 13 Nov 2014 15:20:02 +0000 (10:20 -0500)
committerChris Rienzo <chris.rienzo@grasshopper.com>
Thu, 13 Nov 2014 15:20:02 +0000 (10:20 -0500)
Example configuration:

  <profiles>
    <profile name="s3">
       <!-- Credentials for AWS account. -->
       <aws-s3>
          <!-- 20 character key identifier -->
          <access-key-id><![CDATA[AKIAIOSFODNN7EXAMPLE]]></access-key-id>
          <!-- 40 character secret -->
          <secret-access-key><![CDATA[wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY]]></secret-access-key>
          <base-domain><![CDATA[example.com]]></base-domain>
       </aws-s3>
       <!-- Domains that this profile applies to -->
       <domains>
          <domain name="bucket.example.com"/>
          <domain name="bucket2.example.com"/>
       </domains>
    </profile>
  </profiles>

src/mod/applications/mod_http_cache/aws.c
src/mod/applications/mod_http_cache/aws.h
src/mod/applications/mod_http_cache/conf/autoload_configs/http_cache.conf.xml
src/mod/applications/mod_http_cache/mod_http_cache.c
src/mod/applications/mod_http_cache/test_aws/main.c

index 58c4b4d9171035a3808f2058d15a45fdd82839e0..cb637f8ad0505bbbab5874e5ac7bcdca8b36fd1f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * aws.c for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
- * Copyright (C) 2013, Grasshopper
+ * Copyright (C) 2013-2014, Grasshopper
  *
  * Version: MPL 1.1
  *
  * @param url to check
  * @return true if this is an S3 url
  */
-int aws_s3_is_s3_url(const char *url)
+int aws_s3_is_s3_url(const char *url, const char *base_domain)
 {
+       if (!zstr(base_domain)) {
+               char *base_domain_escaped;
+               char regex[1024];
+               int result;
+               base_domain_escaped = switch_string_replace(base_domain, ".", "\\.");
+               switch_snprintf(regex, 1024, "^https?://\\w[-\\w.]{1,61}\\w\\.%s/.*$", base_domain_escaped);
+               result = !zstr(url) && switch_regex_match(url, regex) == SWITCH_STATUS_SUCCESS;
+               switch_safe_free(base_domain_escaped);
+               return result;
+       }
        /* AWS bucket naming rules are complex... this match only supports virtual hosting of buckets */
        return !zstr(url) && switch_regex_match(url, "^https?://\\w[-\\w.]{1,61}\\w\\.s3([-\\w]+)?\\.amazonaws\\.com/.*$") == SWITCH_STATUS_SUCCESS;
 }
@@ -141,10 +151,11 @@ static char *my_strrstr(const char *haystack, const char *needle)
 /**
  * Parse bucket and object from URL
  * @param url to parse.  This value is modified.
+ * @param base_domain of URL (assumes s3.amazonaws.com if not specified)
  * @param bucket to store result in
  * @param bucket_length of result buffer
  */
-void aws_s3_parse_url(char *url, char **bucket, char **object)
+void aws_s3_parse_url(char *url, const char *base_domain, char **bucket, char **object)
 {
        char *bucket_start = NULL;
        char *bucket_end;
@@ -153,7 +164,7 @@ void aws_s3_parse_url(char *url, char **bucket, char **object)
        *bucket = NULL;
        *object = NULL;
 
-       if (!aws_s3_is_s3_url(url)) {
+       if (!aws_s3_is_s3_url(url, base_domain)) {
                return;
        }
 
@@ -167,8 +178,15 @@ void aws_s3_parse_url(char *url, char **bucket, char **object)
                /* invalid URL */
                return;
        }
-       
-       bucket_end = my_strrstr(bucket_start, ".s3");
+
+       {
+               char base_domain_match[1024];
+               if (zstr(base_domain)) {
+                       base_domain = "s3";
+               }
+               switch_snprintf(base_domain_match, 1024, ".%s", base_domain);
+               bucket_end = my_strrstr(bucket_start, base_domain_match);
+       }
        if (!bucket_end) {
                /* invalid URL */
                return;
@@ -195,6 +213,7 @@ void aws_s3_parse_url(char *url, char **bucket, char **object)
  * Create a pre-signed URL for AWS S3
  * @param verb (PUT/GET)
  * @param url address (virtual-host-style)
+ * @param base_domain (optional - amazon aws assumed if not specified)
  * @param content_type optional content type
  * @param content_md5 optional content MD5 checksum
  * @param aws_access_key_id secret access key identifier
@@ -202,7 +221,7 @@ void aws_s3_parse_url(char *url, char **bucket, char **object)
  * @param expires seconds since the epoch
  * @return presigned_url
  */
-char *aws_s3_presigned_url_create(const char *verb, const char *url, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires)
+char *aws_s3_presigned_url_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires)
 {
        char signature[S3_SIGNATURE_LENGTH_MAX];
        char signature_url_encoded[S3_SIGNATURE_LENGTH_MAX];
@@ -212,7 +231,7 @@ char *aws_s3_presigned_url_create(const char *verb, const char *url, const char
        char *object;
 
        /* create URL encoded signature */
-       aws_s3_parse_url(url_dup, &bucket, &object);
+       aws_s3_parse_url(url_dup, base_domain, &bucket, &object);
        string_to_sign = aws_s3_string_to_sign(verb, bucket, object, content_type, content_md5, expires);
        signature[0] = '\0';
        aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, string_to_sign, aws_secret_access_key);
@@ -230,6 +249,7 @@ char *aws_s3_presigned_url_create(const char *verb, const char *url, const char
  * @param authentication_length maximum result length
  * @param verb (PUT/GET)
  * @param url address (virtual-host-style)
+ * @param base_domain (optional - amazon aws assumed if not specified)
  * @param content_type optional content type
  * @param content_md5 optional content MD5 checksum
  * @param aws_access_key_id secret access key identifier
@@ -237,7 +257,7 @@ char *aws_s3_presigned_url_create(const char *verb, const char *url, const char
  * @param date header
  * @return signature for Authorization header
  */
-char *aws_s3_authentication_create(const char *verb, const char *url, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *date)
+char *aws_s3_authentication_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *date)
 {
        char signature[S3_SIGNATURE_LENGTH_MAX];
        char *string_to_sign;
@@ -246,7 +266,7 @@ char *aws_s3_authentication_create(const char *verb, const char *url, const char
        char *object;
 
        /* create base64 encoded signature */
-       aws_s3_parse_url(url_dup, &bucket, &object);
+       aws_s3_parse_url(url_dup, base_domain, &bucket, &object);
        string_to_sign = aws_s3_string_to_sign(verb, bucket, object, content_type, content_md5, date);
        signature[0] = '\0';
        aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, string_to_sign, aws_secret_access_key);
index d86f75f1dcfcf1be3c5ebe14b6f287e32b1e913f..e95001d02681cea2656352664a9a75462f29e253 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
- * Copyright (C) 2013, Grasshopper
+ * Copyright (C) 2013-2014, Grasshopper
  *
  * Version: MPL 1.1
  *
 /* (SHA1_LENGTH * 1.37 base64 bytes per byte * 3 url-encoded bytes per byte) */
 #define S3_SIGNATURE_LENGTH_MAX 83
 
-int aws_s3_is_s3_url(const char *url);
-void aws_s3_parse_url(char *url, char **bucket, char **object);
+int aws_s3_is_s3_url(const char *url, const char *base_domain);
+void aws_s3_parse_url(char *url, const char *base_domain, char **bucket, char **object);
 char *aws_s3_string_to_sign(const char *verb, const char *bucket, const char *object, const char *content_type, const char *content_md5, const char *date);
 char *aws_s3_signature(char *signature, int signature_length, const char *string_to_sign, const char *aws_secret_access_key);
-char *aws_s3_presigned_url_create(const char *verb, const char *url, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires);
-char *aws_s3_authentication_create(const char *verb, const char *url, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *date);
+char *aws_s3_presigned_url_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires);
+char *aws_s3_authentication_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *date);
 
 #endif
 
index ce68ea09b00b2058d9ae8c2a6775860f3e58f385..6e2698bf908b83074848836864102b57de826a50 100644 (file)
@@ -18,6 +18,7 @@
           <access-key-id><![CDATA[AKIAIOSFODNN7EXAMPLE]]></access-key-id>
           <!-- 40 character secret -->
           <secret-access-key><![CDATA[wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY]]></secret-access-key>
+          <!--base-url><![CDATA[s3.example.com]]></base-url-->
        </aws-s3>
        <!-- Domains that this profile applies to -->
        <domains>
index f1ec2b69ed878ef789296c0749edf364b199c346..8a7718ea49d86aa7d22aaf6ae1ee4d2f77280742 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
- * Copyright (C) 2005-2013, Anthony Minessale II <anthm@freeswitch.org>
+ * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
  *
  * Version: MPL 1.1
  *
@@ -61,6 +61,7 @@ struct http_profile {
        const char *name;
        const char *aws_s3_access_key_id;
        const char *aws_s3_secret_access_key;
+       const char *aws_s3_base_domain;
 };
 typedef struct http_profile http_profile_t;
 
@@ -201,7 +202,7 @@ static void url_cache_unlock(url_cache_t *cache, switch_core_session_t *session)
 static void url_cache_clear(url_cache_t *cache, switch_core_session_t *session);
 static http_profile_t *url_cache_http_profile_find(url_cache_t *cache, const char *name);
 static http_profile_t *url_cache_http_profile_find_by_fqdn(url_cache_t *cache, const char *url);
-static http_profile_t *url_cache_http_profile_add(url_cache_t *cache, const char *name, const char *aws_s3_access_key_id, const char *aws_s3_secret_access_key);
+static http_profile_t *url_cache_http_profile_add(url_cache_t *cache, const char *name, const char *aws_s3_access_key_id, const char *aws_s3_secret_access_key, const char *aws_s3_base_domain);
 
 static switch_curl_slist_t *append_aws_s3_headers(switch_curl_slist_t *headers, http_profile_t *profile, const char *verb, const char *content_type, const char *url);
 
@@ -792,7 +793,7 @@ static http_profile_t *url_cache_http_profile_find_by_fqdn(url_cache_t *cache, c
 /**
  * Add a profile to the cache
  */
-static http_profile_t *url_cache_http_profile_add(url_cache_t *cache, const char *name, const char *aws_s3_access_key_id, const char *aws_s3_secret_access_key)
+static http_profile_t *url_cache_http_profile_add(url_cache_t *cache, const char *name, const char *aws_s3_access_key_id, const char *aws_s3_secret_access_key, const char *aws_s3_base_domain)
 {
        http_profile_t *profile = switch_core_alloc(cache->pool, sizeof(*profile));
        profile->name = switch_core_strdup(cache->pool, name);
@@ -802,6 +803,10 @@ static http_profile_t *url_cache_http_profile_add(url_cache_t *cache, const char
        if (aws_s3_secret_access_key) {
                profile->aws_s3_secret_access_key = switch_core_strdup(cache->pool, aws_s3_secret_access_key);
        }
+       if (aws_s3_base_domain) {
+               profile->aws_s3_base_domain = switch_core_strdup(cache->pool, aws_s3_base_domain);
+       }
+
        switch_core_hash_insert(cache->profiles, profile->name, profile);
        return profile;
 }
@@ -919,7 +924,7 @@ static void cached_url_destroy(cached_url_t *url, switch_memory_pool_t *pool)
 static switch_curl_slist_t *append_aws_s3_headers(switch_curl_slist_t *headers, http_profile_t *profile, const char *verb, const char *content_type, const char *url)
 {
        /* check if Amazon headers are needed */
-       if (profile && profile->aws_s3_access_key_id && aws_s3_is_s3_url(url)) {
+       if (profile && profile->aws_s3_access_key_id && aws_s3_is_s3_url(url, profile->aws_s3_base_domain)) {
                char date[256];
                char header[1024];
                char *authenticate;
@@ -930,7 +935,7 @@ static switch_curl_slist_t *append_aws_s3_headers(switch_curl_slist_t *headers,
                headers = switch_curl_slist_append(headers, header);
 
                /* Authorization: */
-               authenticate = aws_s3_authentication_create(verb, url, content_type, "", profile->aws_s3_access_key_id, profile->aws_s3_secret_access_key, date);
+               authenticate = aws_s3_authentication_create(verb, url, profile->aws_s3_base_domain, content_type, "", profile->aws_s3_access_key_id, profile->aws_s3_secret_access_key, date);
                snprintf(header, 1024, "Authorization: %s", authenticate);
                free(authenticate);
                headers = switch_curl_slist_append(headers, header);
@@ -1397,7 +1402,9 @@ static switch_status_t do_config(url_cache_t *cache)
                                switch_xml_t s3 = switch_xml_child(profile, "aws-s3");
                                char *access_key_id = NULL;
                                char *secret_access_key = NULL;
+                               char *base_domain = NULL;
                                if (s3) {
+                                       switch_xml_t base_domain_xml = switch_xml_child(s3, "base-domain");
                                        switch_xml_t id = switch_xml_child(s3, "access-key-id");
                                        switch_xml_t secret = switch_xml_child(s3, "secret-access-key");
                                        if (id && secret) {
@@ -1412,12 +1419,21 @@ static switch_status_t do_config(url_cache_t *cache)
                                                }
                                        } else {
                                                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing key id or secret\n");
+                                               continue;
+                                       }
+                                       if (base_domain_xml) {
+                                               base_domain = switch_strip_whitespace(switch_xml_txt(base_domain_xml));
+                                               if (zstr(base_domain)) {
+                                                       switch_safe_free(base_domain);
+                                                       base_domain = NULL;
+                                               }
                                        }
                                }
                                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding profile \"%s\" to cache\n", name);
-                               profile_obj = url_cache_http_profile_add(cache, name, access_key_id, secret_access_key);
+                               profile_obj = url_cache_http_profile_add(cache, name, access_key_id, secret_access_key, base_domain);
                                switch_safe_free(access_key_id);
                                switch_safe_free(secret_access_key);
+                               switch_safe_free(base_domain);
 
                                domains = switch_xml_child(profile, "domains");
                                if (domains) {
index f661c5954ffd8b0c6a5053683aaed0dd3cd27b53..e36c746819392f7366e8a1d2b988820bc03021ef 100644 (file)
@@ -41,24 +41,28 @@ static void test_signature(void)
  */
 static void test_check_url(void)
 {
-       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3-us-west-1.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3-us-west-1.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object"));
-       ASSERT_TRUE(aws_s3_is_s3_url("http://red.bucket.s3.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/object.ext"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/object"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/recordings/1240fwjf8we.mp3"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/en/us/8000/1232345.mp3"));
-       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket_with_underscore.s3.amazonaws.com/en/us/8000/1232345.mp3"));
-       ASSERT_FALSE(aws_s3_is_s3_url("bucket.s3.amazonaws.com/object.ext"));
-       ASSERT_FALSE(aws_s3_is_s3_url("https://s3.amazonaws.com/bucket/object"));
-       ASSERT_FALSE(aws_s3_is_s3_url("http://s3.amazonaws.com/bucket/object"));
-       ASSERT_FALSE(aws_s3_is_s3_url("http://google.com/"));
-       ASSERT_FALSE(aws_s3_is_s3_url("http://phono.com/audio/troporocks.mp3"));
-       ASSERT_FALSE(aws_s3_is_s3_url(""));
-       ASSERT_FALSE(aws_s3_is_s3_url(NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3-us-west-1.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3-us-west-1.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.s3.amazonaws.com/object", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://red.bucket.s3.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/object.ext", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/object", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/recordings/1240fwjf8we.mp3", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket.s3.amazonaws.com/en/us/8000/1232345.mp3", NULL));
+       ASSERT_TRUE(aws_s3_is_s3_url("https://bucket_with_underscore.s3.amazonaws.com/en/us/8000/1232345.mp3", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("bucket.s3.amazonaws.com/object.ext", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("https://s3.amazonaws.com/bucket/object", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("http://s3.amazonaws.com/bucket/object", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("http://google.com/", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("http://phono.com/audio/troporocks.mp3", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("", NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url(NULL, NULL));
+       ASSERT_FALSE(aws_s3_is_s3_url("https://example.com/bucket/object", "example.com"));
+       ASSERT_TRUE(aws_s3_is_s3_url("http://bucket.example.com/object", "example.com"));
+       ASSERT_FALSE(aws_s3_is_s3_url("", "example.com"));
+       ASSERT_FALSE(aws_s3_is_s3_url(NULL, "example.com"));
 }
 
 /**
@@ -68,51 +72,55 @@ static void test_parse_url(void)
 {
        char *bucket;
        char *object;
-       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/nelson"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/nelson"), NULL, &bucket, &object);
        ASSERT_STRING_EQUALS("quotes", bucket);
        ASSERT_STRING_EQUALS("nelson", object);
 
-       aws_s3_parse_url(strdup("https://quotes.s3.amazonaws.com/nelson.mp3"), &bucket, &object);
+       aws_s3_parse_url(strdup("https://quotes.s3.amazonaws.com/nelson.mp3"), NULL, &bucket, &object);
        ASSERT_STRING_EQUALS("quotes", bucket);
        ASSERT_STRING_EQUALS("nelson.mp3", object);
 
-       aws_s3_parse_url(strdup("http://s3.amazonaws.com/quotes/nelson"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://s3.amazonaws.com/quotes/nelson"), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup("http://quotes/quotes/nelson"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes/quotes/nelson"), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/"), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com"), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup("http://quotes"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes"), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup(""), &bucket, &object);
+       aws_s3_parse_url(strdup(""), NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(NULL, &bucket, &object);
+       aws_s3_parse_url(NULL, NULL, &bucket, &object);
        ASSERT_NULL(bucket);
        ASSERT_NULL(object);
 
-       aws_s3_parse_url(strdup("http://bucket.s3.amazonaws.com/voicemails/recording.wav"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://bucket.s3.amazonaws.com/voicemails/recording.wav"), NULL, &bucket, &object);
        ASSERT_STRING_EQUALS("bucket", bucket);
        ASSERT_STRING_EQUALS("voicemails/recording.wav", object);
 
-       aws_s3_parse_url(strdup("https://my-bucket-with-dash.s3-us-west-2.amazonaws.com/greeting/file/1002/Lumino.mp3"), &bucket, &object);
+       aws_s3_parse_url(strdup("https://my-bucket-with-dash.s3-us-west-2.amazonaws.com/greeting/file/1002/Lumino.mp3"), NULL, &bucket, &object);
        ASSERT_STRING_EQUALS("my-bucket-with-dash", bucket);
        ASSERT_STRING_EQUALS("greeting/file/1002/Lumino.mp3", object);
        
-       aws_s3_parse_url(strdup("http://quotes.s3.foo.bar.s3.amazonaws.com/greeting/file/1002/Lumino.mp3"), &bucket, &object);
+       aws_s3_parse_url(strdup("http://quotes.s3.foo.bar.s3.amazonaws.com/greeting/file/1002/Lumino.mp3"), NULL, &bucket, &object);
+       ASSERT_STRING_EQUALS("quotes.s3.foo.bar", bucket);
+       ASSERT_STRING_EQUALS("greeting/file/1002/Lumino.mp3", object);
+
+       aws_s3_parse_url(strdup("http://quotes.s3.foo.bar.example.com/greeting/file/1002/Lumino.mp3"), "example.com", &bucket, &object);
        ASSERT_STRING_EQUALS("quotes.s3.foo.bar", bucket);
        ASSERT_STRING_EQUALS("greeting/file/1002/Lumino.mp3", object);
 }
@@ -122,7 +130,9 @@ static void test_parse_url(void)
  */
 static void test_authorization_header(void)
 {
-       ASSERT_STRING_EQUALS("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", aws_s3_authentication_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", aws_s3_authentication_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", NULL, "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", aws_s3_authentication_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "s3.amazonaws.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", aws_s3_authentication_create("GET", "https://vault.example.com/awesome.mp3", "example.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
 }
 
 /**
@@ -130,7 +140,9 @@ static void test_authorization_header(void)
  */
 static void test_presigned_url(void)
 {
-       ASSERT_STRING_EQUALS("https://vault.s3.amazonaws.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", aws_s3_presigned_url_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("https://vault.s3.amazonaws.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", aws_s3_presigned_url_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", NULL, "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("https://vault.s3.amazonaws.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", aws_s3_presigned_url_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "s3.amazonaws.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
+       ASSERT_STRING_EQUALS("https://vault.example.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", aws_s3_presigned_url_create("GET", "https://vault.example.com/awesome.mp3", "example.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890"));
 }
 
 /**