patron 0.4.16 → 0.4.17

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.
@@ -1,29 +1,33 @@
1
- // -------------------------------------------------------------------
2
- //
3
- // Patron HTTP Client: Interface to libcurl
4
- // Copyright (c) 2008 The Hive http://www.thehive.com/
5
- //
6
- // Permission is hereby granted, free of charge, to any person obtaining a copy
7
- // of this software and associated documentation files (the "Software"), to deal
8
- // in the Software without restriction, including without limitation the rights
9
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- // copies of the Software, and to permit persons to whom the Software is
11
- // furnished to do so, subject to the following conditions:
12
- //
13
- // The above copyright notice and this permission notice shall be included in
14
- // all copies or substantial portions of the Software.
15
- //
16
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- // THE SOFTWARE.
23
- //
24
- // -------------------------------------------------------------------
1
+ /* -------------------------------------------------------------------------- *\
2
+ *
3
+ * Patron HTTP Client: Interface to libcurl
4
+ * Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ \* -------------------------------------------------------------------------- */
25
25
  #include <ruby.h>
26
26
  #include <curl/curl.h>
27
+ #include "membuffer.h"
28
+ #include "sglib.h" /* Simple Generic Library -> http://sglib.sourceforge.net */
29
+
30
+ #define UNUSED_ARGUMENT(x) (void)x
27
31
 
28
32
  static VALUE mPatron = Qnil;
29
33
  static VALUE mProxyType = Qnil;
@@ -49,15 +53,23 @@ struct curl_state {
49
53
  struct curl_slist* headers;
50
54
  struct curl_httppost* post;
51
55
  struct curl_httppost* last;
56
+ membuffer header_buffer;
57
+ membuffer body_buffer;
58
+ int interrupt;
52
59
  };
53
60
 
54
- //------------------------------------------------------------------------------
55
- // Curl Callbacks
56
- //
57
61
 
