ruby-libstorj 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +111 -0
- data/Guardfile +21 -0
- data/LICENSE +502 -0
- data/README.md +262 -0
- data/Rakefile +76 -0
- data/ext/libstorj/.gitignore +47 -0
- data/ext/libstorj/.travis.yml +27 -0
- data/ext/libstorj/Doxyfile +2427 -0
- data/ext/libstorj/LICENSE +502 -0
- data/ext/libstorj/Makefile.am +6 -0
- data/ext/libstorj/README.md +198 -0
- data/ext/libstorj/autogen.sh +3 -0
- data/ext/libstorj/configure.ac +64 -0
- data/ext/libstorj/depends/Makefile +153 -0
- data/ext/libstorj/depends/config.guess +1462 -0
- data/ext/libstorj/depends/config.sub +1823 -0
- data/ext/libstorj/depends/extract-osx-sdk.sh +33 -0
- data/ext/libstorj/depends/packages/cctools.mk +7 -0
- data/ext/libstorj/depends/packages/clang.mk +7 -0
- data/ext/libstorj/depends/packages/gmp.mk +23 -0
- data/ext/libstorj/depends/packages/gnutls.mk +25 -0
- data/ext/libstorj/depends/packages/json-c.mk +7 -0
- data/ext/libstorj/depends/packages/libcurl.mk +39 -0
- data/ext/libstorj/depends/packages/libmicrohttpd.mk +7 -0
- data/ext/libstorj/depends/packages/libuv.mk +7 -0
- data/ext/libstorj/depends/packages/nettle.mk +30 -0
- data/ext/libstorj/libstorj.pc.in +11 -0
- data/ext/libstorj/src/Makefile.am +23 -0
- data/ext/libstorj/src/bip39.c +233 -0
- data/ext/libstorj/src/bip39.h +64 -0
- data/ext/libstorj/src/bip39_english.h +2074 -0
- data/ext/libstorj/src/cli.c +1494 -0
- data/ext/libstorj/src/crypto.c +525 -0
- data/ext/libstorj/src/crypto.h +178 -0
- data/ext/libstorj/src/downloader.c +1923 -0
- data/ext/libstorj/src/downloader.h +163 -0
- data/ext/libstorj/src/http.c +688 -0
- data/ext/libstorj/src/http.h +175 -0
- data/ext/libstorj/src/rs.c +962 -0
- data/ext/libstorj/src/rs.h +99 -0
- data/ext/libstorj/src/storj.c +1523 -0
- data/ext/libstorj/src/storj.h +1014 -0
- data/ext/libstorj/src/uploader.c +2736 -0
- data/ext/libstorj/src/uploader.h +181 -0
- data/ext/libstorj/src/utils.c +336 -0
- data/ext/libstorj/src/utils.h +65 -0
- data/ext/libstorj/test/Makefile.am +27 -0
- data/ext/libstorj/test/mockbridge.c +260 -0
- data/ext/libstorj/test/mockbridge.json +687 -0
- data/ext/libstorj/test/mockbridgeinfo.json +1836 -0
- data/ext/libstorj/test/mockfarmer.c +358 -0
- data/ext/libstorj/test/storjtests.h +41 -0
- data/ext/libstorj/test/tests.c +1617 -0
- data/ext/libstorj/test/tests_rs.c +869 -0
- data/ext/ruby-libstorj/extconf.rb +8 -0
- data/ext/ruby-libstorj/ruby-libstorj.cc +17 -0
- data/lib/ruby-libstorj.rb +1 -0
- data/lib/ruby-libstorj/arg_forwarding_task.rb +58 -0
- data/lib/ruby-libstorj/env.rb +178 -0
- data/lib/ruby-libstorj/ext/bucket.rb +71 -0
- data/lib/ruby-libstorj/ext/create_bucket_request.rb +53 -0
- data/lib/ruby-libstorj/ext/curl_code.rb +139 -0
- data/lib/ruby-libstorj/ext/ext.rb +71 -0
- data/lib/ruby-libstorj/ext/file.rb +84 -0
- data/lib/ruby-libstorj/ext/get_bucket_request.rb +45 -0
- data/lib/ruby-libstorj/ext/json_request.rb +51 -0
- data/lib/ruby-libstorj/ext/list_files_request.rb +63 -0
- data/lib/ruby-libstorj/ext/types.rb +226 -0
- data/lib/ruby-libstorj/ext/upload_options.rb +38 -0
- data/lib/ruby-libstorj/libstorj.rb +22 -0
- data/lib/ruby-libstorj/mixins/storj.rb +27 -0
- data/lib/ruby-libstorj/struct.rb +42 -0
- data/ruby-libstorj.gemspec +57 -0
- data/spec/helpers/options.yml.example +22 -0
- data/spec/helpers/shared_rake_examples.rb +132 -0
- data/spec/helpers/storj_options.rb +96 -0
- data/spec/helpers/upload.data +3 -0
- data/spec/helpers/upload.data.sha256 +1 -0
- data/spec/libstorj_spec.rb +0 -0
- data/spec/ruby-libstorj/arg_forwarding_task_spec.rb +311 -0
- data/spec/ruby-libstorj/env_spec.rb +353 -0
- data/spec/ruby-libstorj/ext_spec.rb +75 -0
- data/spec/ruby-libstorj/json_request_spec.rb +13 -0
- data/spec/ruby-libstorj/libstorj_spec.rb +81 -0
- data/spec/ruby-libstorj/struct_spec.rb +64 -0
- data/spec/spec_helper.rb +113 -0
- metadata +136 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file downloader.h
|
|
3
|
+
* @brief Storj download methods and definitions.
|
|
4
|
+
*
|
|
5
|
+
* Structures and functions useful for downloading files.
|
|
6
|
+
*/
|
|
7
|
+
#ifndef STORJ_DOWNLOADER_H
|
|
8
|
+
#define STORJ_DOWNLOADER_H
|
|
9
|
+
|
|
10
|
+
#include "storj.h"
|
|
11
|
+
#include "http.h"
|
|
12
|
+
#include "utils.h"
|
|
13
|
+
#include "crypto.h"
|
|
14
|
+
#include "rs.h"
|
|
15
|
+
|
|
16
|
+
#define STORJ_DOWNLOAD_CONCURRENCY 24
|
|
17
|
+
#define STORJ_DOWNLOAD_WRITESYNC_CONCURRENCY 4
|
|
18
|
+
#define STORJ_DEFAULT_MIRRORS 5
|
|
19
|
+
#define STORJ_MAX_REPORT_TRIES 2
|
|
20
|
+
#define STORJ_MAX_TOKEN_TRIES 6
|
|
21
|
+
#define STORJ_MAX_POINTER_TRIES 6
|
|
22
|
+
#define STORJ_MAX_INFO_TRIES 6
|
|
23
|
+
|
|
24
|
+
/** @brief Enumerable that defines that status of a pointer
|
|
25
|
+
*
|
|
26
|
+
* A pointer will begin as created, and move forward until an error
|
|
27
|
+
* occurs, in which case it will start moving backwards from the error
|
|
28
|
+
* state until it has been replaced and reset back to created. This process
|
|
29
|
+
* can continue until success.
|
|
30
|
+
*/
|
|
31
|
+
typedef enum {
|
|
32
|
+
POINTER_BEING_REPLACED = -3,
|
|
33
|
+
POINTER_ERROR_REPORTED = -2,
|
|
34
|
+
POINTER_ERROR = -1,
|
|
35
|
+
POINTER_CREATED = 0,
|
|
36
|
+
POINTER_BEING_DOWNLOADED = 1,
|
|
37
|
+
POINTER_DOWNLOADED = 2,
|
|
38
|
+
POINTER_MISSING = 3,
|
|
39
|
+
POINTER_FINISHED = 4
|
|
40
|
+
} storj_pointer_status_t;
|
|
41
|
+
|
|
42
|
+
/** @brief A structure for sharing data with worker threads for writing
|
|
43
|
+
* a shard to a file decriptor.
|
|
44
|
+
*/
|
|
45
|
+
typedef struct {
|
|
46
|
+
char *shard_data;
|
|
47
|
+
ssize_t shard_total_bytes;
|
|
48
|
+
int error_status;
|
|
49
|
+
FILE *destination;
|
|
50
|
+
uint32_t pointer_index;
|
|
51
|
+
/* state should not be modified in worker threads */
|
|
52
|
+
storj_download_state_t *state;
|
|
53
|
+
} shard_request_write_t;
|
|
54
|
+
|
|
55
|
+
/** @brief A structure for repairing shards from parity shards */
|
|
56
|
+
typedef struct {
|
|
57
|
+
int fd;
|
|
58
|
+
uint64_t filesize;
|
|
59
|
+
uint64_t data_filesize;
|
|
60
|
+
uint32_t data_shards;
|
|
61
|
+
uint32_t parity_shards;
|
|
62
|
+
uint64_t shard_size;
|
|
63
|
+
uint8_t *decrypt_key;
|
|
64
|
+
uint8_t *decrypt_ctr;
|
|
65
|
+
uint8_t *zilch;
|
|
66
|
+
bool has_missing;
|
|
67
|
+
/* state should not be modified in worker threads */
|
|
68
|
+
storj_download_state_t *state;
|
|
69
|
+
int error_status;
|
|
70
|
+
} file_request_recover_t;
|
|
71
|
+
|
|
72
|
+
/** @brief A structure for sharing data with worker threads for downloading
|
|
73
|
+
* shards from farmers.
|
|
74
|
+
*/
|
|
75
|
+
typedef struct {
|
|
76
|
+
storj_http_options_t *http_options;
|
|
77
|
+
char *farmer_id;
|
|
78
|
+
char *farmer_proto;
|
|
79
|
+
char *farmer_host;
|
|
80
|
+
int farmer_port;
|
|
81
|
+
char *shard_hash;
|
|
82
|
+
uint32_t pointer_index;
|
|
83
|
+
char *token;
|
|
84
|
+
uint64_t start;
|
|
85
|
+
uint64_t end;
|
|
86
|
+
uint64_t shard_total_bytes;
|
|
87
|
+
uv_async_t progress_handle;
|
|
88
|
+
uint64_t byte_position;
|
|
89
|
+
/* state should not be modified in worker threads */
|
|
90
|
+
storj_download_state_t *state;
|
|
91
|
+
int error_status;
|
|
92
|
+
bool *canceled;
|
|
93
|
+
} shard_request_download_t;
|
|
94
|
+
|
|
95
|
+
/** @brief A structure for sharing data with worker threads for sending
|
|
96
|
+
* exchange reports to the bridge.
|
|
97
|
+
*/
|
|
98
|
+
typedef struct {
|
|
99
|
+
uint32_t pointer_index;
|
|
100
|
+
storj_http_options_t *http_options;
|
|
101
|
+
storj_bridge_options_t *options;
|
|
102
|
+
int status_code;
|
|
103
|
+
storj_exchange_report_t *report;
|
|
104
|
+
/* state should not be modified in worker threads */
|
|
105
|
+
storj_download_state_t *state;
|
|
106
|
+
} shard_send_report_t;
|
|
107
|
+
|
|
108
|
+
typedef struct {
|
|
109
|
+
storj_http_options_t *http_options;
|
|
110
|
+
storj_bridge_options_t *options;
|
|
111
|
+
int status_code;
|
|
112
|
+
const char *bucket_id;
|
|
113
|
+
const char *file_id;
|
|
114
|
+
int error_status;
|
|
115
|
+
storj_file_meta_t *info;
|
|
116
|
+
/* state should not be modified in worker threads */
|
|
117
|
+
storj_download_state_t *state;
|
|
118
|
+
} file_info_request_t;
|
|
119
|
+
|
|
120
|
+
/** @brief A structure for sharing data with worker threads for replacing a
|
|
121
|
+
* pointer with a new farmer.
|
|
122
|
+
*/
|
|
123
|
+
typedef struct {
|
|
124
|
+
storj_http_options_t *http_options;
|
|
125
|
+
storj_bridge_options_t *options;
|
|
126
|
+
uint32_t pointer_index;
|
|
127
|
+
const char *bucket_id;
|
|
128
|
+
const char *file_id;
|
|
129
|
+
char *excluded_farmer_ids;
|
|
130
|
+
/* state should not be modified in worker threads */
|
|
131
|
+
storj_download_state_t *state;
|
|
132
|
+
struct json_object *response;
|
|
133
|
+
int error_status;
|
|
134
|
+
int status_code;
|
|
135
|
+
} json_request_replace_pointer_t;
|
|
136
|
+
|
|
137
|
+
/** @brief A structure for sharing data with worker threads for making JSON
|
|
138
|
+
* requests with the bridge.
|
|
139
|
+
*/
|
|
140
|
+
typedef struct {
|
|
141
|
+
storj_http_options_t *http_options;
|
|
142
|
+
storj_bridge_options_t *options;
|
|
143
|
+
char *method;
|
|
144
|
+
char *path;
|
|
145
|
+
bool auth;
|
|
146
|
+
struct json_object *body;
|
|
147
|
+
struct json_object *response;
|
|
148
|
+
/* state should not be modified in worker threads */
|
|
149
|
+
storj_download_state_t *state;
|
|
150
|
+
int status_code;
|
|
151
|
+
} json_request_download_t;
|
|
152
|
+
|
|
153
|
+
/** @brief A method that determines the next work necessary to download a file
|
|
154
|
+
*
|
|
155
|
+
* This method is called after each individual work is complete, and will
|
|
156
|
+
* determine and queue the next set of work that needs to be completed. Once
|
|
157
|
+
* the file is completely downloaded, it will call the finished callback.
|
|
158
|
+
*
|
|
159
|
+
* This method should only be called with in the main loop thread.
|
|
160
|
+
*/
|
|
161
|
+
static void queue_next_work(storj_download_state_t *state);
|
|
162
|
+
|
|
163
|
+
#endif /* STORJ_DOWNLOADER_H */
|
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
#include "http.h"
|
|
2
|
+
|
|
3
|
+
static size_t body_ignore_receive(void *buffer, size_t size, size_t nmemb,
|
|
4
|
+
void *userp)
|
|
5
|
+
{
|
|
6
|
+
size_t buflen = size * nmemb;
|
|
7
|
+
return buflen;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static size_t body_shard_send(void *buffer, size_t size, size_t nmemb,
|
|
11
|
+
void *userp)
|
|
12
|
+
{
|
|
13
|
+
shard_body_send_t *body = userp;
|
|
14
|
+
|
|
15
|
+
if (*body->canceled) {
|
|
16
|
+
return CURL_READFUNC_ABORT;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
size_t read_bytes = 0;
|
|
20
|
+
size_t buflen = size * nmemb / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
|
|
21
|
+
uint8_t clr_txt[buflen];
|
|
22
|
+
memset_zero(clr_txt, buflen);
|
|
23
|
+
|
|
24
|
+
if (buflen > 0) {
|
|
25
|
+
if (body->remain < buflen) {
|
|
26
|
+
buflen = body->remain;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Read shard data from file
|
|
30
|
+
read_bytes = pread(fileno(body->fd), clr_txt, buflen, body->offset + body->total_sent);
|
|
31
|
+
if (read_bytes == -1) {
|
|
32
|
+
body->error_code = errno;
|
|
33
|
+
return CURL_READFUNC_ABORT;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (body->ctx != NULL) {
|
|
37
|
+
ctr_crypt(body->ctx->ctx, (nettle_cipher_func *)aes256_encrypt,
|
|
38
|
+
AES_BLOCK_SIZE, body->ctx->encryption_ctr, read_bytes,
|
|
39
|
+
(uint8_t *)buffer, (uint8_t *)clr_txt);
|
|
40
|
+
} else {
|
|
41
|
+
memcpy(buffer, clr_txt, read_bytes);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (ferror(body->fd)) {
|
|
45
|
+
return CURL_READFUNC_ABORT;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
body->total_sent += read_bytes;
|
|
49
|
+
body->bytes_since_progress += read_bytes;
|
|
50
|
+
|
|
51
|
+
body->remain -= read_bytes;
|
|
52
|
+
|
|
53
|
+
memset_zero(clr_txt, buflen);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// give progress updates at set interval
|
|
57
|
+
if (body->progress_handle && read_bytes > 0 &&
|
|
58
|
+
(body->bytes_since_progress > SHARD_PROGRESS_INTERVAL ||
|
|
59
|
+
body->remain == 0)) {
|
|
60
|
+
|
|
61
|
+
shard_upload_progress_t *progress = body->progress_handle->data;
|
|
62
|
+
progress->bytes = body->total_sent;
|
|
63
|
+
uv_async_send(body->progress_handle);
|
|
64
|
+
|
|
65
|
+
body->bytes_since_progress = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return read_bytes;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
int put_shard(storj_http_options_t *http_options,
|
|
72
|
+
char *farmer_id,
|
|
73
|
+
char *proto,
|
|
74
|
+
char *host,
|
|
75
|
+
int port,
|
|
76
|
+
char *shard_hash,
|
|
77
|
+
uint64_t shard_total_bytes,
|
|
78
|
+
FILE *original_file,
|
|
79
|
+
uint64_t file_position,
|
|
80
|
+
storj_encryption_ctx_t *ctx,
|
|
81
|
+
char *token,
|
|
82
|
+
int *status_code,
|
|
83
|
+
int *read_code,
|
|
84
|
+
uv_async_t *progress_handle,
|
|
85
|
+
bool *canceled)
|
|
86
|
+
{
|
|
87
|
+
int return_code = 0;
|
|
88
|
+
|
|
89
|
+
CURL *curl = curl_easy_init();
|
|
90
|
+
if (!curl) {
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
char query_args[80];
|
|
95
|
+
snprintf(query_args, 80, "?token=%s", token);
|
|
96
|
+
|
|
97
|
+
int url_len = strlen(proto) + 3 + strlen(host) + 1 + 10 + 8
|
|
98
|
+
+ strlen(shard_hash) + strlen(query_args);
|
|
99
|
+
char *url = calloc(url_len + 1, sizeof(char));
|
|
100
|
+
if (!url) {
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
snprintf(url, url_len, "%s://%s:%i/shards/%s%s", proto, host, port,
|
|
105
|
+
shard_hash, query_args);
|
|
106
|
+
|
|
107
|
+
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
108
|
+
|
|
109
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT,
|
|
110
|
+
http_options->low_speed_limit);
|
|
111
|
+
|
|
112
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
|
|
113
|
+
http_options->low_speed_time);
|
|
114
|
+
|
|
115
|
+
if (http_options->user_agent) {
|
|
116
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, http_options->user_agent);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (http_options->proxy_url) {
|
|
120
|
+
curl_easy_setopt(curl, CURLOPT_PROXY, http_options->proxy_url);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (http_options->cainfo_path) {
|
|
124
|
+
curl_easy_setopt(curl, CURLOPT_CAINFO, http_options->cainfo_path);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
|
128
|
+
|
|
129
|
+
struct curl_slist *header_list = NULL;
|
|
130
|
+
header_list = curl_slist_append(header_list, "Content-Type: application/octet-stream");
|
|
131
|
+
|
|
132
|
+
char *header = calloc(17 + 40 + 1, sizeof(char));
|
|
133
|
+
if (!header) {
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
strcat(header, "x-storj-node-id: ");
|
|
137
|
+
strncat(header, farmer_id, 40);
|
|
138
|
+
header_list = curl_slist_append(header_list, header);
|
|
139
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
|
140
|
+
|
|
141
|
+
shard_body_send_t *shard_body = NULL;
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if (original_file && shard_total_bytes) {
|
|
145
|
+
|
|
146
|
+
shard_body = malloc(sizeof(shard_body_send_t));
|
|
147
|
+
if (!shard_body) {
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
shard_body->fd = original_file;
|
|
152
|
+
shard_body->offset = file_position;
|
|
153
|
+
shard_body->ctx = ctx;
|
|
154
|
+
shard_body->length = shard_total_bytes;
|
|
155
|
+
shard_body->remain = shard_total_bytes;
|
|
156
|
+
shard_body->total_sent = 0;
|
|
157
|
+
shard_body->bytes_since_progress = 0;
|
|
158
|
+
shard_body->progress_handle = progress_handle;
|
|
159
|
+
shard_body->canceled = canceled;
|
|
160
|
+
shard_body->error_code = 0;
|
|
161
|
+
|
|
162
|
+
curl_easy_setopt(curl, CURLOPT_READFUNCTION, body_shard_send);
|
|
163
|
+
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)shard_body);
|
|
164
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (uint64_t)shard_total_bytes);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Ignore any data sent back, we only need to know the status code
|
|
168
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_ignore_receive);
|
|
169
|
+
|
|
170
|
+
int req = curl_easy_perform(curl);
|
|
171
|
+
|
|
172
|
+
curl_slist_free_all(header_list);
|
|
173
|
+
free(header);
|
|
174
|
+
|
|
175
|
+
if (*canceled) {
|
|
176
|
+
return_code = 1;
|
|
177
|
+
goto clean_up;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (req != CURLE_OK) {
|
|
181
|
+
return_code = req;
|
|
182
|
+
goto clean_up;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// set the status code
|
|
186
|
+
if (shard_body && shard_total_bytes) {
|
|
187
|
+
*read_code = shard_body->error_code;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
long int _status_code;
|
|
191
|
+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &_status_code);
|
|
192
|
+
*status_code = (int)_status_code;
|
|
193
|
+
|
|
194
|
+
// check that total bytes have been sent
|
|
195
|
+
if (shard_body->total_sent != shard_total_bytes) {
|
|
196
|
+
return_code = 1;
|
|
197
|
+
goto clean_up;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
clean_up:
|
|
201
|
+
|
|
202
|
+
// clean up memory
|
|
203
|
+
if (shard_body) {
|
|
204
|
+
free(shard_body);
|
|
205
|
+
}
|
|
206
|
+
free(url);
|
|
207
|
+
curl_easy_cleanup(curl);
|
|
208
|
+
|
|
209
|
+
return return_code;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static size_t body_shard_receive(void *buffer, size_t size, size_t nmemb,
|
|
213
|
+
void *userp)
|
|
214
|
+
{
|
|
215
|
+
size_t buflen = size * nmemb;
|
|
216
|
+
shard_body_receive_t *body = (shard_body_receive_t *)userp;
|
|
217
|
+
|
|
218
|
+
if (*body->canceled) {
|
|
219
|
+
return CURL_READFUNC_ABORT;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (body->length + body->tail_position + buflen > body->shard_total_bytes) {
|
|
223
|
+
return CURL_READFUNC_ABORT;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Resize the buffer if necessary
|
|
227
|
+
if (body->tail_position + buflen > body->tail_length) {
|
|
228
|
+
body->tail_length = (body->tail_position + buflen) * 2;
|
|
229
|
+
body->tail = realloc(body->tail, body->tail_length);
|
|
230
|
+
|
|
231
|
+
if (!body->tail) {
|
|
232
|
+
return CURL_READFUNC_ABORT;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Copy buffer to tail
|
|
237
|
+
memcpy(body->tail + body->tail_position, buffer, buflen);
|
|
238
|
+
|
|
239
|
+
size_t writelen = body->tail_position + buflen;
|
|
240
|
+
if (body->length + writelen != body->shard_total_bytes) {
|
|
241
|
+
writelen = (writelen / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Update the hash
|
|
245
|
+
sha256_update(body->sha256_ctx, writelen, (uint8_t *)body->tail);
|
|
246
|
+
|
|
247
|
+
// Write directly to the file at the correct position
|
|
248
|
+
if (writelen == pwrite(fileno(body->destination),
|
|
249
|
+
body->tail,
|
|
250
|
+
writelen,
|
|
251
|
+
body->file_position)) {
|
|
252
|
+
|
|
253
|
+
if (writelen == -1) {
|
|
254
|
+
body->error_code = errno;
|
|
255
|
+
return CURL_READFUNC_ABORT;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
body->file_position += writelen;
|
|
259
|
+
} else {
|
|
260
|
+
// TODO handle error
|
|
261
|
+
return CURL_READFUNC_ABORT;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
body->length += writelen;
|
|
265
|
+
body->bytes_since_progress += writelen;
|
|
266
|
+
|
|
267
|
+
// Move any remaining data to the beginning and mark position
|
|
268
|
+
size_t tailing_size = body->tail_position + buflen - writelen;
|
|
269
|
+
if (tailing_size > 0) {
|
|
270
|
+
uint8_t tmp[tailing_size];
|
|
271
|
+
memcpy(&tmp, body->tail + writelen, tailing_size);
|
|
272
|
+
memcpy(body->tail, &tmp, tailing_size);
|
|
273
|
+
body->tail_position = tailing_size;
|
|
274
|
+
} else {
|
|
275
|
+
body->tail_position = 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Give progress updates at set interval
|
|
279
|
+
if (body->progress_handle &&
|
|
280
|
+
body->bytes_since_progress > SHARD_PROGRESS_INTERVAL) {
|
|
281
|
+
shard_download_progress_t *progress = body->progress_handle->data;
|
|
282
|
+
progress->bytes = body->length;
|
|
283
|
+
uv_async_send(body->progress_handle);
|
|
284
|
+
body->bytes_since_progress = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return buflen;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* shard_data must be allocated for shard_total_bytes */
|
|
291
|
+
int fetch_shard(storj_http_options_t *http_options,
|
|
292
|
+
char *farmer_id,
|
|
293
|
+
char *proto,
|
|
294
|
+
char *host,
|
|
295
|
+
int port,
|
|
296
|
+
char *shard_hash,
|
|
297
|
+
uint64_t shard_total_bytes,
|
|
298
|
+
char *token,
|
|
299
|
+
FILE *destination,
|
|
300
|
+
uint64_t file_position,
|
|
301
|
+
int *status_code,
|
|
302
|
+
int *write_code,
|
|
303
|
+
uv_async_t *progress_handle,
|
|
304
|
+
bool *canceled)
|
|
305
|
+
{
|
|
306
|
+
CURL *curl = curl_easy_init();
|
|
307
|
+
if (!curl) {
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (http_options->user_agent) {
|
|
312
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, http_options->user_agent);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (http_options->proxy_url) {
|
|
316
|
+
curl_easy_setopt(curl, CURLOPT_PROXY, http_options->proxy_url);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (http_options->cainfo_path) {
|
|
320
|
+
curl_easy_setopt(curl, CURLOPT_CAINFO, http_options->cainfo_path);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
char query_args[80];
|
|
324
|
+
snprintf(query_args, 80, "?token=%s", token);
|
|
325
|
+
int url_len = strlen(proto) + 3 + strlen(host) + 1 + 10
|
|
326
|
+
+ 8 + strlen(shard_hash) + strlen(query_args);
|
|
327
|
+
char *url = calloc(url_len + 1, sizeof(char));
|
|
328
|
+
if (!url) {
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
snprintf(url, url_len, "%s://%s:%i/shards/%s%s", proto, host, port,
|
|
332
|
+
shard_hash, query_args);
|
|
333
|
+
|
|
334
|
+
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
335
|
+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
|
|
336
|
+
|
|
337
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT,
|
|
338
|
+
http_options->low_speed_limit);
|
|
339
|
+
|
|
340
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
|
|
341
|
+
http_options->low_speed_time);
|
|
342
|
+
|
|
343
|
+
// Set the node id header
|
|
344
|
+
struct curl_slist *node_chunk = NULL;
|
|
345
|
+
char *header = calloc(17 + 40 + 1, sizeof(char));
|
|
346
|
+
if (!header) {
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
strcat(header, "x-storj-node-id: ");
|
|
350
|
+
strncat(header, farmer_id, 40);
|
|
351
|
+
node_chunk = curl_slist_append(node_chunk, header);
|
|
352
|
+
free(header);
|
|
353
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, node_chunk);
|
|
354
|
+
|
|
355
|
+
// Set the body handler
|
|
356
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_shard_receive);
|
|
357
|
+
shard_body_receive_t *body = malloc(sizeof(shard_body_receive_t));
|
|
358
|
+
if (!body) {
|
|
359
|
+
return 1;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
body->tail = malloc(BUFSIZ);
|
|
363
|
+
body->tail_length = BUFSIZ;
|
|
364
|
+
body->tail_position = 0;
|
|
365
|
+
body->length = 0;
|
|
366
|
+
body->progress_handle = progress_handle;
|
|
367
|
+
body->shard_total_bytes = shard_total_bytes;
|
|
368
|
+
body->bytes_since_progress = 0;
|
|
369
|
+
body->canceled = canceled;
|
|
370
|
+
body->sha256_ctx = malloc(sizeof(struct sha256_ctx));
|
|
371
|
+
body->error_code = 0;
|
|
372
|
+
if (!body->sha256_ctx) {
|
|
373
|
+
return 1;
|
|
374
|
+
}
|
|
375
|
+
sha256_init(body->sha256_ctx);
|
|
376
|
+
|
|
377
|
+
body->destination = destination;
|
|
378
|
+
body->file_position = file_position;
|
|
379
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)body);
|
|
380
|
+
|
|
381
|
+
int req = curl_easy_perform(curl);
|
|
382
|
+
|
|
383
|
+
curl_slist_free_all(node_chunk);
|
|
384
|
+
free(body->tail);
|
|
385
|
+
|
|
386
|
+
// set the status code
|
|
387
|
+
if (body) {
|
|
388
|
+
*write_code = body->error_code;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
int error_code = 0;
|
|
393
|
+
if (req != CURLE_OK) {
|
|
394
|
+
// TODO include the actual http error code
|
|
395
|
+
error_code = STORJ_FARMER_REQUEST_ERROR;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// set the status code
|
|
399
|
+
long int _status_code;
|
|
400
|
+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &_status_code);
|
|
401
|
+
*status_code = (int)_status_code;
|
|
402
|
+
|
|
403
|
+
curl_easy_cleanup(curl);
|
|
404
|
+
|
|
405
|
+
free(url);
|
|
406
|
+
|
|
407
|
+
if (error_code) {
|
|
408
|
+
free(body->sha256_ctx);
|
|
409
|
+
free(body);
|
|
410
|
+
return error_code;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (body->length != shard_total_bytes) {
|
|
414
|
+
free(body->sha256_ctx);
|
|
415
|
+
free(body);
|
|
416
|
+
return STORJ_FARMER_INTEGRITY_ERROR;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
uint8_t *hash_sha256 = calloc(SHA256_DIGEST_SIZE, sizeof(uint8_t));
|
|
420
|
+
if (!hash_sha256) {
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
sha256_digest(body->sha256_ctx, SHA256_DIGEST_SIZE, hash_sha256);
|
|
424
|
+
|
|
425
|
+
struct ripemd160_ctx rctx;
|
|
426
|
+
ripemd160_init(&rctx);
|
|
427
|
+
ripemd160_update(&rctx, SHA256_DIGEST_SIZE, hash_sha256);
|
|
428
|
+
|
|
429
|
+
free(hash_sha256);
|
|
430
|
+
|
|
431
|
+
uint8_t *hash_rmd160 = calloc(RIPEMD160_DIGEST_SIZE + 1, sizeof(uint8_t));
|
|
432
|
+
if (!hash_rmd160) {
|
|
433
|
+
return 1;
|
|
434
|
+
}
|
|
435
|
+
ripemd160_digest(&rctx, RIPEMD160_DIGEST_SIZE, hash_rmd160);
|
|
436
|
+
|
|
437
|
+
char *hash = calloc(RIPEMD160_DIGEST_SIZE * 2 + 1, sizeof(char));
|
|
438
|
+
if (!hash) {
|
|
439
|
+
return 1;
|
|
440
|
+
}
|
|
441
|
+
for (unsigned i = 0; i < RIPEMD160_DIGEST_SIZE; i++) {
|
|
442
|
+
sprintf(&hash[i*2], "%02x", hash_rmd160[i]);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
free(body->sha256_ctx);
|
|
446
|
+
free(body);
|
|
447
|
+
free(hash_rmd160);
|
|
448
|
+
|
|
449
|
+
if (strcmp(shard_hash, hash) != 0) {
|
|
450
|
+
error_code = STORJ_FARMER_INTEGRITY_ERROR;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
free(hash);
|
|
454
|
+
|
|
455
|
+
if (error_code) {
|
|
456
|
+
return error_code;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// final progress update
|
|
460
|
+
if (progress_handle) {
|
|
461
|
+
shard_download_progress_t *progress = progress_handle->data;
|
|
462
|
+
progress->bytes = shard_total_bytes;
|
|
463
|
+
uv_async_send(progress_handle);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
static size_t body_json_send(void *buffer, size_t size, size_t nmemb,
|
|
470
|
+
void *userp)
|
|
471
|
+
{
|
|
472
|
+
http_body_send_t *body = (http_body_send_t *)userp;
|
|
473
|
+
|
|
474
|
+
size_t buflen = size * nmemb;
|
|
475
|
+
|
|
476
|
+
if (buflen > 0) {
|
|
477
|
+
if (body->remain < buflen) {
|
|
478
|
+
buflen = body->remain;
|
|
479
|
+
}
|
|
480
|
+
memcpy(buffer, body->pnt, buflen);
|
|
481
|
+
|
|
482
|
+
body->pnt += buflen;
|
|
483
|
+
body->remain -= buflen;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return buflen;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
static size_t body_json_receive(void *buffer, size_t size, size_t nmemb,
|
|
490
|
+
void *userp)
|
|
491
|
+
{
|
|
492
|
+
size_t buflen = size * nmemb;
|
|
493
|
+
http_body_receive_t *body = (http_body_receive_t *)userp;
|
|
494
|
+
|
|
495
|
+
body->data = realloc(body->data, body->length + buflen + 1);
|
|
496
|
+
if (body->data == NULL) {
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
memcpy(&(body->data[body->length]), buffer, buflen);
|
|
501
|
+
|
|
502
|
+
body->length += buflen;
|
|
503
|
+
body->data[body->length] = 0;
|
|
504
|
+
|
|
505
|
+
return buflen;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
int fetch_json(storj_http_options_t *http_options,
|
|
509
|
+
storj_bridge_options_t *options,
|
|
510
|
+
char *method,
|
|
511
|
+
char *path,
|
|
512
|
+
struct json_object *request_body,
|
|
513
|
+
bool auth,
|
|
514
|
+
struct json_object **response,
|
|
515
|
+
int *status_code)
|
|
516
|
+
{
|
|
517
|
+
CURL *curl = curl_easy_init();
|
|
518
|
+
if (!curl) {
|
|
519
|
+
return 1;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
523
|
+
|
|
524
|
+
char *user_pass = NULL;
|
|
525
|
+
|
|
526
|
+
// Set the url
|
|
527
|
+
int url_len = strlen(options->proto) + 3 + strlen(options->host) +
|
|
528
|
+
1 + 10 + strlen(path);
|
|
529
|
+
char *url = calloc(url_len + 1, sizeof(char));
|
|
530
|
+
if (!url) {
|
|
531
|
+
return 1;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
snprintf(url, url_len, "%s://%s:%i%s", options->proto, options->host,
|
|
535
|
+
options->port, path);
|
|
536
|
+
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
537
|
+
|
|
538
|
+
// Set the user agent
|
|
539
|
+
if (http_options->user_agent) {
|
|
540
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, http_options->user_agent);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Set the HTTP method
|
|
544
|
+
if (0 == strcmp(method, "PUT")) {
|
|
545
|
+
curl_easy_setopt(curl, CURLOPT_PUT, 1);
|
|
546
|
+
} else if (0 == strcmp(method, "POST")) {
|
|
547
|
+
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
|
548
|
+
} else if (0 == strcmp(method, "GET")) {
|
|
549
|
+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
|
|
550
|
+
} else if (0 == strcmp(method, "DELETE")) {
|
|
551
|
+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
552
|
+
} else {
|
|
553
|
+
return 1;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Set the proxy
|
|
557
|
+
if (http_options->proxy_url) {
|
|
558
|
+
curl_easy_setopt(curl, CURLOPT_PROXY, http_options->proxy_url);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Set the path to the Certificate Authority (CA) bundle
|
|
562
|
+
if (http_options->cainfo_path) {
|
|
563
|
+
curl_easy_setopt(curl, CURLOPT_CAINFO, http_options->cainfo_path);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Set the timeout
|
|
567
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, http_options->timeout);
|
|
568
|
+
|
|
569
|
+
// Setup the body handler
|
|
570
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_json_receive);
|
|
571
|
+
http_body_receive_t *body = malloc(sizeof(http_body_receive_t));
|
|
572
|
+
if (!body) {
|
|
573
|
+
return 1;
|
|
574
|
+
}
|
|
575
|
+
body->data = NULL;
|
|
576
|
+
body->length = 0;
|
|
577
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)body);
|
|
578
|
+
|
|
579
|
+
// Include authentication headers if info is provided
|
|
580
|
+
if (auth && options->user && options->pass) {
|
|
581
|
+
|
|
582
|
+
// Hash password
|
|
583
|
+
uint8_t *pass_hash = calloc(SHA256_DIGEST_SIZE, sizeof(uint8_t));
|
|
584
|
+
if (!pass_hash) {
|
|
585
|
+
return 1;
|
|
586
|
+
}
|
|
587
|
+
char *pass = calloc(SHA256_DIGEST_SIZE * 2 + 1, sizeof(char));
|
|
588
|
+
if (!pass) {
|
|
589
|
+
return 1;
|
|
590
|
+
}
|
|
591
|
+
struct sha256_ctx ctx;
|
|
592
|
+
sha256_init(&ctx);
|
|
593
|
+
sha256_update(&ctx, strlen(options->pass), (uint8_t *)options->pass);
|
|
594
|
+
sha256_digest(&ctx, SHA256_DIGEST_SIZE, pass_hash);
|
|
595
|
+
for (unsigned i = 0; i < SHA256_DIGEST_SIZE; i++) {
|
|
596
|
+
sprintf(&pass[i*2], "%02x", pass_hash[i]);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
free(pass_hash);
|
|
600
|
+
|
|
601
|
+
int user_pass_len = strlen(options->user) + 1 + strlen(pass);
|
|
602
|
+
user_pass = calloc(user_pass_len + 1, sizeof(char));
|
|
603
|
+
if (!user_pass) {
|
|
604
|
+
return 1;
|
|
605
|
+
}
|
|
606
|
+
strcat(user_pass, options->user);
|
|
607
|
+
strcat(user_pass, ":");
|
|
608
|
+
strcat(user_pass, pass);
|
|
609
|
+
|
|
610
|
+
free(pass);
|
|
611
|
+
|
|
612
|
+
curl_easy_setopt(curl, CURLOPT_USERPWD, user_pass);
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
struct curl_slist *header_list = NULL;
|
|
617
|
+
|
|
618
|
+
// Include body if request body json is provided
|
|
619
|
+
http_body_send_t *post_body = NULL;
|
|
620
|
+
const char *req_buf = NULL;
|
|
621
|
+
if (request_body) {
|
|
622
|
+
req_buf = json_object_to_json_string(request_body);
|
|
623
|
+
|
|
624
|
+
header_list = curl_slist_append(header_list,
|
|
625
|
+
"Content-Type: application/json");
|
|
626
|
+
|
|
627
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
|
628
|
+
|
|
629
|
+
post_body = malloc(sizeof(http_body_send_t));
|
|
630
|
+
if (!post_body) {
|
|
631
|
+
return 1;
|
|
632
|
+
}
|
|
633
|
+
post_body->pnt = (char *)req_buf;
|
|
634
|
+
post_body->remain = strlen(req_buf);
|
|
635
|
+
|
|
636
|
+
curl_easy_setopt(curl, CURLOPT_READFUNCTION, body_json_send);
|
|
637
|
+
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)post_body);
|
|
638
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (uint64_t)strlen(req_buf));
|
|
639
|
+
} else {
|
|
640
|
+
header_list = curl_slist_append(header_list, "Content-Length: 0");
|
|
641
|
+
header_list = curl_slist_append(header_list, "Content-Type: application/json");
|
|
642
|
+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
int ret = 0;
|
|
646
|
+
int req = curl_easy_perform(curl);
|
|
647
|
+
|
|
648
|
+
free(url);
|
|
649
|
+
|
|
650
|
+
if (header_list) {
|
|
651
|
+
curl_slist_free_all(header_list);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (post_body) {
|
|
655
|
+
free(post_body);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (user_pass) {
|
|
659
|
+
free(user_pass);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
*response = NULL;
|
|
663
|
+
|
|
664
|
+
if (req != CURLE_OK) {
|
|
665
|
+
ret = req;
|
|
666
|
+
goto cleanup;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// set the status code
|
|
670
|
+
long int _status_code;
|
|
671
|
+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &_status_code);
|
|
672
|
+
*status_code = (int)_status_code;
|
|
673
|
+
|
|
674
|
+
if (body->data && body->length > 0) {
|
|
675
|
+
*response = json_tokener_parse((char *)body->data);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
cleanup:
|
|
679
|
+
curl_easy_cleanup(curl);
|
|
680
|
+
if (body->data) {
|
|
681
|
+
free(body->data);
|
|
682
|
+
}
|
|
683
|
+
if (body) {
|
|
684
|
+
free(body);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return ret;
|
|
688
|
+
}
|