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 }