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