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