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