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