1 // Written in the D programming language. 2 3 /** 4 Networking client functionality as provided by $(HTTP _curl.haxx.se/libcurl, 5 libcurl). The libcurl library must be installed on the system in order to use 6 this module. 7 8 $(SCRIPT inhibitQuickIndex = 1;) 9 10 $(DIVC quickindex, 11 $(BOOKTABLE , 12 $(TR $(TH Category) $(TH Functions) 13 ) 14 $(TR $(TDNW High level) $(TD $(MYREF download) $(MYREF upload) $(MYREF get) 15 $(MYREF post) $(MYREF put) $(MYREF del) $(MYREF options) $(MYREF trace) 16 $(MYREF connect) $(MYREF byLine) $(MYREF byChunk) 17 $(MYREF byLineAsync) $(MYREF byChunkAsync) ) 18 ) 19 $(TR $(TDNW Low level) $(TD $(MYREF HTTP) $(MYREF FTP) $(MYREF 20 SMTP) ) 21 ) 22 ) 23 ) 24 25 Note: 26 You may need to link to the $(B curl) library, e.g. by adding $(D "libs": ["curl"]) 27 to your $(B dub.json) file if you are using $(LINK2 http://code.dlang.org, DUB). 28 29 Windows x86 note: 30 A DMD compatible libcurl static library can be downloaded from the dlang.org 31 $(LINK2 http://dlang.org/download.html, download page). 32 33 Compared to using libcurl directly this module allows simpler client code for 34 common uses, requires no unsafe operations, and integrates better with the rest 35 of the language. Futhermore it provides <a href="std_range.html">$(D range)</a> 36 access to protocols supported by libcurl both synchronously and asynchronously. 37 38 A high level and a low level API are available. The high level API is built 39 entirely on top of the low level one. 40 41 The high level API is for commonly used functionality such as HTTP/FTP get. The 42 $(LREF byLineAsync) and $(LREF byChunkAsync) provides asynchronous <a 43 href="std_range.html">$(D ranges)</a> that performs the request in another 44 thread while handling a line/chunk in the current thread. 45 46 The low level API allows for streaming and other advanced features. 47 48 $(BOOKTABLE Cheat Sheet, 49 $(TR $(TH Function Name) $(TH Description) 50 ) 51 $(LEADINGROW High level) 52 $(TR $(TDNW $(LREF download)) $(TD $(D 53 download("ftp.digitalmars.com/sieve.ds", "/tmp/downloaded-ftp-file")) 54 downloads file from URL to file system.) 55 ) 56 $(TR $(TDNW $(LREF upload)) $(TD $(D 57 upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds");) 58 uploads file from file system to URL.) 59 ) 60 $(TR $(TDNW $(LREF get)) $(TD $(D 61 get("dlang.org")) returns a char[] containing the dlang.org web page.) 62 ) 63 $(TR $(TDNW $(LREF put)) $(TD $(D 64 put("dlang.org", "Hi")) returns a char[] containing 65 the dlang.org web page. after a HTTP PUT of "hi") 66 ) 67 $(TR $(TDNW $(LREF post)) $(TD $(D 68 post("dlang.org", "Hi")) returns a char[] containing 69 the dlang.org web page. after a HTTP POST of "hi") 70 ) 71 $(TR $(TDNW $(LREF byLine)) $(TD $(D 72 byLine("dlang.org")) returns a range of char[] containing the 73 dlang.org web page.) 74 ) 75 $(TR $(TDNW $(LREF byChunk)) $(TD $(D 76 byChunk("dlang.org", 10)) returns a range of ubyte[10] containing the 77 dlang.org web page.) 78 ) 79 $(TR $(TDNW $(LREF byLineAsync)) $(TD $(D 80 byLineAsync("dlang.org")) returns a range of char[] containing the dlang.org web 81 page asynchronously.) 82 ) 83 $(TR $(TDNW $(LREF byChunkAsync)) $(TD $(D 84 byChunkAsync("dlang.org", 10)) returns a range of ubyte[10] containing the 85 dlang.org web page asynchronously.) 86 ) 87 $(LEADINGROW Low level 88 ) 89 $(TR $(TDNW $(LREF HTTP)) $(TD $(D HTTP) struct for advanced usage)) 90 $(TR $(TDNW $(LREF FTP)) $(TD $(D FTP) struct for advanced usage)) 91 $(TR $(TDNW $(LREF SMTP)) $(TD $(D SMTP) struct for advanced usage)) 92 ) 93 94 95 Example: 96 --- 97 import std.net.curl, std.stdio; 98 99 // Return a char[] containing the content specified by a URL 100 auto content = get("dlang.org"); 101 102 // Post data and return a char[] containing the content specified by a URL 103 auto content = post("mydomain.com/here.cgi", ["name1" : "value1", "name2" : "value2"]); 104 105 // Get content of file from ftp server 106 auto content = get("ftp.digitalmars.com/sieve.ds"); 107 108 // Post and print out content line by line. The request is done in another thread. 109 foreach (line; byLineAsync("dlang.org", "Post data")) 110 writeln(line); 111 112 // Get using a line range and proxy settings 113 auto client = HTTP(); 114 client.proxy = "1.2.3.4"; 115 foreach (line; byLine("dlang.org", client)) 116 writeln(line); 117 --- 118 119 For more control than the high level functions provide, use the low level API: 120 121 Example: 122 --- 123 import std.net.curl, std.stdio; 124 125 // GET with custom data receivers 126 auto http = HTTP("dlang.org"); 127 http.onReceiveHeader = 128 (in char[] key, in char[] value) { writeln(key, ": ", value); }; 129 http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; }; 130 http.perform(); 131 --- 132 133 First, an instance of the reference-counted HTTP struct is created. Then the 134 custom delegates are set. These will be called whenever the HTTP instance 135 receives a header and a data buffer, respectively. In this simple example, the 136 headers are written to stdout and the data is ignored. If the request should be 137 stopped before it has finished then return something less than data.length from 138 the onReceive callback. See $(LREF onReceiveHeader)/$(LREF onReceive) for more 139 information. Finally the HTTP request is effected by calling perform(), which is 140 synchronous. 141 142 Source: $(PHOBOSSRC std/net/_curl.d) 143 144 Copyright: Copyright Jonas Drewsen 2011-2012 145 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 146 Authors: Jonas Drewsen. Some of the SMTP code contributed by Jimmy Cao. 147 148 Credits: The functionally is based on $(HTTP _curl.haxx.se/libcurl, libcurl). 149 LibCurl is licensed under an MIT/X derivative license. 150 */ 151 /* 152 Copyright Jonas Drewsen 2011 - 2012. 153 Distributed under the Boost Software License, Version 1.0. 154 (See accompanying file LICENSE_1_0.txt or copy at 155 http://www.boost.org/LICENSE_1_0.txt) 156 */ 157 module std.net.curl; 158 159 import core.thread; 160 import etc.c.curl; 161 import std.concurrency; 162 import std.encoding; 163 import std.exception; 164 import std.meta; 165 import std.range.primitives; 166 import std.socket : InternetAddress; 167 import std.traits; 168 import std.typecons; 169 170 import std.internal.cstring; 171 172 public import etc.c.curl : CurlOption; 173 174 version (unittest) 175 { 176 // Run unit test with the PHOBOS_TEST_ALLOW_NET=1 set in order to 177 // allow net traffic 178 import std.range; 179 import std.stdio; 180 181 import std.socket : Address, INADDR_LOOPBACK, Socket, SocketShutdown, TcpSocket; 182 183 private struct TestServer 184 { 185 string addr() { return _addr; } 186 187 void handle(void function(Socket s) dg) 188 { 189 tid.send(dg); 190 } 191 192 private: 193 string _addr; 194 Tid tid; 195 TcpSocket sock; 196 197 static void loop(shared TcpSocket listener) 198 { 199 try while (true) 200 { 201 void function(Socket) handler = void; 202 try 203 handler = receiveOnly!(typeof(handler)); 204 catch (OwnerTerminated) 205 return; 206 handler((cast() listener).accept); 207 } 208 catch (Throwable e) 209 { 210 stderr.writeln(e); // Bugzilla 7018 211 } 212 } 213 } 214 215 private TestServer startServer() 216 { 217 tlsInit = true; 218 auto sock = new TcpSocket; 219 sock.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY)); 220 sock.listen(1); 221 auto addr = sock.localAddress.toString(); 222 auto tid = spawn(&TestServer.loop, cast(shared) sock); 223 return TestServer(addr, tid, sock); 224 } 225 226 __gshared TestServer server; 227 bool tlsInit; 228 229 private ref TestServer testServer() 230 { 231 return initOnce!server(startServer()); 232 } 233 234 static ~this() 235 { 236 // terminate server from a thread local dtor of the thread that started it, 237 // because thread_joinall is called before shared module dtors 238 if (tlsInit && server.sock) 239 { 240 server.sock.shutdown(SocketShutdown.RECEIVE); 241 server.sock.close(); 242 } 243 } 244 245 private struct Request(T) 246 { 247 string hdrs; 248 immutable(T)[] bdy; 249 } 250 251 private Request!T recvReq(T=char)(Socket s) 252 { 253 import std.algorithm.comparison : min; 254 import std.algorithm.searching : find, canFind; 255 import std.conv : to; 256 import std.regex : ctRegex, matchFirst; 257 258 ubyte[1024] tmp=void; 259 ubyte[] buf; 260 261 while (true) 262 { 263 auto nbytes = s.receive(tmp[]); 264 assert(nbytes >= 0); 265 266 immutable beg = buf.length > 3 ? buf.length - 3 : 0; 267 buf ~= tmp[0 .. nbytes]; 268 auto bdy = buf[beg .. $].find(cast(ubyte[])"\r\n\r\n"); 269 if (bdy.empty) 270 continue; 271 272 auto hdrs = cast(string) buf[0 .. $ - bdy.length]; 273 bdy.popFrontN(4); 274 // no support for chunked transfer-encoding 275 if (auto m = hdrs.matchFirst(ctRegex!(`Content-Length: ([0-9]+)`, "i"))) 276 { 277 import std.uni : asUpperCase; 278 if (hdrs.asUpperCase.canFind("EXPECT: 100-CONTINUE")) 279 s.send(httpContinue); 280 281 size_t remain = m.captures[1].to!size_t - bdy.length; 282 while (remain) 283 { 284 nbytes = s.receive(tmp[0 .. min(remain, $)]); 285 assert(nbytes >= 0); 286 buf ~= tmp[0 .. nbytes]; 287 remain -= nbytes; 288 } 289 } 290 else 291 { 292 assert(bdy.empty); 293 } 294 bdy = buf[hdrs.length + 4 .. $]; 295 return typeof(return)(hdrs, cast(immutable(T)[])bdy); 296 } 297 } 298 299 private string httpOK(string msg) 300 { 301 import std.conv : to; 302 303 return "HTTP/1.1 200 OK\r\n"~ 304 "Content-Type: text/plain\r\n"~ 305 "Content-Length: "~msg.length.to!string~"\r\n"~ 306 "\r\n"~ 307 msg; 308 } 309 310 private string httpOK() 311 { 312 return "HTTP/1.1 200 OK\r\n"~ 313 "Content-Length: 0\r\n"~ 314 "\r\n"; 315 } 316 317 private string httpNotFound() 318 { 319 return "HTTP/1.1 404 Not Found\r\n"~ 320 "Content-Length: 0\r\n"~ 321 "\r\n"; 322 } 323 324 private enum httpContinue = "HTTP/1.1 100 Continue\r\n\r\n"; 325 } 326 version (StdDdoc) import std.stdio; 327 328 // Default data timeout for Protocols 329 private enum _defaultDataTimeout = dur!"minutes"(2); 330 331 /** 332 Macros: 333 334 CALLBACK_PARAMS = $(TABLE , 335 $(DDOC_PARAM_ROW 336 $(DDOC_PARAM_ID $(DDOC_PARAM dlTotal)) 337 $(DDOC_PARAM_DESC total bytes to download) 338 ) 339 $(DDOC_PARAM_ROW 340 $(DDOC_PARAM_ID $(DDOC_PARAM dlNow)) 341 $(DDOC_PARAM_DESC currently downloaded bytes) 342 ) 343 $(DDOC_PARAM_ROW 344 $(DDOC_PARAM_ID $(DDOC_PARAM ulTotal)) 345 $(DDOC_PARAM_DESC total bytes to upload) 346 ) 347 $(DDOC_PARAM_ROW 348 $(DDOC_PARAM_ID $(DDOC_PARAM ulNow)) 349 $(DDOC_PARAM_DESC currently uploaded bytes) 350 ) 351 ) 352 */ 353 354 /** Connection type used when the URL should be used to auto detect the protocol. 355 * 356 * This struct is used as placeholder for the connection parameter when calling 357 * the high level API and the connection type (HTTP/FTP) should be guessed by 358 * inspecting the URL parameter. 359 * 360 * The rules for guessing the protocol are: 361 * 1, if URL starts with ftp://, ftps:// or ftp. then FTP connection is assumed. 362 * 2, HTTP connection otherwise. 363 * 364 * Example: 365 * --- 366 * import std.net.curl; 367 * // Two requests below will do the same. 368 * string content; 369 * 370 * // Explicit connection provided 371 * content = get!HTTP("dlang.org"); 372 * 373 * // Guess connection type by looking at the URL 374 * content = get!AutoProtocol("ftp://foo.com/file"); 375 * // and since AutoProtocol is default this is the same as 376 * content = get("ftp://foo.com/file"); 377 * // and will end up detecting FTP from the url and be the same as 378 * content = get!FTP("ftp://foo.com/file"); 379 * --- 380 */ 381 struct AutoProtocol { } 382 383 // Returns true if the url points to an FTP resource 384 private bool isFTPUrl(const(char)[] url) 385 { 386 import std.algorithm.searching : startsWith; 387 import std.uni : toLower; 388 389 return startsWith(url.toLower(), "ftp://", "ftps://", "ftp.") != 0; 390 } 391 392 // Is true if the Conn type is a valid Curl Connection type. 393 private template isCurlConn(Conn) 394 { 395 enum auto isCurlConn = is(Conn : HTTP) || 396 is(Conn : FTP) || is(Conn : AutoProtocol); 397 } 398 399 /** HTTP/FTP download to local file system. 400 * 401 * Params: 402 * url = resource to download 403 * saveToPath = path to store the downloaded content on local disk 404 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 405 * guess connection type and create a new instance for this call only. 406 * 407 * Example: 408 * ---- 409 * import std.net.curl; 410 * download("d-lang.appspot.com/testUrl2", "/tmp/downloaded-http-file"); 411 * ---- 412 */ 413 void download(Conn = AutoProtocol)(const(char)[] url, string saveToPath, Conn conn = Conn()) 414 if (isCurlConn!Conn) 415 { 416 static if (is(Conn : HTTP) || is(Conn : FTP)) 417 { 418 import std.stdio : File; 419 conn.url = url; 420 auto f = File(saveToPath, "wb"); 421 conn.onReceive = (ubyte[] data) { f.rawWrite(data); return data.length; }; 422 conn.perform(); 423 } 424 else 425 { 426 if (isFTPUrl(url)) 427 return download!FTP(url, saveToPath, FTP()); 428 else 429 return download!HTTP(url, saveToPath, HTTP()); 430 } 431 } 432 433 @system unittest 434 { 435 import std.algorithm.searching : canFind; 436 static import std.file; 437 438 foreach (host; [testServer.addr, "http://"~testServer.addr]) 439 { 440 testServer.handle((s) { 441 assert(s.recvReq.hdrs.canFind("GET /")); 442 s.send(httpOK("Hello world")); 443 }); 444 auto fn = std.file.deleteme; 445 scope (exit) 446 { 447 if (std.file.exists(fn)) 448 std.file.remove(fn); 449 } 450 download(host, fn); 451 assert(std.file.readText(fn) == "Hello world"); 452 } 453 } 454 455 /** Upload file from local files system using the HTTP or FTP protocol. 456 * 457 * Params: 458 * loadFromPath = path load data from local disk. 459 * url = resource to upload to 460 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 461 * guess connection type and create a new instance for this call only. 462 * 463 * Example: 464 * ---- 465 * import std.net.curl; 466 * upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds"); 467 * upload("/tmp/downloaded-http-file", "d-lang.appspot.com/testUrl2"); 468 * ---- 469 */ 470 void upload(Conn = AutoProtocol)(string loadFromPath, const(char)[] url, Conn conn = Conn()) 471 if (isCurlConn!Conn) 472 { 473 static if (is(Conn : HTTP)) 474 { 475 conn.url = url; 476 conn.method = HTTP.Method.put; 477 } 478 else static if (is(Conn : FTP)) 479 { 480 conn.url = url; 481 conn.handle.set(CurlOption.upload, 1L); 482 } 483 else 484 { 485 if (isFTPUrl(url)) 486 return upload!FTP(loadFromPath, url, FTP()); 487 else 488 return upload!HTTP(loadFromPath, url, HTTP()); 489 } 490 491 static if (is(Conn : HTTP) || is(Conn : FTP)) 492 { 493 import std.stdio : File; 494 auto f = File(loadFromPath, "rb"); 495 conn.onSend = buf => f.rawRead(buf).length; 496 immutable sz = f.size; 497 if (sz != ulong.max) 498 conn.contentLength = sz; 499 conn.perform(); 500 } 501 } 502 503 @system unittest 504 { 505 import std.algorithm.searching : canFind; 506 static import std.file; 507 508 foreach (host; [testServer.addr, "http://"~testServer.addr]) 509 { 510 auto fn = std.file.deleteme; 511 scope (exit) 512 { 513 if (std.file.exists(fn)) 514 std.file.remove(fn); 515 } 516 std.file.write(fn, "upload data\n"); 517 testServer.handle((s) { 518 auto req = s.recvReq; 519 assert(req.hdrs.canFind("PUT /path")); 520 assert(req.bdy.canFind("upload data")); 521 s.send(httpOK()); 522 }); 523 upload(fn, host ~ "/path"); 524 } 525 } 526 527 /** HTTP/FTP get content. 528 * 529 * Params: 530 * url = resource to get 531 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 532 * guess connection type and create a new instance for this call only. 533 * 534 * The template parameter $(D T) specifies the type to return. Possible values 535 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking 536 * for $(D char), content will be converted from the connection character set 537 * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 538 * by default) to UTF-8. 539 * 540 * Example: 541 * ---- 542 * import std.net.curl; 543 * auto content = get("d-lang.appspot.com/testUrl2"); 544 * ---- 545 * 546 * Returns: 547 * A T[] range containing the content of the resource pointed to by the URL. 548 * 549 * Throws: 550 * 551 * $(D CurlException) on error. 552 * 553 * See_Also: $(LREF HTTP.Method) 554 */ 555 T[] get(Conn = AutoProtocol, T = char)(const(char)[] url, Conn conn = Conn()) 556 if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) 557 { 558 static if (is(Conn : HTTP)) 559 { 560 conn.method = HTTP.Method.get; 561 return _basicHTTP!(T)(url, "", conn); 562 563 } 564 else static if (is(Conn : FTP)) 565 { 566 return _basicFTP!(T)(url, "", conn); 567 } 568 else 569 { 570 if (isFTPUrl(url)) 571 return get!(FTP,T)(url, FTP()); 572 else 573 return get!(HTTP,T)(url, HTTP()); 574 } 575 } 576 577 @system unittest 578 { 579 import std.algorithm.searching : canFind; 580 581 foreach (host; [testServer.addr, "http://"~testServer.addr]) 582 { 583 testServer.handle((s) { 584 assert(s.recvReq.hdrs.canFind("GET /path")); 585 s.send(httpOK("GETRESPONSE")); 586 }); 587 auto res = get(host ~ "/path"); 588 assert(res == "GETRESPONSE"); 589 } 590 } 591 592 593 /** HTTP post content. 594 * 595 * Params: 596 * url = resource to post to 597 * postDict = data to send as the body of the request. An associative array 598 * of $(D string) is accepted and will be encoded using 599 * www-form-urlencoding 600 * postData = data to send as the body of the request. An array 601 * of an arbitrary type is accepted and will be cast to ubyte[] 602 * before sending it. 603 * conn = HTTP connection to use 604 * T = The template parameter $(D T) specifies the type to return. Possible values 605 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking 606 * for $(D char), content will be converted from the connection character set 607 * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 608 * by default) to UTF-8. 609 * 610 * Examples: 611 * ---- 612 * import std.net.curl; 613 * 614 * auto content1 = post("d-lang.appspot.com/testUrl2", ["name1" : "value1", "name2" : "value2"]); 615 * auto content2 = post("d-lang.appspot.com/testUrl2", [1,2,3,4]); 616 * ---- 617 * 618 * Returns: 619 * A T[] range containing the content of the resource pointed to by the URL. 620 * 621 * See_Also: $(LREF HTTP.Method) 622 */ 623 T[] post(T = char, PostUnit)(const(char)[] url, const(PostUnit)[] postData, HTTP conn = HTTP()) 624 if (is(T == char) || is(T == ubyte)) 625 { 626 conn.method = HTTP.Method.post; 627 return _basicHTTP!(T)(url, postData, conn); 628 } 629 630 @system unittest 631 { 632 import std.algorithm.searching : canFind; 633 634 foreach (host; [testServer.addr, "http://"~testServer.addr]) 635 { 636 testServer.handle((s) { 637 auto req = s.recvReq; 638 assert(req.hdrs.canFind("POST /path")); 639 assert(req.bdy.canFind("POSTBODY")); 640 s.send(httpOK("POSTRESPONSE")); 641 }); 642 auto res = post(host ~ "/path", "POSTBODY"); 643 assert(res == "POSTRESPONSE"); 644 } 645 } 646 647 @system unittest 648 { 649 import std.algorithm.searching : canFind; 650 651 auto data = new ubyte[](256); 652 foreach (i, ref ub; data) 653 ub = cast(ubyte) i; 654 655 testServer.handle((s) { 656 auto req = s.recvReq!ubyte; 657 assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4])); 658 assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255])); 659 s.send(httpOK(cast(ubyte[])[17, 27, 35, 41])); 660 }); 661 auto res = post!ubyte(testServer.addr, data); 662 assert(res == cast(ubyte[])[17, 27, 35, 41]); 663 } 664 665 /// ditto 666 T[] post(T = char)(const(char)[] url, string[string] postDict, HTTP conn = HTTP()) 667 if (is(T == char) || is(T == ubyte)) 668 { 669 import std.uri : urlEncode; 670 671 return post(url, urlEncode(postDict), conn); 672 } 673 674 @system unittest 675 { 676 foreach (host; [testServer.addr, "http://" ~ testServer.addr]) 677 { 678 testServer.handle((s) { 679 auto req = s.recvReq!char; 680 s.send(httpOK(req.bdy)); 681 }); 682 auto res = post(host ~ "/path", ["name1" : "value1", "name2" : "value2"]); 683 assert(res == "name1=value1&name2=value2" || res == "name2=value2&name1=value1"); 684 } 685 } 686 687 /** HTTP/FTP put content. 688 * 689 * Params: 690 * url = resource to put 691 * putData = data to send as the body of the request. An array 692 * of an arbitrary type is accepted and will be cast to ubyte[] 693 * before sending it. 694 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 695 * guess connection type and create a new instance for this call only. 696 * 697 * The template parameter $(D T) specifies the type to return. Possible values 698 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking 699 * for $(D char), content will be converted from the connection character set 700 * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 701 * by default) to UTF-8. 702 * 703 * Example: 704 * ---- 705 * import std.net.curl; 706 * auto content = put("d-lang.appspot.com/testUrl2", 707 * "Putting this data"); 708 * ---- 709 * 710 * Returns: 711 * A T[] range containing the content of the resource pointed to by the URL. 712 * 713 * See_Also: $(LREF HTTP.Method) 714 */ 715 T[] put(Conn = AutoProtocol, T = char, PutUnit)(const(char)[] url, const(PutUnit)[] putData, 716 Conn conn = Conn()) 717 if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) 718 { 719 static if (is(Conn : HTTP)) 720 { 721 conn.method = HTTP.Method.put; 722 return _basicHTTP!(T)(url, putData, conn); 723 } 724 else static if (is(Conn : FTP)) 725 { 726 return _basicFTP!(T)(url, putData, conn); 727 } 728 else 729 { 730 if (isFTPUrl(url)) 731 return put!(FTP,T)(url, putData, FTP()); 732 else 733 return put!(HTTP,T)(url, putData, HTTP()); 734 } 735 } 736 737 @system unittest 738 { 739 import std.algorithm.searching : canFind; 740 741 foreach (host; [testServer.addr, "http://"~testServer.addr]) 742 { 743 testServer.handle((s) { 744 auto req = s.recvReq; 745 assert(req.hdrs.canFind("PUT /path")); 746 assert(req.bdy.canFind("PUTBODY")); 747 s.send(httpOK("PUTRESPONSE")); 748 }); 749 auto res = put(host ~ "/path", "PUTBODY"); 750 assert(res == "PUTRESPONSE"); 751 } 752 } 753 754 755 /** HTTP/FTP delete content. 756 * 757 * Params: 758 * url = resource to delete 759 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 760 * guess connection type and create a new instance for this call only. 761 * 762 * Example: 763 * ---- 764 * import std.net.curl; 765 * del("d-lang.appspot.com/testUrl2"); 766 * ---- 767 * 768 * See_Also: $(LREF HTTP.Method) 769 */ 770 void del(Conn = AutoProtocol)(const(char)[] url, Conn conn = Conn()) 771 if (isCurlConn!Conn) 772 { 773 static if (is(Conn : HTTP)) 774 { 775 conn.method = HTTP.Method.del; 776 _basicHTTP!char(url, cast(void[]) null, conn); 777 } 778 else static if (is(Conn : FTP)) 779 { 780 import std.algorithm.searching : findSplitAfter; 781 import std.conv : text; 782 783 auto trimmed = url.findSplitAfter("ftp://")[1]; 784 auto t = trimmed.findSplitAfter("/"); 785 enum minDomainNameLength = 3; 786 enforce!CurlException(t[0].length > minDomainNameLength, 787 text("Invalid FTP URL for delete ", url)); 788 conn.url = t[0]; 789 790 enforce!CurlException(!t[1].empty, 791 text("No filename specified to delete for URL ", url)); 792 conn.addCommand("DELE " ~ t[1]); 793 conn.perform(); 794 } 795 else 796 { 797 if (isFTPUrl(url)) 798 return del!FTP(url, FTP()); 799 else 800 return del!HTTP(url, HTTP()); 801 } 802 } 803 804 @system unittest 805 { 806 import std.algorithm.searching : canFind; 807 808 foreach (host; [testServer.addr, "http://"~testServer.addr]) 809 { 810 testServer.handle((s) { 811 auto req = s.recvReq; 812 assert(req.hdrs.canFind("DELETE /path")); 813 s.send(httpOK()); 814 }); 815 del(host ~ "/path"); 816 } 817 } 818 819 820 /** HTTP options request. 821 * 822 * Params: 823 * url = resource make a option call to 824 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 825 * guess connection type and create a new instance for this call only. 826 * 827 * The template parameter $(D T) specifies the type to return. Possible values 828 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). 829 * 830 * Example: 831 * ---- 832 * import std.net.curl; 833 * auto http = HTTP(); 834 * options("d-lang.appspot.com/testUrl2", http); 835 * writeln("Allow set to " ~ http.responseHeaders["Allow"]); 836 * ---- 837 * 838 * Returns: 839 * A T[] range containing the options of the resource pointed to by the URL. 840 * 841 * See_Also: $(LREF HTTP.Method) 842 */ 843 T[] options(T = char)(const(char)[] url, HTTP conn = HTTP()) 844 if (is(T == char) || is(T == ubyte)) 845 { 846 conn.method = HTTP.Method.options; 847 return _basicHTTP!(T)(url, null, conn); 848 } 849 850 @system unittest 851 { 852 import std.algorithm.searching : canFind; 853 854 testServer.handle((s) { 855 auto req = s.recvReq; 856 assert(req.hdrs.canFind("OPTIONS /path")); 857 s.send(httpOK("OPTIONSRESPONSE")); 858 }); 859 auto res = options(testServer.addr ~ "/path"); 860 assert(res == "OPTIONSRESPONSE"); 861 } 862 863 864 /** HTTP trace request. 865 * 866 * Params: 867 * url = resource make a trace call to 868 * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will 869 * guess connection type and create a new instance for this call only. 870 * 871 * The template parameter $(D T) specifies the type to return. Possible values 872 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). 873 * 874 * Example: 875 * ---- 876 * import std.net.curl; 877 * trace("d-lang.appspot.com/testUrl1"); 878 * ---- 879 * 880 * Returns: 881 * A T[] range containing the trace info of the resource pointed to by the URL. 882 * 883 * See_Also: $(LREF HTTP.Method) 884 */ 885 T[] trace(T = char)(const(char)[] url, HTTP conn = HTTP()) 886 if (is(T == char) || is(T == ubyte)) 887 { 888 conn.method = HTTP.Method.trace; 889 return _basicHTTP!(T)(url, cast(void[]) null, conn); 890 } 891 892 @system unittest 893 { 894 import std.algorithm.searching : canFind; 895 896 testServer.handle((s) { 897 auto req = s.recvReq; 898 assert(req.hdrs.canFind("TRACE /path")); 899 s.send(httpOK("TRACERESPONSE")); 900 }); 901 auto res = trace(testServer.addr ~ "/path"); 902 assert(res == "TRACERESPONSE"); 903 } 904 905 906 /** HTTP connect request. 907 * 908 * Params: 909 * url = resource make a connect to 910 * conn = HTTP connection to use 911 * 912 * The template parameter $(D T) specifies the type to return. Possible values 913 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). 914 * 915 * Example: 916 * ---- 917 * import std.net.curl; 918 * connect("d-lang.appspot.com/testUrl1"); 919 * ---- 920 * 921 * Returns: 922 * A T[] range containing the connect info of the resource pointed to by the URL. 923 * 924 * See_Also: $(LREF HTTP.Method) 925 */ 926 T[] connect(T = char)(const(char)[] url, HTTP conn = HTTP()) 927 if (is(T == char) || is(T == ubyte)) 928 { 929 conn.method = HTTP.Method.connect; 930 return _basicHTTP!(T)(url, cast(void[]) null, conn); 931 } 932 933 @system unittest 934 { 935 import std.algorithm.searching : canFind; 936 937 testServer.handle((s) { 938 auto req = s.recvReq; 939 assert(req.hdrs.canFind("CONNECT /path")); 940 s.send(httpOK("CONNECTRESPONSE")); 941 }); 942 auto res = connect(testServer.addr ~ "/path"); 943 assert(res == "CONNECTRESPONSE"); 944 } 945 946 947 /** HTTP patch content. 948 * 949 * Params: 950 * url = resource to patch 951 * patchData = data to send as the body of the request. An array 952 * of an arbitrary type is accepted and will be cast to ubyte[] 953 * before sending it. 954 * conn = HTTP connection to use 955 * 956 * The template parameter $(D T) specifies the type to return. Possible values 957 * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). 958 * 959 * Example: 960 * ---- 961 * auto http = HTTP(); 962 * http.addRequestHeader("Content-Type", "application/json"); 963 * auto content = patch("d-lang.appspot.com/testUrl2", `{"title": "Patched Title"}`, http); 964 * ---- 965 * 966 * Returns: 967 * A T[] range containing the content of the resource pointed to by the URL. 968 * 969 * See_Also: $(LREF HTTP.Method) 970 */ 971 T[] patch(T = char, PatchUnit)(const(char)[] url, const(PatchUnit)[] patchData, 972 HTTP conn = HTTP()) 973 if (is(T == char) || is(T == ubyte)) 974 { 975 conn.method = HTTP.Method.patch; 976 return _basicHTTP!(T)(url, patchData, conn); 977 } 978 979 @system unittest 980 { 981 import std.algorithm.searching : canFind; 982 983 testServer.handle((s) { 984 auto req = s.recvReq; 985 assert(req.hdrs.canFind("PATCH /path")); 986 assert(req.bdy.canFind("PATCHBODY")); 987 s.send(httpOK("PATCHRESPONSE")); 988 }); 989 auto res = patch(testServer.addr ~ "/path", "PATCHBODY"); 990 assert(res == "PATCHRESPONSE"); 991 } 992 993 994 /* 995 * Helper function for the high level interface. 996 * 997 * It performs an HTTP request using the client which must have 998 * been setup correctly before calling this function. 999 */ 1000 private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP client) 1001 { 1002 import std.algorithm.comparison : min; 1003 import std.format : format; 1004 1005 immutable doSend = sendData !is null && 1006 (client.method == HTTP.Method.post || 1007 client.method == HTTP.Method.put || 1008 client.method == HTTP.Method.patch); 1009 1010 scope (exit) 1011 { 1012 client.onReceiveHeader = null; 1013 client.onReceiveStatusLine = null; 1014 client.onReceive = null; 1015 1016 if (doSend) 1017 { 1018 client.onSend = null; 1019 client.handle.onSeek = null; 1020 client.contentLength = 0; 1021 } 1022 } 1023 client.url = url; 1024 HTTP.StatusLine statusLine; 1025 import std.array : appender; 1026 auto content = appender!(ubyte[])(); 1027 client.onReceive = (ubyte[] data) 1028 { 1029 content ~= data; 1030 return data.length; 1031 }; 1032 1033 if (doSend) 1034 { 1035 client.contentLength = sendData.length; 1036 auto remainingData = sendData; 1037 client.onSend = delegate size_t(void[] buf) 1038 { 1039 size_t minLen = min(buf.length, remainingData.length); 1040 if (minLen == 0) return 0; 1041 buf[0 .. minLen] = remainingData[0 .. minLen]; 1042 remainingData = remainingData[minLen..$]; 1043 return minLen; 1044 }; 1045 client.handle.onSeek = delegate(long offset, CurlSeekPos mode) 1046 { 1047 switch (mode) 1048 { 1049 case CurlSeekPos.set: 1050 remainingData = sendData[cast(size_t) offset..$]; 1051 return CurlSeek.ok; 1052 default: 1053 // As of curl 7.18.0, libcurl will not pass 1054 // anything other than CurlSeekPos.set. 1055 return CurlSeek.cantseek; 1056 } 1057 }; 1058 } 1059 1060 client.onReceiveHeader = (in char[] key, 1061 in char[] value) 1062 { 1063 if (key == "content-length") 1064 { 1065 import std.conv : to; 1066 content.reserve(value.to!size_t); 1067 } 1068 }; 1069 client.onReceiveStatusLine = (HTTP.StatusLine l) { statusLine = l; }; 1070 client.perform(); 1071 enforce(statusLine.code / 100 == 2, new HTTPStatusException(statusLine.code, 1072 format("HTTP request returned status code %d (%s)", statusLine.code, statusLine.reason))); 1073 1074 return _decodeContent!T(content.data, client.p.charset); 1075 } 1076 1077 @system unittest 1078 { 1079 import std.algorithm.searching : canFind; 1080 1081 testServer.handle((s) { 1082 auto req = s.recvReq; 1083 assert(req.hdrs.canFind("GET /path")); 1084 s.send(httpNotFound()); 1085 }); 1086 auto e = collectException!HTTPStatusException(get(testServer.addr ~ "/path")); 1087 assert(e.msg == "HTTP request returned status code 404 (Not Found)"); 1088 assert(e.status == 404); 1089 } 1090 1091 // Bugzilla 14760 - content length must be reset after post 1092 @system unittest 1093 { 1094 import std.algorithm.searching : canFind; 1095 1096 testServer.handle((s) { 1097 auto req = s.recvReq; 1098 assert(req.hdrs.canFind("POST /")); 1099 assert(req.bdy.canFind("POSTBODY")); 1100 s.send(httpOK("POSTRESPONSE")); 1101 1102 req = s.recvReq; 1103 assert(req.hdrs.canFind("TRACE /")); 1104 assert(req.bdy.empty); 1105 s.blocking = false; 1106 ubyte[6] buf = void; 1107 assert(s.receive(buf[]) < 0); 1108 s.send(httpOK("TRACERESPONSE")); 1109 }); 1110 auto http = HTTP(); 1111 auto res = post(testServer.addr, "POSTBODY", http); 1112 assert(res == "POSTRESPONSE"); 1113 res = trace(testServer.addr, http); 1114 assert(res == "TRACERESPONSE"); 1115 } 1116 1117 @system unittest // charset detection and transcoding to T 1118 { 1119 testServer.handle((s) { 1120 s.send("HTTP/1.1 200 OK\r\n"~ 1121 "Content-Length: 4\r\n"~ 1122 "Content-Type: text/plain; charset=utf-8\r\n" ~ 1123 "\r\n" ~ 1124 "äbc"); 1125 }); 1126 auto client = HTTP(); 1127 auto result = _basicHTTP!char(testServer.addr, "", client); 1128 assert(result == "äbc"); 1129 1130 testServer.handle((s) { 1131 s.send("HTTP/1.1 200 OK\r\n"~ 1132 "Content-Length: 3\r\n"~ 1133 "Content-Type: text/plain; charset=iso-8859-1\r\n" ~ 1134 "\r\n" ~ 1135 0xE4 ~ "bc"); 1136 }); 1137 client = HTTP(); 1138 result = _basicHTTP!char(testServer.addr, "", client); 1139 assert(result == "äbc"); 1140 } 1141 1142 /* 1143 * Helper function for the high level interface. 1144 * 1145 * It performs an FTP request using the client which must have 1146 * been setup correctly before calling this function. 1147 */ 1148 private auto _basicFTP(T)(const(char)[] url, const(void)[] sendData, FTP client) 1149 { 1150 import std.algorithm.comparison : min; 1151 1152 scope (exit) 1153 { 1154 client.onReceive = null; 1155 if (!sendData.empty) 1156 client.onSend = null; 1157 } 1158 1159 ubyte[] content; 1160 1161 if (client.encoding.empty) 1162 client.encoding = "ISO-8859-1"; 1163 1164 client.url = url; 1165 client.onReceive = (ubyte[] data) 1166 { 1167 content ~= data; 1168 return data.length; 1169 }; 1170 1171 if (!sendData.empty) 1172 { 1173 client.handle.set(CurlOption.upload, 1L); 1174 client.onSend = delegate size_t(void[] buf) 1175 { 1176 size_t minLen = min(buf.length, sendData.length); 1177 if (minLen == 0) return 0; 1178 buf[0 .. minLen] = sendData[0 .. minLen]; 1179 sendData = sendData[minLen..$]; 1180 return minLen; 1181 }; 1182 } 1183 1184 client.perform(); 1185 1186 return _decodeContent!T(content, client.encoding); 1187 } 1188 1189 /* Used by _basicHTTP() and _basicFTP() to decode ubyte[] to 1190 * correct string format 1191 */ 1192 private auto _decodeContent(T)(ubyte[] content, string encoding) 1193 { 1194 static if (is(T == ubyte)) 1195 { 1196 return content; 1197 } 1198 else 1199 { 1200 import std.format : format; 1201 1202 // Optimally just return the utf8 encoded content 1203 if (encoding == "UTF-8") 1204 return cast(char[])(content); 1205 1206 // The content has to be re-encoded to utf8 1207 auto scheme = EncodingScheme.create(encoding); 1208 enforce!CurlException(scheme !is null, 1209 format("Unknown encoding '%s'", encoding)); 1210 1211 auto strInfo = decodeString(content, scheme); 1212 enforce!CurlException(strInfo[0] != size_t.max, 1213 format("Invalid encoding sequence for encoding '%s'", 1214 encoding)); 1215 1216 return strInfo[1]; 1217 } 1218 } 1219 1220 alias KeepTerminator = Flag!"keepTerminator"; 1221 /+ 1222 struct ByLineBuffer(Char) 1223 { 1224 bool linePresent; 1225 bool EOF; 1226 Char[] buffer; 1227 ubyte[] decodeRemainder; 1228 1229 bool append(const(ubyte)[] data) 1230 { 1231 byLineBuffer ~= data; 1232 } 1233 1234 @property bool linePresent() 1235 { 1236 return byLinePresent; 1237 } 1238 1239 Char[] get() 1240 { 1241 if (!linePresent) 1242 { 1243 // Decode ubyte[] into Char[] until a Terminator is found. 1244 // If not Terminator is found and EOF is false then raise an 1245 // exception. 1246 } 1247 return byLineBuffer; 1248 } 1249 1250 } 1251 ++/ 1252 /** HTTP/FTP fetch content as a range of lines. 1253 * 1254 * A range of lines is returned when the request is complete. If the method or 1255 * other request properties is to be customized then set the $(D conn) parameter 1256 * with a HTTP/FTP instance that has these properties set. 1257 * 1258 * Example: 1259 * ---- 1260 * import std.net.curl, std.stdio; 1261 * foreach (line; byLine("dlang.org")) 1262 * writeln(line); 1263 * ---- 1264 * 1265 * Params: 1266 * url = The url to receive content from 1267 * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be 1268 * returned as part of the lines in the range. 1269 * terminator = The character that terminates a line 1270 * conn = The connection to use e.g. HTTP or FTP. 1271 * 1272 * Returns: 1273 * A range of Char[] with the content of the resource pointer to by the URL 1274 */ 1275 auto byLine(Conn = AutoProtocol, Terminator = char, Char = char) 1276 (const(char)[] url, KeepTerminator keepTerminator = No.keepTerminator, 1277 Terminator terminator = '\n', Conn conn = Conn()) 1278 if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) 1279 { 1280 static struct SyncLineInputRange 1281 { 1282 1283 private Char[] lines; 1284 private Char[] current; 1285 private bool currentValid; 1286 private bool keepTerminator; 1287 private Terminator terminator; 1288 1289 this(Char[] lines, bool kt, Terminator terminator) 1290 { 1291 this.lines = lines; 1292 this.keepTerminator = kt; 1293 this.terminator = terminator; 1294 currentValid = true; 1295 popFront(); 1296 } 1297 1298 @property @safe bool empty() 1299 { 1300 return !currentValid; 1301 } 1302 1303 @property @safe Char[] front() 1304 { 1305 enforce!CurlException(currentValid, "Cannot call front() on empty range"); 1306 return current; 1307 } 1308 1309 void popFront() 1310 { 1311 import std.algorithm.searching : findSplitAfter, findSplit; 1312 1313 enforce!CurlException(currentValid, "Cannot call popFront() on empty range"); 1314 if (lines.empty) 1315 { 1316 currentValid = false; 1317 return; 1318 } 1319 1320 if (keepTerminator) 1321 { 1322 auto r = findSplitAfter(lines, [ terminator ]); 1323 if (r[0].empty) 1324 { 1325 current = r[1]; 1326 lines = r[0]; 1327 } 1328 else 1329 { 1330 current = r[0]; 1331 lines = r[1]; 1332 } 1333 } 1334 else 1335 { 1336 auto r = findSplit(lines, [ terminator ]); 1337 current = r[0]; 1338 lines = r[2]; 1339 } 1340 } 1341 } 1342 1343 auto result = _getForRange!Char(url, conn); 1344 return SyncLineInputRange(result, keepTerminator == Yes.keepTerminator, terminator); 1345 } 1346 1347 @system unittest 1348 { 1349 import std.algorithm.comparison : equal; 1350 1351 foreach (host; [testServer.addr, "http://"~testServer.addr]) 1352 { 1353 testServer.handle((s) { 1354 auto req = s.recvReq; 1355 s.send(httpOK("Line1\nLine2\nLine3")); 1356 }); 1357 assert(byLine(host).equal(["Line1", "Line2", "Line3"])); 1358 } 1359 } 1360 1361 /** HTTP/FTP fetch content as a range of chunks. 1362 * 1363 * A range of chunks is returned when the request is complete. If the method or 1364 * other request properties is to be customized then set the $(D conn) parameter 1365 * with a HTTP/FTP instance that has these properties set. 1366 * 1367 * Example: 1368 * ---- 1369 * import std.net.curl, std.stdio; 1370 * foreach (chunk; byChunk("dlang.org", 100)) 1371 * writeln(chunk); // chunk is ubyte[100] 1372 * ---- 1373 * 1374 * Params: 1375 * url = The url to receive content from 1376 * chunkSize = The size of each chunk 1377 * conn = The connection to use e.g. HTTP or FTP. 1378 * 1379 * Returns: 1380 * A range of ubyte[chunkSize] with the content of the resource pointer to by the URL 1381 */ 1382 auto byChunk(Conn = AutoProtocol) 1383 (const(char)[] url, size_t chunkSize = 1024, Conn conn = Conn()) 1384 if (isCurlConn!(Conn)) 1385 { 1386 static struct SyncChunkInputRange 1387 { 1388 private size_t chunkSize; 1389 private ubyte[] _bytes; 1390 private size_t offset; 1391 1392 this(ubyte[] bytes, size_t chunkSize) 1393 { 1394 this._bytes = bytes; 1395 this.chunkSize = chunkSize; 1396 } 1397 1398 @property @safe auto empty() 1399 { 1400 return offset == _bytes.length; 1401 } 1402 1403 @property ubyte[] front() 1404 { 1405 size_t nextOffset = offset + chunkSize; 1406 if (nextOffset > _bytes.length) nextOffset = _bytes.length; 1407 return _bytes[offset .. nextOffset]; 1408 } 1409 1410 @safe void popFront() 1411 { 1412 offset += chunkSize; 1413 if (offset > _bytes.length) offset = _bytes.length; 1414 } 1415 } 1416 1417 auto result = _getForRange!ubyte(url, conn); 1418 return SyncChunkInputRange(result, chunkSize); 1419 } 1420 1421 @system unittest 1422 { 1423 import std.algorithm.comparison : equal; 1424 1425 foreach (host; [testServer.addr, "http://"~testServer.addr]) 1426 { 1427 testServer.handle((s) { 1428 auto req = s.recvReq; 1429 s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5])); 1430 }); 1431 assert(byChunk(host, 2).equal([[0, 1], [2, 3], [4, 5]])); 1432 } 1433 } 1434 1435 private T[] _getForRange(T,Conn)(const(char)[] url, Conn conn) 1436 { 1437 static if (is(Conn : HTTP)) 1438 { 1439 conn.method = conn.method == HTTP.Method.undefined ? HTTP.Method.get : conn.method; 1440 return _basicHTTP!(T)(url, null, conn); 1441 } 1442 else static if (is(Conn : FTP)) 1443 { 1444 return _basicFTP!(T)(url, null, conn); 1445 } 1446 else 1447 { 1448 if (isFTPUrl(url)) 1449 return get!(FTP,T)(url, FTP()); 1450 else 1451 return get!(HTTP,T)(url, HTTP()); 1452 } 1453 } 1454 1455 /* 1456 Main thread part of the message passing protocol used for all async 1457 curl protocols. 1458 */ 1459 private mixin template WorkerThreadProtocol(Unit, alias units) 1460 { 1461 @property bool empty() 1462 { 1463 tryEnsureUnits(); 1464 return state == State.done; 1465 } 1466 1467 @property Unit[] front() 1468 { 1469 import std.format : format; 1470 tryEnsureUnits(); 1471 assert(state == State.gotUnits, 1472 format("Expected %s but got $s", 1473 State.gotUnits, state)); 1474 return units; 1475 } 1476 1477 void popFront() 1478 { 1479 import std.format : format; 1480 tryEnsureUnits(); 1481 assert(state == State.gotUnits, 1482 format("Expected %s but got $s", 1483 State.gotUnits, state)); 1484 state = State.needUnits; 1485 // Send to worker thread for buffer reuse 1486 workerTid.send(cast(immutable(Unit)[]) units); 1487 units = null; 1488 } 1489 1490 /** Wait for duration or until data is available and return true if data is 1491 available 1492 */ 1493 bool wait(Duration d) 1494 { 1495 import std.datetime.stopwatch : StopWatch; 1496 1497 if (state == State.gotUnits) 1498 return true; 1499 1500 enum noDur = dur!"hnsecs"(0); 1501 StopWatch sw; 1502 sw.start(); 1503 while (state != State.gotUnits && d > noDur) 1504 { 1505 final switch (state) 1506 { 1507 case State.needUnits: 1508 receiveTimeout(d, 1509 (Tid origin, CurlMessage!(immutable(Unit)[]) _data) 1510 { 1511 if (origin != workerTid) 1512 return false; 1513 units = cast(Unit[]) _data.data; 1514 state = State.gotUnits; 1515 return true; 1516 }, 1517 (Tid origin, CurlMessage!bool f) 1518 { 1519 if (origin != workerTid) 1520 return false; 1521 state = state.done; 1522 return true; 1523 } 1524 ); 1525 break; 1526 case State.gotUnits: return true; 1527 case State.done: 1528 return false; 1529 } 1530 d -= sw.peek(); 1531 sw.reset(); 1532 } 1533 return state == State.gotUnits; 1534 } 1535 1536 enum State 1537 { 1538 needUnits, 1539 gotUnits, 1540 done 1541 } 1542 State state; 1543 1544 void tryEnsureUnits() 1545 { 1546 while (true) 1547 { 1548 final switch (state) 1549 { 1550 case State.needUnits: 1551 receive( 1552 (Tid origin, CurlMessage!(immutable(Unit)[]) _data) 1553 { 1554 if (origin != workerTid) 1555 return false; 1556 units = cast(Unit[]) _data.data; 1557 state = State.gotUnits; 1558 return true; 1559 }, 1560 (Tid origin, CurlMessage!bool f) 1561 { 1562 if (origin != workerTid) 1563 return false; 1564 state = state.done; 1565 return true; 1566 } 1567 ); 1568 break; 1569 case State.gotUnits: return; 1570 case State.done: 1571 return; 1572 } 1573 } 1574 } 1575 } 1576 1577 // @@@@BUG 15831@@@@ 1578 // this should be inside byLineAsync 1579 // Range that reads one line at a time asynchronously. 1580 private static struct AsyncLineInputRange(Char) 1581 { 1582 private Char[] line; 1583 mixin WorkerThreadProtocol!(Char, line); 1584 1585 private Tid workerTid; 1586 private State running; 1587 1588 private this(Tid tid, size_t transmitBuffers, size_t bufferSize) 1589 { 1590 workerTid = tid; 1591 state = State.needUnits; 1592 1593 // Send buffers to other thread for it to use. Since no mechanism is in 1594 // place for moving ownership a cast to shared is done here and casted 1595 // back to non-shared in the receiving end. 1596 foreach (i ; 0 .. transmitBuffers) 1597 { 1598 auto arr = new Char[](bufferSize); 1599 workerTid.send(cast(immutable(Char[]))arr); 1600 } 1601 } 1602 } 1603 1604 /** HTTP/FTP fetch content as a range of lines asynchronously. 1605 * 1606 * A range of lines is returned immediately and the request that fetches the 1607 * lines is performed in another thread. If the method or other request 1608 * properties is to be customized then set the $(D conn) parameter with a 1609 * HTTP/FTP instance that has these properties set. 1610 * 1611 * If $(D postData) is non-_null the method will be set to $(D post) for HTTP 1612 * requests. 1613 * 1614 * The background thread will buffer up to transmitBuffers number of lines 1615 * before it stops receiving data from network. When the main thread reads the 1616 * lines from the range it frees up buffers and allows for the background thread 1617 * to receive more data from the network. 1618 * 1619 * If no data is available and the main thread accesses the range it will block 1620 * until data becomes available. An exception to this is the $(D wait(Duration)) method on 1621 * the $(LREF AsyncLineInputRange). This method will wait at maximum for the 1622 * specified duration and return true if data is available. 1623 * 1624 * Example: 1625 * ---- 1626 * import std.net.curl, std.stdio; 1627 * // Get some pages in the background 1628 * auto range1 = byLineAsync("www.google.com"); 1629 * auto range2 = byLineAsync("www.wikipedia.org"); 1630 * foreach (line; byLineAsync("dlang.org")) 1631 * writeln(line); 1632 * 1633 * // Lines already fetched in the background and ready 1634 * foreach (line; range1) writeln(line); 1635 * foreach (line; range2) writeln(line); 1636 * ---- 1637 * 1638 * ---- 1639 * import std.net.curl, std.stdio; 1640 * // Get a line in a background thread and wait in 1641 * // main thread for 2 seconds for it to arrive. 1642 * auto range3 = byLineAsync("dlang.com"); 1643 * if (range3.wait(dur!"seconds"(2))) 1644 * writeln(range3.front); 1645 * else 1646 * writeln("No line received after 2 seconds!"); 1647 * ---- 1648 * 1649 * Params: 1650 * url = The url to receive content from 1651 * postData = Data to HTTP Post 1652 * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be 1653 * returned as part of the lines in the range. 1654 * terminator = The character that terminates a line 1655 * transmitBuffers = The number of lines buffered asynchronously 1656 * conn = The connection to use e.g. HTTP or FTP. 1657 * 1658 * Returns: 1659 * A range of Char[] with the content of the resource pointer to by the 1660 * URL. 1661 */ 1662 auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char, PostUnit) 1663 (const(char)[] url, const(PostUnit)[] postData, 1664 KeepTerminator keepTerminator = No.keepTerminator, 1665 Terminator terminator = '\n', 1666 size_t transmitBuffers = 10, Conn conn = Conn()) 1667 if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) 1668 { 1669 static if (is(Conn : AutoProtocol)) 1670 { 1671 if (isFTPUrl(url)) 1672 return byLineAsync(url, postData, keepTerminator, 1673 terminator, transmitBuffers, FTP()); 1674 else 1675 return byLineAsync(url, postData, keepTerminator, 1676 terminator, transmitBuffers, HTTP()); 1677 } 1678 else 1679 { 1680 // 50 is just an arbitrary number for now 1681 setMaxMailboxSize(thisTid, 50, OnCrowding.block); 1682 auto tid = spawn(&_spawnAsync!(Conn, Char, Terminator)); 1683 tid.send(thisTid); 1684 tid.send(terminator); 1685 tid.send(keepTerminator == Yes.keepTerminator); 1686 1687 _asyncDuplicateConnection(url, conn, postData, tid); 1688 1689 return AsyncLineInputRange!Char(tid, transmitBuffers, 1690 Conn.defaultAsyncStringBufferSize); 1691 } 1692 } 1693 1694 /// ditto 1695 auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char) 1696 (const(char)[] url, KeepTerminator keepTerminator = No.keepTerminator, 1697 Terminator terminator = '\n', 1698 size_t transmitBuffers = 10, Conn conn = Conn()) 1699 { 1700 static if (is(Conn : AutoProtocol)) 1701 { 1702 if (isFTPUrl(url)) 1703 return byLineAsync(url, cast(void[]) null, keepTerminator, 1704 terminator, transmitBuffers, FTP()); 1705 else 1706 return byLineAsync(url, cast(void[]) null, keepTerminator, 1707 terminator, transmitBuffers, HTTP()); 1708 } 1709 else 1710 { 1711 return byLineAsync(url, cast(void[]) null, keepTerminator, 1712 terminator, transmitBuffers, conn); 1713 } 1714 } 1715 1716 @system unittest 1717 { 1718 import std.algorithm.comparison : equal; 1719 1720 foreach (host; [testServer.addr, "http://"~testServer.addr]) 1721 { 1722 testServer.handle((s) { 1723 auto req = s.recvReq; 1724 s.send(httpOK("Line1\nLine2\nLine3")); 1725 }); 1726 assert(byLineAsync(host).equal(["Line1", "Line2", "Line3"])); 1727 } 1728 } 1729 1730 // @@@@BUG 15831@@@@ 1731 // this should be inside byLineAsync 1732 // Range that reads one chunk at a time asynchronously. 1733 private static struct AsyncChunkInputRange 1734 { 1735 private ubyte[] chunk; 1736 mixin WorkerThreadProtocol!(ubyte, chunk); 1737 1738 private Tid workerTid; 1739 private State running; 1740 1741 private this(Tid tid, size_t transmitBuffers, size_t chunkSize) 1742 { 1743 workerTid = tid; 1744 state = State.needUnits; 1745 1746 // Send buffers to other thread for it to use. Since no mechanism is in 1747 // place for moving ownership a cast to shared is done here and a cast 1748 // back to non-shared in the receiving end. 1749 foreach (i ; 0 .. transmitBuffers) 1750 { 1751 ubyte[] arr = new ubyte[](chunkSize); 1752 workerTid.send(cast(immutable(ubyte[]))arr); 1753 } 1754 } 1755 } 1756 1757 /** HTTP/FTP fetch content as a range of chunks asynchronously. 1758 * 1759 * A range of chunks is returned immediately and the request that fetches the 1760 * chunks is performed in another thread. If the method or other request 1761 * properties is to be customized then set the $(D conn) parameter with a 1762 * HTTP/FTP instance that has these properties set. 1763 * 1764 * If $(D postData) is non-_null the method will be set to $(D post) for HTTP 1765 * requests. 1766 * 1767 * The background thread will buffer up to transmitBuffers number of chunks 1768 * before is stops receiving data from network. When the main thread reads the 1769 * chunks from the range it frees up buffers and allows for the background 1770 * thread to receive more data from the network. 1771 * 1772 * If no data is available and the main thread access the range it will block 1773 * until data becomes available. An exception to this is the $(D wait(Duration)) 1774 * method on the $(LREF AsyncChunkInputRange). This method will wait at maximum for the specified 1775 * duration and return true if data is available. 1776 * 1777 * Example: 1778 * ---- 1779 * import std.net.curl, std.stdio; 1780 * // Get some pages in the background 1781 * auto range1 = byChunkAsync("www.google.com", 100); 1782 * auto range2 = byChunkAsync("www.wikipedia.org"); 1783 * foreach (chunk; byChunkAsync("dlang.org")) 1784 * writeln(chunk); // chunk is ubyte[100] 1785 * 1786 * // Chunks already fetched in the background and ready 1787 * foreach (chunk; range1) writeln(chunk); 1788 * foreach (chunk; range2) writeln(chunk); 1789 * ---- 1790 * 1791 * ---- 1792 * import std.net.curl, std.stdio; 1793 * // Get a line in a background thread and wait in 1794 * // main thread for 2 seconds for it to arrive. 1795 * auto range3 = byChunkAsync("dlang.com", 10); 1796 * if (range3.wait(dur!"seconds"(2))) 1797 * writeln(range3.front); 1798 * else 1799 * writeln("No chunk received after 2 seconds!"); 1800 * ---- 1801 * 1802 * Params: 1803 * url = The url to receive content from 1804 * postData = Data to HTTP Post 1805 * chunkSize = The size of the chunks 1806 * transmitBuffers = The number of chunks buffered asynchronously 1807 * conn = The connection to use e.g. HTTP or FTP. 1808 * 1809 * Returns: 1810 * A range of ubyte[chunkSize] with the content of the resource pointer to by 1811 * the URL. 1812 */ 1813 auto byChunkAsync(Conn = AutoProtocol, PostUnit) 1814 (const(char)[] url, const(PostUnit)[] postData, 1815 size_t chunkSize = 1024, size_t transmitBuffers = 10, 1816 Conn conn = Conn()) 1817 if (isCurlConn!(Conn)) 1818 { 1819 static if (is(Conn : AutoProtocol)) 1820 { 1821 if (isFTPUrl(url)) 1822 return byChunkAsync(url, postData, chunkSize, 1823 transmitBuffers, FTP()); 1824 else 1825 return byChunkAsync(url, postData, chunkSize, 1826 transmitBuffers, HTTP()); 1827 } 1828 else 1829 { 1830 // 50 is just an arbitrary number for now 1831 setMaxMailboxSize(thisTid, 50, OnCrowding.block); 1832 auto tid = spawn(&_spawnAsync!(Conn, ubyte)); 1833 tid.send(thisTid); 1834 1835 _asyncDuplicateConnection(url, conn, postData, tid); 1836 1837 return AsyncChunkInputRange(tid, transmitBuffers, chunkSize); 1838 } 1839 } 1840 1841 /// ditto 1842 auto byChunkAsync(Conn = AutoProtocol) 1843 (const(char)[] url, 1844 size_t chunkSize = 1024, size_t transmitBuffers = 10, 1845 Conn conn = Conn()) 1846 if (isCurlConn!(Conn)) 1847 { 1848 static if (is(Conn : AutoProtocol)) 1849 { 1850 if (isFTPUrl(url)) 1851 return byChunkAsync(url, cast(void[]) null, chunkSize, 1852 transmitBuffers, FTP()); 1853 else 1854 return byChunkAsync(url, cast(void[]) null, chunkSize, 1855 transmitBuffers, HTTP()); 1856 } 1857 else 1858 { 1859 return byChunkAsync(url, cast(void[]) null, chunkSize, 1860 transmitBuffers, conn); 1861 } 1862 } 1863 1864 @system unittest 1865 { 1866 import std.algorithm.comparison : equal; 1867 1868 foreach (host; [testServer.addr, "http://"~testServer.addr]) 1869 { 1870 testServer.handle((s) { 1871 auto req = s.recvReq; 1872 s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5])); 1873 }); 1874 assert(byChunkAsync(host, 2).equal([[0, 1], [2, 3], [4, 5]])); 1875 } 1876 } 1877 1878 1879 /* Used by byLineAsync/byChunkAsync to duplicate an existing connection 1880 * that can be used exclusively in a spawned thread. 1881 */ 1882 private void _asyncDuplicateConnection(Conn, PostData) 1883 (const(char)[] url, Conn conn, PostData postData, Tid tid) 1884 { 1885 // no move semantic available in std.concurrency ie. must use casting. 1886 auto connDup = conn.dup(); 1887 connDup.url = url; 1888 1889 static if ( is(Conn : HTTP) ) 1890 { 1891 connDup.p.headersOut = null; 1892 connDup.method = conn.method == HTTP.Method.undefined ? 1893 HTTP.Method.get : conn.method; 1894 if (postData !is null) 1895 { 1896 if (connDup.method == HTTP.Method.put) 1897 { 1898 connDup.handle.set(CurlOption.infilesize_large, 1899 postData.length); 1900 } 1901 else 1902 { 1903 // post 1904 connDup.method = HTTP.Method.post; 1905 connDup.handle.set(CurlOption.postfieldsize_large, 1906 postData.length); 1907 } 1908 connDup.handle.set(CurlOption.copypostfields, 1909 cast(void*) postData.ptr); 1910 } 1911 tid.send(cast(ulong) connDup.handle.handle); 1912 tid.send(connDup.method); 1913 } 1914 else 1915 { 1916 enforce!CurlException(postData is null, 1917 "Cannot put ftp data using byLineAsync()"); 1918 tid.send(cast(ulong) connDup.handle.handle); 1919 tid.send(HTTP.Method.undefined); 1920 } 1921 connDup.p.curl.handle = null; // make sure handle is not freed 1922 } 1923 1924 /* 1925 Mixin template for all supported curl protocols. This is the commom 1926 functionallity such as timeouts and network interface settings. This should 1927 really be in the HTTP/FTP/SMTP structs but the documentation tool does not 1928 support a mixin to put its doc strings where a mixin is done. Therefore docs 1929 in this template is copied into each of HTTP/FTP/SMTP below. 1930 */ 1931 private mixin template Protocol() 1932 { 1933 1934 /// Value to return from $(D onSend)/$(D onReceive) delegates in order to 1935 /// pause a request 1936 alias requestPause = CurlReadFunc.pause; 1937 1938 /// Value to return from onSend delegate in order to abort a request 1939 alias requestAbort = CurlReadFunc.abort; 1940 1941 static uint defaultAsyncStringBufferSize = 100; 1942 1943 /** 1944 The curl handle used by this connection. 1945 */ 1946 @property ref Curl handle() return 1947 { 1948 return p.curl; 1949 } 1950 1951 /** 1952 True if the instance is stopped. A stopped instance is not usable. 1953 */ 1954 @property bool isStopped() 1955 { 1956 return p.curl.stopped; 1957 } 1958 1959 /// Stop and invalidate this instance. 1960 void shutdown() 1961 { 1962 p.curl.shutdown(); 1963 } 1964 1965 /** Set verbose. 1966 This will print request information to stderr. 1967 */ 1968 @property void verbose(bool on) 1969 { 1970 p.curl.set(CurlOption.verbose, on ? 1L : 0L); 1971 } 1972 1973 // Connection settings 1974 1975 /// Set timeout for activity on connection. 1976 @property void dataTimeout(Duration d) 1977 { 1978 p.curl.set(CurlOption.low_speed_limit, 1); 1979 p.curl.set(CurlOption.low_speed_time, d.total!"seconds"); 1980 } 1981 1982 /** Set maximum time an operation is allowed to take. 1983 This includes dns resolution, connecting, data transfer, etc. 1984 */ 1985 @property void operationTimeout(Duration d) 1986 { 1987 p.curl.set(CurlOption.timeout_ms, d.total!"msecs"); 1988 } 1989 1990 /// Set timeout for connecting. 1991 @property void connectTimeout(Duration d) 1992 { 1993 p.curl.set(CurlOption.connecttimeout_ms, d.total!"msecs"); 1994 } 1995 1996 // Network settings 1997 1998 /** Proxy 1999 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) 2000 */ 2001 @property void proxy(const(char)[] host) 2002 { 2003 p.curl.set(CurlOption.proxy, host); 2004 } 2005 2006 /** Proxy port 2007 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) 2008 */ 2009 @property void proxyPort(ushort port) 2010 { 2011 p.curl.set(CurlOption.proxyport, cast(long) port); 2012 } 2013 2014 /// Type of proxy 2015 alias CurlProxy = etc.c.curl.CurlProxy; 2016 2017 /** Proxy type 2018 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) 2019 */ 2020 @property void proxyType(CurlProxy type) 2021 { 2022 p.curl.set(CurlOption.proxytype, cast(long) type); 2023 } 2024 2025 /// DNS lookup timeout. 2026 @property void dnsTimeout(Duration d) 2027 { 2028 p.curl.set(CurlOption.dns_cache_timeout, d.total!"msecs"); 2029 } 2030 2031 /** 2032 * The network interface to use in form of the the IP of the interface. 2033 * 2034 * Example: 2035 * ---- 2036 * theprotocol.netInterface = "192.168.1.32"; 2037 * theprotocol.netInterface = [ 192, 168, 1, 32 ]; 2038 * ---- 2039 * 2040 * See: $(REF InternetAddress, std,socket) 2041 */ 2042 @property void netInterface(const(char)[] i) 2043 { 2044 p.curl.set(CurlOption.intrface, i); 2045 } 2046 2047 /// ditto 2048 @property void netInterface(const(ubyte)[4] i) 2049 { 2050 import std.format : format; 2051 const str = format("%d.%d.%d.%d", i[0], i[1], i[2], i[3]); 2052 netInterface = str; 2053 } 2054 2055 /// ditto 2056 @property void netInterface(InternetAddress i) 2057 { 2058 netInterface = i.toAddrString(); 2059 } 2060 2061 /** 2062 Set the local outgoing port to use. 2063 Params: 2064 port = the first outgoing port number to try and use 2065 */ 2066 @property void localPort(ushort port) 2067 { 2068 p.curl.set(CurlOption.localport, cast(long) port); 2069 } 2070 2071 /** 2072 Set the no proxy flag for the specified host names. 2073 Params: 2074 test = a list of comma host names that do not require 2075 proxy to get reached 2076 */ 2077 void setNoProxy(string hosts) 2078 { 2079 p.curl.set(CurlOption.noproxy, hosts); 2080 } 2081 2082 /** 2083 Set the local outgoing port range to use. 2084 This can be used together with the localPort property. 2085 Params: 2086 range = if the first port is occupied then try this many 2087 port number forwards 2088 */ 2089 @property void localPortRange(ushort range) 2090 { 2091 p.curl.set(CurlOption.localportrange, cast(long) range); 2092 } 2093 2094 /** Set the tcp no-delay socket option on or off. 2095 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) 2096 */ 2097 @property void tcpNoDelay(bool on) 2098 { 2099 p.curl.set(CurlOption.tcp_nodelay, cast(long) (on ? 1 : 0) ); 2100 } 2101 2102 /** Sets whether SSL peer certificates should be verified. 2103 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYPEER, verifypeer) 2104 */ 2105 @property void verifyPeer(bool on) 2106 { 2107 p.curl.set(CurlOption.ssl_verifypeer, on ? 1 : 0); 2108 } 2109 2110 /** Sets whether the host within an SSL certificate should be verified. 2111 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYHOST, verifypeer) 2112 */ 2113 @property void verifyHost(bool on) 2114 { 2115 p.curl.set(CurlOption.ssl_verifyhost, on ? 2 : 0); 2116 } 2117 2118 // Authentication settings 2119 2120 /** 2121 Set the user name, password and optionally domain for authentication 2122 purposes. 2123 2124 Some protocols may need authentication in some cases. Use this 2125 function to provide credentials. 2126 2127 Params: 2128 username = the username 2129 password = the password 2130 domain = used for NTLM authentication only and is set to the NTLM domain 2131 name 2132 */ 2133 void setAuthentication(const(char)[] username, const(char)[] password, 2134 const(char)[] domain = "") 2135 { 2136 import std.format : format; 2137 if (!domain.empty) 2138 username = format("%s/%s", domain, username); 2139 p.curl.set(CurlOption.userpwd, format("%s:%s", username, password)); 2140 } 2141 2142 @system unittest 2143 { 2144 import std.algorithm.searching : canFind; 2145 2146 testServer.handle((s) { 2147 auto req = s.recvReq; 2148 assert(req.hdrs.canFind("GET /")); 2149 assert(req.hdrs.canFind("Basic dXNlcjpwYXNz")); 2150 s.send(httpOK()); 2151 }); 2152 2153 auto http = HTTP(testServer.addr); 2154 http.onReceive = (ubyte[] data) { return data.length; }; 2155 http.setAuthentication("user", "pass"); 2156 http.perform(); 2157 2158 // Bugzilla 17540 2159 http.setNoProxy("www.example.com"); 2160 } 2161 2162 /** 2163 Set the user name and password for proxy authentication. 2164 2165 Params: 2166 username = the username 2167 password = the password 2168 */ 2169 void setProxyAuthentication(const(char)[] username, const(char)[] password) 2170 { 2171 import std.array : replace; 2172 import std.format : format; 2173 2174 p.curl.set(CurlOption.proxyuserpwd, 2175 format("%s:%s", 2176 username.replace(":", "%3A"), 2177 password.replace(":", "%3A")) 2178 ); 2179 } 2180 2181 /** 2182 * The event handler that gets called when data is needed for sending. The 2183 * length of the $(D void[]) specifies the maximum number of bytes that can 2184 * be sent. 2185 * 2186 * Returns: 2187 * The callback returns the number of elements in the buffer that have been 2188 * filled and are ready to send. 2189 * The special value $(D .abortRequest) can be returned in order to abort the 2190 * current request. 2191 * The special value $(D .pauseRequest) can be returned in order to pause the 2192 * current request. 2193 * 2194 * Example: 2195 * ---- 2196 * import std.net.curl; 2197 * string msg = "Hello world"; 2198 * auto client = HTTP("dlang.org"); 2199 * client.onSend = delegate size_t(void[] data) 2200 * { 2201 * auto m = cast(void[]) msg; 2202 * size_t length = m.length > data.length ? data.length : m.length; 2203 * if (length == 0) return 0; 2204 * data[0 .. length] = m[0 .. length]; 2205 * msg = msg[length..$]; 2206 * return length; 2207 * }; 2208 * client.perform(); 2209 * ---- 2210 */ 2211 @property void onSend(size_t delegate(void[]) callback) 2212 { 2213 p.curl.clear(CurlOption.postfields); // cannot specify data when using callback 2214 p.curl.onSend = callback; 2215 } 2216 2217 /** 2218 * The event handler that receives incoming data. Be sure to copy the 2219 * incoming ubyte[] since it is not guaranteed to be valid after the 2220 * callback returns. 2221 * 2222 * Returns: 2223 * The callback returns the number of incoming bytes read. If the entire array is 2224 * not read the request will abort. 2225 * The special value .pauseRequest can be returned in order to pause the 2226 * current request. 2227 * 2228 * Example: 2229 * ---- 2230 * import std.net.curl, std.stdio; 2231 * auto client = HTTP("dlang.org"); 2232 * client.onReceive = (ubyte[] data) 2233 * { 2234 * writeln("Got data", to!(const(char)[])(data)); 2235 * return data.length; 2236 * }; 2237 * client.perform(); 2238 * ---- 2239 */ 2240 @property void onReceive(size_t delegate(ubyte[]) callback) 2241 { 2242 p.curl.onReceive = callback; 2243 } 2244 2245 /** 2246 * The event handler that gets called to inform of upload/download progress. 2247 * 2248 * Params: 2249 * dlTotal = total bytes to download 2250 * dlNow = currently downloaded bytes 2251 * ulTotal = total bytes to upload 2252 * ulNow = currently uploaded bytes 2253 * 2254 * Returns: 2255 * Return 0 from the callback to signal success, return non-zero to abort 2256 * transfer 2257 * 2258 * Example: 2259 * ---- 2260 * import std.net.curl, std.stdio; 2261 * auto client = HTTP("dlang.org"); 2262 * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult) 2263 * { 2264 * writeln("Progress: downloaded ", dln, " of ", dl); 2265 * writeln("Progress: uploaded ", uln, " of ", ul); 2266 * }; 2267 * client.perform(); 2268 * ---- 2269 */ 2270 @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, 2271 size_t ulTotal, size_t ulNow) callback) 2272 { 2273 p.curl.onProgress = callback; 2274 } 2275 } 2276 2277 /* 2278 Decode $(D ubyte[]) array using the provided EncodingScheme up to maxChars 2279 Returns: Tuple of ubytes read and the $(D Char[]) characters decoded. 2280 Not all ubytes are guaranteed to be read in case of decoding error. 2281 */ 2282 private Tuple!(size_t,Char[]) 2283 decodeString(Char = char)(const(ubyte)[] data, 2284 EncodingScheme scheme, 2285 size_t maxChars = size_t.max) 2286 { 2287 Char[] res; 2288 immutable startLen = data.length; 2289 size_t charsDecoded = 0; 2290 while (data.length && charsDecoded < maxChars) 2291 { 2292 immutable dchar dc = scheme.safeDecode(data); 2293 if (dc == INVALID_SEQUENCE) 2294 { 2295 return typeof(return)(size_t.max, cast(Char[]) null); 2296 } 2297 charsDecoded++; 2298 res ~= dc; 2299 } 2300 return typeof(return)(startLen-data.length, res); 2301 } 2302 2303 /* 2304 Decode $(D ubyte[]) array using the provided $(D EncodingScheme) until a the 2305 line terminator specified is found. The basesrc parameter is effectively 2306 prepended to src as the first thing. 2307 2308 This function is used for decoding as much of the src buffer as 2309 possible until either the terminator is found or decoding fails. If 2310 it fails as the last data in the src it may mean that the src buffer 2311 were missing some bytes in order to represent a correct code 2312 point. Upon the next call to this function more bytes have been 2313 received from net and the failing bytes should be given as the 2314 basesrc parameter. It is done this way to minimize data copying. 2315 2316 Returns: true if a terminator was found 2317 Not all ubytes are guaranteed to be read in case of decoding error. 2318 any decoded chars will be inserted into dst. 2319 */ 2320 private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, 2321 ref const(ubyte)[] src, 2322 ref Char[] dst, 2323 EncodingScheme scheme, 2324 Terminator terminator) 2325 { 2326 import std.algorithm.searching : endsWith; 2327 2328 // if there is anything in the basesrc then try to decode that 2329 // first. 2330 if (basesrc.length != 0) 2331 { 2332 // Try to ensure 4 entries in the basesrc by copying from src. 2333 immutable blen = basesrc.length; 2334 immutable len = (basesrc.length + src.length) >= 4 ? 2335 4 : basesrc.length + src.length; 2336 basesrc.length = len; 2337 2338 immutable dchar dc = scheme.safeDecode(basesrc); 2339 if (dc == INVALID_SEQUENCE) 2340 { 2341 enforce!CurlException(len != 4, "Invalid code sequence"); 2342 return false; 2343 } 2344 dst ~= dc; 2345 src = src[len-basesrc.length-blen .. $]; // remove used ubytes from src 2346 basesrc.length = 0; 2347 } 2348 2349 while (src.length) 2350 { 2351 const lsrc = src; 2352 dchar dc = scheme.safeDecode(src); 2353 if (dc == INVALID_SEQUENCE) 2354 { 2355 if (src.empty) 2356 { 2357 // The invalid sequence was in the end of the src. Maybe there 2358 // just need to be more bytes available so these last bytes are 2359 // put back to src for later use. 2360 src = lsrc; 2361 return false; 2362 } 2363 dc = '?'; 2364 } 2365 dst ~= dc; 2366 2367 if (dst.endsWith(terminator)) 2368 return true; 2369 } 2370 return false; // no terminator found 2371 } 2372 2373 /** 2374 * HTTP client functionality. 2375 * 2376 * Example: 2377 * --- 2378 * import std.net.curl, std.stdio; 2379 * 2380 * // Get with custom data receivers 2381 * auto http = HTTP("dlang.org"); 2382 * http.onReceiveHeader = 2383 * (in char[] key, in char[] value) { writeln(key ~ ": " ~ value); }; 2384 * http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; }; 2385 * http.perform(); 2386 * 2387 * // Put with data senders 2388 * auto msg = "Hello world"; 2389 * http.contentLength = msg.length; 2390 * http.onSend = (void[] data) 2391 * { 2392 * auto m = cast(void[]) msg; 2393 * size_t len = m.length > data.length ? data.length : m.length; 2394 * if (len == 0) return len; 2395 * data[0 .. len] = m[0 .. len]; 2396 * msg = msg[len..$]; 2397 * return len; 2398 * }; 2399 * http.perform(); 2400 * 2401 * // Track progress 2402 * http.method = HTTP.Method.get; 2403 * http.url = "http://upload.wikimedia.org/wikipedia/commons/" 2404 * "5/53/Wikipedia-logo-en-big.png"; 2405 * http.onReceive = (ubyte[] data) { return data.length; }; 2406 * http.onProgress = (size_t dltotal, size_t dlnow, 2407 * size_t ultotal, size_t ulnow) 2408 * { 2409 * writeln("Progress ", dltotal, ", ", dlnow, ", ", ultotal, ", ", ulnow); 2410 * return 0; 2411 * }; 2412 * http.perform(); 2413 * --- 2414 * 2415 * See_Also: $(_HTTP www.ietf.org/rfc/rfc2616.txt, RFC2616) 2416 * 2417 */ 2418 struct HTTP 2419 { 2420 mixin Protocol; 2421 2422 import std.datetime.systime : SysTime; 2423 2424 /// Authentication method equal to $(REF CurlAuth, etc,c,curl) 2425 alias AuthMethod = CurlAuth; 2426 2427 static private uint defaultMaxRedirects = 10; 2428 2429 private struct Impl 2430 { 2431 ~this() 2432 { 2433 if (headersOut !is null) 2434 Curl.curl.slist_free_all(headersOut); 2435 if (curl.handle !is null) // work around RefCounted/emplace bug 2436 curl.shutdown(); 2437 } 2438 Curl curl; 2439 curl_slist* headersOut; 2440 string[string] headersIn; 2441 string charset; 2442 2443 /// The status line of the final sub-request in a request. 2444 StatusLine status; 2445 private void delegate(StatusLine) onReceiveStatusLine; 2446 2447 /// The HTTP method to use. 2448 Method method = Method.undefined; 2449 2450 @system @property void onReceiveHeader(void delegate(in char[] key, 2451 in char[] value) callback) 2452 { 2453 import std.algorithm.searching : startsWith; 2454 import std.conv : to; 2455 import std.regex : regex, match; 2456 import std.uni : toLower; 2457 2458 // Wrap incoming callback in order to separate http status line from 2459 // http headers. On redirected requests there may be several such 2460 // status lines. The last one is the one recorded. 2461 auto dg = (in char[] header) 2462 { 2463 import std.utf : UTFException; 2464 try 2465 { 2466 if (header.empty) 2467 { 2468 // header delimiter 2469 return; 2470 } 2471 if (header.startsWith("HTTP/")) 2472 { 2473 headersIn.clear(); 2474 2475 const m = match(header, regex(r"^HTTP/(\d+)\.(\d+) (\d+) (.*)$")); 2476 if (m.empty) 2477 { 2478 // Invalid status line 2479 } 2480 else 2481 { 2482 status.majorVersion = to!ushort(m.captures[1]); 2483 status.minorVersion = to!ushort(m.captures[2]); 2484 status.code = to!ushort(m.captures[3]); 2485 status.reason = m.captures[4].idup; 2486 if (onReceiveStatusLine != null) 2487 onReceiveStatusLine(status); 2488 } 2489 return; 2490 } 2491 2492 // Normal http header 2493 auto m = match(cast(char[]) header, regex("(.*?): (.*)$")); 2494 2495 auto fieldName = m.captures[1].toLower().idup; 2496 if (fieldName == "content-type") 2497 { 2498 auto mct = match(cast(char[]) m.captures[2], 2499 regex("charset=([^;]*)", "i")); 2500 if (!mct.empty && mct.captures.length > 1) 2501 charset = mct.captures[1].idup; 2502 } 2503 2504 if (!m.empty && callback !is null) 2505 callback(fieldName, m.captures[2]); 2506 headersIn[fieldName] = m.captures[2].idup; 2507 } 2508 catch (UTFException e) 2509 { 2510 //munch it - a header should be all ASCII, any "wrong UTF" is broken header 2511 } 2512 }; 2513 2514 curl.onReceiveHeader = dg; 2515 } 2516 } 2517 2518 private RefCounted!Impl p; 2519 2520 /** Time condition enumeration as an alias of $(REF CurlTimeCond, etc,c,curl) 2521 2522 $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25) 2523 */ 2524 alias TimeCond = CurlTimeCond; 2525 2526 /** 2527 Constructor taking the url as parameter. 2528 */ 2529 static HTTP opCall(const(char)[] url) 2530 { 2531 HTTP http; 2532 http.initialize(); 2533 http.url = url; 2534 return http; 2535 } 2536 2537 /// 2538 static HTTP opCall() 2539 { 2540 HTTP http; 2541 http.initialize(); 2542 return http; 2543 } 2544 2545 /// 2546 HTTP dup() 2547 { 2548 HTTP copy; 2549 copy.initialize(); 2550 copy.p.method = p.method; 2551 curl_slist* cur = p.headersOut; 2552 curl_slist* newlist = null; 2553 while (cur) 2554 { 2555 newlist = Curl.curl.slist_append(newlist, cur.data); 2556 cur = cur.next; 2557 } 2558 copy.p.headersOut = newlist; 2559 copy.p.curl.set(CurlOption.httpheader, copy.p.headersOut); 2560 copy.p.curl = p.curl.dup(); 2561 copy.dataTimeout = _defaultDataTimeout; 2562 copy.onReceiveHeader = null; 2563 return copy; 2564 } 2565 2566 private void initialize() 2567 { 2568 p.curl.initialize(); 2569 maxRedirects = HTTP.defaultMaxRedirects; 2570 p.charset = "ISO-8859-1"; // Default charset defined in HTTP RFC 2571 p.method = Method.undefined; 2572 setUserAgent(HTTP.defaultUserAgent); 2573 dataTimeout = _defaultDataTimeout; 2574 onReceiveHeader = null; 2575 verifyPeer = true; 2576 verifyHost = true; 2577 } 2578 2579 /** 2580 Perform a http request. 2581 2582 After the HTTP client has been setup and possibly assigned callbacks the 2583 $(D perform()) method will start performing the request towards the 2584 specified server. 2585 2586 Params: 2587 throwOnError = whether to throw an exception or return a CurlCode on error 2588 */ 2589 CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) 2590 { 2591 p.status.reset(); 2592 2593 CurlOption opt; 2594 final switch (p.method) 2595 { 2596 case Method.head: 2597 p.curl.set(CurlOption.nobody, 1L); 2598 opt = CurlOption.nobody; 2599 break; 2600 case Method.undefined: 2601 case Method.get: 2602 p.curl.set(CurlOption.httpget, 1L); 2603 opt = CurlOption.httpget; 2604 break; 2605 case Method.post: 2606 p.curl.set(CurlOption.post, 1L); 2607 opt = CurlOption.post; 2608 break; 2609 case Method.put: 2610 p.curl.set(CurlOption.upload, 1L); 2611 opt = CurlOption.upload; 2612 break; 2613 case Method.del: 2614 p.curl.set(CurlOption.customrequest, "DELETE"); 2615 opt = CurlOption.customrequest; 2616 break; 2617 case Method.options: 2618 p.curl.set(CurlOption.customrequest, "OPTIONS"); 2619 opt = CurlOption.customrequest; 2620 break; 2621 case Method.trace: 2622 p.curl.set(CurlOption.customrequest, "TRACE"); 2623 opt = CurlOption.customrequest; 2624 break; 2625 case Method.connect: 2626 p.curl.set(CurlOption.customrequest, "CONNECT"); 2627 opt = CurlOption.customrequest; 2628 break; 2629 case Method.patch: 2630 p.curl.set(CurlOption.customrequest, "PATCH"); 2631 opt = CurlOption.customrequest; 2632 break; 2633 } 2634 2635 scope (exit) p.curl.clear(opt); 2636 return p.curl.perform(throwOnError); 2637 } 2638 2639 /// The URL to specify the location of the resource. 2640 @property void url(const(char)[] url) 2641 { 2642 import std.algorithm.searching : startsWith; 2643 import std.uni : toLower; 2644 if (!startsWith(url.toLower(), "http://", "https://")) 2645 url = "http://" ~ url; 2646 p.curl.set(CurlOption.url, url); 2647 } 2648 2649 /// Set the CA certificate bundle file to use for SSL peer verification 2650 @property void caInfo(const(char)[] caFile) 2651 { 2652 p.curl.set(CurlOption.cainfo, caFile); 2653 } 2654 2655 // This is a workaround for mixed in content not having its 2656 // docs mixed in. 2657 version (StdDdoc) 2658 { 2659 /// Value to return from $(D onSend)/$(D onReceive) delegates in order to 2660 /// pause a request 2661 alias requestPause = CurlReadFunc.pause; 2662 2663 /// Value to return from onSend delegate in order to abort a request 2664 alias requestAbort = CurlReadFunc.abort; 2665 2666 /** 2667 True if the instance is stopped. A stopped instance is not usable. 2668 */ 2669 @property bool isStopped(); 2670 2671 /// Stop and invalidate this instance. 2672 void shutdown(); 2673 2674 /** Set verbose. 2675 This will print request information to stderr. 2676 */ 2677 @property void verbose(bool on); 2678 2679 // Connection settings 2680 2681 /// Set timeout for activity on connection. 2682 @property void dataTimeout(Duration d); 2683 2684 /** Set maximum time an operation is allowed to take. 2685 This includes dns resolution, connecting, data transfer, etc. 2686 */ 2687 @property void operationTimeout(Duration d); 2688 2689 /// Set timeout for connecting. 2690 @property void connectTimeout(Duration d); 2691 2692 // Network settings 2693 2694 /** Proxy 2695 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) 2696 */ 2697 @property void proxy(const(char)[] host); 2698 2699 /** Proxy port 2700 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) 2701 */ 2702 @property void proxyPort(ushort port); 2703 2704 /// Type of proxy 2705 alias CurlProxy = etc.c.curl.CurlProxy; 2706 2707 /** Proxy type 2708 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) 2709 */ 2710 @property void proxyType(CurlProxy type); 2711 2712 /// DNS lookup timeout. 2713 @property void dnsTimeout(Duration d); 2714 2715 /** 2716 * The network interface to use in form of the the IP of the interface. 2717 * 2718 * Example: 2719 * ---- 2720 * theprotocol.netInterface = "192.168.1.32"; 2721 * theprotocol.netInterface = [ 192, 168, 1, 32 ]; 2722 * ---- 2723 * 2724 * See: $(REF InternetAddress, std,socket) 2725 */ 2726 @property void netInterface(const(char)[] i); 2727 2728 /// ditto 2729 @property void netInterface(const(ubyte)[4] i); 2730 2731 /// ditto 2732 @property void netInterface(InternetAddress i); 2733 2734 /** 2735 Set the local outgoing port to use. 2736 Params: 2737 port = the first outgoing port number to try and use 2738 */ 2739 @property void localPort(ushort port); 2740 2741 /** 2742 Set the local outgoing port range to use. 2743 This can be used together with the localPort property. 2744 Params: 2745 range = if the first port is occupied then try this many 2746 port number forwards 2747 */ 2748 @property void localPortRange(ushort range); 2749 2750 /** Set the tcp no-delay socket option on or off. 2751 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) 2752 */ 2753 @property void tcpNoDelay(bool on); 2754 2755 // Authentication settings 2756 2757 /** 2758 Set the user name, password and optionally domain for authentication 2759 purposes. 2760 2761 Some protocols may need authentication in some cases. Use this 2762 function to provide credentials. 2763 2764 Params: 2765 username = the username 2766 password = the password 2767 domain = used for NTLM authentication only and is set to the NTLM domain 2768 name 2769 */ 2770 void setAuthentication(const(char)[] username, const(char)[] password, 2771 const(char)[] domain = ""); 2772 2773 /** 2774 Set the user name and password for proxy authentication. 2775 2776 Params: 2777 username = the username 2778 password = the password 2779 */ 2780 void setProxyAuthentication(const(char)[] username, const(char)[] password); 2781 2782 /** 2783 * The event handler that gets called when data is needed for sending. The 2784 * length of the $(D void[]) specifies the maximum number of bytes that can 2785 * be sent. 2786 * 2787 * Returns: 2788 * The callback returns the number of elements in the buffer that have been 2789 * filled and are ready to send. 2790 * The special value $(D .abortRequest) can be returned in order to abort the 2791 * current request. 2792 * The special value $(D .pauseRequest) can be returned in order to pause the 2793 * current request. 2794 * 2795 * Example: 2796 * ---- 2797 * import std.net.curl; 2798 * string msg = "Hello world"; 2799 * auto client = HTTP("dlang.org"); 2800 * client.onSend = delegate size_t(void[] data) 2801 * { 2802 * auto m = cast(void[]) msg; 2803 * size_t length = m.length > data.length ? data.length : m.length; 2804 * if (length == 0) return 0; 2805 * data[0 .. length] = m[0 .. length]; 2806 * msg = msg[length..$]; 2807 * return length; 2808 * }; 2809 * client.perform(); 2810 * ---- 2811 */ 2812 @property void onSend(size_t delegate(void[]) callback); 2813 2814 /** 2815 * The event handler that receives incoming data. Be sure to copy the 2816 * incoming ubyte[] since it is not guaranteed to be valid after the 2817 * callback returns. 2818 * 2819 * Returns: 2820 * The callback returns the incoming bytes read. If not the entire array is 2821 * the request will abort. 2822 * The special value .pauseRequest can be returned in order to pause the 2823 * current request. 2824 * 2825 * Example: 2826 * ---- 2827 * import std.net.curl, std.stdio; 2828 * auto client = HTTP("dlang.org"); 2829 * client.onReceive = (ubyte[] data) 2830 * { 2831 * writeln("Got data", to!(const(char)[])(data)); 2832 * return data.length; 2833 * }; 2834 * client.perform(); 2835 * ---- 2836 */ 2837 @property void onReceive(size_t delegate(ubyte[]) callback); 2838 2839 /** 2840 * Register an event handler that gets called to inform of 2841 * upload/download progress. 2842 * 2843 * Callback_parameters: 2844 * $(CALLBACK_PARAMS) 2845 * 2846 * Callback_returns: Return 0 to signal success, return non-zero to 2847 * abort transfer. 2848 * 2849 * Example: 2850 * ---- 2851 * import std.net.curl, std.stdio; 2852 * auto client = HTTP("dlang.org"); 2853 * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult) 2854 * { 2855 * writeln("Progress: downloaded ", dln, " of ", dl); 2856 * writeln("Progress: uploaded ", uln, " of ", ul); 2857 * }; 2858 * client.perform(); 2859 * ---- 2860 */ 2861 @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, 2862 size_t ulTotal, size_t ulNow) callback); 2863 } 2864 2865 /** Clear all outgoing headers. 2866 */ 2867 void clearRequestHeaders() 2868 { 2869 if (p.headersOut !is null) 2870 Curl.curl.slist_free_all(p.headersOut); 2871 p.headersOut = null; 2872 p.curl.clear(CurlOption.httpheader); 2873 } 2874 2875 /** Add a header e.g. "X-CustomField: Something is fishy". 2876 * 2877 * There is no remove header functionality. Do a $(LREF clearRequestHeaders) 2878 * and set the needed headers instead. 2879 * 2880 * Example: 2881 * --- 2882 * import std.net.curl; 2883 * auto client = HTTP(); 2884 * client.addRequestHeader("X-Custom-ABC", "This is the custom value"); 2885 * auto content = get("dlang.org", client); 2886 * --- 2887 */ 2888 void addRequestHeader(const(char)[] name, const(char)[] value) 2889 { 2890 import std.format : format; 2891 import std.uni : icmp; 2892 2893 if (icmp(name, "User-Agent") == 0) 2894 return setUserAgent(value); 2895 string nv = format("%s: %s", name, value); 2896 p.headersOut = Curl.curl.slist_append(p.headersOut, 2897 nv.tempCString().buffPtr); 2898 p.curl.set(CurlOption.httpheader, p.headersOut); 2899 } 2900 2901 /** 2902 * The default "User-Agent" value send with a request. 2903 * It has the form "Phobos-std.net.curl/$(I PHOBOS_VERSION) (libcurl/$(I CURL_VERSION))" 2904 */ 2905 static string defaultUserAgent() @property 2906 { 2907 import std.compiler : version_major, version_minor; 2908 import std.format : format, sformat; 2909 2910 // http://curl.haxx.se/docs/versions.html 2911 enum fmt = "Phobos-std.net.curl/%d.%03d (libcurl/%d.%d.%d)"; 2912 enum maxLen = fmt.length - "%d%03d%d%d%d".length + 10 + 10 + 3 + 3 + 3; 2913 2914 static char[maxLen] buf = void; 2915 static string userAgent; 2916 2917 if (!userAgent.length) 2918 { 2919 auto curlVer = Curl.curl.version_info(CURLVERSION_NOW).version_num; 2920 userAgent = cast(immutable) sformat( 2921 buf, fmt, version_major, version_minor, 2922 curlVer >> 16 & 0xFF, curlVer >> 8 & 0xFF, curlVer & 0xFF); 2923 } 2924 return userAgent; 2925 } 2926 2927 /** Set the value of the user agent request header field. 2928 * 2929 * By default a request has it's "User-Agent" field set to $(LREF 2930 * defaultUserAgent) even if $(D setUserAgent) was never called. Pass 2931 * an empty string to suppress the "User-Agent" field altogether. 2932 */ 2933 void setUserAgent(const(char)[] userAgent) 2934 { 2935 p.curl.set(CurlOption.useragent, userAgent); 2936 } 2937 2938 /** 2939 * Get various timings defined in $(REF CurlInfo, etc, c, curl). 2940 * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok). 2941 * 2942 * Params: 2943 * timing = one of the timings defined in $(REF CurlInfo, etc, c, curl). 2944 * The values are: 2945 * $(D etc.c.curl.CurlInfo.namelookup_time), 2946 * $(D etc.c.curl.CurlInfo.connect_time), 2947 * $(D etc.c.curl.CurlInfo.pretransfer_time), 2948 * $(D etc.c.curl.CurlInfo.starttransfer_time), 2949 * $(D etc.c.curl.CurlInfo.redirect_time), 2950 * $(D etc.c.curl.CurlInfo.appconnect_time), 2951 * $(D etc.c.curl.CurlInfo.total_time). 2952 * val = the actual value of the inquired timing. 2953 * 2954 * Returns: 2955 * The return code of the operation. The value stored in val 2956 * should be used only if the return value is $(D etc.c.curl.CurlInfo.ok). 2957 * 2958 * Example: 2959 * --- 2960 * import std.net.curl; 2961 * import etc.c.curl : CurlError, CurlInfo; 2962 * 2963 * auto client = HTTP("dlang.org"); 2964 * client.perform(); 2965 * 2966 * double val; 2967 * CurlCode code; 2968 * 2969 * code = http.getTiming(CurlInfo.namelookup_time, val); 2970 * assert(code == CurlError.ok); 2971 * --- 2972 */ 2973 CurlCode getTiming(CurlInfo timing, ref double val) 2974 { 2975 return p.curl.getTiming(timing, val); 2976 } 2977 2978 /** The headers read from a successful response. 2979 * 2980 */ 2981 @property string[string] responseHeaders() 2982 { 2983 return p.headersIn; 2984 } 2985 2986 /// HTTP method used. 2987 @property void method(Method m) 2988 { 2989 p.method = m; 2990 } 2991 2992 /// ditto 2993 @property Method method() 2994 { 2995 return p.method; 2996 } 2997 2998 /** 2999 HTTP status line of last response. One call to perform may 3000 result in several requests because of redirection. 3001 */ 3002 @property StatusLine statusLine() 3003 { 3004 return p.status; 3005 } 3006 3007 /// Set the active cookie string e.g. "name1=value1;name2=value2" 3008 void setCookie(const(char)[] cookie) 3009 { 3010 p.curl.set(CurlOption.cookie, cookie); 3011 } 3012 3013 /// Set a file path to where a cookie jar should be read/stored. 3014 void setCookieJar(const(char)[] path) 3015 { 3016 p.curl.set(CurlOption.cookiefile, path); 3017 if (path.length) 3018 p.curl.set(CurlOption.cookiejar, path); 3019 } 3020 3021 /// Flush cookie jar to disk. 3022 void flushCookieJar() 3023 { 3024 p.curl.set(CurlOption.cookielist, "FLUSH"); 3025 } 3026 3027 /// Clear session cookies. 3028 void clearSessionCookies() 3029 { 3030 p.curl.set(CurlOption.cookielist, "SESS"); 3031 } 3032 3033 /// Clear all cookies. 3034 void clearAllCookies() 3035 { 3036 p.curl.set(CurlOption.cookielist, "ALL"); 3037 } 3038 3039 /** 3040 Set time condition on the request. 3041 3042 Params: 3043 cond = $(D CurlTimeCond.{none,ifmodsince,ifunmodsince,lastmod}) 3044 timestamp = Timestamp for the condition 3045 3046 $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25) 3047 */ 3048 void setTimeCondition(HTTP.TimeCond cond, SysTime timestamp) 3049 { 3050 p.curl.set(CurlOption.timecondition, cond); 3051 p.curl.set(CurlOption.timevalue, timestamp.toUnixTime()); 3052 } 3053 3054 /** Specifying data to post when not using the onSend callback. 3055 * 3056 * The data is NOT copied by the library. Content-Type will default to 3057 * application/octet-stream. Data is not converted or encoded by this 3058 * method. 3059 * 3060 * Example: 3061 * ---- 3062 * import std.net.curl, std.stdio; 3063 * auto http = HTTP("http://www.mydomain.com"); 3064 * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; 3065 * http.postData = [1,2,3,4,5]; 3066 * http.perform(); 3067 * ---- 3068 */ 3069 @property void postData(const(void)[] data) 3070 { 3071 setPostData(data, "application/octet-stream"); 3072 } 3073 3074 /** Specifying data to post when not using the onSend callback. 3075 * 3076 * The data is NOT copied by the library. Content-Type will default to 3077 * text/plain. Data is not converted or encoded by this method. 3078 * 3079 * Example: 3080 * ---- 3081 * import std.net.curl, std.stdio; 3082 * auto http = HTTP("http://www.mydomain.com"); 3083 * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; 3084 * http.postData = "The quick...."; 3085 * http.perform(); 3086 * ---- 3087 */ 3088 @property void postData(const(char)[] data) 3089 { 3090 setPostData(data, "text/plain"); 3091 } 3092 3093 /** 3094 * Specify data to post when not using the onSend callback, with 3095 * user-specified Content-Type. 3096 * Params: 3097 * data = Data to post. 3098 * contentType = MIME type of the data, for example, "text/plain" or 3099 * "application/octet-stream". See also: 3100 * $(LINK2 http://en.wikipedia.org/wiki/Internet_media_type, 3101 * Internet media type) on Wikipedia. 3102 * ----- 3103 * import std.net.curl; 3104 * auto http = HTTP("http://onlineform.example.com"); 3105 * auto data = "app=login&username=bob&password=s00perS3kret"; 3106 * http.setPostData(data, "application/x-www-form-urlencoded"); 3107 * http.onReceive = (ubyte[] data) { return data.length; }; 3108 * http.perform(); 3109 * ----- 3110 */ 3111 void setPostData(const(void)[] data, string contentType) 3112 { 3113 // cannot use callback when specifying data directly so it is disabled here. 3114 p.curl.clear(CurlOption.readfunction); 3115 addRequestHeader("Content-Type", contentType); 3116 p.curl.set(CurlOption.postfields, cast(void*) data.ptr); 3117 p.curl.set(CurlOption.postfieldsize, data.length); 3118 if (method == Method.undefined) 3119 method = Method.post; 3120 } 3121 3122 @system unittest 3123 { 3124 import std.algorithm.searching : canFind; 3125 3126 testServer.handle((s) { 3127 auto req = s.recvReq!ubyte; 3128 assert(req.hdrs.canFind("POST /path")); 3129 assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4])); 3130 assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255])); 3131 s.send(httpOK(cast(ubyte[])[17, 27, 35, 41])); 3132 }); 3133 auto data = new ubyte[](256); 3134 foreach (i, ref ub; data) 3135 ub = cast(ubyte) i; 3136 3137 auto http = HTTP(testServer.addr~"/path"); 3138 http.postData = data; 3139 ubyte[] res; 3140 http.onReceive = (data) { res ~= data; return data.length; }; 3141 http.perform(); 3142 assert(res == cast(ubyte[])[17, 27, 35, 41]); 3143 } 3144 3145 /** 3146 * Set the event handler that receives incoming headers. 3147 * 3148 * The callback will receive a header field key, value as parameter. The 3149 * $(D const(char)[]) arrays are not valid after the delegate has returned. 3150 * 3151 * Example: 3152 * ---- 3153 * import std.net.curl, std.stdio; 3154 * auto http = HTTP("dlang.org"); 3155 * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; 3156 * http.onReceiveHeader = (in char[] key, in char[] value) { writeln(key, " = ", value); }; 3157 * http.perform(); 3158 * ---- 3159 */ 3160 @property void onReceiveHeader(void delegate(in char[] key, 3161 in char[] value) callback) 3162 { 3163 p.onReceiveHeader = callback; 3164 } 3165 3166 /** 3167 Callback for each received StatusLine. 3168 3169 Notice that several callbacks can be done for each call to 3170 $(D perform()) due to redirections. 3171 3172 See_Also: $(LREF StatusLine) 3173 */ 3174 @property void onReceiveStatusLine(void delegate(StatusLine) callback) 3175 { 3176 p.onReceiveStatusLine = callback; 3177 } 3178 3179 /** 3180 The content length in bytes when using request that has content 3181 e.g. POST/PUT and not using chunked transfer. Is set as the 3182 "Content-Length" header. Set to ulong.max to reset to chunked transfer. 3183 */ 3184 @property void contentLength(ulong len) 3185 { 3186 import std.conv : to; 3187 3188 CurlOption lenOpt; 3189 3190 // Force post if necessary 3191 if (p.method != Method.put && p.method != Method.post && 3192 p.method != Method.patch) 3193 p.method = Method.post; 3194 3195 if (p.method == Method.post || p.method == Method.patch) 3196 lenOpt = CurlOption.postfieldsize_large; 3197 else 3198 lenOpt = CurlOption.infilesize_large; 3199 3200 if (size_t.max != ulong.max && len == size_t.max) 3201 len = ulong.max; // check size_t.max for backwards compat, turn into error 3202 3203 if (len == ulong.max) 3204 { 3205 // HTTP 1.1 supports requests with no length header set. 3206 addRequestHeader("Transfer-Encoding", "chunked"); 3207 addRequestHeader("Expect", "100-continue"); 3208 } 3209 else 3210 { 3211 p.curl.set(lenOpt, to!curl_off_t(len)); 3212 } 3213 } 3214 3215 /** 3216 Authentication method as specified in $(LREF AuthMethod). 3217 */ 3218 @property void authenticationMethod(AuthMethod authMethod) 3219 { 3220 p.curl.set(CurlOption.httpauth, cast(long) authMethod); 3221 } 3222 3223 /** 3224 Set max allowed redirections using the location header. 3225 uint.max for infinite. 3226 */ 3227 @property void maxRedirects(uint maxRedirs) 3228 { 3229 if (maxRedirs == uint.max) 3230 { 3231 // Disable 3232 p.curl.set(CurlOption.followlocation, 0); 3233 } 3234 else 3235 { 3236 p.curl.set(CurlOption.followlocation, 1); 3237 p.curl.set(CurlOption.maxredirs, maxRedirs); 3238 } 3239 } 3240 3241 /** <a name="HTTP.Method"/>The standard HTTP methods : 3242 * $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1, _RFC2616 Section 5.1.1) 3243 */ 3244 enum Method 3245 { 3246 undefined, 3247 head, /// 3248 get, /// 3249 post, /// 3250 put, /// 3251 del, /// 3252 options, /// 3253 trace, /// 3254 connect, /// 3255 patch, /// 3256 } 3257 3258 /** 3259 HTTP status line ie. the first line returned in an HTTP response. 3260 3261 If authentication or redirections are done then the status will be for 3262 the last response received. 3263 */ 3264 struct StatusLine 3265 { 3266 ushort majorVersion; /// Major HTTP version ie. 1 in HTTP/1.0. 3267 ushort minorVersion; /// Minor HTTP version ie. 0 in HTTP/1.0. 3268 ushort code; /// HTTP status line code e.g. 200. 3269 string reason; /// HTTP status line reason string. 3270 3271 /// Reset this status line 3272 @safe void reset() 3273 { 3274 majorVersion = 0; 3275 minorVersion = 0; 3276 code = 0; 3277 reason = ""; 3278 } 3279 3280 /// 3281 string toString() const 3282 { 3283 import std.format : format; 3284 return format("%s %s (%s.%s)", 3285 code, reason, majorVersion, minorVersion); 3286 } 3287 } 3288 3289 } // HTTP 3290 3291 @system unittest // charset/Charset/CHARSET/... 3292 { 3293 import std.meta : AliasSeq; 3294 3295 foreach (c; AliasSeq!("charset", "Charset", "CHARSET", "CharSet", "charSet", 3296 "ChArSeT", "cHaRsEt")) 3297 { 3298 testServer.handle((s) { 3299 s.send("HTTP/1.1 200 OK\r\n"~ 3300 "Content-Length: 0\r\n"~ 3301 "Content-Type: text/plain; " ~ c ~ "=foo\r\n" ~ 3302 "\r\n"); 3303 }); 3304 3305 auto http = HTTP(testServer.addr); 3306 http.perform(); 3307 assert(http.p.charset == "foo"); 3308 3309 // Bugzilla 16736 3310 double val; 3311 CurlCode code; 3312 3313 code = http.getTiming(CurlInfo.total_time, val); 3314 assert(code == CurlError.ok); 3315 code = http.getTiming(CurlInfo.namelookup_time, val); 3316 assert(code == CurlError.ok); 3317 code = http.getTiming(CurlInfo.connect_time, val); 3318 assert(code == CurlError.ok); 3319 code = http.getTiming(CurlInfo.pretransfer_time, val); 3320 assert(code == CurlError.ok); 3321 code = http.getTiming(CurlInfo.starttransfer_time, val); 3322 assert(code == CurlError.ok); 3323 code = http.getTiming(CurlInfo.redirect_time, val); 3324 assert(code == CurlError.ok); 3325 code = http.getTiming(CurlInfo.appconnect_time, val); 3326 assert(code == CurlError.ok); 3327 } 3328 } 3329 3330 /** 3331 FTP client functionality. 3332 3333 See_Also: $(HTTP tools.ietf.org/html/rfc959, RFC959) 3334 */ 3335 struct FTP 3336 { 3337 3338 mixin Protocol; 3339 3340 private struct Impl 3341 { 3342 ~this() 3343 { 3344 if (commands !is null) 3345 Curl.curl.slist_free_all(commands); 3346 if (curl.handle !is null) // work around RefCounted/emplace bug 3347 curl.shutdown(); 3348 } 3349 curl_slist* commands; 3350 Curl curl; 3351 string encoding; 3352 } 3353 3354 private RefCounted!Impl p; 3355 3356 /** 3357 FTP access to the specified url. 3358 */ 3359 static FTP opCall(const(char)[] url) 3360 { 3361 FTP ftp; 3362 ftp.initialize(); 3363 ftp.url = url; 3364 return ftp; 3365 } 3366 3367 /// 3368 static FTP opCall() 3369 { 3370 FTP ftp; 3371 ftp.initialize(); 3372 return ftp; 3373 } 3374 3375 /// 3376 FTP dup() 3377 { 3378 FTP copy = FTP(); 3379 copy.initialize(); 3380 copy.p.encoding = p.encoding; 3381 copy.p.curl = p.curl.dup(); 3382 curl_slist* cur = p.commands; 3383 curl_slist* newlist = null; 3384 while (cur) 3385 { 3386 newlist = Curl.curl.slist_append(newlist, cur.data); 3387 cur = cur.next; 3388 } 3389 copy.p.commands = newlist; 3390 copy.p.curl.set(CurlOption.postquote, copy.p.commands); 3391 copy.dataTimeout = _defaultDataTimeout; 3392 return copy; 3393 } 3394 3395 private void initialize() 3396 { 3397 p.curl.initialize(); 3398 p.encoding = "ISO-8859-1"; 3399 dataTimeout = _defaultDataTimeout; 3400 } 3401 3402 /** 3403 Performs the ftp request as it has been configured. 3404 3405 After a FTP client has been setup and possibly assigned callbacks the $(D 3406 perform()) method will start performing the actual communication with the 3407 server. 3408 3409 Params: 3410 throwOnError = whether to throw an exception or return a CurlCode on error 3411 */ 3412 CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) 3413 { 3414 return p.curl.perform(throwOnError); 3415 } 3416 3417 /// The URL to specify the location of the resource. 3418 @property void url(const(char)[] url) 3419 { 3420 import std.algorithm.searching : startsWith; 3421 import std.uni : toLower; 3422 3423 if (!startsWith(url.toLower(), "ftp://", "ftps://")) 3424 url = "ftp://" ~ url; 3425 p.curl.set(CurlOption.url, url); 3426 } 3427 3428 // This is a workaround for mixed in content not having its 3429 // docs mixed in. 3430 version (StdDdoc) 3431 { 3432 /// Value to return from $(D onSend)/$(D onReceive) delegates in order to 3433 /// pause a request 3434 alias requestPause = CurlReadFunc.pause; 3435 3436 /// Value to return from onSend delegate in order to abort a request 3437 alias requestAbort = CurlReadFunc.abort; 3438 3439 /** 3440 True if the instance is stopped. A stopped instance is not usable. 3441 */ 3442 @property bool isStopped(); 3443 3444 /// Stop and invalidate this instance. 3445 void shutdown(); 3446 3447 /** Set verbose. 3448 This will print request information to stderr. 3449 */ 3450 @property void verbose(bool on); 3451 3452 // Connection settings 3453 3454 /// Set timeout for activity on connection. 3455 @property void dataTimeout(Duration d); 3456 3457 /** Set maximum time an operation is allowed to take. 3458 This includes dns resolution, connecting, data transfer, etc. 3459 */ 3460 @property void operationTimeout(Duration d); 3461 3462 /// Set timeout for connecting. 3463 @property void connectTimeout(Duration d); 3464 3465 // Network settings 3466 3467 /** Proxy 3468 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) 3469 */ 3470 @property void proxy(const(char)[] host); 3471 3472 /** Proxy port 3473 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) 3474 */ 3475 @property void proxyPort(ushort port); 3476 3477 /// Type of proxy 3478 alias CurlProxy = etc.c.curl.CurlProxy; 3479 3480 /** Proxy type 3481 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) 3482 */ 3483 @property void proxyType(CurlProxy type); 3484 3485 /// DNS lookup timeout. 3486 @property void dnsTimeout(Duration d); 3487 3488 /** 3489 * The network interface to use in form of the the IP of the interface. 3490 * 3491 * Example: 3492 * ---- 3493 * theprotocol.netInterface = "192.168.1.32"; 3494 * theprotocol.netInterface = [ 192, 168, 1, 32 ]; 3495 * ---- 3496 * 3497 * See: $(REF InternetAddress, std,socket) 3498 */ 3499 @property void netInterface(const(char)[] i); 3500 3501 /// ditto 3502 @property void netInterface(const(ubyte)[4] i); 3503 3504 /// ditto 3505 @property void netInterface(InternetAddress i); 3506 3507 /** 3508 Set the local outgoing port to use. 3509 Params: 3510 port = the first outgoing port number to try and use 3511 */ 3512 @property void localPort(ushort port); 3513 3514 /** 3515 Set the local outgoing port range to use. 3516 This can be used together with the localPort property. 3517 Params: 3518 range = if the first port is occupied then try this many 3519 port number forwards 3520 */ 3521 @property void localPortRange(ushort range); 3522 3523 /** Set the tcp no-delay socket option on or off. 3524 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) 3525 */ 3526 @property void tcpNoDelay(bool on); 3527 3528 // Authentication settings 3529 3530 /** 3531 Set the user name, password and optionally domain for authentication 3532 purposes. 3533 3534 Some protocols may need authentication in some cases. Use this 3535 function to provide credentials. 3536 3537 Params: 3538 username = the username 3539 password = the password 3540 domain = used for NTLM authentication only and is set to the NTLM domain 3541 name 3542 */ 3543 void setAuthentication(const(char)[] username, const(char)[] password, 3544 const(char)[] domain = ""); 3545 3546 /** 3547 Set the user name and password for proxy authentication. 3548 3549 Params: 3550 username = the username 3551 password = the password 3552 */ 3553 void setProxyAuthentication(const(char)[] username, const(char)[] password); 3554 3555 /** 3556 * The event handler that gets called when data is needed for sending. The 3557 * length of the $(D void[]) specifies the maximum number of bytes that can 3558 * be sent. 3559 * 3560 * Returns: 3561 * The callback returns the number of elements in the buffer that have been 3562 * filled and are ready to send. 3563 * The special value $(D .abortRequest) can be returned in order to abort the 3564 * current request. 3565 * The special value $(D .pauseRequest) can be returned in order to pause the 3566 * current request. 3567 * 3568 */ 3569 @property void onSend(size_t delegate(void[]) callback); 3570 3571 /** 3572 * The event handler that receives incoming data. Be sure to copy the 3573 * incoming ubyte[] since it is not guaranteed to be valid after the 3574 * callback returns. 3575 * 3576 * Returns: 3577 * The callback returns the incoming bytes read. If not the entire array is 3578 * the request will abort. 3579 * The special value .pauseRequest can be returned in order to pause the 3580 * current request. 3581 * 3582 */ 3583 @property void onReceive(size_t delegate(ubyte[]) callback); 3584 3585 /** 3586 * The event handler that gets called to inform of upload/download progress. 3587 * 3588 * Callback_parameters: 3589 * $(CALLBACK_PARAMS) 3590 * 3591 * Callback_returns: 3592 * Return 0 from the callback to signal success, return non-zero to 3593 * abort transfer. 3594 */ 3595 @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, 3596 size_t ulTotal, size_t ulNow) callback); 3597 } 3598 3599 /** Clear all commands send to ftp server. 3600 */ 3601 void clearCommands() 3602 { 3603 if (p.commands !is null) 3604 Curl.curl.slist_free_all(p.commands); 3605 p.commands = null; 3606 p.curl.clear(CurlOption.postquote); 3607 } 3608 3609 /** Add a command to send to ftp server. 3610 * 3611 * There is no remove command functionality. Do a $(LREF clearCommands) and 3612 * set the needed commands instead. 3613 * 3614 * Example: 3615 * --- 3616 * import std.net.curl; 3617 * auto client = FTP(); 3618 * client.addCommand("RNFR my_file.txt"); 3619 * client.addCommand("RNTO my_renamed_file.txt"); 3620 * upload("my_file.txt", "ftp.digitalmars.com", client); 3621 * --- 3622 */ 3623 void addCommand(const(char)[] command) 3624 { 3625 p.commands = Curl.curl.slist_append(p.commands, 3626 command.tempCString().buffPtr); 3627 p.curl.set(CurlOption.postquote, p.commands); 3628 } 3629 3630 /// Connection encoding. Defaults to ISO-8859-1. 3631 @property void encoding(string name) 3632 { 3633 p.encoding = name; 3634 } 3635 3636 /// ditto 3637 @property string encoding() 3638 { 3639 return p.encoding; 3640 } 3641 3642 /** 3643 The content length in bytes of the ftp data. 3644 */ 3645 @property void contentLength(ulong len) 3646 { 3647 import std.conv : to; 3648 p.curl.set(CurlOption.infilesize_large, to!curl_off_t(len)); 3649 } 3650 3651 /** 3652 * Get various timings defined in $(REF CurlInfo, etc, c, curl). 3653 * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok). 3654 * 3655 * Params: 3656 * timing = one of the timings defined in $(REF CurlInfo, etc, c, curl). 3657 * The values are: 3658 * $(D etc.c.curl.CurlInfo.namelookup_time), 3659 * $(D etc.c.curl.CurlInfo.connect_time), 3660 * $(D etc.c.curl.CurlInfo.pretransfer_time), 3661 * $(D etc.c.curl.CurlInfo.starttransfer_time), 3662 * $(D etc.c.curl.CurlInfo.redirect_time), 3663 * $(D etc.c.curl.CurlInfo.appconnect_time), 3664 * $(D etc.c.curl.CurlInfo.total_time). 3665 * val = the actual value of the inquired timing. 3666 * 3667 * Returns: 3668 * The return code of the operation. The value stored in val 3669 * should be used only if the return value is $(D etc.c.curl.CurlInfo.ok). 3670 * 3671 * Example: 3672 * --- 3673 * import std.net.curl; 3674 * import etc.c.curl : CurlError, CurlInfo; 3675 * 3676 * auto client = FTP(); 3677 * client.addCommand("RNFR my_file.txt"); 3678 * client.addCommand("RNTO my_renamed_file.txt"); 3679 * upload("my_file.txt", "ftp.digitalmars.com", client); 3680 * 3681 * double val; 3682 * CurlCode code; 3683 * 3684 * code = http.getTiming(CurlInfo.namelookup_time, val); 3685 * assert(code == CurlError.ok); 3686 * --- 3687 */ 3688 CurlCode getTiming(CurlInfo timing, ref double val) 3689 { 3690 return p.curl.getTiming(timing, val); 3691 } 3692 3693 @system unittest 3694 { 3695 auto client = FTP(); 3696 3697 double val; 3698 CurlCode code; 3699 3700 code = client.getTiming(CurlInfo.total_time, val); 3701 assert(code == CurlError.ok); 3702 code = client.getTiming(CurlInfo.namelookup_time, val); 3703 assert(code == CurlError.ok); 3704 code = client.getTiming(CurlInfo.connect_time, val); 3705 assert(code == CurlError.ok); 3706 code = client.getTiming(CurlInfo.pretransfer_time, val); 3707 assert(code == CurlError.ok); 3708 code = client.getTiming(CurlInfo.starttransfer_time, val); 3709 assert(code == CurlError.ok); 3710 code = client.getTiming(CurlInfo.redirect_time, val); 3711 assert(code == CurlError.ok); 3712 code = client.getTiming(CurlInfo.appconnect_time, val); 3713 assert(code == CurlError.ok); 3714 } 3715 } 3716 3717 /** 3718 * Basic SMTP protocol support. 3719 * 3720 * Example: 3721 * --- 3722 * import std.net.curl; 3723 * 3724 * // Send an email with SMTPS 3725 * auto smtp = SMTP("smtps://smtp.gmail.com"); 3726 * smtp.setAuthentication("from.addr@gmail.com", "password"); 3727 * smtp.mailTo = ["<to.addr@gmail.com>"]; 3728 * smtp.mailFrom = "<from.addr@gmail.com>"; 3729 * smtp.message = "Example Message"; 3730 * smtp.perform(); 3731 * --- 3732 * 3733 * See_Also: $(HTTP www.ietf.org/rfc/rfc2821.txt, RFC2821) 3734 */ 3735 struct SMTP 3736 { 3737 mixin Protocol; 3738 3739 private struct Impl 3740 { 3741 ~this() 3742 { 3743 if (curl.handle !is null) // work around RefCounted/emplace bug 3744 curl.shutdown(); 3745 } 3746 Curl curl; 3747 3748 @property void message(string msg) 3749 { 3750 import std.algorithm.comparison : min; 3751 3752 auto _message = msg; 3753 /** 3754 This delegate reads the message text and copies it. 3755 */ 3756 curl.onSend = delegate size_t(void[] data) 3757 { 3758 if (!msg.length) return 0; 3759 size_t to_copy = min(data.length, _message.length); 3760 data[0 .. to_copy] = (cast(void[])_message)[0 .. to_copy]; 3761 _message = _message[to_copy..$]; 3762 return to_copy; 3763 }; 3764 } 3765 } 3766 3767 private RefCounted!Impl p; 3768 3769 /** 3770 Sets to the URL of the SMTP server. 3771 */ 3772 static SMTP opCall(const(char)[] url) 3773 { 3774 SMTP smtp; 3775 smtp.initialize(); 3776 smtp.url = url; 3777 return smtp; 3778 } 3779 3780 /// 3781 static SMTP opCall() 3782 { 3783 SMTP smtp; 3784 smtp.initialize(); 3785 return smtp; 3786 } 3787 3788 /+ TODO: The other structs have this function. 3789 SMTP dup() 3790 { 3791 SMTP copy = SMTP(); 3792 copy.initialize(); 3793 copy.p.encoding = p.encoding; 3794 copy.p.curl = p.curl.dup(); 3795 curl_slist* cur = p.commands; 3796 curl_slist* newlist = null; 3797 while (cur) 3798 { 3799 newlist = Curl.curl.slist_append(newlist, cur.data); 3800 cur = cur.next; 3801 } 3802 copy.p.commands = newlist; 3803 copy.p.curl.set(CurlOption.postquote, copy.p.commands); 3804 copy.dataTimeout = _defaultDataTimeout; 3805 return copy; 3806 } 3807 +/ 3808 3809 /** 3810 Performs the request as configured. 3811 Params: 3812 throwOnError = whether to throw an exception or return a CurlCode on error 3813 */ 3814 CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) 3815 { 3816 return p.curl.perform(throwOnError); 3817 } 3818 3819 /// The URL to specify the location of the resource. 3820 @property void url(const(char)[] url) 3821 { 3822 import std.algorithm.searching : startsWith; 3823 import std.uni : toLower; 3824 3825 auto lowered = url.toLower(); 3826 3827 if (lowered.startsWith("smtps://")) 3828 { 3829 p.curl.set(CurlOption.use_ssl, CurlUseSSL.all); 3830 } 3831 else 3832 { 3833 enforce!CurlException(lowered.startsWith("smtp://"), 3834 "The url must be for the smtp protocol."); 3835 } 3836 p.curl.set(CurlOption.url, url); 3837 } 3838 3839 private void initialize() 3840 { 3841 p.curl.initialize(); 3842 p.curl.set(CurlOption.upload, 1L); 3843 dataTimeout = _defaultDataTimeout; 3844 verifyPeer = true; 3845 verifyHost = true; 3846 } 3847 3848 // This is a workaround for mixed in content not having its 3849 // docs mixed in. 3850 version (StdDdoc) 3851 { 3852 /// Value to return from $(D onSend)/$(D onReceive) delegates in order to 3853 /// pause a request 3854 alias requestPause = CurlReadFunc.pause; 3855 3856 /// Value to return from onSend delegate in order to abort a request 3857 alias requestAbort = CurlReadFunc.abort; 3858 3859 /** 3860 True if the instance is stopped. A stopped instance is not usable. 3861 */ 3862 @property bool isStopped(); 3863 3864 /// Stop and invalidate this instance. 3865 void shutdown(); 3866 3867 /** Set verbose. 3868 This will print request information to stderr. 3869 */ 3870 @property void verbose(bool on); 3871 3872 // Connection settings 3873 3874 /// Set timeout for activity on connection. 3875 @property void dataTimeout(Duration d); 3876 3877 /** Set maximum time an operation is allowed to take. 3878 This includes dns resolution, connecting, data transfer, etc. 3879 */ 3880 @property void operationTimeout(Duration d); 3881 3882 /// Set timeout for connecting. 3883 @property void connectTimeout(Duration d); 3884 3885 // Network settings 3886 3887 /** Proxy 3888 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) 3889 */ 3890 @property void proxy(const(char)[] host); 3891 3892 /** Proxy port 3893 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) 3894 */ 3895 @property void proxyPort(ushort port); 3896 3897 /// Type of proxy 3898 alias CurlProxy = etc.c.curl.CurlProxy; 3899 3900 /** Proxy type 3901 * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) 3902 */ 3903 @property void proxyType(CurlProxy type); 3904 3905 /// DNS lookup timeout. 3906 @property void dnsTimeout(Duration d); 3907 3908 /** 3909 * The network interface to use in form of the the IP of the interface. 3910 * 3911 * Example: 3912 * ---- 3913 * theprotocol.netInterface = "192.168.1.32"; 3914 * theprotocol.netInterface = [ 192, 168, 1, 32 ]; 3915 * ---- 3916 * 3917 * See: $(REF InternetAddress, std,socket) 3918 */ 3919 @property void netInterface(const(char)[] i); 3920 3921 /// ditto 3922 @property void netInterface(const(ubyte)[4] i); 3923 3924 /// ditto 3925 @property void netInterface(InternetAddress i); 3926 3927 /** 3928 Set the local outgoing port to use. 3929 Params: 3930 port = the first outgoing port number to try and use 3931 */ 3932 @property void localPort(ushort port); 3933 3934 /** 3935 Set the local outgoing port range to use. 3936 This can be used together with the localPort property. 3937 Params: 3938 range = if the first port is occupied then try this many 3939 port number forwards 3940 */ 3941 @property void localPortRange(ushort range); 3942 3943 /** Set the tcp no-delay socket option on or off. 3944 See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) 3945 */ 3946 @property void tcpNoDelay(bool on); 3947 3948 // Authentication settings 3949 3950 /** 3951 Set the user name, password and optionally domain for authentication 3952 purposes. 3953 3954 Some protocols may need authentication in some cases. Use this 3955 function to provide credentials. 3956 3957 Params: 3958 username = the username 3959 password = the password 3960 domain = used for NTLM authentication only and is set to the NTLM domain 3961 name 3962 */ 3963 void setAuthentication(const(char)[] username, const(char)[] password, 3964 const(char)[] domain = ""); 3965 3966 /** 3967 Set the user name and password for proxy authentication. 3968 3969 Params: 3970 username = the username 3971 password = the password 3972 */ 3973 void setProxyAuthentication(const(char)[] username, const(char)[] password); 3974 3975 /** 3976 * The event handler that gets called when data is needed for sending. The 3977 * length of the $(D void[]) specifies the maximum number of bytes that can 3978 * be sent. 3979 * 3980 * Returns: 3981 * The callback returns the number of elements in the buffer that have been 3982 * filled and are ready to send. 3983 * The special value $(D .abortRequest) can be returned in order to abort the 3984 * current request. 3985 * The special value $(D .pauseRequest) can be returned in order to pause the 3986 * current request. 3987 */ 3988 @property void onSend(size_t delegate(void[]) callback); 3989 3990 /** 3991 * The event handler that receives incoming data. Be sure to copy the 3992 * incoming ubyte[] since it is not guaranteed to be valid after the 3993 * callback returns. 3994 * 3995 * Returns: 3996 * The callback returns the incoming bytes read. If not the entire array is 3997 * the request will abort. 3998 * The special value .pauseRequest can be returned in order to pause the 3999 * current request. 4000 */ 4001 @property void onReceive(size_t delegate(ubyte[]) callback); 4002 4003 /** 4004 * The event handler that gets called to inform of upload/download progress. 4005 * 4006 * Callback_parameters: 4007 * $(CALLBACK_PARAMS) 4008 * 4009 * Callback_returns: 4010 * Return 0 from the callback to signal success, return non-zero to 4011 * abort transfer. 4012 */ 4013 @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, 4014 size_t ulTotal, size_t ulNow) callback); 4015 } 4016 4017 /** 4018 Setter for the sender's email address. 4019 */ 4020 @property void mailFrom()(const(char)[] sender) 4021 { 4022 assert(!sender.empty, "Sender must not be empty"); 4023 p.curl.set(CurlOption.mail_from, sender); 4024 } 4025 4026 /** 4027 Setter for the recipient email addresses. 4028 */ 4029 void mailTo()(const(char)[][] recipients...) 4030 { 4031 assert(!recipients.empty, "Recipient must not be empty"); 4032 curl_slist* recipients_list = null; 4033 foreach (recipient; recipients) 4034 { 4035 recipients_list = 4036 Curl.curl.slist_append(recipients_list, 4037 recipient.tempCString().buffPtr); 4038 } 4039 p.curl.set(CurlOption.mail_rcpt, recipients_list); 4040 } 4041 4042 /** 4043 Sets the message body text. 4044 */ 4045 4046 @property void message(string msg) 4047 { 4048 p.message = msg; 4049 } 4050 } 4051 4052 /++ 4053 Exception thrown on errors in std.net.curl functions. 4054 +/ 4055 class CurlException : Exception 4056 { 4057 /++ 4058 Params: 4059 msg = The message for the exception. 4060 file = The file where the exception occurred. 4061 line = The line number where the exception occurred. 4062 next = The previous exception in the chain of exceptions, if any. 4063 +/ 4064 @safe pure nothrow 4065 this(string msg, 4066 string file = __FILE__, 4067 size_t line = __LINE__, 4068 Throwable next = null) 4069 { 4070 super(msg, file, line, next); 4071 } 4072 } 4073 4074 /++ 4075 Exception thrown on timeout errors in std.net.curl functions. 4076 +/ 4077 class CurlTimeoutException : CurlException 4078 { 4079 /++ 4080 Params: 4081 msg = The message for the exception. 4082 file = The file where the exception occurred. 4083 line = The line number where the exception occurred. 4084 next = The previous exception in the chain of exceptions, if any. 4085 +/ 4086 @safe pure nothrow 4087 this(string msg, 4088 string file = __FILE__, 4089 size_t line = __LINE__, 4090 Throwable next = null) 4091 { 4092 super(msg, file, line, next); 4093 } 4094 } 4095 4096 /++ 4097 Exception thrown on HTTP request failures, e.g. 404 Not Found. 4098 +/ 4099 class HTTPStatusException : CurlException 4100 { 4101 /++ 4102 Params: 4103 status = The HTTP status code. 4104 msg = The message for the exception. 4105 file = The file where the exception occurred. 4106 line = The line number where the exception occurred. 4107 next = The previous exception in the chain of exceptions, if any. 4108 +/ 4109 @safe pure nothrow 4110 this(int status, 4111 string msg, 4112 string file = __FILE__, 4113 size_t line = __LINE__, 4114 Throwable next = null) 4115 { 4116 super(msg, file, line, next); 4117 this.status = status; 4118 } 4119 4120 immutable int status; /// The HTTP status code 4121 } 4122 4123 /// Equal to $(REF CURLcode, etc,c,curl) 4124 alias CurlCode = CURLcode; 4125 4126 import std.typecons : Flag, Yes, No; 4127 /// Flag to specify whether or not an exception is thrown on error. 4128 alias ThrowOnError = Flag!"throwOnError"; 4129 4130 private struct CurlAPI 4131 { 4132 static struct API 4133 { 4134 extern(C): 4135 import core.stdc.config : c_long; 4136 CURLcode function(c_long flags) global_init; 4137 void function() global_cleanup; 4138 curl_version_info_data * function(CURLversion) version_info; 4139 CURL* function() easy_init; 4140 CURLcode function(CURL *curl, CURLoption option,...) easy_setopt; 4141 CURLcode function(CURL *curl) easy_perform; 4142 CURLcode function(CURL *curl, CURLINFO info,...) easy_getinfo; 4143 CURL* function(CURL *curl) easy_duphandle; 4144 char* function(CURLcode) easy_strerror; 4145 CURLcode function(CURL *handle, int bitmask) easy_pause; 4146 void function(CURL *curl) easy_cleanup; 4147 curl_slist* function(curl_slist *, char *) slist_append; 4148 void function(curl_slist *) slist_free_all; 4149 } 4150 __gshared API _api; 4151 __gshared void* _handle; 4152 4153 static ref API instance() @property 4154 { 4155 import std.concurrency : initOnce; 4156 initOnce!_handle(loadAPI()); 4157 return _api; 4158 } 4159 4160 static void* loadAPI() 4161 { 4162 version (Posix) 4163 { 4164 import core.sys.posix.dlfcn : dlsym, dlopen, dlclose, RTLD_LAZY; 4165 alias loadSym = dlsym; 4166 } 4167 else version (Windows) 4168 { 4169 import core.sys.windows.windows : GetProcAddress, GetModuleHandleA, 4170 LoadLibraryA; 4171 alias loadSym = GetProcAddress; 4172 } 4173 else 4174 static assert(0, "unimplemented"); 4175 4176 void* handle; 4177 version (Posix) 4178 handle = dlopen(null, RTLD_LAZY); 4179 else version (Windows) 4180 handle = GetModuleHandleA(null); 4181 assert(handle !is null); 4182 4183 // try to load curl from the executable to allow static linking 4184 if (loadSym(handle, "curl_global_init") is null) 4185 { 4186 import std.format : format; 4187 version (Posix) 4188 dlclose(handle); 4189 4190 version (OSX) 4191 static immutable names = ["libcurl.4.dylib"]; 4192 else version (Posix) 4193 { 4194 static immutable names = ["libcurl.so", "libcurl.so.4", 4195 "libcurl-gnutls.so.4", "libcurl-nss.so.4", "libcurl.so.3"]; 4196 } 4197 else version (Windows) 4198 static immutable names = ["libcurl.dll", "curl.dll"]; 4199 4200 foreach (name; names) 4201 { 4202 version (Posix) 4203 handle = dlopen(name.ptr, RTLD_LAZY); 4204 else version (Windows) 4205 handle = LoadLibraryA(name.ptr); 4206 if (handle !is null) break; 4207 } 4208 4209 enforce!CurlException(handle !is null, "Failed to load curl, tried %(%s, %).".format(names)); 4210 } 4211 4212 foreach (i, FP; typeof(API.tupleof)) 4213 { 4214 enum name = __traits(identifier, _api.tupleof[i]); 4215 auto p = enforce!CurlException(loadSym(handle, "curl_"~name), 4216 "Couldn't load curl_"~name~" from libcurl."); 4217 _api.tupleof[i] = cast(FP) p; 4218 } 4219 4220 enforce!CurlException(!_api.global_init(CurlGlobal.all), 4221 "Failed to initialize libcurl"); 4222 4223 static extern(C) void cleanup() 4224 { 4225 if (_handle is null) return; 4226 _api.global_cleanup(); 4227 version (Posix) 4228 { 4229 import core.sys.posix.dlfcn : dlclose; 4230 dlclose(_handle); 4231 } 4232 else version (Windows) 4233 { 4234 import core.sys.windows.windows : FreeLibrary; 4235 FreeLibrary(_handle); 4236 } 4237 else 4238 static assert(0, "unimplemented"); 4239 _api = API.init; 4240 _handle = null; 4241 } 4242 4243 import core.stdc.stdlib : atexit; 4244 atexit(&cleanup); 4245 4246 return handle; 4247 } 4248 } 4249 4250 /** 4251 Wrapper to provide a better interface to libcurl than using the plain C API. 4252 It is recommended to use the $(D HTTP)/$(D FTP) etc. structs instead unless 4253 raw access to libcurl is needed. 4254 4255 Warning: This struct uses interior pointers for callbacks. Only allocate it 4256 on the stack if you never move or copy it. This also means passing by reference 4257 when passing Curl to other functions. Otherwise always allocate on 4258 the heap. 4259 */ 4260 struct Curl 4261 { 4262 alias OutData = void[]; 4263 alias InData = ubyte[]; 4264 private bool _stopped; 4265 4266 private static auto ref curl() @property { return CurlAPI.instance; } 4267 4268 // A handle should not be used by two threads simultaneously 4269 private CURL* handle; 4270 4271 // May also return $(D CURL_READFUNC_ABORT) or $(D CURL_READFUNC_PAUSE) 4272 private size_t delegate(OutData) _onSend; 4273 private size_t delegate(InData) _onReceive; 4274 private void delegate(in char[]) _onReceiveHeader; 4275 private CurlSeek delegate(long,CurlSeekPos) _onSeek; 4276 private int delegate(curl_socket_t,CurlSockType) _onSocketOption; 4277 private int delegate(size_t dltotal, size_t dlnow, 4278 size_t ultotal, size_t ulnow) _onProgress; 4279 4280 alias requestPause = CurlReadFunc.pause; 4281 alias requestAbort = CurlReadFunc.abort; 4282 4283 /** 4284 Initialize the instance by creating a working curl handle. 4285 */ 4286 void initialize() 4287 { 4288 enforce!CurlException(!handle, "Curl instance already initialized"); 4289 handle = curl.easy_init(); 4290 enforce!CurlException(handle, "Curl instance couldn't be initialized"); 4291 _stopped = false; 4292 set(CurlOption.nosignal, 1); 4293 } 4294 4295 /// 4296 @property bool stopped() const 4297 { 4298 return _stopped; 4299 } 4300 4301 /** 4302 Duplicate this handle. 4303 4304 The new handle will have all options set as the one it was duplicated 4305 from. An exception to this is that all options that cannot be shared 4306 across threads are reset thereby making it safe to use the duplicate 4307 in a new thread. 4308 */ 4309 Curl dup() 4310 { 4311 Curl copy; 4312 copy.handle = curl.easy_duphandle(handle); 4313 copy._stopped = false; 4314 4315 with (CurlOption) { 4316 auto tt = AliasSeq!(file, writefunction, writeheader, 4317 headerfunction, infile, readfunction, ioctldata, ioctlfunction, 4318 seekdata, seekfunction, sockoptdata, sockoptfunction, 4319 opensocketdata, opensocketfunction, progressdata, 4320 progressfunction, debugdata, debugfunction, interleavedata, 4321 interleavefunction, chunk_data, chunk_bgn_function, 4322 chunk_end_function, fnmatch_data, fnmatch_function, cookiejar, postfields); 4323 4324 foreach (option; tt) 4325 copy.clear(option); 4326 } 4327 4328 // The options are only supported by libcurl when it has been built 4329 // against certain versions of OpenSSL - if your libcurl uses an old 4330 // OpenSSL, or uses an entirely different SSL engine, attempting to 4331 // clear these normally will raise an exception 4332 copy.clearIfSupported(CurlOption.ssl_ctx_function); 4333 copy.clearIfSupported(CurlOption.ssh_keydata); 4334 4335 // Enable for curl version > 7.21.7 4336 static if (LIBCURL_VERSION_MAJOR >= 7 && 4337 LIBCURL_VERSION_MINOR >= 21 && 4338 LIBCURL_VERSION_PATCH >= 7) 4339 { 4340 copy.clear(CurlOption.closesocketdata); 4341 copy.clear(CurlOption.closesocketfunction); 4342 } 4343 4344 copy.set(CurlOption.nosignal, 1); 4345 4346 // copy.clear(CurlOption.ssl_ctx_data); Let ssl function be shared 4347 // copy.clear(CurlOption.ssh_keyfunction); Let key function be shared 4348 4349 /* 4350 Allow sharing of conv functions 4351 copy.clear(CurlOption.conv_to_network_function); 4352 copy.clear(CurlOption.conv_from_network_function); 4353 copy.clear(CurlOption.conv_from_utf8_function); 4354 */ 4355 4356 return copy; 4357 } 4358 4359 private void _check(CurlCode code) 4360 { 4361 enforce!CurlTimeoutException(code != CurlError.operation_timedout, 4362 errorString(code)); 4363 4364 enforce!CurlException(code == CurlError.ok, 4365 errorString(code)); 4366 } 4367 4368 private string errorString(CurlCode code) 4369 { 4370 import core.stdc.string : strlen; 4371 import std.format : format; 4372 4373 auto msgZ = curl.easy_strerror(code); 4374 // doing the following (instead of just using std.conv.to!string) avoids 1 allocation 4375 return format("%s on handle %s", msgZ[0 .. strlen(msgZ)], handle); 4376 } 4377 4378 private void throwOnStopped(string message = null) 4379 { 4380 auto def = "Curl instance called after being cleaned up"; 4381 enforce!CurlException(!stopped, 4382 message == null ? def : message); 4383 } 4384 4385 /** 4386 Stop and invalidate this curl instance. 4387 Warning: Do not call this from inside a callback handler e.g. $(D onReceive). 4388 */ 4389 void shutdown() 4390 { 4391 throwOnStopped(); 4392 _stopped = true; 4393 curl.easy_cleanup(this.handle); 4394 this.handle = null; 4395 } 4396 4397 /** 4398 Pausing and continuing transfers. 4399 */ 4400 void pause(bool sendingPaused, bool receivingPaused) 4401 { 4402 throwOnStopped(); 4403 _check(curl.easy_pause(this.handle, 4404 (sendingPaused ? CurlPause.send_cont : CurlPause.send) | 4405 (receivingPaused ? CurlPause.recv_cont : CurlPause.recv))); 4406 } 4407 4408 /** 4409 Set a string curl option. 4410 Params: 4411 option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation 4412 value = The string 4413 */ 4414 void set(CurlOption option, const(char)[] value) 4415 { 4416 throwOnStopped(); 4417 _check(curl.easy_setopt(this.handle, option, value.tempCString().buffPtr)); 4418 } 4419 4420 /** 4421 Set a long curl option. 4422 Params: 4423 option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation 4424 value = The long 4425 */ 4426 void set(CurlOption option, long value) 4427 { 4428 throwOnStopped(); 4429 _check(curl.easy_setopt(this.handle, option, value)); 4430 } 4431 4432 /** 4433 Set a void* curl option. 4434 Params: 4435 option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation 4436 value = The pointer 4437 */ 4438 void set(CurlOption option, void* value) 4439 { 4440 throwOnStopped(); 4441 _check(curl.easy_setopt(this.handle, option, value)); 4442 } 4443 4444 /** 4445 Clear a pointer option. 4446 Params: 4447 option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation 4448 */ 4449 void clear(CurlOption option) 4450 { 4451 throwOnStopped(); 4452 _check(curl.easy_setopt(this.handle, option, null)); 4453 } 4454 4455 /** 4456 Clear a pointer option. Does not raise an exception if the underlying 4457 libcurl does not support the option. Use sparingly. 4458 Params: 4459 option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation 4460 */ 4461 void clearIfSupported(CurlOption option) 4462 { 4463 throwOnStopped(); 4464 auto rval = curl.easy_setopt(this.handle, option, null); 4465 if (rval != CurlError.unknown_option && rval != CurlError.not_built_in) 4466 _check(rval); 4467 } 4468 4469 /** 4470 perform the curl request by doing the HTTP,FTP etc. as it has 4471 been setup beforehand. 4472 4473 Params: 4474 throwOnError = whether to throw an exception or return a CurlCode on error 4475 */ 4476 CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) 4477 { 4478 throwOnStopped(); 4479 CurlCode code = curl.easy_perform(this.handle); 4480 if (throwOnError) 4481 _check(code); 4482 return code; 4483 } 4484 4485 /** 4486 Get the various timings like name lookup time, total time, connect time etc. 4487 The timed category is passed through the timing parameter while the timing 4488 value is stored at val. The value is usable only if res is equal to 4489 $(D etc.c.curl.CurlError.ok). 4490 */ 4491 CurlCode getTiming(CurlInfo timing, ref double val) 4492 { 4493 CurlCode code; 4494 code = curl.easy_getinfo(handle, timing, &val); 4495 return code; 4496 } 4497 4498 /** 4499 * The event handler that receives incoming data. 4500 * 4501 * Params: 4502 * callback = the callback that receives the $(D ubyte[]) data. 4503 * Be sure to copy the incoming data and not store 4504 * a slice. 4505 * 4506 * Returns: 4507 * The callback returns the incoming bytes read. If not the entire array is 4508 * the request will abort. 4509 * The special value HTTP.pauseRequest can be returned in order to pause the 4510 * current request. 4511 * 4512 * Example: 4513 * ---- 4514 * import std.net.curl, std.stdio; 4515 * Curl curl; 4516 * curl.initialize(); 4517 * curl.set(CurlOption.url, "http://dlang.org"); 4518 * curl.onReceive = (ubyte[] data) { writeln("Got data", to!(const(char)[])(data)); return data.length;}; 4519 * curl.perform(); 4520 * ---- 4521 */ 4522 @property void onReceive(size_t delegate(InData) callback) 4523 { 4524 _onReceive = (InData id) 4525 { 4526 throwOnStopped("Receive callback called on cleaned up Curl instance"); 4527 return callback(id); 4528 }; 4529 set(CurlOption.file, cast(void*) &this); 4530 set(CurlOption.writefunction, cast(void*) &Curl._receiveCallback); 4531 } 4532 4533 /** 4534 * The event handler that receives incoming headers for protocols 4535 * that uses headers. 4536 * 4537 * Params: 4538 * callback = the callback that receives the header string. 4539 * Make sure the callback copies the incoming params if 4540 * it needs to store it because they are references into 4541 * the backend and may very likely change. 4542 * 4543 * Example: 4544 * ---- 4545 * import std.net.curl, std.stdio; 4546 * Curl curl; 4547 * curl.initialize(); 4548 * curl.set(CurlOption.url, "http://dlang.org"); 4549 * curl.onReceiveHeader = (in char[] header) { writeln(header); }; 4550 * curl.perform(); 4551 * ---- 4552 */ 4553 @property void onReceiveHeader(void delegate(in char[]) callback) 4554 { 4555 _onReceiveHeader = (in char[] od) 4556 { 4557 throwOnStopped("Receive header callback called on "~ 4558 "cleaned up Curl instance"); 4559 callback(od); 4560 }; 4561 set(CurlOption.writeheader, cast(void*) &this); 4562 set(CurlOption.headerfunction, 4563 cast(void*) &Curl._receiveHeaderCallback); 4564 } 4565 4566 /** 4567 * The event handler that gets called when data is needed for sending. 4568 * 4569 * Params: 4570 * callback = the callback that has a $(D void[]) buffer to be filled 4571 * 4572 * Returns: 4573 * The callback returns the number of elements in the buffer that have been 4574 * filled and are ready to send. 4575 * The special value $(D Curl.abortRequest) can be returned in 4576 * order to abort the current request. 4577 * The special value $(D Curl.pauseRequest) can be returned in order to 4578 * pause the current request. 4579 * 4580 * Example: 4581 * ---- 4582 * import std.net.curl; 4583 * Curl curl; 4584 * curl.initialize(); 4585 * curl.set(CurlOption.url, "http://dlang.org"); 4586 * 4587 * string msg = "Hello world"; 4588 * curl.onSend = (void[] data) 4589 * { 4590 * auto m = cast(void[]) msg; 4591 * size_t length = m.length > data.length ? data.length : m.length; 4592 * if (length == 0) return 0; 4593 * data[0 .. length] = m[0 .. length]; 4594 * msg = msg[length..$]; 4595 * return length; 4596 * }; 4597 * curl.perform(); 4598 * ---- 4599 */ 4600 @property void onSend(size_t delegate(OutData) callback) 4601 { 4602 _onSend = (OutData od) 4603 { 4604 throwOnStopped("Send callback called on cleaned up Curl instance"); 4605 return callback(od); 4606 }; 4607 set(CurlOption.infile, cast(void*) &this); 4608 set(CurlOption.readfunction, cast(void*) &Curl._sendCallback); 4609 } 4610 4611 /** 4612 * The event handler that gets called when the curl backend needs to seek 4613 * the data to be sent. 4614 * 4615 * Params: 4616 * callback = the callback that receives a seek offset and a seek position 4617 * $(REF CurlSeekPos, etc,c,curl) 4618 * 4619 * Returns: 4620 * The callback returns the success state of the seeking 4621 * $(REF CurlSeek, etc,c,curl) 4622 * 4623 * Example: 4624 * ---- 4625 * import std.net.curl; 4626 * Curl curl; 4627 * curl.initialize(); 4628 * curl.set(CurlOption.url, "http://dlang.org"); 4629 * curl.onSeek = (long p, CurlSeekPos sp) 4630 * { 4631 * return CurlSeek.cantseek; 4632 * }; 4633 * curl.perform(); 4634 * ---- 4635 */ 4636 @property void onSeek(CurlSeek delegate(long, CurlSeekPos) callback) 4637 { 4638 _onSeek = (long ofs, CurlSeekPos sp) 4639 { 4640 throwOnStopped("Seek callback called on cleaned up Curl instance"); 4641 return callback(ofs, sp); 4642 }; 4643 set(CurlOption.seekdata, cast(void*) &this); 4644 set(CurlOption.seekfunction, cast(void*) &Curl._seekCallback); 4645 } 4646 4647 /** 4648 * The event handler that gets called when the net socket has been created 4649 * but a $(D connect()) call has not yet been done. This makes it possible to set 4650 * misc. socket options. 4651 * 4652 * Params: 4653 * callback = the callback that receives the socket and socket type 4654 * $(REF CurlSockType, etc,c,curl) 4655 * 4656 * Returns: 4657 * Return 0 from the callback to signal success, return 1 to signal error 4658 * and make curl close the socket 4659 * 4660 * Example: 4661 * ---- 4662 * import std.net.curl; 4663 * Curl curl; 4664 * curl.initialize(); 4665 * curl.set(CurlOption.url, "http://dlang.org"); 4666 * curl.onSocketOption = delegate int(curl_socket_t s, CurlSockType t) { /+ do stuff +/ }; 4667 * curl.perform(); 4668 * ---- 4669 */ 4670 @property void onSocketOption(int delegate(curl_socket_t, 4671 CurlSockType) callback) 4672 { 4673 _onSocketOption = (curl_socket_t sock, CurlSockType st) 4674 { 4675 throwOnStopped("Socket option callback called on "~ 4676 "cleaned up Curl instance"); 4677 return callback(sock, st); 4678 }; 4679 set(CurlOption.sockoptdata, cast(void*) &this); 4680 set(CurlOption.sockoptfunction, 4681 cast(void*) &Curl._socketOptionCallback); 4682 } 4683 4684 /** 4685 * The event handler that gets called to inform of upload/download progress. 4686 * 4687 * Params: 4688 * callback = the callback that receives the (total bytes to download, 4689 * currently downloaded bytes, total bytes to upload, currently uploaded 4690 * bytes). 4691 * 4692 * Returns: 4693 * Return 0 from the callback to signal success, return non-zero to abort 4694 * transfer 4695 * 4696 * Example: 4697 * ---- 4698 * import std.net.curl; 4699 * Curl curl; 4700 * curl.initialize(); 4701 * curl.set(CurlOption.url, "http://dlang.org"); 4702 * curl.onProgress = delegate int(size_t dltotal, size_t dlnow, size_t ultotal, size_t uln) 4703 * { 4704 * writeln("Progress: downloaded bytes ", dlnow, " of ", dltotal); 4705 * writeln("Progress: uploaded bytes ", ulnow, " of ", ultotal); 4706 * curl.perform(); 4707 * }; 4708 * ---- 4709 */ 4710 @property void onProgress(int delegate(size_t dlTotal, 4711 size_t dlNow, 4712 size_t ulTotal, 4713 size_t ulNow) callback) 4714 { 4715 _onProgress = (size_t dlt, size_t dln, size_t ult, size_t uln) 4716 { 4717 throwOnStopped("Progress callback called on cleaned "~ 4718 "up Curl instance"); 4719 return callback(dlt, dln, ult, uln); 4720 }; 4721 set(CurlOption.noprogress, 0); 4722 set(CurlOption.progressdata, cast(void*) &this); 4723 set(CurlOption.progressfunction, cast(void*) &Curl._progressCallback); 4724 } 4725 4726 // Internal C callbacks to register with libcurl 4727 extern (C) private static 4728 size_t _receiveCallback(const char* str, 4729 size_t size, size_t nmemb, void* ptr) 4730 { 4731 auto b = cast(Curl*) ptr; 4732 if (b._onReceive != null) 4733 return b._onReceive(cast(InData)(str[0 .. size*nmemb])); 4734 return size*nmemb; 4735 } 4736 4737 extern (C) private static 4738 size_t _receiveHeaderCallback(const char* str, 4739 size_t size, size_t nmemb, void* ptr) 4740 { 4741 import std.string : chomp; 4742 4743 auto b = cast(Curl*) ptr; 4744 auto s = str[0 .. size*nmemb].chomp(); 4745 if (b._onReceiveHeader != null) 4746 b._onReceiveHeader(s); 4747 4748 return size*nmemb; 4749 } 4750 4751 extern (C) private static 4752 size_t _sendCallback(char *str, size_t size, size_t nmemb, void *ptr) 4753 { 4754 Curl* b = cast(Curl*) ptr; 4755 auto a = cast(void[]) str[0 .. size*nmemb]; 4756 if (b._onSend == null) 4757 return 0; 4758 return b._onSend(a); 4759 } 4760 4761 extern (C) private static 4762 int _seekCallback(void *ptr, curl_off_t offset, int origin) 4763 { 4764 auto b = cast(Curl*) ptr; 4765 if (b._onSeek == null) 4766 return CurlSeek.cantseek; 4767 4768 // origin: CurlSeekPos.set/current/end 4769 // return: CurlSeek.ok/fail/cantseek 4770 return b._onSeek(cast(long) offset, cast(CurlSeekPos) origin); 4771 } 4772 4773 extern (C) private static 4774 int _socketOptionCallback(void *ptr, 4775 curl_socket_t curlfd, curlsocktype purpose) 4776 { 4777 auto b = cast(Curl*) ptr; 4778 if (b._onSocketOption == null) 4779 return 0; 4780 4781 // return: 0 ok, 1 fail 4782 return b._onSocketOption(curlfd, cast(CurlSockType) purpose); 4783 } 4784 4785 extern (C) private static 4786 int _progressCallback(void *ptr, 4787 double dltotal, double dlnow, 4788 double ultotal, double ulnow) 4789 { 4790 auto b = cast(Curl*) ptr; 4791 if (b._onProgress == null) 4792 return 0; 4793 4794 // return: 0 ok, 1 fail 4795 return b._onProgress(cast(size_t) dltotal, cast(size_t) dlnow, 4796 cast(size_t) ultotal, cast(size_t) ulnow); 4797 } 4798 4799 } 4800 4801 // Internal messages send between threads. 4802 // The data is wrapped in this struct in order to ensure that 4803 // other std.concurrency.receive calls does not pick up our messages 4804 // by accident. 4805 private struct CurlMessage(T) 4806 { 4807 public T data; 4808 } 4809 4810 private static CurlMessage!T curlMessage(T)(T data) 4811 { 4812 return CurlMessage!T(data); 4813 } 4814 4815 // Pool of to be used for reusing buffers 4816 private struct Pool(Data) 4817 { 4818 private struct Entry 4819 { 4820 Data data; 4821 Entry* next; 4822 } 4823 private Entry* root; 4824 private Entry* freeList; 4825 4826 @safe @property bool empty() 4827 { 4828 return root == null; 4829 } 4830 4831 @safe nothrow void push(Data d) 4832 { 4833 if (freeList == null) 4834 { 4835 // Allocate new Entry since there is no one 4836 // available in the freeList 4837 freeList = new Entry; 4838 } 4839 freeList.data = d; 4840 Entry* oldroot = root; 4841 root = freeList; 4842 freeList = freeList.next; 4843 root.next = oldroot; 4844 } 4845 4846 @safe Data pop() 4847 { 4848 enforce!Exception(root != null, "pop() called on empty pool"); 4849 auto d = root.data; 4850 auto n = root.next; 4851 root.next = freeList; 4852 freeList = root; 4853 root = n; 4854 return d; 4855 } 4856 } 4857 4858 // Shared function for reading incoming chunks of data and 4859 // sending the to a parent thread 4860 private static size_t _receiveAsyncChunks(ubyte[] data, ref ubyte[] outdata, 4861 Pool!(ubyte[]) freeBuffers, 4862 ref ubyte[] buffer, Tid fromTid, 4863 ref bool aborted) 4864 { 4865 immutable datalen = data.length; 4866 4867 // Copy data to fill active buffer 4868 while (!data.empty) 4869 { 4870 4871 // Make sure a buffer is present 4872 while ( outdata.empty && freeBuffers.empty) 4873 { 4874 // Active buffer is invalid and there are no 4875 // available buffers in the pool. Wait for buffers 4876 // to return from main thread in order to reuse 4877 // them. 4878 receive((immutable(ubyte)[] buf) 4879 { 4880 buffer = cast(ubyte[]) buf; 4881 outdata = buffer[]; 4882 }, 4883 (bool flag) { aborted = true; } 4884 ); 4885 if (aborted) return cast(size_t) 0; 4886 } 4887 if (outdata.empty) 4888 { 4889 buffer = freeBuffers.pop(); 4890 outdata = buffer[]; 4891 } 4892 4893 // Copy data 4894 auto copyBytes = outdata.length < data.length ? 4895 outdata.length : data.length; 4896 4897 outdata[0 .. copyBytes] = data[0 .. copyBytes]; 4898 outdata = outdata[copyBytes..$]; 4899 data = data[copyBytes..$]; 4900 4901 if (outdata.empty) 4902 fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); 4903 } 4904 4905 return datalen; 4906 } 4907 4908 // ditto 4909 private static void _finalizeAsyncChunks(ubyte[] outdata, ref ubyte[] buffer, 4910 Tid fromTid) 4911 { 4912 if (!outdata.empty) 4913 { 4914 // Resize the last buffer 4915 buffer.length = buffer.length - outdata.length; 4916 fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); 4917 } 4918 } 4919 4920 4921 // Shared function for reading incoming lines of data and sending the to a 4922 // parent thread 4923 private static size_t _receiveAsyncLines(Terminator, Unit) 4924 (const(ubyte)[] data, ref EncodingScheme encodingScheme, 4925 bool keepTerminator, Terminator terminator, 4926 ref const(ubyte)[] leftOverBytes, ref bool bufferValid, 4927 ref Pool!(Unit[]) freeBuffers, ref Unit[] buffer, 4928 Tid fromTid, ref bool aborted) 4929 { 4930 import std.format : format; 4931 4932 immutable datalen = data.length; 4933 4934 // Terminator is specified and buffers should be resized as determined by 4935 // the terminator 4936 4937 // Copy data to active buffer until terminator is found. 4938 4939 // Decode as many lines as possible 4940 while (true) 4941 { 4942 4943 // Make sure a buffer is present 4944 while (!bufferValid && freeBuffers.empty) 4945 { 4946 // Active buffer is invalid and there are no available buffers in 4947 // the pool. Wait for buffers to return from main thread in order to 4948 // reuse them. 4949 receive((immutable(Unit)[] buf) 4950 { 4951 buffer = cast(Unit[]) buf; 4952 buffer.length = 0; 4953 buffer.assumeSafeAppend(); 4954 bufferValid = true; 4955 }, 4956 (bool flag) { aborted = true; } 4957 ); 4958 if (aborted) return cast(size_t) 0; 4959 } 4960 if (!bufferValid) 4961 { 4962 buffer = freeBuffers.pop(); 4963 bufferValid = true; 4964 } 4965 4966 // Try to read a line from left over bytes from last onReceive plus the 4967 // newly received bytes. 4968 try 4969 { 4970 if (decodeLineInto(leftOverBytes, data, buffer, 4971 encodingScheme, terminator)) 4972 { 4973 if (keepTerminator) 4974 { 4975 fromTid.send(thisTid, 4976 curlMessage(cast(immutable(Unit)[])buffer)); 4977 } 4978 else 4979 { 4980 static if (isArray!Terminator) 4981 fromTid.send(thisTid, 4982 curlMessage(cast(immutable(Unit)[]) 4983 buffer[0..$-terminator.length])); 4984 else 4985 fromTid.send(thisTid, 4986 curlMessage(cast(immutable(Unit)[]) 4987 buffer[0..$-1])); 4988 } 4989 bufferValid = false; 4990 } 4991 else 4992 { 4993 // Could not decode an entire line. Save 4994 // bytes left in data for next call to 4995 // onReceive. Can be up to a max of 4 bytes. 4996 enforce!CurlException(data.length <= 4, 4997 format( 4998 "Too many bytes left not decoded %s"~ 4999 " > 4. Maybe the charset specified in"~ 5000 " headers does not match "~ 5001 "the actual content downloaded?", 5002 data.length)); 5003 leftOverBytes ~= data; 5004 break; 5005 } 5006 } 5007 catch (CurlException ex) 5008 { 5009 prioritySend(fromTid, cast(immutable(CurlException))ex); 5010 return cast(size_t) 0; 5011 } 5012 } 5013 return datalen; 5014 } 5015 5016 // ditto 5017 private static 5018 void _finalizeAsyncLines(Unit)(bool bufferValid, Unit[] buffer, Tid fromTid) 5019 { 5020 if (bufferValid && buffer.length != 0) 5021 fromTid.send(thisTid, curlMessage(cast(immutable(Unit)[])buffer[0..$])); 5022 } 5023 5024 5025 // Spawn a thread for handling the reading of incoming data in the 5026 // background while the delegate is executing. This will optimize 5027 // throughput by allowing simultaneous input (this struct) and 5028 // output (e.g. AsyncHTTPLineOutputRange). 5029 private static void _spawnAsync(Conn, Unit, Terminator = void)() 5030 { 5031 Tid fromTid = receiveOnly!Tid(); 5032 5033 // Get buffer to read into 5034 Pool!(Unit[]) freeBuffers; // Free list of buffer objects 5035 5036 // Number of bytes filled into active buffer 5037 Unit[] buffer; 5038 bool aborted = false; 5039 5040 EncodingScheme encodingScheme; 5041 static if ( !is(Terminator == void)) 5042 { 5043 // Only lines reading will receive a terminator 5044 const terminator = receiveOnly!Terminator(); 5045 const keepTerminator = receiveOnly!bool(); 5046 5047 // max number of bytes to carry over from an onReceive 5048 // callback. This is 4 because it is the max code units to 5049 // decode a code point in the supported encodings. 5050 auto leftOverBytes = new const(ubyte)[4]; 5051 leftOverBytes.length = 0; 5052 auto bufferValid = false; 5053 } 5054 else 5055 { 5056 Unit[] outdata; 5057 } 5058 5059 // no move semantic available in std.concurrency ie. must use casting. 5060 auto connDup = cast(CURL*) receiveOnly!ulong(); 5061 auto client = Conn(); 5062 client.p.curl.handle = connDup; 5063 5064 // receive a method for both ftp and http but just use it for http 5065 auto method = receiveOnly!(HTTP.Method)(); 5066 5067 client.onReceive = (ubyte[] data) 5068 { 5069 // If no terminator is specified the chunk size is fixed. 5070 static if ( is(Terminator == void) ) 5071 return _receiveAsyncChunks(data, outdata, freeBuffers, buffer, 5072 fromTid, aborted); 5073 else 5074 return _receiveAsyncLines(data, encodingScheme, 5075 keepTerminator, terminator, leftOverBytes, 5076 bufferValid, freeBuffers, buffer, 5077 fromTid, aborted); 5078 }; 5079 5080 static if ( is(Conn == HTTP) ) 5081 { 5082 client.method = method; 5083 // register dummy header handler 5084 client.onReceiveHeader = (in char[] key, in char[] value) 5085 { 5086 if (key == "content-type") 5087 encodingScheme = EncodingScheme.create(client.p.charset); 5088 }; 5089 } 5090 else 5091 { 5092 encodingScheme = EncodingScheme.create(client.encoding); 5093 } 5094 5095 // Start the request 5096 CurlCode code; 5097 try 5098 { 5099 code = client.perform(No.throwOnError); 5100 } 5101 catch (Exception ex) 5102 { 5103 prioritySend(fromTid, cast(immutable(Exception)) ex); 5104 fromTid.send(thisTid, curlMessage(true)); // signal done 5105 return; 5106 } 5107 5108 if (code != CurlError.ok) 5109 { 5110 if (aborted && (code == CurlError.aborted_by_callback || 5111 code == CurlError.write_error)) 5112 { 5113 fromTid.send(thisTid, curlMessage(true)); // signal done 5114 return; 5115 } 5116 prioritySend(fromTid, cast(immutable(CurlException)) 5117 new CurlException(client.p.curl.errorString(code))); 5118 5119 fromTid.send(thisTid, curlMessage(true)); // signal done 5120 return; 5121 } 5122 5123 // Send remaining data that is not a full chunk size 5124 static if ( is(Terminator == void) ) 5125 _finalizeAsyncChunks(outdata, buffer, fromTid); 5126 else 5127 _finalizeAsyncLines(bufferValid, buffer, fromTid); 5128 5129 fromTid.send(thisTid, curlMessage(true)); // signal done 5130 } 5131