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