|
|
| version 1.44, 2016/10/04 13:23:45 | version 1.57, 2017/11/28 20:48:37 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: @b curl parser class. | Parser: @b curl parser class. |
| Copyright (c) 2001-2015 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2017 Art. Lebedev Studio (http://www.artlebedev.com) |
| */ | */ |
| #include "pa_config_includes.h" | #include "pa_config_includes.h" |
| Line 69 static const char *dlink(const char *dlo | Line 69 static const char *dlink(const char *dlo |
| } | } |
| class ParserOptions { | struct ParserOptions : public PA_Allocated { |
| public: | |
| // real options | // real options |
| const String *filename; | const String *filename; |
| const String *content_type; | const String *content_type; |
| Line 82 public: | Line 81 public: |
| struct curl_httppost *f_post; | struct curl_httppost *f_post; |
| FILE *f_stderr; | FILE *f_stderr; |
| ParserOptions() : filename(0), content_type(0), is_text(true), charset(0), response_charset(0), url(0), f_post(0), f_stderr(0){} | // stuff to walkaround curl content-length bugs |
| bool is_post; | |
| bool has_content_length; | |
| ParserOptions() : filename(0), content_type(0), is_text(true), charset(0), response_charset(0), url(0), f_post(0), f_stderr(0), is_post(false), has_content_length(false){} | |
| ~ParserOptions() { | ~ParserOptions() { |
| f_curl_formfree(f_post); | f_curl_formfree(f_post); |
| if(f_stderr) | if(f_stderr) |
| fclose(f_stderr); | fclose(f_stderr); |
| } | } |
| }; | }; |
| // using TLS instead of keeping variables in request | // using TLS instead of keeping variables in request |
| Line 115 public: | Line 118 public: |
| Temp_curl() : saved_curl(fcurl), saved_options(foptions){ | Temp_curl() : saved_curl(fcurl), saved_options(foptions){ |
| fcurl = f_curl_easy_init(); | fcurl = f_curl_easy_init(); |
| foptions = new ParserOptions(); | foptions = new ParserOptions(); |
| f_curl_easy_setopt(fcurl, CURLOPT_POSTFIELDSIZE, 0); // fix libcurl bug | |
| f_curl_easy_setopt(fcurl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // avoid ipv6 by default | f_curl_easy_setopt(fcurl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // avoid ipv6 by default |
| } | } |
| ~Temp_curl() { | ~Temp_curl() { |
| f_curl_easy_cleanup(fcurl); | f_curl_easy_cleanup(fcurl); |
| fcurl = saved_curl; | fcurl = saved_curl; |
| Line 153 static void _curl_session(Request& r, Me | Line 156 static void _curl_session(Request& r, Me |
| } | } |
| static void _curl_version_action(Request& r, MethodParams& ){ | static void _curl_version_action(Request& r, MethodParams& ){ |
| r.write_no_lang(*new VString(*new String(f_curl_version(), String::L_TAINTED))); | r.write(*new VString(*new String(f_curl_version(), String::L_TAINTED))); |
| } | } |
| static void _curl_version(Request& r, MethodParams& params){ | static void _curl_version(Request& r, MethodParams& params){ |
| fcurl ? _curl_version_action(r, params) : temp_curl(_curl_version_action, r, params); | fcurl ? _curl_version_action(r, params) : temp_curl(_curl_version_action, r, params); |
| } | } |
| class CurlOption { | struct CurlOption : public PA_Allocated{ |
| public: | |
| enum OptionType { | enum OptionType { |
| CURL_STRING, | CURL_STRING, |
| Line 169 public: | Line 171 public: |
| CURL_URL, | CURL_URL, |
| CURL_INT, | CURL_INT, |
| CURL_POST, | CURL_POST, |
| CURL_POSTFIELDS, | |
| CURL_FORM, | CURL_FORM, |
| CURL_HEADERS, | CURL_HEADERS, |
| CURL_FILE, | CURL_FILE, |
| CURL_STDERR, | CURL_STDERR, |
| CURL_HTTP_VERSION, | |
| PARSER_LIBRARY, | PARSER_LIBRARY, |
| PARSER_NAME, | PARSER_NAME, |
| PARSER_CONTENT_TYPE, | PARSER_CONTENT_TYPE, |
| Line 217 public: | Line 221 public: |
| CURL_OPT(CURL_INT, UNRESTRICTED_AUTH); | CURL_OPT(CURL_INT, UNRESTRICTED_AUTH); |
| CURL_OPT(CURL_INT, IPRESOLVE); | CURL_OPT(CURL_INT, IPRESOLVE); |
| CURL_OPT(CURL_INT, POST); | CURL_OPT(CURL_POST, POST); |
| CURL_OPT(CURL_INT, HTTPGET); | CURL_OPT(CURL_INT, HTTPGET); |
| CURL_OPT(CURL_INT, NOBODY); | CURL_OPT(CURL_INT, NOBODY); |
| CURL_OPT(CURL_STRING, CUSTOMREQUEST); | CURL_OPT(CURL_STRING, CUSTOMREQUEST); |
| CURL_OPT(CURL_POST, POSTFIELDS); // hopefully is safe too | CURL_OPT(CURL_POSTFIELDS, POSTFIELDS); // hopefully is safe too |
| CURL_OPT(CURL_POST, COPYPOSTFIELDS); | CURL_OPT(CURL_POSTFIELDS, COPYPOSTFIELDS); |
| CURL_OPT(CURL_FORM, HTTPPOST); | CURL_OPT(CURL_FORM, HTTPPOST); |
| CURL_OPT(CURL_HEADERS, HTTPHEADER); | CURL_OPT(CURL_HEADERS, HTTPHEADER); |
| Line 277 public: | Line 281 public: |
| CURL_OPT(CURL_STRING, SSL_CIPHER_LIST); | CURL_OPT(CURL_STRING, SSL_CIPHER_LIST); |
| CURL_OPT(CURL_INT, SSL_SESSIONID_CACHE); | CURL_OPT(CURL_INT, SSL_SESSIONID_CACHE); |
| CURL_OPT(CURL_INT, SSLVERSION); | CURL_OPT(CURL_INT, SSLVERSION); |
| CURL_OPT(CURL_HTTP_VERSION, HTTP_VERSION); | |
| PARSER_OPT(PARSER_LIBRARY, "library"); | PARSER_OPT(PARSER_LIBRARY, "library"); |
| PARSER_OPT(PARSER_NAME, "name"); | PARSER_OPT(PARSER_NAME, "name"); |
| Line 288 public: | Line 293 public: |
| } *curl_options=0; | } *curl_options=0; |
| class CurlInfo { | struct CurlInfo : public PA_Allocated{ |
| public: | |
| enum OptionType { | enum OptionType { |
| CURL_STRING, | CURL_STRING, |
| CURL_INT, | CURL_INT, |
| CURL_DOUBLE | CURL_DOUBLE, |
| CURL_HTTP_VERSION | |
| }; | }; |
| CURLINFO id; | CURLINFO id; |
| Line 332 public: | Line 337 public: |
| CURL_INF(CURL_INT, SSL_VERIFYRESULT); | CURL_INF(CURL_INT, SSL_VERIFYRESULT); |
| CURL_INF(CURL_DOUBLE, STARTTRANSFER_TIME); | CURL_INF(CURL_DOUBLE, STARTTRANSFER_TIME); |
| CURL_INF(CURL_DOUBLE, TOTAL_TIME); | CURL_INF(CURL_DOUBLE, TOTAL_TIME); |
| CURL_INF(CURL_HTTP_VERSION, HTTP_VERSION); | |
| CURL_INF(CURL_INT, PROTOCOL); | |
| CURL_INF(CURL_STRING, SCHEME); | |
| } | } |
| } *curl_infos=0; | } *curl_infos=0; |
| Line 390 static void curl_form(HashStringValue *v | Line 398 static void curl_form(HashStringValue *v |
| CURLFORM_CONTENTTYPE, fvalue->fields().get("content-type")->as_string().taint_cstr(String::L_URI), | CURLFORM_CONTENTTYPE, fvalue->fields().get("content-type")->as_string().taint_cstr(String::L_URI), |
| CURLFORM_END); | CURLFORM_END); |
| } else { | } else { |
| throw Exception("curl", new String(i.key(), String::L_TAINTED), "is %s, form option value can be string, table or file only", i.value()->type()); | throw Exception("curl", new String(i.key(), String::L_TAINTED), "is %s, form option value can be string, table or file only", i.value()->type()); |
| } | } |
| } | } |
| } | } |
| Line 398 static void curl_form(HashStringValue *v | Line 406 static void curl_form(HashStringValue *v |
| static const char *curl_check_file(const String &file_spec){ | static const char *curl_check_file(const String &file_spec){ |
| const char *file_spec_cstr=file_spec.taint_cstr(String::L_FILE_SPEC); | const char *file_spec_cstr=file_spec.taint_cstr(String::L_FILE_SPEC); |
| struct stat finfo; | struct stat finfo; |
| if(stat(file_spec_cstr, &finfo)==0) | if(pa_stat(file_spec_cstr, &finfo)==0) |
| check_safe_mode(finfo, file_spec, file_spec_cstr); | check_safe_mode(finfo, file_spec, file_spec_cstr); |
| return file_spec_cstr; | return file_spec_cstr; |
| } | } |
| static long curl_http_version(const String &name){ | |
| if(name.is_empty()) return CURL_HTTP_VERSION_NONE; | |
| if(name == "1.0") return CURL_HTTP_VERSION_1_0; | |
| if(name == "1.1") return CURL_HTTP_VERSION_1_1; | |
| if(name == "2") return CURL_HTTP_VERSION_2; | |
| if(name == "2.0") return CURL_HTTP_VERSION_2_0; | |
| const char *sname = str_upper(name.cstr()); | |
| if(!strcmp(sname,"2TLS")) return CURL_HTTP_VERSION_2TLS; | |
| if(!strcmp(sname,"2ONLY")) return CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE; | |
| throw Exception("curl", &name, "invalid http_version option value"); | |
| } | |
| static const char *curl_http_version_name(long value){ | |
| if(value == CURL_HTTP_VERSION_NONE) return "none"; | |
| if(value == CURL_HTTP_VERSION_1_0) return "1.0"; | |
| if(value == CURL_HTTP_VERSION_1_1) return "1.1"; | |
| if(value == CURL_HTTP_VERSION_2) return "2"; | |
| throw Exception("curl", 0, "invalid http version '%d' in info", value); | |
| } | |
| static void curl_setopt(HashStringValue::key_type key, HashStringValue::value_type value, Request& r) { | static void curl_setopt(HashStringValue::key_type key, HashStringValue::value_type value, Request& r) { |
| CurlOption *opt=curl_options->get(key); | CurlOption *opt=curl_options->get(key); |
| Line 441 static void curl_setopt(HashStringValue: | Line 471 static void curl_setopt(HashStringValue: |
| break; | break; |
| } | } |
| case CurlOption::CURL_POST:{ | case CurlOption::CURL_POST:{ |
| // integer curl option | |
| long value_int=(long)v.as_double(); | |
| res=f_curl_easy_setopt(curl(), opt->id, value_int); | |
| options().is_post=value_int != 0; | |
| break; | |
| } | |
| case CurlOption::CURL_POSTFIELDS:{ | |
| // http post curl option | // http post curl option |
| if(v.get_string()){ | if(v.get_string()){ |
| if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, -1L)) == CURLE_OK ) | if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, -1L)) == CURLE_OK ) |
| Line 450 static void curl_setopt(HashStringValue: | Line 487 static void curl_setopt(HashStringValue: |
| if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, (long)file->value_size())) == CURLE_OK ) | if( (res=f_curl_easy_setopt(curl(), CURLOPT_POSTFIELDSIZE, (long)file->value_size())) == CURLE_OK ) |
| res=f_curl_easy_setopt(curl(), opt->id, file->value_ptr()); | res=f_curl_easy_setopt(curl(), opt->id, file->value_ptr()); |
| } | } |
| options().has_content_length=true; | |
| break; | break; |
| } | } |
| case CurlOption::CURL_FORM:{ | case CurlOption::CURL_FORM:{ |
| HashStringValue *value_hash = v.get_hash(); | HashStringValue *value_hash = v.as_hash("failed to set option 'httppost': value"); |
| if(value_hash){ | if(value_hash){ |
| curl_form(value_hash, r); | curl_form(value_hash, r); |
| } else if(v.get_string()->is_empty()){ | |
| f_curl_formfree(options().f_post); | |
| options().f_post = 0; | |
| } else { | } else { |
| throw Exception("curl", 0, "failed to set option '%s': value must be a hash", key.cstr()); | if(options().f_post) |
| f_curl_formfree(options().f_post); | |
| options().f_post = 0; | |
| } | } |
| res=f_curl_easy_setopt(curl(), CURLOPT_HTTPPOST, foptions->f_post); | res=f_curl_easy_setopt(curl(), CURLOPT_HTTPPOST, foptions->f_post); |
| options().has_content_length=true; | |
| break; | break; |
| } | } |
| case CurlOption::CURL_HEADERS:{ | case CurlOption::CURL_HEADERS:{ |
| // http headers curl option | // http headers curl option |
| HashStringValue *value_hash=v.get_hash(); | HashStringValue *value_hash=v.as_hash("failed to set option 'httpheader': value"); |
| res=f_curl_easy_setopt(curl(), opt->id, value_hash ? curl_headers(value_hash, r) : 0); | res=f_curl_easy_setopt(curl(), opt->id, value_hash ? curl_headers(value_hash, r) : 0); |
| break; | break; |
| } | } |
| Line 480 static void curl_setopt(HashStringValue: | Line 518 static void curl_setopt(HashStringValue: |
| case CurlOption::CURL_STDERR:{ | case CurlOption::CURL_STDERR:{ |
| // verbose output redirection from stderr to file curl option | // verbose output redirection from stderr to file curl option |
| const char *file_spec_cstr=curl_check_file(r.absolute(v.as_string())); | const char *file_spec_cstr=curl_check_file(r.absolute(v.as_string())); |
| FILE *f_stderr=options().f_stderr=fopen(file_spec_cstr, "wt"); | FILE *f_stderr=options().f_stderr=pa_fopen(file_spec_cstr, "wt"); |
| if (f_stderr){ | if (f_stderr){ |
| res=f_curl_easy_setopt(curl(), opt->id, f_stderr); | res=f_curl_easy_setopt(curl(), opt->id, f_stderr); |
| } else { | } else { |
| Line 488 static void curl_setopt(HashStringValue: | Line 526 static void curl_setopt(HashStringValue: |
| } | } |
| break; | break; |
| } | } |
| case CurlOption::CURL_HTTP_VERSION:{ | |
| // http protocol version name curl option | |
| long value_int=curl_http_version(v.as_string()); | |
| res=f_curl_easy_setopt(curl(), opt->id, value_int); | |
| break; | |
| } | |
| case CurlOption::PARSER_LIBRARY:{ | case CurlOption::PARSER_LIBRARY:{ |
| // 'library' parser option | // 'library' parser option |
| if(!curl_linked){ | if(!curl_linked){ |
| Line 566 static Value *curl_getinfo(const String: | Line 610 static Value *curl_getinfo(const String: |
| CURL_GETINFO(d); | CURL_GETINFO(d); |
| return new VDouble(d); | return new VDouble(d); |
| } | } |
| case CurlInfo::CURL_HTTP_VERSION:{ | |
| long l=0; | |
| CURL_GETINFO(l); | |
| return new VString(*new String(curl_http_version_name(l), String::L_TAINTED)); | |
| } | |
| } | } |
| return VVoid::get(); | return VVoid::get(); |
| } | } |
| Line 575 static void _curl_info(Request& r, Metho | Line 624 static void _curl_info(Request& r, Metho |
| curl_infos=new CurlInfoHash(); | curl_infos=new CurlInfoHash(); |
| if(params.count()==1){ | if(params.count()==1){ |
| const String &name=params.as_string(0, "name must be string"); | const String &name=params.as_string(0, "name must be string"); |
| r.write_assign_lang(*curl_getinfo(name)); | r.write(*curl_getinfo(name)); |
| } else { | } else { |
| VHash& result=*new VHash; | VHash& result=*new VHash; |
| for(CurlInfoHash::Iterator i(*curl_infos); i; i.next() ){ | for(CurlInfoHash::Iterator i(*curl_infos); i; i.next() ){ |
| result.get_hash()->put(i.key(), curl_getinfo(i.key(), i.value())); | result.get_hash()->put(i.key(), curl_getinfo(i.key(), i.value())); |
| } | } |
| r.write_no_lang(result); | r.write(result); |
| } | } |
| } | } |
| Line 591 public: | Line 640 public: |
| char *buf; | char *buf; |
| size_t length; | size_t length; |
| size_t buf_size; | size_t buf_size; |
| ResponseHeaders& headers; | |
| Curl_buffer(ResponseHeaders& aheaders) : buf((char *)pa_malloc_atomic(MAX_STRING)), length(0), buf_size(MAX_STRING-1), headers(aheaders){} | |
| Curl_buffer() : buf((char *)pa_malloc(MAX_STRING+1)), length(0), buf_size(MAX_STRING){} | void resize(size_t size){ |
| buf_size=size; | |
| buf=(char *)pa_realloc(buf, size+1); | |
| } | |
| }; | }; |
| static int curl_writer(char *data, size_t size, size_t nmemb, Curl_buffer *result){ | static int curl_writer(char *data, size_t size, size_t nmemb, Curl_buffer *result){ |
| Line 601 static int curl_writer(char *data, size_ | Line 656 static int curl_writer(char *data, size_ |
| size=size*nmemb; | size=size*nmemb; |
| if(size>0){ | if(size>0){ |
| if(result->length + size >= result->buf_size){ | size_t buf_required = result->length + size; |
| result->buf_size = result->buf_size*2 + size; | if(buf_required > result->buf_size) |
| result->buf = (char *)pa_realloc(result->buf, result->buf_size+1); | result->resize(buf_required <= result->headers.content_length ? (size_t)result->headers.content_length : result->buf_size*2 + size); |
| } | |
| memcpy(result->buf+result->length, data, size); | memcpy(result->buf+result->length, data, size); |
| result->length += size; | result->length += size; |
| } | } |
| Line 617 static int curl_header(char *data, size_ | Line 671 static int curl_header(char *data, size_ |
| size=size*nmemb; | size=size*nmemb; |
| if(size>0){ | if(size>0){ |
| result->add_header(pa_strdup(data, size)); | char *header=pa_strdup(data, size); |
| if(!pa_strncasecmp(header, "HTTP/") && !strchr(header, ':')){ | |
| // response code, clearing possible headers from previous requests | |
| result->clear(); | |
| } else { | |
| result->add_header(header); | |
| if(result->content_length>pa_file_size_limit) | |
| return 0; | |
| } | |
| } | } |
| return size; | return size; |
| } | } |
| Line 633 static void _curl_load_action(Request& r | Line 695 static void _curl_load_action(Request& r |
| CURLcode res; | CURLcode res; |
| Curl_buffer body; | |
| CURL_SETOPT(CURLOPT_WRITEFUNCTION, curl_writer, "curl writer function"); | |
| CURL_SETOPT(CURLOPT_WRITEDATA, &body, "curl write buffer"); | |
| // we need a container for headers as VFile fields can be put only after VFile.set | // we need a container for headers as VFile fields can be put only after VFile.set |
| ResponseHeaders response; | ResponseHeaders response; |
| CURL_SETOPT(CURLOPT_HEADERFUNCTION, curl_header, "curl header function"); | CURL_SETOPT(CURLOPT_HEADERFUNCTION, curl_header, "curl header function"); |
| CURL_SETOPT(CURLOPT_WRITEHEADER, &response, "curl header buffer"); | CURL_SETOPT(CURLOPT_WRITEHEADER, &response, "curl header buffer"); |
| Curl_buffer body(response); | |
| CURL_SETOPT(CURLOPT_WRITEFUNCTION, curl_writer, "curl writer function"); | |
| CURL_SETOPT(CURLOPT_WRITEDATA, &body, "curl write buffer"); | |
| if(options().is_post && !options().has_content_length){ | |
| // libcurl bug walkaround. Prior to 7.38 (Debian Jessie) curl passed Content-length: -1 | |
| // after that no Content-length header is passed, that hangs request to nginx. | |
| CURL_SETOPT(CURLOPT_POSTFIELDSIZE, 0, "post content-length"); | |
| } | |
| if((res=f_curl_easy_perform(curl())) != CURLE_OK){ | if((res=f_curl_easy_perform(curl())) != CURLE_OK){ |
| const char *ex_type = 0; | const char *ex_type = 0; |
| switch(res){ | switch(res){ |
| Line 659 static void _curl_load_action(Request& r | Line 727 static void _curl_load_action(Request& r |
| case CURLE_SSL_CACERT: | case CURLE_SSL_CACERT: |
| case CURLE_SSL_ENGINE_INITFAILED: | case CURLE_SSL_ENGINE_INITFAILED: |
| ex_type = "curl.ssl"; break; | ex_type = "curl.ssl"; break; |
| case CURLE_WRITE_ERROR: | |
| check_file_size(response.content_length, *new String(options().url)); break; | |
| default: break; | default: break; |
| } | } |
| throw Exception( PA_DEFAULT(ex_type, "curl.fail"), 0, "%s", f_curl_easy_strerror(res)); | throw Exception( PA_DEFAULT(ex_type, "curl.fail"), 0, "%s", f_curl_easy_strerror(res)); |
| Line 714 static void _curl_load_action(Request& r | Line 784 static void _curl_load_action(Request& r |
| if(Value *vcookies=vtables->hash().get("SET-COOKIE")) | if(Value *vcookies=vtables->hash().get("SET-COOKIE")) |
| result.fields().put(HTTP_COOKIES_NAME, new VTable(parse_cookies(r, vcookies->get_table()))); | result.fields().put(HTTP_COOKIES_NAME, new VTable(parse_cookies(r, vcookies->get_table()))); |
| r.write_no_lang(result); | r.write(result); |
| } | } |
| static void _curl_load(Request& r, MethodParams& params){ | static void _curl_load(Request& r, MethodParams& params){ |