mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-07-26 19:57:42 +00:00
mod_http_cache: added native Amazon S3 support
This commit is contained in:
parent
fe000f18fd
commit
8c8b2176d2
@ -1,4 +1,9 @@
|
|||||||
BASE=../../../..
|
BASE=../../../..
|
||||||
|
|
||||||
|
LOCAL_OBJS= \
|
||||||
|
aws.o
|
||||||
|
LOCAL_SOURCES= \
|
||||||
|
aws.c
|
||||||
|
|
||||||
include $(BASE)/build/modmake.rules
|
include $(BASE)/build/modmake.rules
|
||||||
|
|
||||||
|
238
src/mod/applications/mod_http_cache/aws.c
Normal file
238
src/mod/applications/mod_http_cache/aws.c
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* aws.c for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2013, Grasshopper
|
||||||
|
*
|
||||||
|
* Version: MPL 1.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is aws.c for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is Grasshopper
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Chris Rienzo <chris.rienzo@grasshopper.com>
|
||||||
|
*
|
||||||
|
* aws.c -- Some Amazon Web Services helper functions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "aws.h"
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#if defined(HAVE_OPENSSL)
|
||||||
|
#include <openssl/hmac.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 160 bits / 8 bits per byte */
|
||||||
|
#define SHA1_LENGTH 20
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url to check
|
||||||
|
* @return true if this is an S3 url
|
||||||
|
*/
|
||||||
|
int aws_s3_is_s3_url(const char *url)
|
||||||
|
{
|
||||||
|
/* AWS bucket naming rules are complex... this match only supports virtual hosting of buckets */
|
||||||
|
return !zstr(url) && switch_regex_match(url, "^https?://[a-z0-9][-a-z0-9.]{1,61}[a-z0-9]\\.s3\\.amazonaws\\.com/.*$") == SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the string to sign for a AWS signature calculation
|
||||||
|
* @param verb (PUT/GET)
|
||||||
|
* @param bucket bucket object is stored in
|
||||||
|
* @param object to access (filename.ext)
|
||||||
|
* @param content_type optional content type
|
||||||
|
* @param content_md5 optional content MD5 checksum
|
||||||
|
* @param date header
|
||||||
|
* @return the string_to_sign (must be freed)
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* String to sign has the following format:
|
||||||
|
* <HTTP-VERB>\n<Content-MD5>\n<Content-Type>\n<Expires/Date>\n/bucket/object
|
||||||
|
*/
|
||||||
|
return switch_mprintf("%s\n%s\n%s\n%s\n/%s/%s",
|
||||||
|
verb, content_md5 ? content_md5 : "", content_type ? content_type : "",
|
||||||
|
date, bucket, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the AWS S3 signature
|
||||||
|
* @param signature buffer to store the signature
|
||||||
|
* @param signature_length length of signature buffer
|
||||||
|
* @param string_to_sign
|
||||||
|
* @param aws_secret_access_key secret access key
|
||||||
|
* @return the signature buffer or NULL if missing input
|
||||||
|
*/
|
||||||
|
char *aws_s3_signature(char *signature, int signature_length, const char *string_to_sign, const char *aws_secret_access_key)
|
||||||
|
{
|
||||||
|
#if defined(HAVE_OPENSSL)
|
||||||
|
unsigned int signature_raw_length = SHA1_LENGTH;
|
||||||
|
char signature_raw[SHA1_LENGTH];
|
||||||
|
signature_raw[0] = '\0';
|
||||||
|
if (!signature || signature_length <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (zstr(aws_secret_access_key)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!string_to_sign) {
|
||||||
|
string_to_sign = "";
|
||||||
|
}
|
||||||
|
HMAC(EVP_sha1(),
|
||||||
|
aws_secret_access_key,
|
||||||
|
strlen(aws_secret_access_key),
|
||||||
|
(const unsigned char *)string_to_sign,
|
||||||
|
strlen(string_to_sign),
|
||||||
|
(unsigned char *)signature_raw,
|
||||||
|
&signature_raw_length);
|
||||||
|
|
||||||
|
/* convert result to base64 */
|
||||||
|
memset(signature, 0, signature_length);
|
||||||
|
switch_b64_encode((unsigned char *)signature_raw, signature_raw_length, (unsigned char *)signature, signature_length);
|
||||||
|
#endif
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse bucket and object from URL
|
||||||
|
* @param url to parse. This value is modified.
|
||||||
|
* @param bucket to store result in
|
||||||
|
* @param bucket_length of result buffer
|
||||||
|
*/
|
||||||
|
void aws_s3_parse_url(char *url, char **bucket, char **object)
|
||||||
|
{
|
||||||
|
char *bucket_start;
|
||||||
|
char *bucket_end;
|
||||||
|
char *object_start;
|
||||||
|
|
||||||
|
*bucket = NULL;
|
||||||
|
*object = NULL;
|
||||||
|
|
||||||
|
if (!aws_s3_is_s3_url(url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expect: http(s)://bucket.s3.amazonaws.com/object */
|
||||||
|
bucket_start = strstr(url, "://");
|
||||||
|
if (!bucket_start) {
|
||||||
|
/* invalid URL */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bucket_start += 3;
|
||||||
|
|
||||||
|
bucket_end = strchr(bucket_start, '.');
|
||||||
|
if (!bucket_end) {
|
||||||
|
/* invalid URL */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*bucket_end = '\0';
|
||||||
|
|
||||||
|
object_start = strchr(bucket_end + 1, '/');
|
||||||
|
if (!object_start) {
|
||||||
|
/* invalid URL */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object_start++;
|
||||||
|
|
||||||
|
if (strchr(object_start, '/')) {
|
||||||
|
/* invalid URL */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zstr(bucket_start) || zstr(object_start)) {
|
||||||
|
/* invalid URL */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*bucket = bucket_start;
|
||||||
|
*object = object_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a pre-signed URL for AWS S3
|
||||||
|
* @param verb (PUT/GET)
|
||||||
|
* @param url address (virtual-host-style)
|
||||||
|
* @param content_type optional content type
|
||||||
|
* @param content_md5 optional content MD5 checksum
|
||||||
|
* @param aws_access_key_id secret access key identifier
|
||||||
|
* @param aws_secret_access_key secret access key
|
||||||
|
* @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 signature[S3_SIGNATURE_LENGTH_MAX];
|
||||||
|
char signature_url_encoded[S3_SIGNATURE_LENGTH_MAX];
|
||||||
|
char *string_to_sign;
|
||||||
|
char *url_dup = strdup(url);
|
||||||
|
char *bucket;
|
||||||
|
char *object;
|
||||||
|
|
||||||
|
/* create URL encoded signature */
|
||||||
|
aws_s3_parse_url(url_dup, &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);
|
||||||
|
switch_url_encode(signature, signature_url_encoded, S3_SIGNATURE_LENGTH_MAX);
|
||||||
|
free(string_to_sign);
|
||||||
|
free(url_dup);
|
||||||
|
|
||||||
|
/* create the presigned URL */
|
||||||
|
return switch_mprintf("%s?Signature=%s&Expires=%s&AWSAccessKeyId=%s", url, signature_url_encoded, expires, aws_access_key_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an authentication signature for AWS S3
|
||||||
|
* @param authentication buffer to store result
|
||||||
|
* @param authentication_length maximum result length
|
||||||
|
* @param verb (PUT/GET)
|
||||||
|
* @param url address (virtual-host-style)
|
||||||
|
* @param content_type optional content type
|
||||||
|
* @param content_md5 optional content MD5 checksum
|
||||||
|
* @param aws_access_key_id secret access key identifier
|
||||||
|
* @param aws_secret_access_key secret access key
|
||||||
|
* @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 signature[S3_SIGNATURE_LENGTH_MAX];
|
||||||
|
char *string_to_sign;
|
||||||
|
char *url_dup = strdup(url);
|
||||||
|
char *bucket;
|
||||||
|
char *object;
|
||||||
|
|
||||||
|
/* create base64 encoded signature */
|
||||||
|
aws_s3_parse_url(url_dup, &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);
|
||||||
|
free(string_to_sign);
|
||||||
|
free(url_dup);
|
||||||
|
|
||||||
|
return switch_mprintf("AWS %s:%s", aws_access_key_id, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For Emacs:
|
||||||
|
* Local Variables:
|
||||||
|
* mode:c
|
||||||
|
* indent-tabs-mode:t
|
||||||
|
* tab-width:4
|
||||||
|
* c-basic-offset:4
|
||||||
|
* End:
|
||||||
|
* For VIM:
|
||||||
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4
|
||||||
|
*/
|
55
src/mod/applications/mod_http_cache/aws.h
Normal file
55
src/mod/applications/mod_http_cache/aws.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2013, Grasshopper
|
||||||
|
*
|
||||||
|
* Version: MPL 1.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is Grasshopper
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Chris Rienzo <chris.rienzo@grasshopper.com>
|
||||||
|
*
|
||||||
|
* aws.h - Some Amazon Web Services helper functions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef AWS_H
|
||||||
|
#define AWS_H
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
/* (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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* For Emacs:
|
||||||
|
* Local Variables:
|
||||||
|
* mode:c
|
||||||
|
* indent-tabs-mode:t
|
||||||
|
* tab-width:4
|
||||||
|
* c-basic-offset:4
|
||||||
|
* End:
|
||||||
|
* For VIM:
|
||||||
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4
|
||||||
|
*/
|
@ -9,4 +9,19 @@
|
|||||||
<param name="ssl-verifyhost" value="true"/>
|
<param name="ssl-verifyhost" value="true"/>
|
||||||
<param name="ssl-verifypeer" value="true"/>
|
<param name="ssl-verifypeer" value="true"/>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile name="s3">
|
||||||
|
<!-- Credentials for AWS account. Reference by name when sending HTTP request -->
|
||||||
|
<!-- http_get {profile=s3}http://bucket.s3.amazonaws.com/object.wav -->
|
||||||
|
<!-- http_put {profile=s3}http://bucket.s3.amazonaws.com/object.wav /tmp/file.wav -->
|
||||||
|
<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>
|
||||||
|
<aws-s3>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
* Copyright (C) 2005-2012, Anthony Minessale II <anthm@freeswitch.org>
|
* Copyright (C) 2005-2013, Anthony Minessale II <anthm@freeswitch.org>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
@ -34,6 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <switch_curl.h>
|
#include <switch_curl.h>
|
||||||
|
#include "aws.h"
|
||||||
|
|
||||||
/* Defines module interface to FreeSWITCH */
|
/* Defines module interface to FreeSWITCH */
|
||||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_http_cache_shutdown);
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_http_cache_shutdown);
|
||||||
@ -49,6 +50,17 @@ SWITCH_STANDARD_API(http_cache_prefetch);
|
|||||||
|
|
||||||
typedef struct url_cache url_cache_t;
|
typedef struct url_cache url_cache_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An http profile. Defines optional credentials
|
||||||
|
* for access to Amazon S3.
|
||||||
|
*/
|
||||||
|
struct http_profile {
|
||||||
|
const char *name;
|
||||||
|
const char *aws_s3_access_key_id;
|
||||||
|
const char *aws_s3_secret_access_key;
|
||||||
|
};
|
||||||
|
typedef struct http_profile http_profile_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status if the cache entry
|
* status if the cache entry
|
||||||
*/
|
*/
|
||||||
@ -99,12 +111,12 @@ struct http_get_data {
|
|||||||
};
|
};
|
||||||
typedef struct http_get_data http_get_data_t;
|
typedef struct http_get_data http_get_data_t;
|
||||||
|
|
||||||
static switch_status_t http_get(url_cache_t *cache, cached_url_t *url, switch_core_session_t *session);
|
static switch_status_t http_get(url_cache_t *cache, http_profile_t *profile, cached_url_t *url, switch_core_session_t *session);
|
||||||
static size_t get_file_callback(void *ptr, size_t size, size_t nmemb, void *get);
|
static size_t get_file_callback(void *ptr, size_t size, size_t nmemb, void *get);
|
||||||
static size_t get_header_callback(void *ptr, size_t size, size_t nmemb, void *url);
|
static size_t get_header_callback(void *ptr, size_t size, size_t nmemb, void *url);
|
||||||
static void process_cache_control_header(cached_url_t *url, char *data);
|
static void process_cache_control_header(cached_url_t *url, char *data);
|
||||||
|
|
||||||
static switch_status_t http_put(url_cache_t *cache, switch_core_session_t *session, const char *url, const char *filename);
|
static switch_status_t http_put(url_cache_t *cache, http_profile_t *profile, switch_core_session_t *session, const char *url, const char *filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue used for clock cache replacement algorithm. This
|
* Queue used for clock cache replacement algorithm. This
|
||||||
@ -123,7 +135,6 @@ struct simple_queue {
|
|||||||
};
|
};
|
||||||
typedef struct simple_queue simple_queue_t;
|
typedef struct simple_queue simple_queue_t;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cache
|
* The cache
|
||||||
*/
|
*/
|
||||||
@ -138,6 +149,8 @@ struct url_cache {
|
|||||||
size_t size;
|
size_t size;
|
||||||
/** The location of the cache in the filesystem */
|
/** The location of the cache in the filesystem */
|
||||||
char *location;
|
char *location;
|
||||||
|
/** HTTP profiles */
|
||||||
|
switch_hash_t *profiles;
|
||||||
/** Cache mapped by URL */
|
/** Cache mapped by URL */
|
||||||
switch_hash_t *map;
|
switch_hash_t *map;
|
||||||
/** Cached URLs queued for replacement */
|
/** Cached URLs queued for replacement */
|
||||||
@ -173,7 +186,7 @@ struct url_cache {
|
|||||||
};
|
};
|
||||||
static url_cache_t gcache;
|
static url_cache_t gcache;
|
||||||
|
|
||||||
static char *url_cache_get(url_cache_t *cache, switch_core_session_t *session, const char *url, int download, switch_memory_pool_t *pool);
|
static char *url_cache_get(url_cache_t *cache, http_profile_t *profile, switch_core_session_t *session, const char *url, int download, switch_memory_pool_t *pool);
|
||||||
static switch_status_t url_cache_add(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
static switch_status_t url_cache_add(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
||||||
static void url_cache_remove(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
static void url_cache_remove(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
||||||
static void url_cache_remove_soft(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
static void url_cache_remove_soft(url_cache_t *cache, switch_core_session_t *session, cached_url_t *url);
|
||||||
@ -181,16 +194,21 @@ static switch_status_t url_cache_replace(url_cache_t *cache, switch_core_session
|
|||||||
static void url_cache_lock(url_cache_t *cache, switch_core_session_t *session);
|
static void url_cache_lock(url_cache_t *cache, switch_core_session_t *session);
|
||||||
static void url_cache_unlock(url_cache_t *cache, switch_core_session_t *session);
|
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 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 void 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 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put a file to the URL
|
* Put a file to the URL
|
||||||
* @param cache the cache
|
* @param cache the cache
|
||||||
|
* @param profile the HTTP profile
|
||||||
* @param session the (optional) session uploading the file
|
* @param session the (optional) session uploading the file
|
||||||
* @param url The URL
|
* @param url The URL
|
||||||
* @param filename The file to upload
|
* @param filename The file to upload
|
||||||
* @return SWITCH_STATUS_SUCCESS if successful
|
* @return SWITCH_STATUS_SUCCESS if successful
|
||||||
*/
|
*/
|
||||||
static switch_status_t http_put(url_cache_t *cache, switch_core_session_t *session, const char *url, const char *filename)
|
static switch_status_t http_put(url_cache_t *cache, http_profile_t *profile, switch_core_session_t *session, const char *url, const char *filename)
|
||||||
{
|
{
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
|
|
||||||
@ -215,6 +233,7 @@ static switch_status_t http_put(url_cache_t *cache, switch_core_session_t *sessi
|
|||||||
|
|
||||||
buf = switch_mprintf("Content-Type: %s", mime_type);
|
buf = switch_mprintf("Content-Type: %s", mime_type);
|
||||||
headers = switch_curl_slist_append(headers, buf);
|
headers = switch_curl_slist_append(headers, buf);
|
||||||
|
headers = append_aws_s3_headers(headers, profile, "PUT", mime_type, url);
|
||||||
|
|
||||||
/* open file and get the file size */
|
/* open file and get the file size */
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "opening %s for upload to %s\n", filename, url);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "opening %s for upload to %s\n", filename, url);
|
||||||
@ -316,6 +335,7 @@ static size_t get_file_callback(void *ptr, size_t size, size_t nmemb, void *get)
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* trim whitespace characters from string
|
* trim whitespace characters from string
|
||||||
* @param str the string to trim
|
* @param str the string to trim
|
||||||
@ -492,13 +512,14 @@ static void url_cache_clear(url_cache_t *cache, switch_core_session_t *session)
|
|||||||
/**
|
/**
|
||||||
* Get a URL from the cache, add it if it does not exist
|
* Get a URL from the cache, add it if it does not exist
|
||||||
* @param cache The cache
|
* @param cache The cache
|
||||||
|
* @param profile optional profile
|
||||||
* @param session the (optional) session requesting the URL
|
* @param session the (optional) session requesting the URL
|
||||||
* @param url The URL
|
* @param url The URL
|
||||||
* @param download If true, the file will be downloaded if it does not exist in the cache.
|
* @param download If true, the file will be downloaded if it does not exist in the cache.
|
||||||
* @param pool The pool to use for allocating the filename
|
* @param pool The pool to use for allocating the filename
|
||||||
* @return The filename or NULL if there is an error
|
* @return The filename or NULL if there is an error
|
||||||
*/
|
*/
|
||||||
static char *url_cache_get(url_cache_t *cache, switch_core_session_t *session, const char *url, int download, switch_memory_pool_t *pool)
|
static char *url_cache_get(url_cache_t *cache, http_profile_t *profile, switch_core_session_t *session, const char *url, int download, switch_memory_pool_t *pool)
|
||||||
{
|
{
|
||||||
char *filename = NULL;
|
char *filename = NULL;
|
||||||
cached_url_t *u = NULL;
|
cached_url_t *u = NULL;
|
||||||
@ -537,7 +558,7 @@ static char *url_cache_get(url_cache_t *cache, switch_core_session_t *session, c
|
|||||||
|
|
||||||
/* download the file */
|
/* download the file */
|
||||||
url_cache_unlock(cache, session);
|
url_cache_unlock(cache, session);
|
||||||
if (http_get(cache, u, session) == SWITCH_STATUS_SUCCESS) {
|
if (http_get(cache, profile, u, session) == SWITCH_STATUS_SUCCESS) {
|
||||||
/* Got the file, let the waiters know it is available */
|
/* Got the file, let the waiters know it is available */
|
||||||
url_cache_lock(cache, session);
|
url_cache_lock(cache, session);
|
||||||
u->status = CACHED_URL_AVAILABLE;
|
u->status = CACHED_URL_AVAILABLE;
|
||||||
@ -685,6 +706,33 @@ static void url_cache_remove(url_cache_t *cache, switch_core_session_t *session,
|
|||||||
cache->size -= url->size;
|
cache->size -= url->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a profile
|
||||||
|
*/
|
||||||
|
static http_profile_t *url_cache_http_profile_find(url_cache_t *cache, const char *name)
|
||||||
|
{
|
||||||
|
if (cache && !zstr(name)) {
|
||||||
|
return (http_profile_t *)switch_core_hash_find(cache->profiles, name);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a profile to the cache
|
||||||
|
*/
|
||||||
|
static void 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)
|
||||||
|
{
|
||||||
|
http_profile_t *profile = switch_core_alloc(cache->pool, sizeof(*profile));
|
||||||
|
profile->name = switch_core_strdup(cache->pool, name);
|
||||||
|
if (aws_s3_access_key_id) {
|
||||||
|
profile->aws_s3_access_key_id = switch_core_strdup(cache->pool, aws_s3_access_key_id);
|
||||||
|
}
|
||||||
|
if (aws_s3_secret_access_key) {
|
||||||
|
profile->aws_s3_secret_access_key = switch_core_strdup(cache->pool, aws_s3_secret_access_key);
|
||||||
|
}
|
||||||
|
switch_core_hash_insert(cache->profiles, profile->name, profile);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a cached URL entry
|
* Create a cached URL entry
|
||||||
* @param cache the cache
|
* @param cache the cache
|
||||||
@ -759,16 +807,49 @@ static void cached_url_destroy(cached_url_t *url, switch_memory_pool_t *pool)
|
|||||||
switch_safe_free(url);
|
switch_safe_free(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append Amazon S3 headers to request if necessary
|
||||||
|
* @param headers to add to. If NULL, new headers are created.
|
||||||
|
* @param profile with S3 credentials
|
||||||
|
* @param content_type of object (PUT only)
|
||||||
|
* @param verb (GET/PUT)
|
||||||
|
* @param url
|
||||||
|
* @return updated headers
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
char date[256];
|
||||||
|
char header[1024];
|
||||||
|
char *authenticate;
|
||||||
|
|
||||||
|
/* Date: */
|
||||||
|
switch_rfc822_date(date, switch_time_now());
|
||||||
|
snprintf(header, 1024, "Date: %s", date);
|
||||||
|
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);
|
||||||
|
snprintf(header, 1024, "Authorization: %s", authenticate);
|
||||||
|
free(authenticate);
|
||||||
|
headers = switch_curl_slist_append(headers, header);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a file via HTTP
|
* Fetch a file via HTTP
|
||||||
* @param cache the cache
|
* @param cache the cache
|
||||||
|
* @param profile the HTTP profile
|
||||||
* @param url The cached URL entry
|
* @param url The cached URL entry
|
||||||
* @param session the (optional) session
|
* @param session the (optional) session
|
||||||
* @return SWITCH_STATUS_SUCCESS if successful
|
* @return SWITCH_STATUS_SUCCESS if successful
|
||||||
*/
|
*/
|
||||||
static switch_status_t http_get(url_cache_t *cache, cached_url_t *url, switch_core_session_t *session)
|
static switch_status_t http_get(url_cache_t *cache, http_profile_t *profile, cached_url_t *url, switch_core_session_t *session)
|
||||||
{
|
{
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
|
switch_curl_slist_t *headers = NULL; /* optional linked-list of HTTP headers */
|
||||||
switch_CURL *curl_handle = NULL;
|
switch_CURL *curl_handle = NULL;
|
||||||
http_get_data_t get_data = {0};
|
http_get_data_t get_data = {0};
|
||||||
long httpRes = 0;
|
long httpRes = 0;
|
||||||
@ -778,12 +859,18 @@ static switch_status_t http_get(url_cache_t *cache, cached_url_t *url, switch_co
|
|||||||
get_data.fd = 0;
|
get_data.fd = 0;
|
||||||
get_data.url = url;
|
get_data.url = url;
|
||||||
|
|
||||||
|
/* add optional AWS S3 headers if necessary */
|
||||||
|
headers = append_aws_s3_headers(headers, profile, "GET", "", url->url);
|
||||||
|
|
||||||
curl_handle = switch_curl_easy_init();
|
curl_handle = switch_curl_easy_init();
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "opening %s for URL cache\n", get_data.url->filename);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "opening %s for URL cache\n", get_data.url->filename);
|
||||||
if ((get_data.fd = open(get_data.url->filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
|
if ((get_data.fd = open(get_data.url->filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
|
||||||
|
if (headers) {
|
||||||
|
switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
|
||||||
|
}
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_URL, get_data.url->url);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_URL, get_data.url->url);
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, get_file_callback);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, get_file_callback);
|
||||||
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &get_data);
|
switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &get_data);
|
||||||
@ -808,7 +895,8 @@ static switch_status_t http_get(url_cache_t *cache, cached_url_t *url, switch_co
|
|||||||
close(get_data.fd);
|
close(get_data.fd);
|
||||||
} else {
|
} else {
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "open() error: %s\n", strerror(errno));
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "open() error: %s\n", strerror(errno));
|
||||||
return SWITCH_STATUS_GENERR;
|
status = SWITCH_STATUS_GENERR;
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpRes == 200) {
|
if (httpRes == 200) {
|
||||||
@ -821,7 +909,14 @@ static switch_status_t http_get(url_cache_t *cache, cached_url_t *url, switch_co
|
|||||||
} else {
|
} else {
|
||||||
url->size = 0; // nothing downloaded or download interrupted
|
url->size = 0; // nothing downloaded or download interrupted
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Received HTTP error %ld trying to fetch %s\n", httpRes, url->url);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Received HTTP error %ld trying to fetch %s\n", httpRes, url->url);
|
||||||
return SWITCH_STATUS_GENERR;
|
status = SWITCH_STATUS_GENERR;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
switch_curl_slist_free_all(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -858,18 +953,13 @@ static void setup_dir(url_cache_t *cache)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int isUrl(const char *filename)
|
#define HTTP_PREFETCH_SYNTAX "{param=val}<url>"
|
||||||
{
|
|
||||||
return !zstr(filename) && (!strncmp("http://", filename, strlen("http://")) || !strncmp("https://", filename, strlen("https://")));
|
|
||||||
}
|
|
||||||
|
|
||||||
#define HTTP_PREFETCH_SYNTAX "<url>"
|
|
||||||
SWITCH_STANDARD_API(http_cache_prefetch)
|
SWITCH_STANDARD_API(http_cache_prefetch)
|
||||||
{
|
{
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
char *url;
|
char *url;
|
||||||
|
|
||||||
if (!isUrl(cmd)) {
|
if (zstr(cmd)) {
|
||||||
stream->write_function(stream, "USAGE: %s\n", HTTP_PREFETCH_SYNTAX);
|
stream->write_function(stream, "USAGE: %s\n", HTTP_PREFETCH_SYNTAX);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -887,7 +977,7 @@ SWITCH_STANDARD_API(http_cache_prefetch)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HTTP_GET_SYNTAX "<url>"
|
#define HTTP_GET_SYNTAX "{param=val}<url>"
|
||||||
/**
|
/**
|
||||||
* Get a file from the cache, download if it isn't cached
|
* Get a file from the cache, download if it isn't cached
|
||||||
*/
|
*/
|
||||||
@ -896,9 +986,12 @@ SWITCH_STANDARD_API(http_cache_get)
|
|||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
switch_memory_pool_t *lpool = NULL;
|
switch_memory_pool_t *lpool = NULL;
|
||||||
switch_memory_pool_t *pool = NULL;
|
switch_memory_pool_t *pool = NULL;
|
||||||
|
http_profile_t *profile = NULL;
|
||||||
char *filename;
|
char *filename;
|
||||||
|
switch_event_t *params = NULL;
|
||||||
|
char *url;
|
||||||
|
|
||||||
if (!isUrl(cmd)) {
|
if (zstr(cmd)) {
|
||||||
stream->write_function(stream, "USAGE: %s\n", HTTP_GET_SYNTAX);
|
stream->write_function(stream, "USAGE: %s\n", HTTP_GET_SYNTAX);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -910,7 +1003,16 @@ SWITCH_STANDARD_API(http_cache_get)
|
|||||||
pool = lpool;
|
pool = lpool;
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = url_cache_get(&gcache, session, cmd, 1, pool);
|
/* parse params and get profile */
|
||||||
|
url = switch_core_strdup(pool, cmd);
|
||||||
|
if (*url == '{') {
|
||||||
|
switch_event_create_brackets(url, '{', '}', ',', ¶ms, &url, SWITCH_FALSE);
|
||||||
|
}
|
||||||
|
if (params) {
|
||||||
|
profile = url_cache_http_profile_find(&gcache, switch_event_get_header(params, "profile"));
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = url_cache_get(&gcache, profile, session, url, 1, pool);
|
||||||
if (filename) {
|
if (filename) {
|
||||||
stream->write_function(stream, "%s", filename);
|
stream->write_function(stream, "%s", filename);
|
||||||
|
|
||||||
@ -923,10 +1025,14 @@ SWITCH_STANDARD_API(http_cache_get)
|
|||||||
switch_core_destroy_memory_pool(&lpool);
|
switch_core_destroy_memory_pool(&lpool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
switch_event_destroy(¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HTTP_TRYGET_SYNTAX "<url>"
|
#define HTTP_TRYGET_SYNTAX "{param=val}<url>"
|
||||||
/**
|
/**
|
||||||
* Get a file from the cache, fail if download is needed
|
* Get a file from the cache, fail if download is needed
|
||||||
*/
|
*/
|
||||||
@ -935,9 +1041,12 @@ SWITCH_STANDARD_API(http_cache_tryget)
|
|||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
switch_memory_pool_t *lpool = NULL;
|
switch_memory_pool_t *lpool = NULL;
|
||||||
switch_memory_pool_t *pool = NULL;
|
switch_memory_pool_t *pool = NULL;
|
||||||
|
http_profile_t *profile = NULL;
|
||||||
char *filename;
|
char *filename;
|
||||||
|
switch_event_t *params = NULL;
|
||||||
|
char *url;
|
||||||
|
|
||||||
if (!isUrl(cmd)) {
|
if (zstr(cmd)) {
|
||||||
stream->write_function(stream, "USAGE: %s\n", HTTP_GET_SYNTAX);
|
stream->write_function(stream, "USAGE: %s\n", HTTP_GET_SYNTAX);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -949,7 +1058,16 @@ SWITCH_STANDARD_API(http_cache_tryget)
|
|||||||
pool = lpool;
|
pool = lpool;
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = url_cache_get(&gcache, session, cmd, 0, pool);
|
/* parse params and get profile */
|
||||||
|
url = switch_core_strdup(pool, cmd);
|
||||||
|
if (*url == '{') {
|
||||||
|
switch_event_create_brackets(url, '{', '}', ',', ¶ms, &url, SWITCH_FALSE);
|
||||||
|
}
|
||||||
|
if (params) {
|
||||||
|
profile = url_cache_http_profile_find(&gcache, switch_event_get_header(params, "profile"));
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = url_cache_get(&gcache, profile, session, url, 0, pool);
|
||||||
if (filename) {
|
if (filename) {
|
||||||
if (!strcmp(DOWNLOAD_NEEDED, filename)) {
|
if (!strcmp(DOWNLOAD_NEEDED, filename)) {
|
||||||
stream->write_function(stream, "-ERR %s\n", DOWNLOAD_NEEDED);
|
stream->write_function(stream, "-ERR %s\n", DOWNLOAD_NEEDED);
|
||||||
@ -965,21 +1083,37 @@ SWITCH_STANDARD_API(http_cache_tryget)
|
|||||||
switch_core_destroy_memory_pool(&lpool);
|
switch_core_destroy_memory_pool(&lpool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
switch_event_destroy(¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HTTP_PUT_SYNTAX "<url> <file>"
|
#define HTTP_PUT_SYNTAX "{param=val}<url> <file>"
|
||||||
/**
|
/**
|
||||||
* Put a file to the server
|
* Put a file to the server
|
||||||
*/
|
*/
|
||||||
SWITCH_STANDARD_API(http_cache_put)
|
SWITCH_STANDARD_API(http_cache_put)
|
||||||
{
|
{
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
|
http_profile_t *profile = NULL;
|
||||||
|
switch_memory_pool_t *lpool = NULL;
|
||||||
|
switch_memory_pool_t *pool = NULL;
|
||||||
char *args = NULL;
|
char *args = NULL;
|
||||||
char *argv[10] = { 0 };
|
char *argv[10] = { 0 };
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
|
switch_event_t *params = NULL;
|
||||||
|
char *url;
|
||||||
|
|
||||||
if (zstr(cmd) || (strncmp("http://", cmd, strlen("http://")) && strncmp("https://", cmd, strlen("https://")))) {
|
if (session) {
|
||||||
|
pool = switch_core_session_get_pool(session);
|
||||||
|
} else {
|
||||||
|
switch_core_new_memory_pool(&lpool);
|
||||||
|
pool = lpool;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zstr(cmd)) {
|
||||||
stream->write_function(stream, "USAGE: %s\n", HTTP_PUT_SYNTAX);
|
stream->write_function(stream, "USAGE: %s\n", HTTP_PUT_SYNTAX);
|
||||||
status = SWITCH_STATUS_SUCCESS;
|
status = SWITCH_STATUS_SUCCESS;
|
||||||
goto done;
|
goto done;
|
||||||
@ -993,7 +1127,16 @@ SWITCH_STANDARD_API(http_cache_put)
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = http_put(&gcache, session, argv[0], argv[1]);
|
/* parse params and get profile */
|
||||||
|
url = switch_core_strdup(pool, argv[0]);
|
||||||
|
if (*url == '{') {
|
||||||
|
switch_event_create_brackets(url, '{', '}', ',', ¶ms, &url, SWITCH_FALSE);
|
||||||
|
}
|
||||||
|
if (params) {
|
||||||
|
profile = url_cache_http_profile_find(&gcache, switch_event_get_header(params, "profile"));
|
||||||
|
}
|
||||||
|
|
||||||
|
status = http_put(&gcache, profile, session, url, argv[1]);
|
||||||
if (status == SWITCH_STATUS_SUCCESS) {
|
if (status == SWITCH_STATUS_SUCCESS) {
|
||||||
stream->write_function(stream, "+OK\n");
|
stream->write_function(stream, "+OK\n");
|
||||||
} else {
|
} else {
|
||||||
@ -1002,6 +1145,15 @@ SWITCH_STANDARD_API(http_cache_put)
|
|||||||
|
|
||||||
done:
|
done:
|
||||||
switch_safe_free(args);
|
switch_safe_free(args);
|
||||||
|
|
||||||
|
if (lpool) {
|
||||||
|
switch_core_destroy_memory_pool(&lpool);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
switch_event_destroy(¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,7 +1218,7 @@ static void *SWITCH_THREAD_FUNC prefetch_thread(switch_thread_t *thread, void *o
|
|||||||
static switch_status_t do_config(url_cache_t *cache)
|
static switch_status_t do_config(url_cache_t *cache)
|
||||||
{
|
{
|
||||||
char *cf = "http_cache.conf";
|
char *cf = "http_cache.conf";
|
||||||
switch_xml_t cfg, xml, param, settings;
|
switch_xml_t cfg, xml, param, settings, profiles;
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
int max_urls;
|
int max_urls;
|
||||||
switch_time_t default_max_age_sec;
|
switch_time_t default_max_age_sec;
|
||||||
@ -1128,6 +1280,39 @@ static switch_status_t do_config(url_cache_t *cache)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* get profiles */
|
||||||
|
profiles = switch_xml_child(cfg, "profiles");
|
||||||
|
if (profiles) {
|
||||||
|
switch_xml_t profile;
|
||||||
|
for (profile = switch_xml_child(profiles, "profile"); profile; profile = profile->next) {
|
||||||
|
const char *name = switch_xml_attr_soft(profile, "name");
|
||||||
|
if (!zstr(name)) {
|
||||||
|
switch_xml_t s3 = switch_xml_child(profile, "aws-s3");
|
||||||
|
const char *access_key_id = NULL;
|
||||||
|
const char *secret_access_key = NULL;
|
||||||
|
if (s3) {
|
||||||
|
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) {
|
||||||
|
access_key_id = switch_xml_txt(id);
|
||||||
|
secret_access_key = switch_xml_txt(secret);
|
||||||
|
if (!access_key_id || !secret_access_key) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing aws s3 credentials for profile \"%s\"\n", name);
|
||||||
|
access_key_id = NULL;
|
||||||
|
secret_access_key = NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing key id or secret\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Adding profile \"%s\" to cache\n", name);
|
||||||
|
url_cache_http_profile_add(cache, name, access_key_id, secret_access_key);
|
||||||
|
} else {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "HTTP profile missing name\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* check config */
|
/* check config */
|
||||||
if (max_urls <= 0) {
|
if (max_urls <= 0) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "max-urls must be > 0\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "max-urls must be > 0\n");
|
||||||
@ -1179,6 +1364,7 @@ struct http_context {
|
|||||||
static switch_status_t http_cache_file_open(switch_file_handle_t *handle, const char *path)
|
static switch_status_t http_cache_file_open(switch_file_handle_t *handle, const char *path)
|
||||||
{
|
{
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||||
|
http_profile_t *profile = NULL;
|
||||||
struct http_context *context = switch_core_alloc(handle->memory_pool, sizeof(*context));
|
struct http_context *context = switch_core_alloc(handle->memory_pool, sizeof(*context));
|
||||||
const char *local_path;
|
const char *local_path;
|
||||||
|
|
||||||
@ -1187,7 +1373,10 @@ static switch_status_t http_cache_file_open(switch_file_handle_t *handle, const
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
local_path = url_cache_get(&gcache, NULL, path, 1, handle->memory_pool);
|
if (handle->params) {
|
||||||
|
profile = url_cache_http_profile_find(&gcache, switch_event_get_header(handle->params, "profile"));
|
||||||
|
}
|
||||||
|
local_path = url_cache_get(&gcache, profile, NULL, path, 1, handle->memory_pool);
|
||||||
if (!local_path) {
|
if (!local_path) {
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
@ -1289,6 +1478,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_http_cache_load)
|
|||||||
|
|
||||||
memset(&gcache, 0, sizeof(url_cache_t));
|
memset(&gcache, 0, sizeof(url_cache_t));
|
||||||
gcache.pool = pool;
|
gcache.pool = pool;
|
||||||
|
switch_core_hash_init(&gcache.map, gcache.pool);
|
||||||
|
switch_core_hash_init(&gcache.profiles, gcache.pool);
|
||||||
|
switch_mutex_init(&gcache.mutex, SWITCH_MUTEX_UNNESTED, gcache.pool);
|
||||||
|
switch_thread_rwlock_create(&gcache.shutdown_lock, gcache.pool);
|
||||||
|
|
||||||
if (do_config(&gcache) != SWITCH_STATUS_SUCCESS) {
|
if (do_config(&gcache) != SWITCH_STATUS_SUCCESS) {
|
||||||
return SWITCH_STATUS_TERM;
|
return SWITCH_STATUS_TERM;
|
||||||
@ -1317,11 +1510,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_http_cache_load)
|
|||||||
file_interface->file_read = http_file_read;
|
file_interface->file_read = http_file_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch_core_hash_init(&gcache.map, gcache.pool);
|
/* create the queue from configuration */
|
||||||
switch_mutex_init(&gcache.mutex, SWITCH_MUTEX_UNNESTED, gcache.pool);
|
|
||||||
switch_thread_rwlock_create(&gcache.shutdown_lock, gcache.pool);
|
|
||||||
|
|
||||||
/* create the queue */
|
|
||||||
gcache.queue.max_size = gcache.max_url;
|
gcache.queue.max_size = gcache.max_url;
|
||||||
gcache.queue.data = switch_core_alloc(gcache.pool, sizeof(void *) * gcache.queue.max_size);
|
gcache.queue.data = switch_core_alloc(gcache.pool, sizeof(void *) * gcache.queue.max_size);
|
||||||
gcache.queue.pos = 0;
|
gcache.queue.pos = 0;
|
||||||
|
12
src/mod/applications/mod_http_cache/test_aws/Makefile
Normal file
12
src/mod/applications/mod_http_cache/test_aws/Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
BASE=../../../../..
|
||||||
|
|
||||||
|
LOCAL_CFLAGS += -I../ -I./
|
||||||
|
LOCAL_OBJS= main.o ../aws.o
|
||||||
|
LOCAL_SOURCES= main.c
|
||||||
|
include $(BASE)/build/modmake.rules
|
||||||
|
|
||||||
|
local_all:
|
||||||
|
libtool --mode=link gcc main.o ../aws.o -o test test_aws.la
|
||||||
|
|
||||||
|
local_clean:
|
||||||
|
-rm test
|
131
src/mod/applications/mod_http_cache/test_aws/main.c
Normal file
131
src/mod/applications/mod_http_cache/test_aws/main.c
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "test.h"
|
||||||
|
#include "aws.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test string to sign generation
|
||||||
|
*/
|
||||||
|
static void test_string_to_sign(void)
|
||||||
|
{
|
||||||
|
ASSERT_STRING_EQUALS("GET\n\n\nFri, 17 May 2013 19:35:26 GMT\n/rienzo-vault/troporocks.mp3", aws_s3_string_to_sign("GET", "rienzo-vault", "troporocks.mp3", "", "", "Fri, 17 May 2013 19:35:26 GMT"));
|
||||||
|
ASSERT_STRING_EQUALS("GET\nc8fdb181845a4ca6b8fec737b3581d76\naudio/mpeg\nThu, 17 Nov 2005 18:49:58 GMT\n/foo/man.chu", aws_s3_string_to_sign("GET", "foo", "man.chu", "audio/mpeg", "c8fdb181845a4ca6b8fec737b3581d76", "Thu, 17 Nov 2005 18:49:58 GMT"));
|
||||||
|
ASSERT_STRING_EQUALS("\n\n\n\n//", aws_s3_string_to_sign("", "", "", "", "", ""));
|
||||||
|
ASSERT_STRING_EQUALS("\n\n\n\n//", aws_s3_string_to_sign(NULL, NULL, NULL, NULL, NULL, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test signature generation
|
||||||
|
*/
|
||||||
|
static void test_signature(void)
|
||||||
|
{
|
||||||
|
char signature[S3_SIGNATURE_LENGTH_MAX];
|
||||||
|
signature[0] = '\0';
|
||||||
|
ASSERT_STRING_EQUALS("weGrLrc9HDlkYPTepVl0A9VYNlw=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\nFri, 17 May 2013 19:35:26 GMT\n/rienzo-vault/troporocks.mp3", "hOIZt1oeTX1JzINOMBoKf0BxONRZNQT1J8gIznLx"));
|
||||||
|
ASSERT_STRING_EQUALS("jZNOcbfWmD/A/f3hSvVzXZjM2HU=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
ASSERT_STRING_EQUALS("5m+HAmc5JsrgyDelh9+a2dNrzN8=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\n\nx-amz-date:Thu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
ASSERT_STRING_EQUALS("OKA87rVp3c4kd59t8D3diFmTfuo=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
ASSERT_STRING_EQUALS("OKA87rVp3c4kd59t8D3diFmTfuo=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, NULL, "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
ASSERT_NULL(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\n\nx-amz-date:Thu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n/quotes/nelson", ""));
|
||||||
|
ASSERT_NULL(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "", ""));
|
||||||
|
ASSERT_NULL(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, NULL, NULL));
|
||||||
|
ASSERT_NULL(aws_s3_signature(NULL, S3_SIGNATURE_LENGTH_MAX, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
ASSERT_NULL(aws_s3_signature(signature, 0, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
/* TODO freeswitch bug... */
|
||||||
|
//ASSERT_STRING_EQUALS("jZNO", aws_s3_signature(signature, 5, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test amazon URL detection
|
||||||
|
*/
|
||||||
|
static void test_check_url(void)
|
||||||
|
{
|
||||||
|
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_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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test bucket/object extraction from URL
|
||||||
|
*/
|
||||||
|
static void test_parse_url(void)
|
||||||
|
{
|
||||||
|
char *bucket;
|
||||||
|
char *object;
|
||||||
|
aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/nelson"), &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);
|
||||||
|
ASSERT_STRING_EQUALS("quotes", bucket);
|
||||||
|
ASSERT_STRING_EQUALS("nelson.mp3", object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup("http://s3.amazonaws.com/quotes/nelson"), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup("http://quotes/quotes/nelson"), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com/"), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup("http://quotes.s3.amazonaws.com"), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup("http://quotes"), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(strdup(""), &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
|
||||||
|
aws_s3_parse_url(NULL, &bucket, &object);
|
||||||
|
ASSERT_NULL(bucket);
|
||||||
|
ASSERT_NULL(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Authorization header creation
|
||||||
|
*/
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pre-signed URL creation
|
||||||
|
*/
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* main program
|
||||||
|
*/
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
TEST_INIT
|
||||||
|
TEST(test_string_to_sign);
|
||||||
|
TEST(test_signature);
|
||||||
|
TEST(test_check_url);
|
||||||
|
TEST(test_parse_url);
|
||||||
|
TEST(test_authorization_header);
|
||||||
|
TEST(test_presigned_url);
|
||||||
|
return 0;
|
||||||
|
}
|
113
src/mod/applications/mod_http_cache/test_aws/test.h
Normal file
113
src/mod/applications/mod_http_cache/test_aws/test.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* test.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2013, Grasshopper
|
||||||
|
*
|
||||||
|
* Version: MPL 1.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is test.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is Grasshopper
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Chris Rienzo <chris.rienzo@grasshopper.com>
|
||||||
|
*
|
||||||
|
* test.h -- simple unit testing macros
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef TEST_H
|
||||||
|
#define TEST_H
|
||||||
|
|
||||||
|
#define assert_equals(test, expected_str, expected, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
int actual_val = actual; \
|
||||||
|
if (expected != actual_val) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t%s\t%i\t!=\t%i\t%s:%i\n", test, expected_str, expected, actual_val, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_string_equals(test, expected, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
const char *actual_str = actual; \
|
||||||
|
if (!actual_str || strcmp(expected, actual_str)) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t\t%s\t!=\t%s\t%s:%i\n", test, expected, actual_str, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_not_null(test, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
const void *actual_val = actual; \
|
||||||
|
if (!actual_val) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t\t\t\t\t%s:%i\n", test, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_null(test, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
const void *actual_val = actual; \
|
||||||
|
if (actual_val) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t\t\t\t\t%s:%i\n", test, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_true(test, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
int actual_val = actual; \
|
||||||
|
if (!actual_val) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t\t\t\t\t%s:%i\n", test, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_false(test, actual, file, line) \
|
||||||
|
{ \
|
||||||
|
int actual_val = actual; \
|
||||||
|
if (actual_val) { \
|
||||||
|
printf("TEST\t%s\tFAIL\t\t\t\t\t%s:%i\n", test, file, line); \
|
||||||
|
exit(1); \
|
||||||
|
} else { \
|
||||||
|
printf("TEST\t%s\tPASS\n", test); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_EQUALS(expected, actual) assert_equals(#actual, #expected, expected, actual, __FILE__, __LINE__)
|
||||||
|
#define ASSERT_STRING_EQUALS(expected, actual) assert_string_equals(#actual, expected, actual, __FILE__, __LINE__)
|
||||||
|
#define ASSERT_NOT_NULL(actual) assert_not_null(#actual " not null", actual, __FILE__, __LINE__)
|
||||||
|
#define ASSERT_NULL(actual) assert_null(#actual " is null", actual, __FILE__, __LINE__)
|
||||||
|
#define ASSERT_TRUE(actual) assert_true(#actual " is true", actual, __FILE__, __LINE__)
|
||||||
|
#define ASSERT_FALSE(actual) assert_false(#actual " is false", actual, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define SKIP_ASSERT_EQUALS(expected, actual) if (0) { ASSERT_EQUALS(expected, actual); }
|
||||||
|
|
||||||
|
#define TEST(name) printf("TEST BEGIN\t" #name "\n"); name(); printf("TEST END\t"#name "\tPASS\n");
|
||||||
|
|
||||||
|
#define SKIP_TEST(name) if (0) { TEST(name) };
|
||||||
|
|
||||||
|
#define TEST_INIT const char *err; switch_core_init(0, SWITCH_TRUE, &err);
|
||||||
|
|
||||||
|
#endif
|
6
src/mod/applications/mod_http_cache/test_aws/test_aws.c
Normal file
6
src/mod/applications/mod_http_cache/test_aws/test_aws.c
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
int dummy(int i)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user