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