1
/* Copyright (c) 2009 PrimeBase Technologies GmbH, Germany
3
* PrimeBase Media Stream for MySQL
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
* Created by Barry Leslie on 10/02/09.
26
#include <curl/curl.h>
30
#include "CSStrUtil.h"
32
#include "CSS3Protocol.h"
36
//#define SHOW_SIGNING
37
// Uncomment this line to trace network action during request. Very Usefull!!
44
//#define SHOW_SIGNING
46
#define HEX_CHECKSUM_VALUE_SIZE (2 *CHECKSUM_VALUE_SIZE)
48
#define THROW_CURL_IF(v) { if (v) CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);}
50
//-------------------------------
51
static const char *retryCodes[] = {
60
//======================================
61
static size_t receive_data(void *ptr, size_t size, size_t nmemb, void *stream);
62
static size_t receive_header(void *ptr, size_t size, size_t nmemb, void *stream);
63
static size_t send_callback(void *ptr, size_t size, size_t nmemb, void *stream);
65
class S3ProtocolCon : CSXMLBuffer, public CSObject {
69
virtual bool openNode(char *path, char *value) {
70
if (value && *value && (strcmp(path,"/error/code/") == 0)) {
71
printf("S3 ERROR Code: %s\n", value);
72
for (int i = 0; retryCodes[i] && !ms_retry; i++)
73
ms_retry = (strcmp(value, retryCodes[i]) == 0);
75
if (ms_retry && !strcmp("slowdown", value))
77
} else if (value && *value && (strcmp(path,"/error/message/") == 0)) {
78
printf("S3 ERROR MESSAGE: %s\n", value);
83
virtual bool closeNode(char *path) {
88
virtual bool addAttribute(char *path, char *name, char *value) {
95
//-------------------------------
101
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Missing HTTP reply: possible S3 connection failure.");
104
printf("ms_errorReply:\n===========\n%s\n===========\n", ms_errorReply->getCString());
107
if (!parseData(ms_errorReply->getCString(), ms_errorReply->length(), 0)){
111
getError(&err, &msg);
112
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, msg);
120
CSHTTPHeaders ms_reply_headers;
121
CSStringBuffer ms_buffer; // A scratch buffer
124
struct curl_slist *ms_header_list; // A curl list of headers to be sent with the next request.
126
CSInputStream *ms_inputStream;
127
CSOutputStream *ms_outputStream;
130
char ms_s3Checksum[HEX_CHECKSUM_VALUE_SIZE +1];
132
bool ms_notFound; // True if the object could not be found
133
bool ms_retry; // True if the request failed with a retry error.
136
CSStringBuffer *ms_errorReply;
137
char ms_curl_error[CURL_ERROR_SIZE];
139
off64_t ms_data_size;
141
unsigned int ms_replyStatus;
142
bool ms_throw_error; // Gets set if an exception occurs in a callback.
148
ms_header_list(NULL),
149
ms_inputStream(NULL),
150
ms_outputStream(NULL),
157
ms_throw_error(false),
158
ms_old_libcurl(false),
162
ms_curl = curl_easy_init();
164
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_easy_init() failed.");
166
curl_version_info_data *curl_ver = curl_version_info(CURLVERSION_NOW);
168
// libCurl versions prior to 7.17.0 did not make copies of strings passed into curl_easy_setopt()
169
// If this version requirement is a problem I can do this myself, if I have to, I guess. :(
170
if (curl_ver->version_num < 0X071700 ) {
171
ms_old_libcurl = true;
174
//snprintf(msg, 200, "libcurl version %s is too old, require version 7.17.0 or newer.", curl_ver->version);
175
//CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, msg);
178
if (curl_easy_setopt(ms_curl, CURLOPT_ERRORBUFFER, ms_curl_error))
179
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed.");
182
curl_easy_setopt(ms_curl, CURLOPT_VERBOSE, 1L);
184
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_TCP_NODELAY, 1L));
187
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_NOPROGRESS, 1L));
188
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEFUNCTION, receive_data));
189
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_READFUNCTION, send_callback));
190
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HEADERFUNCTION, receive_header));
191
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEDATA, this));
192
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_READDATA, this));
193
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEHEADER, this));
196
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_FOLLOWLOCATION, 1L)); // Follow redirects.
203
curl_easy_cleanup(ms_curl);
205
curl_slist_free_all(ms_header_list);
207
ms_inputStream->release();
209
ms_outputStream->release();
211
ms_errorReply->release();
213
ms_reply_headers.clearHeaders();
216
cs_free(ms_safe_url);
219
inline void check_reply_status()
221
if (ms_replyStatus > 199 && ms_replyStatus < 300)
226
switch (ms_replyStatus) {
228
case 204: // No Content
229
//case 301: // Moved Permanently
230
//case 307: // Temporary Redirect
232
case 404: // Not Found
245
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_errorReply->getCString());
247
} else if (ms_slowDown) {
249
CSException::logException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 slow down request.");
250
self->sleep(10); // sleep for 1/100 second.
259
inline void ms_reset()
261
// Remove any old headers
262
if (ms_header_list) {
263
curl_slist_free_all(ms_header_list);
264
ms_header_list = NULL;
267
ms_reply_headers.clearHeaders();
269
ms_throw_error = false;
271
ms_errorReply->setLength(0);
273
ms_s3Checksum[0] = 0;
277
if (ms_outputStream) {
278
ms_outputStream->release();
279
ms_outputStream = NULL;
281
if (ms_inputStream) {
282
ms_inputStream->release();
283
ms_inputStream = NULL;
287
cs_free(ms_safe_url);
292
inline void ms_setHeader(const char *header)
294
ms_header_list = curl_slist_append(ms_header_list, header);
296
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_slist_append() failed.");
301
inline const char *safe_url(const char *url)
303
if (ms_old_libcurl == false)
307
cs_free(ms_safe_url);
310
ms_safe_url = cs_strdup(url);
315
inline void ms_setURL(const char *url)
317
//printf("URL: \"%s\n", url);
318
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_URL, safe_url(url)));
321
inline void ms_execute_delete_request()
325
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
326
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_CUSTOMREQUEST, "DELETE"));
328
rtc = curl_easy_perform(ms_curl);
330
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_CUSTOMREQUEST, NULL)); // IMPORTANT: Reset this to it's default value
332
if (rtc && !ms_throw_error)
333
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
335
if (ms_throw_error) {
341
check_reply_status();
344
inline void ms_execute_copy_request()
348
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
349
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_INFILESIZE_LARGE, 0));
350
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_UPLOAD, 1L));
352
rtc = curl_easy_perform(ms_curl);
354
if (rtc && !ms_throw_error)
355
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
357
if (ms_throw_error) {
363
check_reply_status();
366
inline void ms_execute_get_request(CSOutputStream *output)
371
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
372
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPGET, 1L));
374
ms_outputStream = output;
375
if (curl_easy_perform(ms_curl) && !ms_throw_error) {
376
ms_outputStream = NULL;
377
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
379
ms_outputStream = NULL;
385
check_reply_status();
389
inline void ms_execute_put_request(CSInputStream *input, off64_t size, Md5Digest *digest)
394
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
395
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_INFILESIZE_LARGE, size));
396
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_UPLOAD, 1L));
397
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_POSTFIELDSIZE_LARGE, size));
398
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_POST, 1L));
403
ms_inputStream = input;
404
if (curl_easy_perform(ms_curl) && !ms_throw_error) {
405
ms_inputStream = NULL;
406
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
408
ms_inputStream = NULL;
415
check_reply_status();
417
// Check that the checksums agree
418
char checksum[HEX_CHECKSUM_VALUE_SIZE +1];
420
ms_md5.md5_digest(digest);
421
cs_bin_to_hex(HEX_CHECKSUM_VALUE_SIZE, checksum, CHECKSUM_VALUE_SIZE, digest->val);
422
checksum[HEX_CHECKSUM_VALUE_SIZE] = 0;
424
cs_strToUpper(ms_s3Checksum);
425
if (strcmp(checksum, ms_s3Checksum)) {
426
// The request should be restarted in this case.
428
CSException::logException(CS_CONTEXT, CS_ERR_CHECKSUM_ERROR, "Calculated checksum did not match S3 checksum");
436
//======================================
441
//======================================
442
CSS3Protocol::CSS3Protocol():
445
s3_private_key(NULL),
448
new_(s3_server, CSStringBuffer());
449
s3_server->append("s3.amazonaws.com/");
451
s3_public_key = CSString::newString("");
452
s3_private_key = CSString::newString("");
457
CSS3Protocol::~CSS3Protocol()
460
s3_server->release();
463
s3_public_key->release();
466
s3_private_key->release();
470
CSString *CSS3Protocol::s3_getSignature(const char *verb,
472
const char *content_type,
479
CSStringBuffer *s3_buffer;
484
new_(s3_buffer, CSStringBuffer());
487
s3_buffer->setLength(0);
488
s3_buffer->append(verb);
489
s3_buffer->append("\n");
490
if (md5) s3_buffer->append(md5);
491
s3_buffer->append("\n");
492
if (content_type) s3_buffer->append(content_type);
493
s3_buffer->append("\n");
494
s3_buffer->append(date);
496
// Note: headers are assumed to be in lower case, sorted, and containing no white space.
497
s3_buffer->append("\n");
498
s3_buffer->append(headers->getCString());
500
s3_buffer->append("\n/");
501
s3_buffer->append(bucket);
502
s3_buffer->append("/");
503
s3_buffer->append(key);
506
printf("signing:\n=================\n%s\n=================\n", s3_buffer->getCString());
508
const char *ptr = s3_buffer->getCString();
510
printf("%x ", *ptr); ptr++;
516
CSString *sig = signature(s3_buffer->getCString(), s3_private_key->getCString());
523
//----------------------
524
// CURL callback functions:
525
////////////////////////////
526
//----------------------
527
static size_t send_callback(void *ptr, size_t objs, size_t obj_size, void *v_con)
529
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
530
size_t data_sent, buffer_size = objs * obj_size;
532
if (!con->ms_data_size)
537
data_sent = con->ms_inputStream->read((char*)ptr, buffer_size);
538
if (data_sent <= con->ms_data_size) {
539
con->ms_data_size -= data_sent;
541
con->ms_md5.md5_append((u_char*)ptr, data_sent); // Calculating the checksum for the data sent.
542
} else if (data_sent > con->ms_data_size)
543
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Blob larger than expected.");
544
else if (con->ms_data_size && !data_sent)
545
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Blob smaller than expected.");
548
con->ms_throw_error = true;
555
//----------------------
556
static size_t receive_data(void *vptr, size_t objs, size_t obj_size, void *v_con)
558
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
559
size_t data_len = objs * obj_size;
563
if (con->ms_replyStatus >= 400) { // Collect the error reply.
564
if (!con->ms_errorReply)
565
con->ms_errorReply = new CSStringBuffer(50);
566
con->ms_errorReply->append((char*)vptr, data_len);
567
} else if ( con->ms_outputStream)
568
con->ms_outputStream->write((char*)vptr, data_len);
571
con->ms_throw_error = true;
578
#define IS_REDIRECT(s) ((s >= 300) && (s < 400))
579
//----------------------
580
static size_t receive_header(void *header, size_t objs, size_t obj_size, void *v_con)
582
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
583
size_t size = objs * obj_size;
584
char *end, *ptr = (char*) header, *name, *value = NULL;
585
uint32_t name_len =0, value_len = 0;
587
CLOBBER_PROTECT(con);
588
CLOBBER_PROTECT(size);
589
CLOBBER_PROTECT(ptr);
590
CLOBBER_PROTECT(value);
591
CLOBBER_PROTECT(value_len);
592
CLOBBER_PROTECT(name_len);
594
//printf( "receive_header: %s\n", ptr);
596
if (*(end -2) == '\r' && *(end -1) == '\n')
599
while ((end != ptr) && (*ptr == ' ')) ptr++;
603
// Get the reply status.
604
// Status 100 = Continue
605
if (((!con->ms_replyStatus) || (con->ms_replyStatus == 100) || IS_REDIRECT(con->ms_replyStatus) )
606
&& !strncasecmp(ptr, "HTTP", 4)
609
while ((end != ptr) && (*ptr != ' ')) ptr++; // skip HTTP stuff
610
while ((end != ptr) && (*ptr == ' ')) ptr++; // find the start of eh status code.
614
if (end < (ptr +3)) // expecting a 3 digit status code.
617
memcpy(status, ptr, 3);
620
con->ms_replyStatus = atoi(status);
624
while ((end != ptr) && (*ptr != ':')) ptr++;
627
name_len = ptr - name;
630
while ((end != ptr) && (*ptr == ' ')) ptr++;
635
value_len = end - value;
637
while (name[name_len-1] == ' ') name_len--;
638
while (value[value_len-1] == ' ') value_len--;
640
if (!strncasecmp(name, "ETag", 4)) {
641
value++; value_len -=2; // Strip quotation marks from checksum string.
642
if (value_len == HEX_CHECKSUM_VALUE_SIZE) {
643
memcpy(con->ms_s3Checksum, value, value_len);
644
con->ms_s3Checksum[value_len] = 0;
650
con->ms_reply_headers.addHeader(name, name_len, value, value_len);
654
con->ms_throw_error = true;
661
//----------------------
663
#define SET_DATE_FROM_TIME(t, d) {strftime(d, sizeof(d), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));}
664
#define SET_DATE(d) {time_t t = time(NULL); SET_DATE_FROM_TIME(t, d);}
666
bool CSS3Protocol::s3_delete(const char *bucket, const char *key)
668
CSStringBuffer *s3_buffer;
670
CSString *signed_str;
671
uint32_t retry_count = 0;
672
S3ProtocolCon *con_data;
676
new_(s3_buffer, CSStringBuffer());
679
new_(con_data, S3ProtocolCon());
683
// Clear old settings.
684
con_data->ms_reset();
689
s3_buffer->setLength(0);
690
s3_buffer->append("http://");
691
s3_buffer->append(bucket);
692
s3_buffer->append(".");
693
s3_buffer->append(s3_server->getCString());
694
s3_buffer->append(key);
696
con_data->ms_setURL(s3_buffer->getCString());
698
// Add the 'DATE' header
699
s3_buffer->setLength(0);
700
s3_buffer->append("Date: ");
701
s3_buffer->append(date);
702
con_data->ms_setHeader(s3_buffer->getCString());
704
// Create the authentication signature and add the 'Authorization' header
705
signed_str = s3_getSignature("DELETE", NULL, NULL, date, bucket, key);
707
s3_buffer->setLength(0);
708
s3_buffer->append("Authorization: AWS ");
709
s3_buffer->append(s3_public_key->getCString());
710
s3_buffer->append(":");
711
s3_buffer->append(signed_str->getCString());
712
release_(signed_str); signed_str = NULL;
714
con_data->ms_setHeader(s3_buffer->getCString());
716
con_data->ms_execute_delete_request();
718
if (con_data->ms_retry) {
719
if (retry_count == s3_maxRetrys) {
720
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
722
printf("RETRY: s3_delete()\n");
727
bool notFound = con_data->ms_notFound;
734
//-------------------------------
735
void CSS3Protocol::s3_copy(const char *dest_server, const char *dest_bucket, const char *dest_key, const char *src_bucket, const char *src_key)
737
CSStringBuffer *s3_buffer;
739
CSString *signed_str;
740
uint32_t retry_count = 0;
741
S3ProtocolCon *con_data;
745
new_(s3_buffer, CSStringBuffer());
748
new_(con_data, S3ProtocolCon());
752
dest_server = s3_server->getCString();
755
// Clear old settings.
756
con_data->ms_reset();
761
s3_buffer->setLength(0);
762
s3_buffer->append("http://");
763
s3_buffer->append(dest_bucket);
764
s3_buffer->append(".");
765
s3_buffer->append(s3_server->getCString());
766
s3_buffer->append(dest_key);
768
con_data->ms_setURL(s3_buffer->getCString());
770
// Add the destination location
771
s3_buffer->setLength(0);
772
s3_buffer->append("Host: ");
773
s3_buffer->append(dest_bucket);
774
s3_buffer->append(".");
775
s3_buffer->append(dest_server);
776
s3_buffer->setLength(s3_buffer->length() -1); // trim the '/'
777
con_data->ms_setHeader(s3_buffer->getCString());
779
// Add the source location
780
s3_buffer->setLength(0);
781
s3_buffer->append("x-amz-copy-source:");
782
s3_buffer->append(src_bucket);
783
s3_buffer->append("/");
784
s3_buffer->append(src_key);
785
con_data->ms_setHeader(s3_buffer->getCString());
787
// Create the authentication signature and add the 'Authorization' header
788
signed_str = s3_getSignature("PUT", NULL, NULL, date, dest_bucket, dest_key, CSString::newString(s3_buffer->getCString()));
791
// Add the 'DATE' header
792
s3_buffer->setLength(0);
793
s3_buffer->append("Date: ");
794
s3_buffer->append(date);
795
con_data->ms_setHeader(s3_buffer->getCString());
798
s3_buffer->setLength(0);
799
s3_buffer->append("Authorization: AWS ");
800
s3_buffer->append(s3_public_key->getCString());
801
s3_buffer->append(":");
802
s3_buffer->append(signed_str->getCString());
803
release_(signed_str); signed_str = NULL;
804
con_data->ms_setHeader(s3_buffer->getCString());
806
con_data->ms_execute_copy_request();
808
if (con_data->ms_notFound) {
809
s3_buffer->setLength(0);
810
s3_buffer->append("Cloud copy failed, object not found: ");
811
s3_buffer->append(src_bucket);
812
s3_buffer->append(" ");
813
s3_buffer->append(src_key);
814
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, s3_buffer->getCString());
817
if (con_data->ms_retry) {
818
if (retry_count == s3_maxRetrys) {
819
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
821
printf("RETRY: s3_copy()\n");
833
//-------------------------------
834
CSVector *CSS3Protocol::s3_receive(CSOutputStream *output, const char *bucket, const char *key, bool *found)
836
CSStringBuffer *s3_buffer;
838
CSString *signed_str;
839
uint32_t retry_count = 0;
840
S3ProtocolCon *con_data;
841
CSVector *replyHeaders;
847
new_(s3_buffer, CSStringBuffer());
850
new_(con_data, S3ProtocolCon());
854
// Clear old settings.
855
con_data->ms_reset();
860
s3_buffer->setLength(0);
861
s3_buffer->append("http://");
862
s3_buffer->append(bucket);
863
s3_buffer->append(".");
864
s3_buffer->append(s3_server->getCString());
865
s3_buffer->append(key);
867
con_data->ms_setURL(s3_buffer->getCString());
869
// Add the 'DATE' header
870
s3_buffer->setLength(0);
871
s3_buffer->append("Date: ");
872
s3_buffer->append(date);
873
con_data->ms_setHeader(s3_buffer->getCString());
875
// Create the authentication signature and add the 'Authorization' header
876
signed_str = s3_getSignature("GET", NULL, NULL, date, bucket, key);
878
s3_buffer->setLength(0);
879
s3_buffer->append("Authorization: AWS ");
880
s3_buffer->append(s3_public_key->getCString());
881
s3_buffer->append(":");
882
s3_buffer->append(signed_str->getCString());
883
release_(signed_str); signed_str = NULL;
884
con_data->ms_setHeader(s3_buffer->getCString());
886
con_data->ms_execute_get_request(RETAIN(output));
888
if (con_data->ms_retry) {
889
if (retry_count == s3_maxRetrys) {
890
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
892
printf("RETRY: s3_receive()\n");
898
*found = !con_data->ms_notFound;
899
replyHeaders = con_data->ms_reply_headers.takeHeaders();
904
return_(replyHeaders);
907
class S3ListParser : public CSXMLBuffer {
913
bool parseListData(const char *data, size_t len, CSVector *keys)
916
return parseData(data, len, 0);
920
virtual bool openNode(char *path, char *value) {
921
if (value && *value && (strcmp(path,"/listbucketresult/contents/key/") == 0))
922
list->add(CSString::newString(value));
926
virtual bool closeNode(char *path) {
931
virtual bool addAttribute(char *path, char *name, char *value) {
940
//-------------------------------
941
static CSVector *parse_s3_list(CSMemoryOutputStream *output)
943
S3ListParser s3ListParser;
952
new_(vector, CSVector(10));
955
data = (const char *) output->getMemory(&len);
956
if (!s3ListParser.parseListData(data, len, vector)) {
960
s3ListParser.getError(&err, &msg);
961
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, msg);
970
//-------------------------------
971
CSVector *CSS3Protocol::s3_list(const char *bucket, const char *key_prefix, uint32_t max)
973
CSStringBuffer *s3_buffer;
975
CSString *signed_str;
976
CSMemoryOutputStream *output;
977
uint32_t retry_count = 0;
978
S3ProtocolCon *con_data;
981
new_(s3_buffer, CSStringBuffer());
984
output = CSMemoryOutputStream::newStream(1024, 1024);
987
new_(con_data, S3ProtocolCon());
992
// Clear old settings.
993
con_data->ms_reset();
998
s3_buffer->setLength(0);
999
s3_buffer->append("http://");
1000
s3_buffer->append(bucket);
1001
s3_buffer->append(".");
1002
s3_buffer->append(s3_server->getCString());
1004
s3_buffer->append("?prefix=");
1005
s3_buffer->append(key_prefix);
1010
s3_buffer->append("&max-keys=");
1012
s3_buffer->append("?max-keys=");
1013
s3_buffer->append(max);
1016
con_data->ms_setURL(s3_buffer->getCString());
1018
// Add the 'DATE' header
1019
s3_buffer->setLength(0);
1020
s3_buffer->append("Date: ");
1021
s3_buffer->append(date);
1022
con_data->ms_setHeader(s3_buffer->getCString());
1024
// Create the authentication signature and add the 'Authorization' header
1025
signed_str = s3_getSignature("GET", NULL, NULL, date, bucket, "");
1027
s3_buffer->setLength(0);
1028
s3_buffer->append("Authorization: AWS ");
1029
s3_buffer->append(s3_public_key->getCString());
1030
s3_buffer->append(":");
1031
s3_buffer->append(signed_str->getCString());
1032
release_(signed_str); signed_str = NULL;
1033
con_data->ms_setHeader(s3_buffer->getCString());
1035
con_data->ms_execute_get_request(RETAIN(output));
1037
if (con_data->ms_retry) {
1038
if (retry_count == s3_maxRetrys) {
1039
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
1041
printf("RETRY: s3_list()\n");
1049
release_(s3_buffer);
1050
return_(parse_s3_list(output));
1053
//-------------------------------
1054
CSString *CSS3Protocol::s3_getAuthorization(const char *bucket, const char *key, const char *content_type, uint32_t *s3AuthorizationTime)
1057
CSString *signed_str;
1063
content_type = "binary/octet-stream";
1065
sys_time = time(NULL);
1067
*s3AuthorizationTime = (uint32_t)sys_time;
1069
SET_DATE_FROM_TIME(sys_time, date);
1070
signed_str = s3_getSignature("PUT", NULL, content_type, date, bucket, key);
1071
return_(signed_str);
1074
//-------------------------------
1075
CSVector *CSS3Protocol::s3_send(CSInputStream *input, const char *bucket, const char *key, off64_t size, const char *content_type, Md5Digest *digest, const char *s3Authorization, time_t s3AuthorizationTime)
1077
CSStringBuffer *s3_buffer;
1079
CSString *signed_str;
1080
Md5Digest dummy_digest;
1081
uint32_t retry_count = 0;
1082
S3ProtocolCon *con_data;
1083
CSVector *replyHeaders;
1088
new_(s3_buffer, CSStringBuffer());
1091
new_(con_data, S3ProtocolCon());
1095
digest = &dummy_digest;
1098
content_type = "binary/octet-stream";
1102
// Clear old settings.
1103
con_data->ms_reset();
1105
if (s3Authorization) {
1106
SET_DATE_FROM_TIME(s3AuthorizationTime, date);
1112
s3_buffer->setLength(0);
1113
s3_buffer->append("http://");
1114
s3_buffer->append(bucket);
1115
s3_buffer->append(".");
1116
s3_buffer->append(s3_server->getCString());
1117
s3_buffer->append(key);
1119
con_data->ms_setURL(s3_buffer->getCString());
1121
// Add the 'DATE' header
1122
s3_buffer->setLength(0);
1123
s3_buffer->append("Date: ");
1124
s3_buffer->append(date);
1125
con_data->ms_setHeader(s3_buffer->getCString());
1127
// Add the 'Content-Type' header
1128
s3_buffer->setLength(0);
1129
s3_buffer->append("Content-Type: ");
1130
s3_buffer->append(content_type);
1131
con_data->ms_setHeader(s3_buffer->getCString());
1133
// Create the authentication signature and add the 'Authorization' header
1134
if (!s3Authorization)
1135
signed_str = s3_getSignature("PUT", NULL, content_type, date, bucket, key);
1137
signed_str = CSString::newString(s3Authorization);
1139
s3_buffer->setLength(0);
1140
s3_buffer->append("Authorization: AWS ");
1141
s3_buffer->append(s3_public_key->getCString());
1142
s3_buffer->append(":");
1143
s3_buffer->append(signed_str->getCString());
1144
release_(signed_str); signed_str = NULL;
1145
con_data->ms_setHeader(s3_buffer->getCString());
1147
con_data->ms_execute_put_request(RETAIN(input), size, digest);
1149
if (con_data->ms_retry) {
1150
if (retry_count == s3_maxRetrys) {
1151
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
1153
printf("RETRY: s3_send()\n");
1159
replyHeaders = con_data->ms_reply_headers.takeHeaders();
1161
release_(s3_buffer);
1163
return_(replyHeaders);
1166
//-------------------------------
1167
CSString *CSS3Protocol::s3_getDataURL(const char *bucket, const char *key, uint32_t keep_alive)
1169
CSStringBuffer *s3_buffer;
1171
CSString *signed_str;
1174
new_(s3_buffer, CSStringBuffer());
1177
snprintf(timeout, 32, "%"PRId32"", ((uint32_t)time(NULL)) + keep_alive);
1179
signed_str = s3_getSignature("GET", NULL, NULL, timeout, bucket, key);
1180
//printf("Unsafe: \"%s\"\n", signed_str->getCString());
1181
signed_str = urlEncode(signed_str); // Because the signature is in the URL it must be URL encoded.
1182
//printf(" Safe: \"%s\"\n", signed_str->getCString());
1185
s3_buffer->setLength(0);
1186
s3_buffer->append("http://");
1187
s3_buffer->append(bucket);
1188
s3_buffer->append(".");
1189
s3_buffer->append(s3_server->getCString());
1190
s3_buffer->append(key);
1192
s3_buffer->append("?AWSAccessKeyId=");
1193
s3_buffer->append(s3_public_key->getCString());
1194
s3_buffer->append("&Expires=");
1195
s3_buffer->append(timeout);
1196
s3_buffer->append("&Signature=");
1197
s3_buffer->append(signed_str->getCString());
1199
release_(signed_str);
1202
CSString *str = CSString::newString(s3_buffer);
1206
//#define S3_UNIT_TEST
1208
static void show_help_info(const char *cmd)
1210
printf("Get authenticated query string:\n\t%s q <bucket> <object_key> <timeout>\n", cmd);
1211
printf("Delete object:\n\t%s d <bucket> <object_key>\n", cmd);
1212
printf("Delete all object with a given prefix:\n\t%s D <bucket> <object_prefix>\n", cmd);
1213
printf("Get object, data will be written to 'prottest.out':\n\t%s g <bucket> <object_key> <timeout>\n", cmd);
1214
printf("Put (Upload) an object:\n\t%s p <bucket> <object_key> <file>\n", cmd);
1215
printf("List objects in the bucket:\n\t%s l <bucket> [<object_prefix> [max_list_size]]\n", cmd);
1216
printf("Copy object:\n\t%s c <src_bucket> <src_object_key> <dst_bucket> <dst_object_key> \n", cmd);
1217
printf("Copy all object with a given prefix:\n\t%s C <src_bucket> <object_key_prefix> <dst_bucket> \n", cmd);
1220
void dump_headers(CSVector *header_array)
1222
CSHTTPHeaders headers;
1224
headers.setHeaders(header_array);
1225
printf("Reply Headers:\n");
1226
printf("--------------\n");
1228
for (uint32_t i = 0; i < headers.numHeaders(); i++) {
1229
CSHeader *h = headers.getHeader(i);
1231
printf("%s : %s\n", h->getNameCString(), h->getValueCString());
1234
printf("--------------\n");
1235
headers.clearHeaders();
1238
int main(int argc, char **argv)
1240
CSThread *main_thread;
1241
const char *pub_key;
1242
const char *priv_key;
1243
CSS3Protocol *prot = NULL;
1246
show_help_info(argv[0]);
1250
if (! CSThread::startUp()) {
1251
CSException::throwException(CS_CONTEXT, ENOMEM, "CSThread::startUp() failed.");
1257
main_thread = new CSThread( NULL);
1258
CSThread::setSelf(main_thread);
1263
pub_key = getenv("S3_ACCESS_KEY_ID");
1264
priv_key = getenv("S3_SECRET_ACCESS_KEY");
1265
new_(prot, CSS3Protocol());
1268
prot->s3_setServer("s3.amazonaws.com/");
1269
prot->s3_setPublicKey(pub_key);
1270
prot->s3_setPrivateKey(priv_key);
1272
switch (argv[1][0]) {
1273
case 'q': // Get the query string
1275
CSString *qstr = prot->s3_getDataURL(argv[2], argv[3], atoi(argv[4]));
1276
printf("To test call:\ncurl -L -D - \"%s\"\n", qstr->getCString());
1279
printf("Bad command: q <bucket> <object_key> <timeout>\n");
1282
case 'd': // Delete the object
1284
printf("delete %s %s\n", argv[2], argv[3]);
1285
if (!prot->s3_delete(argv[2], argv[3]))
1286
printf("%s/%s could not be found.\n", argv[2], argv[3]);
1289
printf("Bad command: d <bucket> <object_key>\n");
1292
case 'D': // Delete objects like
1297
list = prot->s3_list(argv[2], argv[3]);
1299
while (key = (CSString*) list->take(0)) {
1300
printf("Deleting %s\n", key->getCString());
1301
prot->s3_delete(argv[2], key->getCString());
1307
printf("Bad command: D <bucket> <object_key_prefix>\n");
1310
case 'g': // Get the object
1316
output = CSFile::newFile("prottest.out");
1318
output->open(CSFile::CREATE | CSFile::TRUNCATE);
1319
headers = prot->s3_receive(output->getOutputStream(), argv[2], argv[3], &found);
1321
printf("%s/%s could not be found.\n", argv[2], argv[3]);
1323
dump_headers(headers);
1327
printf("Bad command: g <bucket> <object_key>\n");
1331
case 'p': // Put (Upload) the object
1336
input = CSFile::newFile(argv[4]);
1338
input->open(CSFile::READONLY);
1339
headers = prot->s3_send(input->getInputStream(), argv[2], argv[3], input->myFilePath->getSize());
1340
dump_headers(headers);
1343
printf("Bad command: p <bucket> <object_key> <file> \n");
1347
case 'c': // Copy the object
1349
prot->s3_copy(NULL, argv[4], argv[5], argv[2], argv[3]);
1351
printf("Bad command: c <src_bucket> <src_object_key> <dst_bucket> <dst_object_key>\n");
1355
case 'C': // Copy objects like
1360
list = prot->s3_list(argv[2], argv[3]);
1362
while (key = (CSString*) list->take(0)) {
1363
printf("Copying %s\n", key->getCString());
1364
prot->s3_copy(NULL, argv[4], key->getCString(), argv[2], key->getCString());
1370
printf("Bad command: C <src_bucket> <object_key_prefix> <dst_bucket>\n");
1373
case 'l': // List the object
1374
if ((argc == 3) || (argc == 4) || (argc == 5)) {
1376
char *prefix = NULL;
1382
if (!strlen(prefix))
1387
max = atol(argv[4]);
1389
list = prot->s3_list(argv[2], prefix, max);
1391
while (key = (CSString*) list->take(0)) {
1392
printf("%s\n", key->getCString());
1398
printf("Bad command: l <bucket> [<object_prefix> [max_list_size]] \n");
1402
printf("Unknown command.\n");
1403
show_help_info(argv[0]);
1410
self->logException();
1415
main_thread->release();
1417
CSThread::shutDown();