Annotation of parser3/src/classes/curl.C, revision 1.2
1.1 misha 1: /** @file
2: Parser: @b curl parser class.
3:
4: Copyright(c) 2001-2009 ArtLebedev Group(http://www.artlebedev.com)
5: */
6:
1.2 ! misha 7: static const char * const IDENT_INET_C="$Date: 2009-12-04 04:20:34 $";
1.1 misha 8:
9: #include "pa_vmethod_frame.h"
10: #include "pa_request.h"
11: #include "pa_vfile.h"
12: #include "pa_charsets.h"
13: #include "ltdl.h"
14:
15: class MCurl: public Methoded {
16: public:
17: MCurl();
18:
19: public: // Methoded
20: bool used_directly() { return true; }
21: };
22:
23: // global variables
24:
25: DECLARE_CLASS_VAR(curl, new MCurl, 0);
26:
27: // from file.C
28: extern bool is_text_mode(const String& mode);
29:
30: #ifdef HAVE_CURL
31: #include "curl.h"
32:
33: typedef CURL *(*t_curl_easy_init)(); t_curl_easy_init f_curl_easy_init;
34: typedef CURLcode (*t_curl_easy_setopt)(CURL *, CURLoption option, ...); t_curl_easy_setopt f_curl_easy_setopt;
35: typedef CURLcode (*t_curl_easy_perform)(CURL *); t_curl_easy_perform f_curl_easy_perform;
36: typedef void (*t_curl_easy_cleanup)(CURL *); t_curl_easy_cleanup f_curl_easy_cleanup;
37: typedef const char *(*t_curl_easy_strerror)(CURLcode); t_curl_easy_strerror f_curl_easy_strerror;
38: typedef struct curl_slist *(*t_curl_slist_append)(struct curl_slist *,const char *); t_curl_slist_append f_curl_slist_append;
39:
40: #define GLINK(name) f_##name=(t_##name)lt_dlsym(handle, #name);
41: #define DLINK(name) GLINK(name) if(!f_##name) return "function " #name " was not found";
42:
43: const char *dlink(const char *dlopen_file_spec) {
44: if(lt_dlinit())
45: return lt_dlerror();
46:
47: lt_dlhandle handle=lt_dlopen(dlopen_file_spec);
48:
49: if(!handle){
50: if(const char* result=lt_dlerror())
51: return result;
52: return "can not open the dynamic link module";
53: }
54:
55: DLINK(curl_easy_init);
56: DLINK(curl_easy_cleanup);
57: DLINK(curl_easy_setopt);
58: DLINK(curl_easy_perform);
59: DLINK(curl_easy_strerror);
60: DLINK(curl_slist_append);
61: return 0;
62: }
63:
64:
65: class ParserOptions {
66: public:
67: const char *filename;
68: const String *content_type;
69: bool is_text;
70: Charset *charset;
71:
72: ParserOptions() : filename(0), content_type(0), is_text(true), charset(0) {}
73: };
74:
75: // using thread local variables instead of keeping them in request
76: // not necessary for cgi version
77: #ifdef WIN32
78: #define __thread __declspec(thread)
79: #endif
80: __thread CURL *fcurl=0;
81: __thread ParserOptions *foptions;
82:
83: static CURL *curl(){
84: if(!fcurl)
85: throw Exception("curl", 0, "outside of 'session' operator");
86: return fcurl;
87: }
88:
89: static ParserOptions &options(){
90: if(!foptions)
91: throw Exception("curl", 0, "outside of 'session' operator");
92: return *foptions;
93: }
94:
95: // using temporal object scheme to garanty cleanup call
96: class Temp_curl {
97: CURL* saved_curl;
98: ParserOptions* saved_options;
99: public:
100: Temp_curl() : saved_curl(fcurl) {
101: fcurl = f_curl_easy_init();
102: foptions = new ParserOptions();
103: }
104: ~Temp_curl() {
105: f_curl_easy_cleanup(fcurl);
106: fcurl = saved_curl;
107: delete foptions;
108: foptions = saved_options;
109: }
110: };
111:
112: bool curl_linked = false;
113: #ifdef WIN32
114: const char *curl_library="libcurl.dll";
115: #else
116: const char *curl_library="libcurl.so";
117: #endif
118:
119: const char *curl_status = 0;
120:
121: static void temp_curl(void (*action)(Request&, MethodParams&), Request& r, MethodParams& params){
122: if(!curl_linked){
123: curl_linked=true;
124: curl_status=dlink(curl_library);
125: }
126:
127: if(curl_status == 0){
128: Temp_curl temp_curl;
129: action(r,params);
130: } else {
131: throw Exception("curl", 0, "failed to load curl library %s: %s", curl_library, curl_status);
132: }
133: }
134:
135: static void _curl_session_action(Request& r, MethodParams& params){
136: Value& body_code=params.as_junction(0, "body must be code");
137: r.process_write(body_code);
138: }
139:
140: static void _curl_session(Request& r, MethodParams& params){
141: temp_curl(_curl_session_action, r, params);
142: }
143:
144:
145: static char *str_lower(const char *str){
146: char *result=pa_strdup(str);
147: for(char* c=result; *c; c++)
148: *c=(char)tolower((unsigned char)*c);
149: return result;
150: }
151:
152: static char *str_upper(const char *str){
153: char *result=pa_strdup(str);
154: for(char* c=result; *c; c++)
155: *c=(char)toupper((unsigned char)*c);
156: return result;
157: }
158:
159: class CurlOption {
160: public:
161:
162: enum OptionType {
163: CURL_STRING,
164: CURL_URLENCODE, // url-encoded string
165: CURL_INT,
166: CURL_POST,
167: CURL_HEADERS,
168: CURL_FILE,
169: PARSER_LIBRARY,
170: PARSER_NAME,
171: PARSER_CONTENT_TYPE,
172: PARSER_MODE,
173: PARSER_CHARSET
174: };
175:
176: CURLoption id;
177: OptionType type;
178: CurlOption(CURLoption aid, OptionType atype): id(aid), type(atype) {}
179: };
180:
181: class CurlOptionHash: public HashString<CurlOption*> {
182: public:
183: CurlOptionHash() {
184: #define CURL_OPT(type, name) put(str_lower(#name),new CurlOption(CURLOPT_##name, CurlOption::type));
185: #define PARSER_OPT(type, name) put(name,new CurlOption((CURLoption)0, CurlOption::type));
186: CURL_OPT(CURL_URLENCODE, URL);
187: CURL_OPT(CURL_STRING, INTERFACE);
188: CURL_OPT(CURL_INT, LOCALPORT);
189: CURL_OPT(CURL_INT, PORT);
190:
191: CURL_OPT(CURL_INT, HTTPAUTH);
192: CURL_OPT(CURL_STRING, USERPWD);
193:
1.2 ! misha 194: #ifdef CURLOPT_USERNAME
1.1 misha 195: CURL_OPT(CURL_STRING, USERNAME);
196: CURL_OPT(CURL_STRING, PASSWORD);
1.2 ! misha 197: #endif
1.1 misha 198:
199: CURL_OPT(CURL_INT, AUTOREFERER);
200: CURL_OPT(CURL_STRING, ENCODING); // gzip or deflate
201: CURL_OPT(CURL_INT, FOLLOWLOCATION);
202: CURL_OPT(CURL_INT, UNRESTRICTED_AUTH);
203:
204: CURL_OPT(CURL_INT, POST);
205: CURL_OPT(CURL_INT, HTTPGET);
206:
207: CURL_OPT(CURL_POST, POSTFIELDS); // hopefully is safe too
208: CURL_OPT(CURL_POST, COPYPOSTFIELDS);
209:
210: CURL_OPT(CURL_HEADERS, HTTPHEADER);
211: CURL_OPT(CURL_URLENCODE, COOKIE);
212: CURL_OPT(CURL_URLENCODE, COOKIELIST);
213: CURL_OPT(CURL_INT, COOKIESESSION);
214:
215: CURL_OPT(CURL_INT, IGNORE_CONTENT_LENGTH);
216: CURL_OPT(CURL_INT, HTTP_CONTENT_DECODING);
217: CURL_OPT(CURL_INT, HTTP_TRANSFER_DECODING);
218:
219: CURL_OPT(CURL_INT, TIMEOUT);
220: CURL_OPT(CURL_INT, TIMEOUT_MS);
221: CURL_OPT(CURL_INT, LOW_SPEED_LIMIT);
222: CURL_OPT(CURL_INT, LOW_SPEED_TIME);
223: CURL_OPT(CURL_INT, MAXCONNECTS);
224:
225: CURL_OPT(CURL_INT, FRESH_CONNECT);
226: CURL_OPT(CURL_INT, FORBID_REUSE);
227: CURL_OPT(CURL_INT, CONNECTTIMEOUT);
228: CURL_OPT(CURL_INT, CONNECTTIMEOUT_MS);
229:
230: CURL_OPT(CURL_FILE, SSLCERT);
231: CURL_OPT(CURL_STRING, SSLCERTTYPE);
232: CURL_OPT(CURL_FILE, SSLKEY);
233: CURL_OPT(CURL_STRING, SSLKEYTYPE);
234: CURL_OPT(CURL_STRING, KEYPASSWD);
235: CURL_OPT(CURL_STRING, SSLENGINE);
236: CURL_OPT(CURL_STRING, SSLENGINE_DEFAULT);
237:
1.2 ! misha 238: #ifdef CURLOPT_ISSUERCERT
1.1 misha 239: CURL_OPT(CURL_FILE, ISSUERCERT);
1.2 ! misha 240: #endif
! 241:
! 242: #ifdef CURLOPT_CRLFILE
1.1 misha 243: CURL_OPT(CURL_FILE, CRLFILE);
1.2 ! misha 244: #endif
1.1 misha 245:
246: CURL_OPT(CURL_STRING, CAINFO);
247: CURL_OPT(CURL_STRING, CAPATH);
248: CURL_OPT(CURL_INT, SSL_VERIFYPEER);
249: CURL_OPT(CURL_INT, SSL_VERIFYHOST);
250: CURL_OPT(CURL_STRING, SSL_CIPHER_LIST);
251: CURL_OPT(CURL_INT, SSL_SESSIONID_CACHE);
252:
253: PARSER_OPT(PARSER_LIBRARY, "library");
254: PARSER_OPT(PARSER_NAME, "name");
255: PARSER_OPT(PARSER_CONTENT_TYPE, "content-type");
256: PARSER_OPT(PARSER_MODE, "mode");
257: PARSER_OPT(PARSER_CHARSET, "charset");
258: }
259:
260: } *curl_options=0;
261:
262: static const char *curl_urlencode(const String &s, Request& r){
263: if(options().charset){
264: Temp_client_charset temp(r.charsets, *options().charset);
265: return s.untaint_and_transcode_cstr(String::L_URI, &r.charsets);
266: } else
267: return s.untaint_cstr(String::L_URI);
268: }
269:
270: static struct curl_slist *curl_headers(HashStringValue *value_hash, Request& r) {
271: struct curl_slist *slist=NULL;
272:
273: for(HashStringValue::Iterator i(*value_hash); i; i.next() ){
274: String header =
275: String(capitalize(i.key().cstr()), String::L_URI)
276: << ": "
277: << String(i.value()->as_string(), String::L_URI);
278:
279: slist=f_curl_slist_append(slist, curl_urlencode(header, r));
280: }
281: return slist;
282: }
283:
284: static void curl_setopt(HashStringValue::key_type key, HashStringValue::value_type value, Request& r) {
285: CurlOption *opt=curl_options->get(key);
286:
287: if(opt==0)
288: throw Exception("curl", 0, "called with invalid option '%s'", key.cstr());
289:
290: CURLcode res = CURLE_OK;
291: Value &v=r.process_to_value(*value);
292:
293: switch (opt->type){
294: case CurlOption::CURL_STRING:{
295: // string curl option
296: const char *value_str=v.as_string().cstr();
297: res=f_curl_easy_setopt(curl(), opt->id, value_str);
298: break;
299: }
300: case CurlOption::CURL_URLENCODE:{
301: // url-encoded string curl option
302: const char *value_str=curl_urlencode(v.as_string(), r);
303: res=f_curl_easy_setopt(curl(), opt->id, value_str);
304: break;
305: }
306: case CurlOption::CURL_INT:{
1.2 ! misha 307: // integer curl option
! 308: long value_int=(long)v.as_double();
1.1 misha 309: res=f_curl_easy_setopt(curl(), opt->id, value_int);
310: break;
311: }
312: case CurlOption::CURL_POST:{
313: // http post curl option
314: if(v.get_string()){
1.2 ! misha 315: if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, -1L)) == CURLE_OK )
1.1 misha 316: res=f_curl_easy_setopt(curl(), opt->id, curl_urlencode(v.as_string(), r));
317: } else {
318: VFile *file=v.as_vfile(String::L_AS_IS);
1.2 ! misha 319: if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, (long)file->value_size())) == CURLE_OK )
1.1 misha 320: res=f_curl_easy_setopt(curl(), opt->id, file->value_ptr());
321: }
322: break;
323: }
324: case CurlOption::CURL_HEADERS:{
325: // http headers curl option
326: HashStringValue *value_hash=v.get_hash();
327: res=f_curl_easy_setopt(curl(), opt->id, value_hash ? curl_headers(value_hash, r) : 0);
328: break;
329: }
330: case CurlOption::CURL_FILE:{
331: // file-spec curl option
332: const char *value_str=r.absolute(v.as_string()).taint_cstr(String::L_FILE_SPEC);
333: res=f_curl_easy_setopt(curl(), opt->id, value_str);
334: break;
335: }
336: case CurlOption::PARSER_LIBRARY:{
337: // 'library' parser option
338: if(fcurl==0){
339: curl_library=v.as_string().taint_cstr(String::L_FILE_SPEC);
340: } else
341: throw Exception("curl", 0, "failed to set option '%s': %s", key.cstr(), "already loaded");
342: break;
343: }
344: case CurlOption::PARSER_NAME:{
345: // 'name' parser option
346: options().filename=v.as_string().taint_cstr(String::L_FILE_SPEC);
347: break;
348: }
349: case CurlOption::PARSER_CONTENT_TYPE:{
350: // 'content-type' parser option
351: options().content_type=&v.as_string();
352: break;
353: }
354: case CurlOption::PARSER_MODE:{
355: // 'mode' parser option
356: options().is_text=is_text_mode(v.as_string());
357: break;
358: }
359: case CurlOption::PARSER_CHARSET:{
360: // 'charset' parser option
361: options().charset=&::charsets.get(v.as_string().change_case(r.charsets.source(), String::CC_UPPER));
362: break;
363: }
364: }
365:
366: if(res != CURLE_OK)
367: throw Exception("curl", 0, "failed to set option '%s': %s", key.cstr(), f_curl_easy_strerror(res));
368: }
369:
370: static void _curl_option(Request& r, MethodParams& params){
371: if(curl_options==0)
372: curl_options=new CurlOptionHash();
373:
374: if(HashStringValue* options=params.as_no_junction(0, OPTIONS_MUST_NOT_BE_CODE).get_hash()){
375: options->for_each<Request&>(curl_setopt, r);
376: } else
377: throw Exception("curl", 0, "options must be hash");
378: }
379:
380:
381: class Curl_buffer{
382: public:
383: char *buf;
384: size_t length;
385: size_t buf_size;
386:
387: Curl_buffer() : buf((char *)pa_malloc(MAX_STRING+1)), length(0), buf_size(MAX_STRING){}
388: };
389:
390: static int curl_writer(char *data, size_t size, size_t nmemb, Curl_buffer *result){
391: if(result == 0)
392: return 0;
393:
394: size=size*nmemb;
395: if(size>0){
396: if(result->length + size >= result->buf_size){
397: result->buf_size = result->buf_size*2 + size;
398: result->buf = (char *)pa_realloc(result->buf, result->buf_size+1);
399: }
400: memcpy(result->buf+result->length, data, size);
401: result->length += size;
402: }
403: return size;
404: }
405:
406: static int curl_header(char *data, size_t size, size_t nmemb, HASH_STRING<char *> *result){
407: if(result == 0)
408: return 0;
409:
410: size=size*nmemb;
411: if(size>0){
412: char *line=pa_strdup(data, size);
413: char *value=lsplit(line,':');
414: if(value && *line){
415: // we need only headers, not the response code
416: result->put(str_upper(line), value);
417: }
418: }
419: return size;
420: }
421:
422: #define CURL_SETOPT(option, arg, message) \
423: if( (res=f_curl_easy_setopt(curl(), option, arg)) != CURLE_OK){ \
424: throw Exception("curl", 0, "failed to set " message ": %s", f_curl_easy_strerror(res)); \
425: }
426:
427: static void _curl_load_action(Request& r, MethodParams& params){
428: if(params.count()==1)
429: _curl_option(r, params);
430:
431: CURLcode res;
432:
433: Curl_buffer body;
434: CURL_SETOPT(CURLOPT_WRITEFUNCTION, curl_writer, "curl writer function");
435: CURL_SETOPT(CURLOPT_WRITEDATA, &body, "curl write buffer");
436:
437: // we need a container for headers as VFile fields can be put only after VFile.set
438: HASH_STRING<char *> headers;
439: CURL_SETOPT(CURLOPT_HEADERFUNCTION, curl_header, "curl header function");
440: CURL_SETOPT(CURLOPT_WRITEHEADER, &headers, "curl header buffer");
441:
442: if((res=f_curl_easy_perform(curl())) != CURLE_OK){
443: throw Exception("curl", 0, "failed to exec curl session: %s", f_curl_easy_strerror(res));
444: }
445:
446: // assure trailing zero
447: body.buf[body.length]=0;
448:
449: Charset *asked_charset=options().charset;
450:
451: if(options().is_text && asked_charset != 0){
452: String::C c=Charset::transcode(String::C(body.buf, body.length), *asked_charset, r.charsets.source());
453: body.buf=(char *)c.str;
454: body.length=c.length;
455: }
456:
457: Value* vcontent_type=
458: options().content_type ? new VString(*options().content_type) :
459: options().filename ? new VString(r.mime_type_of(options().filename)) : 0;
460:
461: VFile& result=*new VFile;
462: result.set(true /*tainted*/, body.buf, body.length, options().filename, vcontent_type);
463: result.set_mode(options().is_text);
464:
465: for(HASH_STRING<char *>::Iterator i(headers); i; i.next() ){
466: String::Body key=i.key();
467: String::Body value=i.value();
468: if(asked_charset){
469: key=Charset::transcode(key, *asked_charset, r.charsets.source());
470: value=Charset::transcode(value, *asked_charset, r.charsets.source());
471: }
472: result.fields().put(key, new VString(*new String(value.trim(String::TRIM_BOTH, " \t\n\r"), String::L_TAINTED)));
473: }
474:
475: r.write_no_lang(result);
476: }
477:
478: static void _curl_load(Request& r, MethodParams& params){
479: fcurl ? _curl_load_action(r, params) : temp_curl(_curl_load_action, r, params);
480: }
481:
482: #endif // HAVE_CURL
483:
484: // constructor
485: MCurl::MCurl(): Methoded("curl") {
486: #ifdef HAVE_CURL
487: add_native_method("session", Method::CT_STATIC, _curl_session, 1, 1);
488: add_native_method("option", Method::CT_STATIC, _curl_option, 1, 1);
489: add_native_method("load", Method::CT_STATIC, _curl_load, 0, 1);
490: #endif // HAVE_CURL
491: }
E-mail: