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.
27
#include <curl/curl.h>
29
#include <libxml/parser.h>
34
#include "CSStrUtil.h"
36
#include "CSS3Protocol.h"
39
//#define SHOW_SIGNING
40
// Uncomment this line to trace network action during request. Very Usefull!!
46
//#define SHOW_SIGNING
48
#define HEX_CHECKSUM_VALUE_SIZE (2 *CHECKSUM_VALUE_SIZE)
50
#define THROW_CURL_IF(v) { if (v) CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);}
52
//-------------------------------
53
static const char *retryCodes[] = {
62
//======================================
63
static size_t receive_data(void *ptr, size_t size, size_t nmemb, void *stream);
64
static size_t receive_header(void *ptr, size_t size, size_t nmemb, void *stream);
65
static size_t send_callback(void *ptr, size_t size, size_t nmemb, void *stream);
67
//==========================
69
bool thow_error; // Must be the first field.
70
}ParserInfoRec, *ParserInfoPtr;
73
bool thow_error; // Must be the first field.
74
class S3ProtocolCon *s3Con;
76
CSStringBuffer *buffer;
77
CSStringBuffer *message;
78
} ParserErrorInfoRec, *ParserErrorInfoPtr;
80
//-------------------------------
81
// There shouldn't be any XML parser errors unless we received an
82
// incomplete reply from te S3 server.
83
static void saxError(void *user_data, const char *msg, ...)
85
ParserInfoPtr info = (ParserInfoPtr) user_data;
92
if (!info->thow_error) {
94
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "xml SAX parser error");
95
info->thow_error = true;
100
class S3ProtocolCon : public CSObject {
104
static void saxEndErrorElement(void *user_data, const xmlChar *name)
106
ParserErrorInfoPtr info = (ParserErrorInfoPtr) user_data;
108
if (info->thow_error)
113
if (!strcmp("Code", (char *) name)) {
114
cs_strcpy(32, info->code, info->buffer->getBuffer(0), info->buffer->length());
116
for (int i = 0; retryCodes[i] && !info->s3Con->ms_retry; i++)
117
info->s3Con->ms_retry = (strcmp(info->code, retryCodes[i]) == 0);
119
if (info->s3Con->ms_retry && !strcmp("SlowDown", info->code))
120
info->s3Con->ms_slowDown = true;
122
} else if (!strcmp("Message", (char *) name)) {
123
info->message->append(info->buffer->getBuffer(0), info->buffer->length());
127
info->thow_error = true;
131
info->buffer->setLength(0);
134
//-------------------------------
135
static void saxErrorData(void *user_data, const xmlChar *ch, int len)
137
ParserErrorInfoPtr info = (ParserErrorInfoPtr) user_data;
139
if (info->thow_error)
144
info->buffer->append( (char*) ch, len);
147
info->thow_error = true;
153
//-------------------------------
154
void parse_s3_error()
156
ParserErrorInfoRec parser_info = {0};
157
struct _xmlSAXHandler sax_handler = {0};
161
parser_info.s3Con = this;
164
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Missing HTTP reply: possible S3 connection failure.");
167
printf("ms_errorReply:\n===========\n%s\n===========\n", ms_errorReply->getCString());
170
//sax_handler.startElement = &saxStartErrorElement;
171
sax_handler.endElement = &saxEndErrorElement;
172
sax_handler.characters = &saxErrorData;
173
sax_handler.cdataBlock = &saxErrorData;
174
sax_handler.error = &saxError;
175
sax_handler.fatalError = &saxError;
177
new_(parser_info.buffer, CSStringBuffer());
178
push_(parser_info.buffer);
180
new_(parser_info.message, CSStringBuffer());
181
push_(parser_info.message);
183
if (xmlSAXUserParseMemory(&sax_handler, &parser_info, ms_errorReply->getBuffer(0), ms_errorReply->length())) {
184
if (!parser_info.thow_error)
185
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "xml SAX parser error");
188
if (parser_info.thow_error)
191
release_(parser_info.message);
192
release_(parser_info.buffer);
199
CSHTTPHeaders ms_reply_headers;
200
CSStringBuffer ms_buffer; // A scratch buffer
203
struct curl_slist *ms_header_list; // A curl list of headers to be sent with the next request.
205
CSInputStream *ms_inputStream;
206
CSOutputStream *ms_outputStream;
209
char ms_s3Checksum[HEX_CHECKSUM_VALUE_SIZE +1];
211
bool ms_notFound; // True if the object could not be found
212
bool ms_retry; // True if the request failed with a retry error.
215
CSStringBuffer *ms_errorReply;
216
char ms_curl_error[CURL_ERROR_SIZE];
220
unsigned int ms_replyStatus;
221
bool ms_throw_error; // Gets set if an exception occurs in a callback.
223
S3ProtocolCon(CSS3Protocol *prot):
225
ms_header_list(NULL),
226
ms_inputStream(NULL),
227
ms_outputStream(NULL),
234
ms_throw_error(false)
237
ms_curl = curl_easy_init();
239
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_easy_init() failed.");
241
if (curl_easy_setopt(ms_curl, CURLOPT_ERRORBUFFER, ms_curl_error))
242
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed.");
245
curl_easy_setopt(ms_curl, CURLOPT_VERBOSE, 1L);
247
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_TCP_NODELAY, 1L));
250
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_NOPROGRESS, 1L));
251
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEFUNCTION, receive_data));
252
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_READFUNCTION, send_callback));
253
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HEADERFUNCTION, receive_header));
254
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEDATA, this));
255
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_READDATA, this));
256
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_WRITEHEADER, this));
259
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_FOLLOWLOCATION, 1L)); // Follow redirects.
266
curl_easy_cleanup(ms_curl);
268
curl_slist_free_all(ms_header_list);
270
ms_inputStream->release();
272
ms_outputStream->release();
274
ms_errorReply->release();
276
ms_reply_headers.clearHeaders();
279
inline void check_reply_status()
281
if (ms_replyStatus > 199 && ms_replyStatus < 300)
286
switch (ms_replyStatus) {
288
case 204: // No Content
289
//case 301: // Moved Permanently
290
//case 307: // Temporary Redirect
292
case 404: // Not Found
305
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_errorReply->getCString());
307
} else if (ms_slowDown) {
309
CSException::logException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 slow down request.");
310
self->sleep(10); // sleep for 1/100 second.
319
inline void ms_reset()
321
// Remove any old headers
322
if (ms_header_list) {
323
curl_slist_free_all(ms_header_list);
324
ms_header_list = NULL;
327
ms_reply_headers.clearHeaders();
329
ms_throw_error = false;
331
ms_errorReply->setLength(0);
333
ms_s3Checksum[0] = 0;
337
if (ms_outputStream) {
338
ms_outputStream->release();
339
ms_outputStream = NULL;
341
if (ms_inputStream) {
342
ms_inputStream->release();
343
ms_inputStream = NULL;
347
inline void ms_setHeader(const char *header)
349
ms_header_list = curl_slist_append(ms_header_list, header);
351
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "curl_slist_append() failed.");
354
inline void ms_setURL(const char *url)
356
//printf("URL: \"%s\n", url);
358
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_URL, url));
361
inline void ms_execute_delete_request()
365
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
366
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_CUSTOMREQUEST, "DELETE"));
368
rtc = curl_easy_perform(ms_curl);
370
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_CUSTOMREQUEST, NULL)); // IMPORTANT: Reset this to it's default value
372
if (rtc && !ms_throw_error)
373
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
375
if (ms_throw_error) {
381
check_reply_status();
384
inline void ms_execute_copy_request()
388
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
389
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_INFILESIZE_LARGE, 0));
390
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_UPLOAD, 1L));
392
rtc = curl_easy_perform(ms_curl);
394
if (rtc && !ms_throw_error)
395
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
397
if (ms_throw_error) {
403
check_reply_status();
406
inline void ms_execute_get_request(CSOutputStream *output)
411
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
412
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPGET, 1L));
414
ms_outputStream = output;
415
if (curl_easy_perform(ms_curl) && !ms_throw_error) {
416
ms_outputStream = NULL;
417
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
419
ms_outputStream = NULL;
425
check_reply_status();
429
inline void ms_execute_put_request(CSInputStream *input, off_t size, Md5Digest *digest)
434
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_HTTPHEADER, ms_header_list));
435
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_INFILESIZE_LARGE, size));
436
THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_UPLOAD, 1L));
437
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_POSTFIELDSIZE_LARGE, size));
438
//THROW_CURL_IF(curl_easy_setopt(ms_curl, CURLOPT_POST, 1L));
443
ms_inputStream = input;
444
if (curl_easy_perform(ms_curl) && !ms_throw_error) {
445
ms_inputStream = NULL;
446
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, ms_curl_error);
448
ms_inputStream = NULL;
455
check_reply_status();
457
// Check that the checksums agree
458
char checksum[HEX_CHECKSUM_VALUE_SIZE +1];
460
ms_md5.md5_digest(digest);
461
cs_bin_to_hex(HEX_CHECKSUM_VALUE_SIZE, checksum, CHECKSUM_VALUE_SIZE, digest->val);
462
checksum[HEX_CHECKSUM_VALUE_SIZE] = 0;
464
cs_strToUpper(ms_s3Checksum);
465
if (strcmp(checksum, ms_s3Checksum)) {
466
// The request should be restarted in this case.
468
CSException::logException(CS_CONTEXT, CS_ERR_CHECKSUM_ERROR, "Calculated checksum did not match S3 checksum");
476
//======================================
481
//======================================
482
CSS3Protocol::CSS3Protocol():
486
s3_private_key(NULL),
489
new_(s3_server, CSStringBuffer());
490
s3_server->append("s3.amazonaws.com/");
492
s3_public_key = CSString::newString("");
493
s3_private_key = CSString::newString("");
498
CSS3Protocol::~CSS3Protocol()
501
s3_server->release();
504
s3_public_key->release();
507
s3_private_key->release();
511
CSString *CSS3Protocol::s3_getSignature(const char *verb,
513
const char *content_type,
520
CSStringBuffer *s3_buffer;
525
new_(s3_buffer, CSStringBuffer());
528
s3_buffer->setLength(0);
529
s3_buffer->append(verb);
530
s3_buffer->append("\n");
531
if (md5) s3_buffer->append(md5);
532
s3_buffer->append("\n");
533
if (content_type) s3_buffer->append(content_type);
534
s3_buffer->append("\n");
535
s3_buffer->append(date);
537
// Note: headers are assumed to be in lower case, sorted, and containing no white space.
538
s3_buffer->append("\n");
539
s3_buffer->append(headers->getCString());
541
s3_buffer->append("\n/");
542
s3_buffer->append(bucket);
543
s3_buffer->append("/");
544
s3_buffer->append(key);
547
printf("signing:\n=================\n%s\n=================\n", s3_buffer->getCString());
549
const char *ptr = s3_buffer->getCString();
551
printf("%x ", *ptr); ptr++;
557
CSString *sig = signature(s3_buffer->getCString(), s3_private_key->getCString());
564
//----------------------
565
// CURL callback functions:
566
////////////////////////////
567
//----------------------
568
static size_t send_callback(void *ptr, size_t objs, size_t obj_size, void *v_con)
570
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
571
size_t data_sent, buffer_size = objs * obj_size;
573
if (!con->ms_data_size)
578
data_sent = con->ms_inputStream->read((char*)ptr, buffer_size);
579
if (data_sent <= con->ms_data_size) {
580
con->ms_data_size -= data_sent;
582
con->ms_md5.md5_append((u_char*)ptr, data_sent); // Calculating the checksum for the data sent.
583
} else if (data_sent > con->ms_data_size)
584
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Blob larger than expected.");
585
else if (con->ms_data_size && !data_sent)
586
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Blob smaller than expected.");
589
con->ms_throw_error = true;
596
//----------------------
597
static size_t receive_data(void *vptr, size_t objs, size_t obj_size, void *v_con)
599
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
600
size_t data_len = objs * obj_size;
604
if (con->ms_replyStatus >= 400) { // Collect the error reply.
605
if (!con->ms_errorReply)
606
con->ms_errorReply = new CSStringBuffer(50);
607
con->ms_errorReply->append((char*)vptr, data_len);
608
} else if ( con->ms_outputStream)
609
con->ms_outputStream->write((char*)vptr, data_len);
612
con->ms_throw_error = true;
619
#define IS_REDIRECT(s) ((s >= 300) && (s < 400))
620
//----------------------
621
static size_t receive_header(void *header, size_t objs, size_t obj_size, void *v_con)
623
S3ProtocolCon *con = (S3ProtocolCon*) v_con;
624
size_t size = objs * obj_size;
625
char *end, *ptr = (char*) header, *name, *value;
626
u_int name_len, value_len;
627
//printf( "receive_header: %s\n", ptr);
629
if (*(end -2) == '\r' && *(end -1) == '\n')
632
while ((end != ptr) && (*ptr == ' ')) ptr++;
636
// Get the reply status.
637
// Status 100 = Continue
638
if (((!con->ms_replyStatus) || (con->ms_replyStatus == 100) || IS_REDIRECT(con->ms_replyStatus) )
639
&& !strncasecmp(ptr, "HTTP", 4)
642
while ((end != ptr) && (*ptr != ' ')) ptr++; // skip HTTP stuff
643
while ((end != ptr) && (*ptr == ' ')) ptr++; // find the start of eh status code.
647
if (end < (ptr +3)) // expecting a 3 digit status code.
650
memcpy(status, ptr, 3);
653
con->ms_replyStatus = atoi(status);
657
while ((end != ptr) && (*ptr != ':')) ptr++;
660
name_len = ptr - name;
663
while ((end != ptr) && (*ptr == ' ')) ptr++;
668
value_len = end - value;
670
while (name[name_len-1] == ' ') name_len--;
671
while (value[value_len-1] == ' ') value_len--;
673
if (!strncasecmp(name, "ETag", 4)) {
674
value++; value_len -=2; // Strip quotation marks from checksum string.
675
if (value_len == HEX_CHECKSUM_VALUE_SIZE) {
676
memcpy(con->ms_s3Checksum, value, value_len);
677
con->ms_s3Checksum[value_len] = 0;
683
con->ms_reply_headers.addHeader(name, name_len, value, value_len);
687
con->ms_throw_error = true;
694
//----------------------
696
#define SET_DATE_FROM_TIME(t, d) {strftime(d, sizeof(d), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));}
697
#define SET_DATE(d) {time_t t = time(NULL); SET_DATE_FROM_TIME(t, d);}
699
bool CSS3Protocol::s3_delete(const char *bucket, const char *key)
701
CSStringBuffer *s3_buffer;
703
CSString *signed_str;
704
uint32_t retry_count = 0;
705
S3ProtocolCon *con_data;
709
new_(s3_buffer, CSStringBuffer());
712
new_(con_data, S3ProtocolCon(this));
716
// Clear old settings.
717
con_data->ms_reset();
722
s3_buffer->setLength(0);
723
s3_buffer->append("http://");
724
s3_buffer->append(bucket);
725
s3_buffer->append(".");
726
s3_buffer->append(s3_server->getCString());
727
s3_buffer->append(key);
729
con_data->ms_setURL(s3_buffer->getCString());
731
// Add the 'DATE' header
732
s3_buffer->setLength(0);
733
s3_buffer->append("Date: ");
734
s3_buffer->append(date);
735
con_data->ms_setHeader(s3_buffer->getCString());
737
// Create the authentication signature and add the 'Authorization' header
738
signed_str = s3_getSignature("DELETE", NULL, NULL, date, bucket, key);
740
s3_buffer->setLength(0);
741
s3_buffer->append("Authorization: AWS ");
742
s3_buffer->append(s3_public_key->getCString());
743
s3_buffer->append(":");
744
s3_buffer->append(signed_str->getCString());
745
release_(signed_str); signed_str = NULL;
747
con_data->ms_setHeader(s3_buffer->getCString());
749
con_data->ms_execute_delete_request();
751
if (con_data->ms_retry) {
752
if (retry_count == s3_maxRetrys) {
753
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
755
printf("RETRY: s3_delete()\n");
760
bool notFound = con_data->ms_notFound;
767
//-------------------------------
768
void CSS3Protocol::s3_copy(const char *dest_server, const char *dest_bucket, const char *dest_key, const char *src_bucket, const char *src_key)
770
CSStringBuffer *s3_buffer;
772
CSString *signed_str;
773
uint32_t retry_count = 0;
774
S3ProtocolCon *con_data;
778
new_(s3_buffer, CSStringBuffer());
781
new_(con_data, S3ProtocolCon(this));
785
dest_server = s3_server->getCString();
788
// Clear old settings.
789
con_data->ms_reset();
794
s3_buffer->setLength(0);
795
s3_buffer->append("http://");
796
s3_buffer->append(dest_bucket);
797
s3_buffer->append(".");
798
s3_buffer->append(s3_server->getCString());
799
s3_buffer->append(dest_key);
801
con_data->ms_setURL(s3_buffer->getCString());
803
// Add the destination location
804
s3_buffer->setLength(0);
805
s3_buffer->append("Host: ");
806
s3_buffer->append(dest_bucket);
807
s3_buffer->append(".");
808
s3_buffer->append(dest_server);
809
s3_buffer->setLength(s3_buffer->length() -1); // trim the '/'
810
con_data->ms_setHeader(s3_buffer->getCString());
812
// Add the source location
813
s3_buffer->setLength(0);
814
s3_buffer->append("x-amz-copy-source:");
815
s3_buffer->append(src_bucket);
816
s3_buffer->append("/");
817
s3_buffer->append(src_key);
818
con_data->ms_setHeader(s3_buffer->getCString());
820
// Create the authentication signature and add the 'Authorization' header
821
signed_str = s3_getSignature("PUT", NULL, NULL, date, dest_bucket, dest_key, CSString::newString(s3_buffer->getCString()));
824
// Add the 'DATE' header
825
s3_buffer->setLength(0);
826
s3_buffer->append("Date: ");
827
s3_buffer->append(date);
828
con_data->ms_setHeader(s3_buffer->getCString());
831
s3_buffer->setLength(0);
832
s3_buffer->append("Authorization: AWS ");
833
s3_buffer->append(s3_public_key->getCString());
834
s3_buffer->append(":");
835
s3_buffer->append(signed_str->getCString());
836
release_(signed_str); signed_str = NULL;
837
con_data->ms_setHeader(s3_buffer->getCString());
839
con_data->ms_execute_copy_request();
841
if (con_data->ms_notFound) {
842
s3_buffer->setLength(0);
843
s3_buffer->append("Cloud copy failed, object not found: ");
844
s3_buffer->append(src_bucket);
845
s3_buffer->append(" ");
846
s3_buffer->append(src_key);
847
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, s3_buffer->getCString());
850
if (con_data->ms_retry) {
851
if (retry_count == s3_maxRetrys) {
852
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
854
printf("RETRY: s3_copy()\n");
866
//-------------------------------
867
CSVector *CSS3Protocol::s3_receive(CSOutputStream *output, const char *bucket, const char *key, bool *found)
869
CSStringBuffer *s3_buffer;
871
CSString *signed_str;
872
uint32_t retry_count = 0;
873
S3ProtocolCon *con_data;
874
CSVector *replyHeaders;
880
new_(s3_buffer, CSStringBuffer());
883
new_(con_data, S3ProtocolCon(this));
887
// Clear old settings.
888
con_data->ms_reset();
893
s3_buffer->setLength(0);
894
s3_buffer->append("http://");
895
s3_buffer->append(bucket);
896
s3_buffer->append(".");
897
s3_buffer->append(s3_server->getCString());
898
s3_buffer->append(key);
900
con_data->ms_setURL(s3_buffer->getCString());
902
// Add the 'DATE' header
903
s3_buffer->setLength(0);
904
s3_buffer->append("Date: ");
905
s3_buffer->append(date);
906
con_data->ms_setHeader(s3_buffer->getCString());
908
// Create the authentication signature and add the 'Authorization' header
909
signed_str = s3_getSignature("GET", NULL, NULL, date, bucket, key);
911
s3_buffer->setLength(0);
912
s3_buffer->append("Authorization: AWS ");
913
s3_buffer->append(s3_public_key->getCString());
914
s3_buffer->append(":");
915
s3_buffer->append(signed_str->getCString());
916
release_(signed_str); signed_str = NULL;
917
con_data->ms_setHeader(s3_buffer->getCString());
919
con_data->ms_execute_get_request(RETAIN(output));
921
if (con_data->ms_retry) {
922
if (retry_count == s3_maxRetrys) {
923
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
925
printf("RETRY: s3_receive()\n");
931
*found = !con_data->ms_notFound;
932
replyHeaders = con_data->ms_reply_headers.takeHeaders();
937
return_(replyHeaders);
941
bool thow_error; // Must be the first field.
943
CSStringBuffer *buffer;
945
} ParserListInfoRec, *ParserListInfoPtr;
948
//==========================
949
// XML parser callbacks:
952
//-------------------------------
953
static void saxStartListElement(void *user_data, const xmlChar *name, const xmlChar **)
955
ParserListInfoPtr info = (ParserListInfoPtr) user_data;
957
if (!strcmp("Key", (char *) name)) {
960
CSException::RecordException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Unexpected <Key> tag");
961
info->thow_error = true;
970
//-------------------------------
971
static void saxEndListElement(void *user_data, const xmlChar *name)
973
ParserListInfoPtr info = (ParserListInfoPtr) user_data;
975
if (info->thow_error)
978
if (!strcmp("Key", (char *) name)) {
982
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "Unexpected </Key> tag");
984
//printf("%s\n", info->buffer->getCString());
986
info->list->add(CSString::newString(info->buffer->getCString()));
987
info->buffer->setLength(0);
990
info->thow_error = true;
997
//-------------------------------
998
static void saxListData(void *user_data, const xmlChar *ch, int len)
1000
ParserListInfoPtr info = (ParserListInfoPtr) user_data;
1002
if (info->thow_error || !info->isKey)
1005
info->buffer->append( (char*) ch, len);
1010
//-------------------------------
1011
static CSVector *parse_s3_list(CSMemoryOutputStream *output)
1013
ParserListInfoRec parser_info = {0};
1014
struct _xmlSAXHandler sax_handler = {0};
1021
data = (const char *) output->getMemory(&len);
1023
sax_handler.startElement = &saxStartListElement;
1024
sax_handler.endElement = &saxEndListElement;
1025
sax_handler.characters = &saxListData;
1026
sax_handler.cdataBlock = &saxListData;
1027
sax_handler.error = &saxError;
1028
sax_handler.fatalError = &saxError;
1030
new_(parser_info.list, CSVector(10));
1031
push_(parser_info.list);
1033
new_(parser_info.buffer, CSStringBuffer());
1034
push_(parser_info.buffer);
1036
if (xmlSAXUserParseMemory(&sax_handler, &parser_info, data, len)) {
1037
if (!parser_info.thow_error)
1038
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "xml SAX parser error");
1041
if (parser_info.thow_error)
1045
release_(parser_info.buffer);
1046
pop_(parser_info.list);
1048
return_(parser_info.list);
1051
//-------------------------------
1052
CSVector *CSS3Protocol::s3_list(const char *bucket, const char *key_prefix, uint32_t max)
1054
CSStringBuffer *s3_buffer;
1056
CSString *signed_str;
1057
CSMemoryOutputStream *output;
1058
uint32_t retry_count = 0;
1059
S3ProtocolCon *con_data;
1063
new_(s3_buffer, CSStringBuffer());
1066
output = CSMemoryOutputStream::newStream(1024, 1024);
1069
new_(con_data, S3ProtocolCon(this));
1074
// Clear old settings.
1075
con_data->ms_reset();
1080
s3_buffer->setLength(0);
1081
s3_buffer->append("http://");
1082
s3_buffer->append(bucket);
1083
s3_buffer->append(".");
1084
s3_buffer->append(s3_server->getCString());
1086
s3_buffer->append("?prefix=");
1087
s3_buffer->append(key_prefix);
1092
s3_buffer->append("&max-keys=");
1094
s3_buffer->append("?max-keys=");
1095
s3_buffer->append(max);
1098
con_data->ms_setURL(s3_buffer->getCString());
1100
// Add the 'DATE' header
1101
s3_buffer->setLength(0);
1102
s3_buffer->append("Date: ");
1103
s3_buffer->append(date);
1104
con_data->ms_setHeader(s3_buffer->getCString());
1106
// Create the authentication signature and add the 'Authorization' header
1107
signed_str = s3_getSignature("GET", NULL, NULL, date, bucket, "");
1109
s3_buffer->setLength(0);
1110
s3_buffer->append("Authorization: AWS ");
1111
s3_buffer->append(s3_public_key->getCString());
1112
s3_buffer->append(":");
1113
s3_buffer->append(signed_str->getCString());
1114
release_(signed_str); signed_str = NULL;
1115
con_data->ms_setHeader(s3_buffer->getCString());
1117
con_data->ms_execute_get_request(RETAIN(output));
1119
if (con_data->ms_retry) {
1120
if (retry_count == s3_maxRetrys) {
1121
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
1123
printf("RETRY: s3_list()\n");
1131
release_(s3_buffer);
1132
return_(parse_s3_list(output));
1136
//-------------------------------
1137
CSString *CSS3Protocol::s3_getAuthorization(const char *bucket, const char *key, const char *content_type, uint32_t *s3AuthorizationTime)
1140
CSString *signed_str;
1146
content_type = "binary/octet-stream";
1148
sys_time = time(NULL);
1150
*s3AuthorizationTime = (uint32_t)sys_time;
1152
SET_DATE_FROM_TIME(sys_time, date);
1153
signed_str = s3_getSignature("PUT", NULL, content_type, date, bucket, key);
1154
return_(signed_str);
1157
//-------------------------------
1158
CSVector *CSS3Protocol::s3_send(CSInputStream *input, const char *bucket, const char *key, off_t size, const char *content_type, Md5Digest *digest, const char *s3Authorization, time_t s3AuthorizationTime)
1160
CSStringBuffer *s3_buffer;
1162
CSString *signed_str;
1163
Md5Digest dummy_digest;
1164
uint32_t retry_count = 0;
1165
S3ProtocolCon *con_data;
1166
CSVector *replyHeaders;
1171
new_(s3_buffer, CSStringBuffer());
1174
new_(con_data, S3ProtocolCon(this));
1178
digest = &dummy_digest;
1181
content_type = "binary/octet-stream";
1185
// Clear old settings.
1186
con_data->ms_reset();
1188
if (s3Authorization) {
1189
SET_DATE_FROM_TIME(s3AuthorizationTime, date);
1195
s3_buffer->setLength(0);
1196
s3_buffer->append("http://");
1197
s3_buffer->append(bucket);
1198
s3_buffer->append(".");
1199
s3_buffer->append(s3_server->getCString());
1200
s3_buffer->append(key);
1202
con_data->ms_setURL(s3_buffer->getCString());
1204
// Add the 'DATE' header
1205
s3_buffer->setLength(0);
1206
s3_buffer->append("Date: ");
1207
s3_buffer->append(date);
1208
con_data->ms_setHeader(s3_buffer->getCString());
1210
// Add the 'Content-Type' header
1211
s3_buffer->setLength(0);
1212
s3_buffer->append("Content-Type: ");
1213
s3_buffer->append(content_type);
1214
con_data->ms_setHeader(s3_buffer->getCString());
1216
// Create the authentication signature and add the 'Authorization' header
1217
if (!s3Authorization)
1218
signed_str = s3_getSignature("PUT", NULL, content_type, date, bucket, key);
1220
signed_str = CSString::newString(s3Authorization);
1222
s3_buffer->setLength(0);
1223
s3_buffer->append("Authorization: AWS ");
1224
s3_buffer->append(s3_public_key->getCString());
1225
s3_buffer->append(":");
1226
s3_buffer->append(signed_str->getCString());
1227
release_(signed_str); signed_str = NULL;
1228
con_data->ms_setHeader(s3_buffer->getCString());
1230
con_data->ms_execute_put_request(RETAIN(input), size, digest);
1232
if (con_data->ms_retry) {
1233
if (retry_count == s3_maxRetrys) {
1234
CSException::throwException(CS_CONTEXT, CS_ERR_GENERIC_ERROR, "S3 operation aborted after max retries.");
1236
printf("RETRY: s3_send()\n");
1242
replyHeaders = con_data->ms_reply_headers.takeHeaders();
1244
release_(s3_buffer);
1246
return_(replyHeaders);
1249
//-------------------------------
1250
CSString *CSS3Protocol::s3_getDataURL(const char *bucket, const char *key, uint32_t keep_alive)
1252
CSStringBuffer *s3_buffer;
1254
CSString *signed_str;
1257
new_(s3_buffer, CSStringBuffer());
1260
snprintf(timeout, 32, "%"PRId32"", ((uint32_t)time(NULL)) + keep_alive);
1262
signed_str = s3_getSignature("GET", NULL, NULL, timeout, bucket, key);
1263
//printf("Unsafe: \"%s\"\n", signed_str->getCString());
1264
signed_str = urlEncode(signed_str); // Because the signature is in the URL it must be URL encoded.
1265
//printf(" Safe: \"%s\"\n", signed_str->getCString());
1268
s3_buffer->setLength(0);
1269
s3_buffer->append("http://");
1270
s3_buffer->append(bucket);
1271
s3_buffer->append(".");
1272
s3_buffer->append(s3_server->getCString());
1273
s3_buffer->append(key);
1275
s3_buffer->append("?AWSAccessKeyId=");
1276
s3_buffer->append(s3_public_key->getCString());
1277
s3_buffer->append("&Expires=");
1278
s3_buffer->append(timeout);
1279
s3_buffer->append("&Signature=");
1280
s3_buffer->append(signed_str->getCString());
1282
release_(signed_str);
1285
CSString *str = CSString::newString(s3_buffer);
1289
//#define S3_UNIT_TEST
1291
static void show_help_info(const char *cmd)
1293
printf("Get authenticated query string:\n\t%s q <bucket> <object_key> <timeout>\n", cmd);
1294
printf("Delete object:\n\t%s d <bucket> <object_key>\n", cmd);
1295
printf("Delete all object with a given prefix:\n\t%s D <bucket> <object_prefix>\n", cmd);
1296
printf("Get object, data will be written to 'prottest.out':\n\t%s g <bucket> <object_key> <timeout>\n", cmd);
1297
printf("Put (Upload) an object:\n\t%s p <bucket> <object_key> <file>\n", cmd);
1298
printf("List objects in the bucket:\n\t%s l <bucket> [<object_prefix> [max_list_size]]\n", cmd);
1299
printf("Copy object:\n\t%s c <src_bucket> <src_object_key> <dst_bucket> <dst_object_key> \n", cmd);
1300
printf("Copy all object with a given prefix:\n\t%s C <src_bucket> <object_key_prefix> <dst_bucket> \n", cmd);
1303
void dump_headers(CSVector *header_array)
1305
CSHTTPHeaders headers;
1307
headers.setHeaders(header_array);
1308
printf("Reply Headers:\n");
1309
printf("--------------\n");
1311
for (u_int i = 0; i < headers.numHeaders(); i++) {
1312
CSHeader *h = headers.getHeader(i);
1314
printf("%s : %s\n", h->getNameCString(), h->getValueCString());
1317
printf("--------------\n");
1318
headers.clearHeaders();
1321
int main(int argc, char **argv)
1323
CSThread *main_thread;
1324
const char *pub_key;
1325
const char *priv_key;
1326
CSS3Protocol *prot = NULL;
1329
show_help_info(argv[0]);
1333
if (! CSThread::startUp()) {
1334
CSException::throwException(CS_CONTEXT, ENOMEM, "CSThread::startUp() failed.");
1340
main_thread = new CSThread( NULL);
1341
CSThread::setSelf(main_thread);
1346
pub_key = getenv("S3_ACCESS_KEY_ID");
1347
priv_key = getenv("S3_SECRET_ACCESS_KEY");
1348
new_(prot, CSS3Protocol());
1351
prot->s3_setServer("s3.amazonaws.com/");
1352
prot->s3_setPublicKey(pub_key);
1353
prot->s3_setPrivateKey(priv_key);
1355
switch (argv[1][0]) {
1356
case 'q': // Get the query string
1358
CSString *qstr = prot->s3_getDataURL(argv[2], argv[3], atoi(argv[4]));
1359
printf("To test call:\ncurl -L -D - \"%s\"\n", qstr->getCString());
1362
printf("Bad command: q <bucket> <object_key> <timeout>\n");
1365
case 'd': // Delete the object
1367
printf("delete %s %s\n", argv[2], argv[3]);
1368
if (!prot->s3_delete(argv[2], argv[3]))
1369
printf("%s/%s could not be found.\n", argv[2], argv[3]);
1372
printf("Bad command: d <bucket> <object_key>\n");
1375
case 'D': // Delete objects like
1380
list = prot->s3_list(argv[2], argv[3]);
1382
while (key = (CSString*) list->take(0)) {
1383
printf("Deleting %s\n", key->getCString());
1384
prot->s3_delete(argv[2], key->getCString());
1390
printf("Bad command: D <bucket> <object_key_prefix>\n");
1393
case 'g': // Get the object
1399
output = CSFile::newFile("prottest.out");
1401
output->open(CSFile::CREATE | CSFile::TRUNCATE);
1402
headers = prot->s3_receive(output->getOutputStream(), argv[2], argv[3], &found);
1404
printf("%s/%s could not be found.\n", argv[2], argv[3]);
1406
dump_headers(headers);
1410
printf("Bad command: g <bucket> <object_key>\n");
1414
case 'p': // Put (Upload) the object
1419
input = CSFile::newFile(argv[4]);
1421
input->open(CSFile::READONLY);
1422
headers = prot->s3_send(input->getInputStream(), argv[2], argv[3], input->myFilePath->getSize());
1423
dump_headers(headers);
1426
printf("Bad command: p <bucket> <object_key> <file> \n");
1430
case 'c': // Copy the object
1432
prot->s3_copy(NULL, argv[4], argv[5], argv[2], argv[3]);
1434
printf("Bad command: c <src_bucket> <src_object_key> <dst_bucket> <dst_object_key>\n");
1438
case 'C': // Copy objects like
1443
list = prot->s3_list(argv[2], argv[3]);
1445
while (key = (CSString*) list->take(0)) {
1446
printf("Copying %s\n", key->getCString());
1447
prot->s3_copy(NULL, argv[4], key->getCString(), argv[2], key->getCString());
1453
printf("Bad command: C <src_bucket> <object_key_prefix> <dst_bucket>\n");
1456
case 'l': // List the object
1457
if ((argc == 3) || (argc == 4) || (argc == 5)) {
1459
char *prefix = NULL;
1465
if (!strlen(prefix))
1470
max = atol(argv[4]);
1472
list = prot->s3_list(argv[2], prefix, max);
1474
while (key = (CSString*) list->take(0)) {
1475
printf("%s\n", key->getCString());
1481
printf("Bad command: l <bucket> [<object_prefix> [max_list_size]] \n");
1485
printf("Unknown command.\n");
1486
show_help_info(argv[0]);
1493
self->logException();
1498
main_thread->release();
1500
CSThread::shutDown();