1 /// My old curl wrapper. Use [arsd.http2] instead on newer projects, or [std.net.curl] in Phobos if you don't trust my homemade implementations :) 2 module arsd.curl; 3 4 // see this for info on making a curl.lib on windows: 5 // http://stackoverflow.com/questions/7933845/where-is-curl-lib-for-dmd 6 7 pragma(lib, "curl"); 8 9 import std.string; 10 extern(C) { 11 struct CURL; 12 struct curl_slist; 13 14 alias int CURLcode; 15 alias int CURLoption; 16 17 enum int CURLOPT_URL = 10002; 18 enum int CURLOPT_WRITEFUNCTION = 20011; 19 enum int CURLOPT_WRITEDATA = 10001; 20 enum int CURLOPT_POSTFIELDS = 10015; 21 enum int CURLOPT_POSTFIELDSIZE = 60; 22 enum int CURLOPT_POST = 47; 23 enum int CURLOPT_HTTPHEADER = 10023; 24 enum int CURLOPT_USERPWD = 0x00002715; 25 26 enum int CURLOPT_VERBOSE = 41; 27 28 // enum int CURLOPT_COOKIE = 22; 29 enum int CURLOPT_COOKIEFILE = 10031; 30 enum int CURLOPT_COOKIEJAR = 10082; 31 32 enum int CURLOPT_SSL_VERIFYPEER = 64; 33 34 enum int CURLOPT_FOLLOWLOCATION = 52; 35 36 CURL* curl_easy_init(); 37 void curl_easy_cleanup(CURL* handle); 38 CURLcode curl_easy_perform(CURL* curl); 39 40 void curl_global_init(int flags); 41 42 enum int CURL_GLOBAL_ALL = 0b1111; 43 44 CURLcode curl_easy_setopt(CURL* handle, CURLoption option, ...); 45 curl_slist* curl_slist_append(curl_slist*, const char*); 46 void curl_slist_free_all(curl_slist*); 47 48 // size is size of item, count is how many items 49 size_t write_data(void* buffer, size_t size, size_t count, void* user) { 50 string* str = cast(string*) user; 51 char* data = cast(char*) buffer; 52 53 assert(size == 1); 54 55 *str ~= data[0..count]; 56 57 return count; 58 } 59 60 char* curl_easy_strerror(CURLcode errornum ); 61 } 62 /* 63 struct CurlOptions { 64 string username; 65 string password; 66 } 67 */ 68 69 string getDigestString(string s) { 70 import std.digest.md; 71 import std.digest; 72 auto hash = md5Of(s); 73 auto a = toHexString(hash); 74 return a.idup; 75 } 76 //import std.md5; 77 import std.file; 78 /// this automatically caches to a local file for the given time. it ignores the expires header in favor of your time to keep. 79 version(linux) 80 string cachedCurl(string url, int maxCacheHours) { 81 string res; 82 83 auto cacheFile = "/tmp/arsd-curl-cache-" ~ getDigestString(url); 84 85 import std.datetime; 86 87 if(!std.file.exists(cacheFile) || std.file.timeLastModified(cacheFile) < Clock.currTime() - dur!"hours"(maxCacheHours)) { 88 res = curl(url); 89 std.file.write(cacheFile, res); 90 } else { 91 res = cast(string) std.file.read(cacheFile); 92 } 93 94 return res; 95 } 96 97 98 string curl(string url, string data = null, string contentType = "application/x-www-form-urlencoded") { 99 return curlAuth(url, data, null, null, contentType); 100 } 101 102 string curlCookie(string cookieFile, string url, string data = null, string contentType = "application/x-www-form-urlencoded") { 103 return curlAuth(url, data, null, null, contentType, null, null, cookieFile); 104 } 105 106 string curlAuth(string url, string data = null, string username = null, string password = null, string contentType = "application/x-www-form-urlencoded", string methodOverride = null, string[] customHeaders = null, string cookieJar = null) { 107 CURL* curl = curl_easy_init(); 108 if(curl is null) 109 throw new Exception("curl init"); 110 scope(exit) 111 curl_easy_cleanup(curl); 112 113 string ret; 114 115 int res; 116 117 debug(arsd_curl_verbose) 118 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); 119 120 res = curl_easy_setopt(curl, CURLOPT_URL, std..string.toStringz(url)); 121 if(res != 0) throw new CurlException(res); 122 if(username !is null) { 123 res = curl_easy_setopt(curl, CURLOPT_USERPWD, std..string.toStringz(username ~ ":" ~ password)); 124 if(res != 0) throw new CurlException(res); 125 } 126 res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_data); 127 if(res != 0) throw new CurlException(res); 128 res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); 129 if(res != 0) throw new CurlException(res); 130 131 curl_slist* headers = null; 132 //if(data !is null) 133 // contentType = ""; 134 if(contentType.length) 135 headers = curl_slist_append(headers, toStringz("Content-Type: " ~ contentType)); 136 137 foreach(h; customHeaders) { 138 headers = curl_slist_append(headers, toStringz(h)); 139 } 140 scope(exit) 141 curl_slist_free_all(headers); 142 143 if(data) { 144 res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.ptr); 145 if(res != 0) throw new CurlException(res); 146 res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.length); 147 if(res != 0) throw new CurlException(res); 148 } 149 150 res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 151 if(res != 0) throw new CurlException(res); 152 153 if(cookieJar !is null) { 154 res = curl_easy_setopt(curl, CURLOPT_COOKIEJAR, toStringz(cookieJar)); 155 if(res != 0) throw new CurlException(res); 156 res = curl_easy_setopt(curl, CURLOPT_COOKIEFILE, toStringz(cookieJar)); 157 if(res != 0) throw new CurlException(res); 158 } else { 159 // just want to enable cookie parsing for location 3xx thingies. 160 // some crappy sites will give you an endless runaround if they can't 161 // place their fucking tracking cookies. 162 res = curl_easy_setopt(curl, CURLOPT_COOKIEFILE, toStringz("lol totally not here")); 163 } 164 165 res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); 166 if(res != 0) throw new CurlException(res); 167 //res = curl_easy_setopt(curl, 81, 0); // FIXME verify host 168 //if(res != 0) throw new CurlException(res); 169 170 version(no_curl_follow) {} else { 171 res = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 172 if(res != 0) throw new CurlException(res); 173 } 174 175 if(methodOverride !is null) { 176 switch(methodOverride) { 177 default: assert(0); 178 case "POST": 179 res = curl_easy_setopt(curl, CURLOPT_POST, 1); 180 break; 181 case "GET": 182 //curl_easy_setopt(curl, CURLOPT_POST, 0); 183 break; 184 } 185 } 186 187 auto failure = curl_easy_perform(curl); 188 if(failure != 0) 189 throw new CurlException(failure, "\nURL" ~ url); 190 191 return ret; 192 } 193 194 class CurlException : Exception { 195 this(CURLcode code, string msg = null, string file = __FILE__, int line = __LINE__) @system { 196 string message = file ~ ":" ~ to!string(line) ~ " (" ~ to!string(code) ~ ") "; 197 198 auto strerror = curl_easy_strerror(code); 199 200 while(*strerror) { 201 message ~= *strerror; 202 strerror++; 203 } 204 205 super(message ~ msg); 206 } 207 } 208 209 210 import std.conv;