The OpenD Programming Language

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