58
- // Takes data streamed from libcurl and writes it to a Ruby string buffer.
59
- static size_t session_write_handler(char* stream, size_t size, size_t nmemb, VALUE out) {
60
- rb_str_buf_cat(out, stream, size * nmemb);
62
+ /*----------------------------------------------------------------------------*/
63
+ /* Curl Callbacks */
64
+
65
+ /* Takes data streamed from libcurl and writes it to a Ruby string buffer. */
66
+ static size_t session_write_handler(char* stream, size_t size, size_t nmemb, membuffer* buf) {
67
+ int rc = membuffer_append(buf, stream, size * nmemb);
68
+
69
+ /* return 0 to signal that we could not append data to our buffer */
70
+ if (MB_OK != rc) { return 0; }
71
+
72
+ /* otherwise, return the number of bytes appended */
61
73
  return size * nmemb;
62
74
  }
63
75
 
@@ -65,133 +77,220 @@ static size_t session_read_handler(char* stream, size_t size, size_t nmemb, char
65
77
  size_t result = 0;
66
78
 
67
79
  if (buffer != NULL && *buffer != NULL) {
68
- int len = size * nmemb;
69
- char *s1 = strncpy(stream, *buffer, len);
70
- result = strlen(s1);
71
- *buffer += result;
80
+ size_t len = size * nmemb;
81
+ char *s1 = strncpy(stream, *buffer, len);
82
+ result = strlen(s1);
83
+ *buffer += result;
72
84
  }
73
85
 
74
86
  return result;
75
87
  }
76
88
 
77
- //------------------------------------------------------------------------------
78
- // Object allocation
79
- //
89
+ /* A non-zero return value from the progress handler will terminate the current
90
+ * request. We use this fact in order to interrupt any request when either the
91
+ * user calls the "interrupt" method on the session or when the Ruby interpreter
92
+ * is attempting to exit.
93
+ */
94
+ static int session_progress_handler(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
95
+ struct curl_state* state = (struct curl_state*) clientp;
96
+ UNUSED_ARGUMENT(dltotal);
97
+ UNUSED_ARGUMENT(dlnow);
98
+ UNUSED_ARGUMENT(ultotal);
99
+ UNUSED_ARGUMENT(ulnow);
100
+ return state->interrupt;
101
+ }
102
+
103
+
104
+ /*----------------------------------------------------------------------------*/
105
+ /* List of active curl sessions */
106
+
107
+ struct curl_state_list {
108
+ struct curl_state *state;
109
+ struct curl_state_list *next;
110
+ };
111
+
112
+ #define CS_LIST_COMPARATOR(p, _state_) (p->state == _state_)
113
+
114
+ static struct curl_state_list *cs_list = NULL;
115
+
116
+ static void cs_list_append( struct curl_state *state ) {
117
+ struct curl_state_list *item = NULL;
118
+
119
+ assert(state != NULL);
120
+ item = ruby_xmalloc(sizeof(struct curl_state_list));
121
+ item->state = state;
122
+ item->next = NULL;
123
+
124
+ SGLIB_LIST_ADD(struct curl_state_list, cs_list, item, next);
125
+ }
126
+
127
+ static void cs_list_remove(struct curl_state *state) {
128
+ struct curl_state_list *item = NULL;
129
+
130
+ assert(state != NULL);
131
+ SGLIB_LIST_FIND_MEMBER(struct curl_state_list, cs_list, state, CS_LIST_COMPARATOR, next, item);
132
+ if (item) {
133
+ SGLIB_LIST_DELETE(struct curl_state_list, cs_list, item, next);
134
+ ruby_xfree(item);
135
+ }
136
+ }
137
+
138
+ static void cs_list_interrupt(VALUE data) {
139
+ UNUSED_ARGUMENT(data);
140
+
141
+ SGLIB_LIST_MAP_ON_ELEMENTS(struct curl_state_list, cs_list, item, next, {
142
+ item->state->interrupt = 1;
143
+ });
144
+ }
80
145
 
81
- // Cleans up the Curl handle when the Session object is garbage collected.
82
- void session_free(struct curl_state *curl) {
83
- curl_easy_cleanup(curl->handle);
84
146
 
85
- if (curl->debug_file) {
147
+ /*----------------------------------------------------------------------------*/
148
+ /* Object allocation */
149
+
150
+ static void session_close_debug_file(struct curl_state *curl) {
151
+ if (curl->debug_file && stderr != curl->debug_file) {
86
152
  fclose(curl->debug_file);
87
- curl->debug_file = NULL;
153
+ }
154
+ curl->debug_file = NULL;
155
+ }
156
+
157
+ /* Cleans up the Curl handle when the Session object is garbage collected. */
158
+ void session_free(struct curl_state *curl) {
159
+ if (curl->handle) {
160
+ curl_easy_cleanup(curl->handle);
161
+ curl->handle = NULL;
88
162
  }
89
163
 
164
+ session_close_debug_file(curl);
165
+
166
+ membuffer_destroy( &curl->header_buffer );
167
+ membuffer_destroy( &curl->body_buffer );
168
+
169
+ cs_list_remove(curl);
170
+
90
171
  free(curl);
91
172
  }
92
173
 
93
- // Allocates curl_state data needed for a new Session object.
174
+ /* Allocates curl_state data needed for a new Session object. */
94
175
  VALUE session_alloc(VALUE klass) {
95
176
  struct curl_state* curl;
96
177
  VALUE obj = Data_Make_Struct(klass, struct curl_state, NULL, session_free, curl);
97
- return obj;
98
- }
99
-
100
178
 
101
- //------------------------------------------------------------------------------
102
- // Method implementations
103
- //
179
+ membuffer_init( &curl->header_buffer );
180
+ membuffer_init( &curl->body_buffer );
181
+ cs_list_append(curl);
104
182
 
105
- // Returns the version of the embedded libcurl as a string.
106
- VALUE libcurl_version(VALUE klass) {
107
- char* value = curl_version();
108
- return rb_str_new2(value);
183
+ return obj;
109
184
  }
110
185
 
111
- // Initializes the libcurl handle on object initialization.
112
- // NOTE: This must be called from Session#initialize.
113
- VALUE session_ext_initialize(VALUE self) {
186
+ /* Return the curl_state from the ruby VALUE which is the Session instance. */
187
+ static struct curl_state* get_curl_state(VALUE self) {
114
188
  struct curl_state *state;
115
189
  Data_Get_Struct(self, struct curl_state, state);
116
190
 
117
- state->handle = curl_easy_init();
118
- state->post = NULL;
119
- state->last = NULL;
191
+ if (NULL == state->handle) {
192
+ state->handle = curl_easy_init();
193
+ curl_easy_setopt(state->handle, CURLOPT_NOSIGNAL, 1);
194
+ curl_easy_setopt(state->handle, CURLOPT_NOPROGRESS, 0);
195
+ curl_easy_setopt(state->handle, CURLOPT_PROGRESSFUNCTION, &session_progress_handler);
196
+ curl_easy_setopt(state->handle, CURLOPT_PROGRESSDATA, state);
197
+ }
120
198
 
121
- return self;
199
+ return state;
122
200
  }
123
201
 
124
- // URL escapes the provided string.
125
- VALUE session_escape(VALUE self, VALUE value) {
126
- struct curl_state *state;
127
- Data_Get_Struct(self, struct curl_state, state);
128
202
 
203
+ /*----------------------------------------------------------------------------*/
204
+ /* Method implementations */
205
+
206
+ /* call-seq:
207
+ * Patron.libcurl_version -> version string
208
+ *
209
+ * Returns the version of the embedded libcurl as a string.
210
+ */
211
+ static VALUE libcurl_version(VALUE klass) {
212
+ char* value = curl_version();
213
+ UNUSED_ARGUMENT(klass);
214
+ return rb_str_new2(value);
215
+ }
216
+
217
+ /* call-seq:
218
+ * Session.escape( string ) -> escaped string
219
+ *
220
+ * URL escapes the provided string.
221
+ */
222
+ static VALUE session_escape(VALUE self, VALUE value) {
223
+ struct curl_state *state = get_curl_state(self);
129
224
  VALUE string = StringValue(value);
130
- char* escaped = curl_easy_escape(state->handle,
131
- RSTRING_PTR(string),
132
- RSTRING_LEN(string));
225
+ char* escaped = NULL;
226
+ VALUE retval = Qnil;
133
227
 
134
- VALUE retval = rb_str_new2(escaped);
228
+ escaped = curl_easy_escape(state->handle,
229
+ RSTRING_PTR(string),
230
+ (int) RSTRING_LEN(string));
231
+
232
+ retval = rb_str_new2(escaped);
135
233
  curl_free(escaped);
136
234
 
137
235
  return retval;
138
236
  }
139
237
 
140
- // Unescapes the provided string.
141
- VALUE session_unescape(VALUE self, VALUE value) {
142
- struct curl_state *state;
143
- Data_Get_Struct(self, struct curl_state, state);
144
-
238
+ /* call-seq:
239
+ * Session.unescape( string ) -> unescaped string
240
+ *
241
+ * Unescapes the provided string.
242
+ */
243
+ static VALUE session_unescape(VALUE self, VALUE value) {
244
+ struct curl_state *state = get_curl_state(self);
145
245
  VALUE string = StringValue(value);
146
- char* unescaped = curl_easy_unescape(state->handle,
147
- RSTRING_PTR(string),
148
- RSTRING_LEN(string),
149
- NULL);
246
+ char* unescaped = NULL;
247
+ VALUE retval = Qnil;
248
+
249
+ unescaped = curl_easy_unescape(state->handle,
250
+ RSTRING_PTR(string),
251
+ (int) RSTRING_LEN(string),
252
+ NULL);
150
253
 
151
- VALUE retval = rb_str_new2(unescaped);
254
+ retval = rb_str_new2(unescaped);
152
255
  curl_free(unescaped);
153
256
 
154
257
  return retval;
155
258
  }
156
259
 
157
- // Callback used to iterate over the HTTP headers and store them in an slist.
260
+ /* Callback used to iterate over the HTTP headers and store them in an slist. */
158
261
  static int each_http_header(VALUE header_key, VALUE header_value, VALUE self) {
159
- struct curl_state *state;
160
- Data_Get_Struct(self, struct curl_state, state);
161
-
262
+ struct curl_state *state = get_curl_state(self);
162
263
  VALUE name = rb_obj_as_string(header_key);
163
264
  VALUE value = rb_obj_as_string(header_value);
164
-
165
265
  VALUE header_str = Qnil;
266
+
166
267
  header_str = rb_str_plus(name, rb_str_new2(": "));
167
268
  header_str = rb_str_plus(header_str, value);
168
269
 
169
270
  state->headers = curl_slist_append(state->headers, StringValuePtr(header_str));
271
+
170
272
  return 0;
171
273
  }
172
274
 
173
275
  static int formadd_values(VALUE data_key, VALUE data_value, VALUE self) {
174
- struct curl_state *state;
175
- Data_Get_Struct(self, struct curl_state, state);
176
-
276
+ struct curl_state *state = get_curl_state(self);
177
277
  VALUE name = rb_obj_as_string(data_key);
178
278
  VALUE value = rb_obj_as_string(data_value);
179
279
 
180
280
  curl_formadd(&state->post, &state->last, CURLFORM_PTRNAME, RSTRING_PTR(name),
181
- CURLFORM_PTRCONTENTS, RSTRING_PTR(value), CURLFORM_END);
281
+ CURLFORM_PTRCONTENTS, RSTRING_PTR(value), CURLFORM_END);
282
+
182
283
  return 0;
183
284
  }
184
285
 
185
286
  static int formadd_files(VALUE data_key, VALUE data_value, VALUE self) {
186
- struct curl_state *state;
187
- Data_Get_Struct(self, struct curl_state, state);
188
-
287
+ struct curl_state *state = get_curl_state(self);
189
288
  VALUE name = rb_obj_as_string(data_key);
190
289
  VALUE value = rb_obj_as_string(data_value);
191
290
 
192
291
  curl_formadd(&state->post, &state->last, CURLFORM_PTRNAME, RSTRING_PTR(name),
193
- CURLFORM_FILE, RSTRING_PTR(value), CURLFORM_END);
194
-
292
+ CURLFORM_FILE, RSTRING_PTR(value), CURLFORM_END);
293
+
195
294
  return 0;
196
295
  }
197
296
 
@@ -199,7 +298,7 @@ static void set_chunked_encoding(struct curl_state *state) {
199
298
  state->headers = curl_slist_append(state->headers, "Transfer-Encoding: chunked");
200
299
  }
201
300
 
202
- static FILE* open_file(VALUE filename, char* perms) {
301
+ static FILE* open_file(VALUE filename, const char* perms) {
203
302
  FILE* handle = fopen(StringValuePtr(filename), perms);
204
303
  if (!handle) {
205
304
  rb_raise(rb_eArgError, "Unable to open specified file.");
@@ -208,16 +307,27 @@ static FILE* open_file(VALUE filename, char* perms) {
208
307
  return handle;
209
308
  }
210
309
 
211
- // Set the options on the Curl handle from a Request object. Takes each field
212
- // in the Request object and uses it to set the appropriate option on the Curl
213
- // handle.
310
+ /* Set the options on the Curl handle from a Request object. Takes each field
311
+ * in the Request object and uses it to set the appropriate option on the Curl
312
+ * handle.
313
+ */
214
314
  static void set_options_from_request(VALUE self, VALUE request) {
215
- struct curl_state *state;
216
- Data_Get_Struct(self, struct curl_state, state);
217
-
315
+ struct curl_state *state = get_curl_state(self);
218
316
  CURL* curl = state->handle;
219
317
 
220
- VALUE headers = rb_iv_get(request, "@headers");
318
+ ID action = Qnil;
319
+ VALUE headers = Qnil;
320
+ VALUE url = Qnil;
321
+ VALUE timeout = Qnil;
322
+ VALUE redirects = Qnil;
323
+ VALUE proxy = Qnil;
324
+ VALUE proxy_type = Qnil;
325
+ VALUE credentials = Qnil;
326
+ VALUE ignore_content_length = Qnil;
327
+ VALUE insecure = Qnil;
328
+ VALUE buffer_size = Qnil;
329
+
330
+ headers = rb_iv_get(request, "@headers");
221
331
  if (!NIL_P(headers)) {
222
332
  if (rb_type(headers) != T_HASH) {
223
333
  rb_raise(rb_eArgError, "Headers must be passed in a hash.");
@@ -225,11 +335,12 @@ static void set_options_from_request(VALUE self, VALUE request) {
225
335
 
226
336
  rb_hash_foreach(headers, each_http_header, self);
227
337
  }
228
- ID action = SYM2ID(rb_iv_get(request, "@action"));
229
- if (action == rb_intern("get")) {
230
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
231
338
 
339
+ action = SYM2ID(rb_iv_get(request, "@action"));
340
+ if (action == rb_intern("get")) {
232
341
  VALUE download_file = rb_iv_get(request, "@file_name");
342
+
343
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
233
344
  if (!NIL_P(download_file)) {
234
345
  state->download_file = open_file(download_file, "w");
235
346
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
@@ -242,8 +353,9 @@ static void set_options_from_request(VALUE self, VALUE request) {
242
353
  VALUE multipart = rb_iv_get(request, "@multipart");
243
354
 
244
355
  if (!NIL_P(data) && NIL_P(multipart)) {
356
+ long len = RSTRING_LEN(data);
357
+
245
358
  state->upload_buf = StringValuePtr(data);
246
- int len = RSTRING_LEN(data);
247
359
 
248
360
  if (action == rb_intern("post")) {
249
361
  curl_easy_setopt(curl, CURLOPT_POST, 1);
@@ -275,11 +387,11 @@ static void set_options_from_request(VALUE self, VALUE request) {
275
387
  } else { rb_raise(rb_eArgError, "Data and Filename must be passed in a hash.");}
276
388
  }
277
389
  curl_easy_setopt(curl, CURLOPT_HTTPPOST, state->post);
278
-
390
+
279
391
  } else {
280
392
  rb_raise(rb_eArgError, "Multipart PUT not supported");
281
393
  }
282
-
394
+
283
395
  } else {
284
396
  rb_raise(rb_eArgError, "Must provide either data or a filename when doing a PUT or POST");
285
397
  }
@@ -293,13 +405,13 @@ static void set_options_from_request(VALUE self, VALUE request) {
293
405
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
294
406
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
295
407
 
296
- VALUE url = rb_iv_get(request, "@url");
408
+ url = rb_iv_get(request, "@url");
297
409
  if (NIL_P(url)) {
298
410
  rb_raise(rb_eArgError, "Must provide a URL");
299
411
  }
300
412
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
301
413
 
302
- VALUE timeout = rb_iv_get(request, "@timeout");
414
+ timeout = rb_iv_get(request, "@timeout");
303
415
  if (!NIL_P(timeout)) {
304
416
  curl_easy_setopt(curl, CURLOPT_TIMEOUT, FIX2INT(timeout));
305
417
  }
@@ -309,41 +421,41 @@ static void set_options_from_request(VALUE self, VALUE request) {
309
421
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, FIX2INT(timeout));
310
422
  }
311
423
 
312
- VALUE redirects = rb_iv_get(request, "@max_redirects");
424
+ redirects = rb_iv_get(request, "@max_redirects");
313
425
  if (!NIL_P(redirects)) {
314
426
  int r = FIX2INT(redirects);
315
427
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
316
428
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
317
429
  }
318
430
 
319
- VALUE proxy = rb_iv_get(request, "@proxy");
431
+ proxy = rb_iv_get(request, "@proxy");
320
432
  if (!NIL_P(proxy)) {
321
433
  curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
322
434
  }
323
435
 
324
- VALUE proxy_type = rb_iv_get(request, "@proxy_type");
436
+ proxy_type = rb_iv_get(request, "@proxy_type");
325
437
  if (!NIL_P(proxy_type)) {
326
438
  curl_easy_setopt(curl, CURLOPT_PROXYTYPE, FIX2INT(proxy_type));
327
439
  }
328
440
 
329
- VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
441
+ credentials = rb_funcall(request, rb_intern("credentials"), 0);
330
442
  if (!NIL_P(credentials)) {
331
443
  curl_easy_setopt(curl, CURLOPT_HTTPAUTH, FIX2INT(rb_iv_get(request, "@auth_type")));
332
444
  curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
333
445
  }
334
446
 
335
- VALUE ignore_content_length = rb_iv_get(request, "@ignore_content_length");
447
+ ignore_content_length = rb_iv_get(request, "@ignore_content_length");
336
448
  if (!NIL_P(ignore_content_length)) {
337
449
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1);
338
450
  }
339
451
 
340
- VALUE insecure = rb_iv_get(request, "@insecure");
452
+ insecure = rb_iv_get(request, "@insecure");
341
453
  if(!NIL_P(insecure)) {
342
454
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
343
455
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
344
456
  }
345
457
 
346
- VALUE buffer_size = rb_iv_get(request, "@buffer_size");
458
+ buffer_size = rb_iv_get(request, "@buffer_size");
347
459
  if (!NIL_P(buffer_size)) {
348
460
  curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, FIX2INT(buffer_size));
349
461
  }
@@ -354,29 +466,30 @@ static void set_options_from_request(VALUE self, VALUE request) {
354
466
  }
355
467
  }
356
468
 
357
- // Use the info in a Curl handle to create a new Response object.
469
+ /* Use the info in a Curl handle to create a new Response object. */
358
470
  static VALUE create_response(VALUE self, CURL* curl, VALUE header_buffer, VALUE body_buffer) {
471
+ VALUE args[6] = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil };
359
472
  char* effective_url = NULL;
473
+ long code = 0;
474
+ long count = 0;
475
+
360
476
  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
361
- VALUE url = rb_str_new2(effective_url);
477
+ args[0] = rb_str_new2(effective_url);
362
478
 
363
- long code = 0;
364
479
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
365
- VALUE status = INT2NUM(code);
480
+ args[1] = INT2NUM(code);
366
481
 
367
- long count = 0;
368
482
  curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
369
- VALUE redirect_count = INT2NUM(count);
483
+ args[2] = INT2NUM(count);
370
484
 
371
- VALUE default_charset = rb_iv_get(self, "@default_response_charset");
485
+ args[3] = header_buffer;
486
+ args[4] = body_buffer;
487
+ args[5] = rb_iv_get(self, "@default_response_charset");
372
488
 
373
- VALUE args[6] = { url, status, redirect_count, header_buffer, body_buffer, default_charset };
374
-
375
- return rb_class_new_instance(6, args,
376
- rb_const_get(mPatron, rb_intern("Response")));
489
+ return rb_class_new_instance(6, args, rb_const_get(mPatron, rb_intern("Response")));
377
490
  }
378
491
 
379
- // Raise an exception based on the Curl error code.
492
+ /* Raise an exception based on the Curl error code. */
380
493
  static VALUE select_error(CURLcode code) {
381
494
  VALUE error = Qnil;
382
495
  switch (code) {
@@ -394,45 +507,57 @@ static VALUE select_error(CURLcode code) {
394
507
  return error;
395
508
  }
396
509
 
397
- // Perform the actual HTTP request by calling libcurl.
510
+ /* Perform the actual HTTP request by calling libcurl. */
398
511
  static VALUE perform_request(VALUE self) {
399
- struct curl_state *state;
400
- Data_Get_Struct(self, struct curl_state, state);
401
-
512
+ struct curl_state *state = get_curl_state(self);
402
513
  CURL* curl = state->handle;
514
+ membuffer* header_buffer = NULL;
515
+ membuffer* body_buffer = NULL;
516
+ CURLcode ret = 0;
403
517
 
404
- // headers
405
- VALUE header_buffer = rb_str_buf_new(32768);
518
+ state->interrupt = 0; /* clear any interrupt flags */
519
+
520
+ header_buffer = &state->header_buffer;
521
+ body_buffer = &state->body_buffer;
522
+
523
+ membuffer_clear(header_buffer);
524
+ membuffer_clear(body_buffer);
525
+
526
+ /* headers */
406
527
  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
407
528
  curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
408
529
 
409
- // body
410
- VALUE body_buffer = Qnil;
530
+ /* body */
411
531
  if (!state->download_file) {
412
- body_buffer = rb_str_buf_new(32768);
413
532
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
414
533
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
415
534
  }
416
535
 
417
536
  #if defined(HAVE_TBR) && defined(USE_TBR)
418
- CURLcode ret = rb_thread_blocking_region(curl_easy_perform, curl, RUBY_UBF_IO, 0);
537
+ ret = (CURLcode) rb_thread_blocking_region(
538
+ (rb_blocking_function_t*) curl_easy_perform, curl,
539
+ RUBY_UBF_IO, 0
540
+ );
419
541
  #else
420
- CURLcode ret = curl_easy_perform(curl);
542
+ ret = curl_easy_perform(curl);
421
543
  #endif
422
544
 
423
545
  if (CURLE_OK == ret) {
424
- return create_response(self, curl, header_buffer, body_buffer);
546
+ VALUE header_str = membuffer_to_rb_str(header_buffer);
547
+ VALUE body_str = Qnil;
548
+ if (!state->download_file) { body_str = membuffer_to_rb_str(body_buffer); }
549
+
550
+ return create_response(self, curl, header_str, body_str);
425
551
  } else {
426
552
  rb_raise(select_error(ret), "%s", state->error_buf);
427
553
  }
428
554
  }
429
555
 
430
- // Cleanup after each request by resetting the Curl handle and deallocating all
431
- // request related objects such as the header slist.
556
+ /* Cleanup after each request by resetting the Curl handle and deallocating
557
+ * all request related objects such as the header slist.
558
+ */
432
559
  static VALUE cleanup(VALUE self) {
433
- struct curl_state *state;
434
- Data_Get_Struct(self, struct curl_state, state);
435
-
560
+ struct curl_state *state = get_curl_state(self);
436
561
  curl_easy_reset(state->handle);
437
562
 
438
563
  if (state->headers) {
@@ -455,32 +580,86 @@ static VALUE cleanup(VALUE self) {
455
580
  return Qnil;
456
581
  }
457
582
 
458
- VALUE session_handle_request(VALUE self, VALUE request) {
583
+ /* call-seq:
584
+ * session.handle_request( request ) -> response
585
+ *
586
+ * Peform the actual HTTP request by calling libcurl. Each filed in the
587
+ * +request+ object will be used to set the appropriate option on the libcurl
588
+ * library. After the request completes, a Response object will be created and
589
+ * returned.
590
+ *
591
+ * In the event of an error in the libcurl library, a Ruby exception will be
592
+ * created and raised. The exception will return the libcurl error code and
593
+ * error message.
594
+ */
595
+ static VALUE session_handle_request(VALUE self, VALUE request) {
459
596
  set_options_from_request(self, request);
460
597
  return rb_ensure(&perform_request, self, &cleanup, self);
461
598
  }
462
599
 
463
- VALUE enable_cookie_session(VALUE self, VALUE file) {
600
+ /* call-seq:
601
+ * session.reset -> session
602
+ *
603
+ * Reset the underlying cURL session. This effectively closes all open
604
+ * connections and disables debug output.
605
+ */
606
+ static VALUE session_reset(VALUE self) {
464
607
  struct curl_state *state;
465
608
  Data_Get_Struct(self, struct curl_state, state);
609
+
610
+ if (NULL != state->handle) {
611
+ cleanup(self);
612
+ curl_easy_cleanup(state->handle);
613
+ state->handle = NULL;
614
+ session_close_debug_file(state);
615
+ }
616
+
617
+ return self;
618
+ }
619
+
620
+ /* call-seq:
621
+ * session.interrupt -> session
622
+ *
623
+ * Interrupt any currently executing request. This will cause the current
624
+ * request to error and raise an exception.
625
+ */
626
+ static VALUE session_interrupt(VALUE self) {
627
+ struct curl_state *state = get_curl_state(self);
628
+ state->interrupt = 1;
629
+ return self;
630
+ }
631
+
632
+ /* call-seq:
633
+ * session.enable_cookie_session( file ) -> session
634
+ *
635
+ * Turn on cookie handling for this session, storing them in memory by
636
+ * default or in +file+ if specified. The +file+ must be readable and
637
+ * writable. Calling multiple times will add more files.
638
+ */
639
+ static VALUE enable_cookie_session(VALUE self, VALUE file) {
640
+ struct curl_state *state = get_curl_state(self);
466
641
  CURL* curl = state->handle;
467
- char* file_path = RSTRING_PTR(file);
642
+ char* file_path = NULL;
643
+
644
+ file_path = RSTRING_PTR(file);
468
645
  if (file_path != NULL && strlen(file_path) != 0) {
469
646
  curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
470
647
  }
471
648
  curl_easy_setopt(curl, CURLOPT_COOKIEFILE, file_path);
472
- return Qnil;
649
+
650
+ return self;
473
651
  }
474
652
 
475
- VALUE set_debug_file(VALUE self, VALUE file) {
476
- struct curl_state *state;
477
- Data_Get_Struct(self, struct curl_state, state);
653
+ /* call-seq:
654
+ * session.set_debug_file( file ) -> session
655
+ *
656
+ * Enable debug output to stderr or to specified +file+.
657
+ */
658
+ static VALUE set_debug_file(VALUE self, VALUE file) {
659
+ struct curl_state *state = get_curl_state(self);
478
660
  char* file_path = RSTRING_PTR(file);
479
661
 
480
- if(state->debug_file){
481
- fclose(state->debug_file);
482
- state->debug_file = NULL;
483
- }
662
+ session_close_debug_file(state);
484
663
 
485
664
  if(file_path != NULL && strlen(file_path) != 0) {
486
665
  state->debug_file = open_file(file, "w");
@@ -488,17 +667,19 @@ VALUE set_debug_file(VALUE self, VALUE file) {
488
667
  state->debug_file = stderr;
489
668
  }
490
669
 
491
- return Qnil;
670
+ return self;
492
671
  }
493
672
 
494
- //------------------------------------------------------------------------------
495
- // Extension initialization
496
- //
673
+
674
+ /*----------------------------------------------------------------------------*/
675
+ /* Extension initialization */
497
676
 
498
677
  void Init_session_ext() {
499
678
  curl_global_init(CURL_GLOBAL_ALL);
500
679
  rb_require("patron/error");
501
680
 
681
+ rb_set_end_proc(&cs_list_interrupt, Qnil);
682
+
502
683
  mPatron = rb_define_module("Patron");
503
684
 
504
685
  ePatronError = rb_const_get(mPatron, rb_intern("Error"));
@@ -517,12 +698,15 @@ void Init_session_ext() {
517
698
  cRequest = rb_define_class_under(mPatron, "Request", rb_cObject);
518
699
  rb_define_alloc_func(cSession, session_alloc);
519
700
 
520
- rb_define_method(cSession, "ext_initialize", session_ext_initialize, 0);
521
701
  rb_define_method(cSession, "escape", session_escape, 1);
522
702
  rb_define_method(cSession, "unescape", session_unescape, 1);
523
703
  rb_define_method(cSession, "handle_request", session_handle_request, 1);
704
+ rb_define_method(cSession, "reset", session_reset, 0);
705
+ rb_define_method(cSession, "interrupt", session_interrupt, 0);
524
706
  rb_define_method(cSession, "enable_cookie_session", enable_cookie_session, 1);
525
707
  rb_define_method(cSession, "set_debug_file", set_debug_file, 1);
708
+ rb_define_alias(cSession, "urlencode", "escape");
709
+ rb_define_alias(cSession, "urldecode", "unescape");
526
710
 
527
711
  rb_define_const(cRequest, "AuthBasic", INT2FIX(CURLAUTH_BASIC));
528
712
  rb_define_const(cRequest, "AuthDigest", INT2FIX(CURLAUTH_DIGEST));