xref: /netbsd-src/external/gpl3/gcc.old/dist/libphobos/src/std/net/curl.d (revision 4c3eb207d36f67d31994830c0a694161fc1ca39b)
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 
version(unittest)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
isFTPUrl(const (char)[]url)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.
isCurlConn(Conn)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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 {
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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  */
_basicHTTP(T)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  */
_basicFTP(T)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  */
_decodeContent(T)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 
thisSyncLineInputRange1289         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 
emptySyncLineInputRange1298         @property @safe bool empty()
1299         {
1300             return !currentValid;
1301         }
1302 
frontSyncLineInputRange1303         @property @safe Char[] front()
1304         {
1305             enforce!CurlException(currentValid, "Cannot call front() on empty range");
1306             return current;
1307         }
1308 
popFrontSyncLineInputRange1309         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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
thisSyncChunkInputRange1392         this(ubyte[] bytes, size_t chunkSize)
1393         {
1394             this._bytes = bytes;
1395             this.chunkSize = chunkSize;
1396         }
1397 
emptySyncChunkInputRange1398         @property @safe auto empty()
1399         {
1400             return offset == _bytes.length;
1401         }
1402 
frontSyncChunkInputRange1403         @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 
popFrontSyncChunkInputRange1410         @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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
_getForRange(T,Conn)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  */
WorkerThreadProtocol(Unit,alias units)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.
AsyncLineInputRange(Char)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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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 
thisAsyncChunkInputRange1741     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 
foreach(host;[testServer.addr,"http://"~testServer.addr])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  */
_asyncDuplicateConnection(Conn,PostData)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 */
Protocol()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     {
~thisHTTP::Impl2431         ~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 
onReceiveHeaderHTTP::Impl2450         @system @property void onReceiveHeader(void delegate(in char[] key,
2451                                                      in char[] value) callback)
2452         {
2453             import std.algorithm.searching : startsWith;
2454             import std.regex : regex, match;
2455             import std.uni : toLower;
2456 
2457             // Wrap incoming callback in order to separate http status line from
2458             // http headers.  On redirected requests there may be several such
2459             // status lines. The last one is the one recorded.
2460             auto dg = (in char[] header)
2461             {
2462                 import std.utf : UTFException;
2463                 try
2464                 {
2465                     if (header.empty)
2466                     {
2467                         // header delimiter
2468                         return;
2469                     }
2470                     if (header.startsWith("HTTP/"))
2471                     {
2472                         headersIn.clear();
2473                         if (parseStatusLine(header, status))
2474                         {
2475                             if (onReceiveStatusLine != null)
2476                                 onReceiveStatusLine(status);
2477                         }
2478                         return;
2479                     }
2480 
2481                     // Normal http header
2482                     auto m = match(cast(char[]) header, regex("(.*?): (.*)$"));
2483 
2484                     auto fieldName = m.captures[1].toLower().idup;
2485                     if (fieldName == "content-type")
2486                     {
2487                         auto mct = match(cast(char[]) m.captures[2],
2488                                          regex("charset=([^;]*)", "i"));
2489                         if (!mct.empty && mct.captures.length > 1)
2490                             charset = mct.captures[1].idup;
2491                     }
2492 
2493                     if (!m.empty && callback !is null)
2494                         callback(fieldName, m.captures[2]);
2495                     headersIn[fieldName] = m.captures[2].idup;
2496                 }
2497                 catch (UTFException e)
2498                 {
2499                     //munch it - a header should be all ASCII, any "wrong UTF" is broken header
2500                 }
2501             };
2502 
2503             curl.onReceiveHeader = dg;
2504         }
2505     }
2506 
2507     private RefCounted!Impl p;
2508 
2509     /// Parse status line, as received from / generated by cURL.
parseStatusLineHTTP2510     private static bool parseStatusLine(in char[] header, out StatusLine status) @safe
2511     {
2512         import std.conv : to;
2513         import std.regex : regex, match;
2514 
2515         const m = match(header, regex(r"^HTTP/(\d+)(?:\.(\d+))? (\d+)(?: (.*))?$"));
2516         if (m.empty)
2517             return false; // Invalid status line
2518         else
2519         {
2520             status.majorVersion = to!ushort(m.captures[1]);
2521             status.minorVersion = m.captures[2].length ? to!ushort(m.captures[2]) : 0;
2522             status.code = to!ushort(m.captures[3]);
2523             status.reason = m.captures[4].idup;
2524             return true;
2525         }
2526     }
2527 
2528     @safe unittest
2529     {
2530         StatusLine status;
2531         assert(parseStatusLine("HTTP/1.1 200 OK", status)
2532             && status == StatusLine(1, 1, 200, "OK"));
2533         assert(parseStatusLine("HTTP/1.0 304 Not Modified", status)
2534             && status == StatusLine(1, 0, 304, "Not Modified"));
2535         // The HTTP2 protocol is binary; cURL generates this fake text header.
2536         assert(parseStatusLine("HTTP/2 200", status)
2537             && status == StatusLine(2, 0, 200, null));
2538     }
2539 
2540     /** Time condition enumeration as an alias of $(REF CurlTimeCond, etc,c,curl)
2541 
2542         $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25)
2543     */
2544     alias TimeCond = CurlTimeCond;
2545 
2546     /**
2547        Constructor taking the url as parameter.
2548     */
opCallHTTP2549     static HTTP opCall(const(char)[] url)
2550     {
2551         HTTP http;
2552         http.initialize();
2553         http.url = url;
2554         return http;
2555     }
2556 
2557     ///
opCallHTTP2558     static HTTP opCall()
2559     {
2560         HTTP http;
2561         http.initialize();
2562         return http;
2563     }
2564 
2565     ///
dupHTTP2566     HTTP dup()
2567     {
2568         HTTP copy;
2569         copy.initialize();
2570         copy.p.method = p.method;
2571         curl_slist* cur = p.headersOut;
2572         curl_slist* newlist = null;
2573         while (cur)
2574         {
2575             newlist = Curl.curl.slist_append(newlist, cur.data);
2576             cur = cur.next;
2577         }
2578         copy.p.headersOut = newlist;
2579         copy.p.curl.set(CurlOption.httpheader, copy.p.headersOut);
2580         copy.p.curl = p.curl.dup();
2581         copy.dataTimeout = _defaultDataTimeout;
2582         copy.onReceiveHeader = null;
2583         return copy;
2584     }
2585 
initializeHTTP2586     private void initialize()
2587     {
2588         p.curl.initialize();
2589         maxRedirects = HTTP.defaultMaxRedirects;
2590         p.charset = "ISO-8859-1"; // Default charset defined in HTTP RFC
2591         p.method = Method.undefined;
2592         setUserAgent(HTTP.defaultUserAgent);
2593         dataTimeout = _defaultDataTimeout;
2594         onReceiveHeader = null;
2595         verifyPeer = true;
2596         verifyHost = true;
2597     }
2598 
2599     /**
2600        Perform a http request.
2601 
2602        After the HTTP client has been setup and possibly assigned callbacks the
2603        $(D perform()) method will start performing the request towards the
2604        specified server.
2605 
2606        Params:
2607        throwOnError = whether to throw an exception or return a CurlCode on error
2608     */
2609     CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError)
2610     {
2611         p.status.reset();
2612 
2613         CurlOption opt;
2614         final switch (p.method)
2615         {
2616         case Method.head:
2617             p.curl.set(CurlOption.nobody, 1L);
2618             opt = CurlOption.nobody;
2619             break;
2620         case Method.undefined:
2621         case Method.get:
2622             p.curl.set(CurlOption.httpget, 1L);
2623             opt = CurlOption.httpget;
2624             break;
2625         case Method.post:
2626             p.curl.set(CurlOption.post, 1L);
2627             opt = CurlOption.post;
2628             break;
2629         case Method.put:
2630             p.curl.set(CurlOption.upload, 1L);
2631             opt = CurlOption.upload;
2632             break;
2633         case Method.del:
2634             p.curl.set(CurlOption.customrequest, "DELETE");
2635             opt = CurlOption.customrequest;
2636             break;
2637         case Method.options:
2638             p.curl.set(CurlOption.customrequest, "OPTIONS");
2639             opt = CurlOption.customrequest;
2640             break;
2641         case Method.trace:
2642             p.curl.set(CurlOption.customrequest, "TRACE");
2643             opt = CurlOption.customrequest;
2644             break;
2645         case Method.connect:
2646             p.curl.set(CurlOption.customrequest, "CONNECT");
2647             opt = CurlOption.customrequest;
2648             break;
2649         case Method.patch:
2650             p.curl.set(CurlOption.customrequest, "PATCH");
2651             opt = CurlOption.customrequest;
2652             break;
2653         }
2654 
2655         scope (exit) p.curl.clear(opt);
2656         return p.curl.perform(throwOnError);
2657     }
2658 
2659     /// The URL to specify the location of the resource.
urlHTTP2660     @property void url(const(char)[] url)
2661     {
2662         import std.algorithm.searching : startsWith;
2663         import std.uni : toLower;
2664         if (!startsWith(url.toLower(), "http://", "https://"))
2665             url = "http://" ~ url;
2666         p.curl.set(CurlOption.url, url);
2667     }
2668 
2669     /// Set the CA certificate bundle file to use for SSL peer verification
caInfoHTTP2670     @property void caInfo(const(char)[] caFile)
2671     {
2672         p.curl.set(CurlOption.cainfo, caFile);
2673     }
2674 
2675     // This is a workaround for mixed in content not having its
2676     // docs mixed in.
versionHTTP2677     version (StdDdoc)
2678     {
2679         /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
2680         /// pause a request
2681         alias requestPause = CurlReadFunc.pause;
2682 
2683         /// Value to return from onSend delegate in order to abort a request
2684         alias requestAbort = CurlReadFunc.abort;
2685 
2686         /**
2687            True if the instance is stopped. A stopped instance is not usable.
2688         */
2689         @property bool isStopped();
2690 
2691         /// Stop and invalidate this instance.
2692         void shutdown();
2693 
2694         /** Set verbose.
2695             This will print request information to stderr.
2696         */
2697         @property void verbose(bool on);
2698 
2699         // Connection settings
2700 
2701         /// Set timeout for activity on connection.
2702         @property void dataTimeout(Duration d);
2703 
2704         /** Set maximum time an operation is allowed to take.
2705             This includes dns resolution, connecting, data transfer, etc.
2706           */
2707         @property void operationTimeout(Duration d);
2708 
2709         /// Set timeout for connecting.
2710         @property void connectTimeout(Duration d);
2711 
2712         // Network settings
2713 
2714         /** Proxy
2715          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
2716          */
2717         @property void proxy(const(char)[] host);
2718 
2719         /** Proxy port
2720          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
2721          */
2722         @property void proxyPort(ushort port);
2723 
2724         /// Type of proxy
2725         alias CurlProxy = etc.c.curl.CurlProxy;
2726 
2727         /** Proxy type
2728          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
2729          */
2730         @property void proxyType(CurlProxy type);
2731 
2732         /// DNS lookup timeout.
2733         @property void dnsTimeout(Duration d);
2734 
2735         /**
2736          * The network interface to use in form of the the IP of the interface.
2737          *
2738          * Example:
2739          * ----
2740          * theprotocol.netInterface = "192.168.1.32";
2741          * theprotocol.netInterface = [ 192, 168, 1, 32 ];
2742          * ----
2743          *
2744          * See: $(REF InternetAddress, std,socket)
2745          */
2746         @property void netInterface(const(char)[] i);
2747 
2748         /// ditto
2749         @property void netInterface(const(ubyte)[4] i);
2750 
2751         /// ditto
2752         @property void netInterface(InternetAddress i);
2753 
2754         /**
2755            Set the local outgoing port to use.
2756            Params:
2757            port = the first outgoing port number to try and use
2758         */
2759         @property void localPort(ushort port);
2760 
2761         /**
2762            Set the local outgoing port range to use.
2763            This can be used together with the localPort property.
2764            Params:
2765            range = if the first port is occupied then try this many
2766            port number forwards
2767         */
2768         @property void localPortRange(ushort range);
2769 
2770         /** Set the tcp no-delay socket option on or off.
2771             See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
2772         */
2773         @property void tcpNoDelay(bool on);
2774 
2775         // Authentication settings
2776 
2777         /**
2778            Set the user name, password and optionally domain for authentication
2779            purposes.
2780 
2781            Some protocols may need authentication in some cases. Use this
2782            function to provide credentials.
2783 
2784            Params:
2785            username = the username
2786            password = the password
2787            domain = used for NTLM authentication only and is set to the NTLM domain
2788            name
2789         */
2790         void setAuthentication(const(char)[] username, const(char)[] password,
2791                                const(char)[] domain = "");
2792 
2793         /**
2794            Set the user name and password for proxy authentication.
2795 
2796            Params:
2797            username = the username
2798            password = the password
2799         */
2800         void setProxyAuthentication(const(char)[] username, const(char)[] password);
2801 
2802         /**
2803          * The event handler that gets called when data is needed for sending. The
2804          * length of the $(D void[]) specifies the maximum number of bytes that can
2805          * be sent.
2806          *
2807          * Returns:
2808          * The callback returns the number of elements in the buffer that have been
2809          * filled and are ready to send.
2810          * The special value $(D .abortRequest) can be returned in order to abort the
2811          * current request.
2812          * The special value $(D .pauseRequest) can be returned in order to pause the
2813          * current request.
2814          *
2815          * Example:
2816          * ----
2817          * import std.net.curl;
2818          * string msg = "Hello world";
2819          * auto client = HTTP("dlang.org");
2820          * client.onSend = delegate size_t(void[] data)
2821          * {
2822          *     auto m = cast(void[]) msg;
2823          *     size_t length = m.length > data.length ? data.length : m.length;
2824          *     if (length == 0) return 0;
2825          *     data[0 .. length] = m[0 .. length];
2826          *     msg = msg[length..$];
2827          *     return length;
2828          * };
2829          * client.perform();
2830          * ----
2831          */
2832         @property void onSend(size_t delegate(void[]) callback);
2833 
2834         /**
2835          * The event handler that receives incoming data. Be sure to copy the
2836          * incoming ubyte[] since it is not guaranteed to be valid after the
2837          * callback returns.
2838          *
2839          * Returns:
2840          * The callback returns the incoming bytes read. If not the entire array is
2841          * the request will abort.
2842          * The special value .pauseRequest can be returned in order to pause the
2843          * current request.
2844          *
2845          * Example:
2846          * ----
2847          * import std.net.curl, std.stdio;
2848          * auto client = HTTP("dlang.org");
2849          * client.onReceive = (ubyte[] data)
2850          * {
2851          *     writeln("Got data", to!(const(char)[])(data));
2852          *     return data.length;
2853          * };
2854          * client.perform();
2855          * ----
2856          */
2857         @property void onReceive(size_t delegate(ubyte[]) callback);
2858 
2859         /**
2860          * Register an event handler that gets called to inform of
2861          * upload/download progress.
2862          *
2863          * Callback_parameters:
2864          * $(CALLBACK_PARAMS)
2865          *
2866          * Callback_returns: Return 0 to signal success, return non-zero to
2867          * abort transfer.
2868          *
2869          * Example:
2870          * ----
2871          * import std.net.curl, std.stdio;
2872          * auto client = HTTP("dlang.org");
2873          * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult)
2874          * {
2875          *     writeln("Progress: downloaded ", dln, " of ", dl);
2876          *     writeln("Progress: uploaded ", uln, " of ", ul);
2877          * };
2878          * client.perform();
2879          * ----
2880          */
2881         @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
2882                                                size_t ulTotal, size_t ulNow) callback);
2883     }
2884 
2885     /** Clear all outgoing headers.
2886     */
clearRequestHeadersHTTP2887     void clearRequestHeaders()
2888     {
2889         if (p.headersOut !is null)
2890             Curl.curl.slist_free_all(p.headersOut);
2891         p.headersOut = null;
2892         p.curl.clear(CurlOption.httpheader);
2893     }
2894 
2895     /** Add a header e.g. "X-CustomField: Something is fishy".
2896      *
2897      * There is no remove header functionality. Do a $(LREF clearRequestHeaders)
2898      * and set the needed headers instead.
2899      *
2900      * Example:
2901      * ---
2902      * import std.net.curl;
2903      * auto client = HTTP();
2904      * client.addRequestHeader("X-Custom-ABC", "This is the custom value");
2905      * auto content = get("dlang.org", client);
2906      * ---
2907      */
addRequestHeaderHTTP2908     void addRequestHeader(const(char)[] name, const(char)[] value)
2909     {
2910         import std.format : format;
2911         import std.uni : icmp;
2912 
2913         if (icmp(name, "User-Agent") == 0)
2914             return setUserAgent(value);
2915         string nv = format("%s: %s", name, value);
2916         p.headersOut = Curl.curl.slist_append(p.headersOut,
2917                                               nv.tempCString().buffPtr);
2918         p.curl.set(CurlOption.httpheader, p.headersOut);
2919     }
2920 
2921     /**
2922      * The default "User-Agent" value send with a request.
2923      * It has the form "Phobos-std.net.curl/$(I PHOBOS_VERSION) (libcurl/$(I CURL_VERSION))"
2924      */
defaultUserAgentHTTP2925     static string defaultUserAgent() @property
2926     {
2927         import std.compiler : version_major, version_minor;
2928         import std.format : format, sformat;
2929 
2930         // http://curl.haxx.se/docs/versions.html
2931         enum fmt = "Phobos-std.net.curl/%d.%03d (libcurl/%d.%d.%d)";
2932         enum maxLen = fmt.length - "%d%03d%d%d%d".length + 10 + 10 + 3 + 3 + 3;
2933 
2934         static char[maxLen] buf = void;
2935         static string userAgent;
2936 
2937         if (!userAgent.length)
2938         {
2939             auto curlVer = Curl.curl.version_info(CURLVERSION_NOW).version_num;
2940             userAgent = cast(immutable) sformat(
2941                 buf, fmt, version_major, version_minor,
2942                 curlVer >> 16 & 0xFF, curlVer >> 8 & 0xFF, curlVer & 0xFF);
2943         }
2944         return userAgent;
2945     }
2946 
2947     /** Set the value of the user agent request header field.
2948      *
2949      * By default a request has it's "User-Agent" field set to $(LREF
2950      * defaultUserAgent) even if $(D setUserAgent) was never called.  Pass
2951      * an empty string to suppress the "User-Agent" field altogether.
2952      */
setUserAgentHTTP2953     void setUserAgent(const(char)[] userAgent)
2954     {
2955         p.curl.set(CurlOption.useragent, userAgent);
2956     }
2957 
2958     /**
2959      * Get various timings defined in $(REF CurlInfo, etc, c, curl).
2960      * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok).
2961      *
2962      * Params:
2963      *      timing = one of the timings defined in $(REF CurlInfo, etc, c, curl).
2964      *               The values are:
2965      *               $(D etc.c.curl.CurlInfo.namelookup_time),
2966      *               $(D etc.c.curl.CurlInfo.connect_time),
2967      *               $(D etc.c.curl.CurlInfo.pretransfer_time),
2968      *               $(D etc.c.curl.CurlInfo.starttransfer_time),
2969      *               $(D etc.c.curl.CurlInfo.redirect_time),
2970      *               $(D etc.c.curl.CurlInfo.appconnect_time),
2971      *               $(D etc.c.curl.CurlInfo.total_time).
2972      *      val    = the actual value of the inquired timing.
2973      *
2974      * Returns:
2975      *      The return code of the operation. The value stored in val
2976      *      should be used only if the return value is $(D etc.c.curl.CurlInfo.ok).
2977      *
2978      * Example:
2979      * ---
2980      * import std.net.curl;
2981      * import etc.c.curl : CurlError, CurlInfo;
2982      *
2983      * auto client = HTTP("dlang.org");
2984      * client.perform();
2985      *
2986      * double val;
2987      * CurlCode code;
2988      *
2989      * code = http.getTiming(CurlInfo.namelookup_time, val);
2990      * assert(code == CurlError.ok);
2991      * ---
2992      */
getTimingHTTP2993     CurlCode getTiming(CurlInfo timing, ref double val)
2994     {
2995         return p.curl.getTiming(timing, val);
2996     }
2997 
2998     /** The headers read from a successful response.
2999      *
3000      */
responseHeadersHTTP3001     @property string[string] responseHeaders()
3002     {
3003         return p.headersIn;
3004     }
3005 
3006     /// HTTP method used.
methodHTTP3007     @property void method(Method m)
3008     {
3009         p.method = m;
3010     }
3011 
3012     /// ditto
methodHTTP3013     @property Method method()
3014     {
3015         return p.method;
3016     }
3017 
3018     /**
3019        HTTP status line of last response. One call to perform may
3020        result in several requests because of redirection.
3021     */
statusLineHTTP3022     @property StatusLine statusLine()
3023     {
3024         return p.status;
3025     }
3026 
3027     /// Set the active cookie string e.g. "name1=value1;name2=value2"
setCookieHTTP3028     void setCookie(const(char)[] cookie)
3029     {
3030         p.curl.set(CurlOption.cookie, cookie);
3031     }
3032 
3033     /// Set a file path to where a cookie jar should be read/stored.
setCookieJarHTTP3034     void setCookieJar(const(char)[] path)
3035     {
3036         p.curl.set(CurlOption.cookiefile, path);
3037         if (path.length)
3038             p.curl.set(CurlOption.cookiejar, path);
3039     }
3040 
3041     /// Flush cookie jar to disk.
flushCookieJarHTTP3042     void flushCookieJar()
3043     {
3044         p.curl.set(CurlOption.cookielist, "FLUSH");
3045     }
3046 
3047     /// Clear session cookies.
clearSessionCookiesHTTP3048     void clearSessionCookies()
3049     {
3050         p.curl.set(CurlOption.cookielist, "SESS");
3051     }
3052 
3053     /// Clear all cookies.
clearAllCookiesHTTP3054     void clearAllCookies()
3055     {
3056         p.curl.set(CurlOption.cookielist, "ALL");
3057     }
3058 
3059     /**
3060        Set time condition on the request.
3061 
3062        Params:
3063        cond =  $(D CurlTimeCond.{none,ifmodsince,ifunmodsince,lastmod})
3064        timestamp = Timestamp for the condition
3065 
3066        $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25)
3067     */
3068     void setTimeCondition(HTTP.TimeCond cond, SysTime timestamp)
3069     {
3070         p.curl.set(CurlOption.timecondition, cond);
3071         p.curl.set(CurlOption.timevalue, timestamp.toUnixTime());
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       * application/octet-stream.  Data is not converted or encoded by this
3078       * method.
3079       *
3080       * Example:
3081       * ----
3082       * import std.net.curl, std.stdio;
3083       * auto http = HTTP("http://www.mydomain.com");
3084       * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
3085       * http.postData = [1,2,3,4,5];
3086       * http.perform();
3087       * ----
3088       */
postDataHTTP3089     @property void postData(const(void)[] data)
3090     {
3091         setPostData(data, "application/octet-stream");
3092     }
3093 
3094     /** Specifying data to post when not using the onSend callback.
3095       *
3096       * The data is NOT copied by the library.  Content-Type will default to
3097       * text/plain.  Data is not converted or encoded by this method.
3098       *
3099       * Example:
3100       * ----
3101       * import std.net.curl, std.stdio;
3102       * auto http = HTTP("http://www.mydomain.com");
3103       * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
3104       * http.postData = "The quick....";
3105       * http.perform();
3106       * ----
3107       */
postDataHTTP3108     @property void postData(const(char)[] data)
3109     {
3110         setPostData(data, "text/plain");
3111     }
3112 
3113     /**
3114      * Specify data to post when not using the onSend callback, with
3115      * user-specified Content-Type.
3116      * Params:
3117      *  data = Data to post.
3118      *  contentType = MIME type of the data, for example, "text/plain" or
3119      *      "application/octet-stream". See also:
3120      *      $(LINK2 http://en.wikipedia.org/wiki/Internet_media_type,
3121      *      Internet media type) on Wikipedia.
3122      * -----
3123      * import std.net.curl;
3124      * auto http = HTTP("http://onlineform.example.com");
3125      * auto data = "app=login&username=bob&password=s00perS3kret";
3126      * http.setPostData(data, "application/x-www-form-urlencoded");
3127      * http.onReceive = (ubyte[] data) { return data.length; };
3128      * http.perform();
3129      * -----
3130      */
setPostDataHTTP3131     void setPostData(const(void)[] data, string contentType)
3132     {
3133         // cannot use callback when specifying data directly so it is disabled here.
3134         p.curl.clear(CurlOption.readfunction);
3135         addRequestHeader("Content-Type", contentType);
3136         p.curl.set(CurlOption.postfields, cast(void*) data.ptr);
3137         p.curl.set(CurlOption.postfieldsize, data.length);
3138         if (method == Method.undefined)
3139             method = Method.post;
3140     }
3141 
3142     @system unittest
3143     {
3144         import std.algorithm.searching : canFind;
3145 
3146         testServer.handle((s) {
3147             auto req = s.recvReq!ubyte;
3148             assert(req.hdrs.canFind("POST /path"));
3149             assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4]));
3150             assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255]));
3151             s.send(httpOK(cast(ubyte[])[17, 27, 35, 41]));
3152         });
3153         auto data = new ubyte[](256);
3154         foreach (i, ref ub; data)
3155             ub = cast(ubyte) i;
3156 
3157         auto http = HTTP(testServer.addr~"/path");
3158         http.postData = data;
3159         ubyte[] res;
3160         http.onReceive = (data) { res ~= data; return data.length; };
3161         http.perform();
3162         assert(res == cast(ubyte[])[17, 27, 35, 41]);
3163     }
3164 
3165     /**
3166       * Set the event handler that receives incoming headers.
3167       *
3168       * The callback will receive a header field key, value as parameter. The
3169       * $(D const(char)[]) arrays are not valid after the delegate has returned.
3170       *
3171       * Example:
3172       * ----
3173       * import std.net.curl, std.stdio;
3174       * auto http = HTTP("dlang.org");
3175       * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; };
3176       * http.onReceiveHeader = (in char[] key, in char[] value) { writeln(key, " = ", value); };
3177       * http.perform();
3178       * ----
3179       */
onReceiveHeaderHTTP3180     @property void onReceiveHeader(void delegate(in char[] key,
3181                                                  in char[] value) callback)
3182     {
3183         p.onReceiveHeader = callback;
3184     }
3185 
3186     /**
3187        Callback for each received StatusLine.
3188 
3189        Notice that several callbacks can be done for each call to
3190        $(D perform()) due to redirections.
3191 
3192        See_Also: $(LREF StatusLine)
3193      */
onReceiveStatusLineHTTP3194     @property void onReceiveStatusLine(void delegate(StatusLine) callback)
3195     {
3196         p.onReceiveStatusLine = callback;
3197     }
3198 
3199     /**
3200        The content length in bytes when using request that has content
3201        e.g. POST/PUT and not using chunked transfer. Is set as the
3202        "Content-Length" header.  Set to ulong.max to reset to chunked transfer.
3203     */
contentLengthHTTP3204     @property void contentLength(ulong len)
3205     {
3206         import std.conv : to;
3207 
3208         CurlOption lenOpt;
3209 
3210         // Force post if necessary
3211         if (p.method != Method.put && p.method != Method.post &&
3212             p.method != Method.patch)
3213             p.method = Method.post;
3214 
3215         if (p.method == Method.post || p.method == Method.patch)
3216             lenOpt = CurlOption.postfieldsize_large;
3217         else
3218             lenOpt = CurlOption.infilesize_large;
3219 
3220         if (size_t.max != ulong.max && len == size_t.max)
3221             len = ulong.max; // check size_t.max for backwards compat, turn into error
3222 
3223         if (len == ulong.max)
3224         {
3225             // HTTP 1.1 supports requests with no length header set.
3226             addRequestHeader("Transfer-Encoding", "chunked");
3227             addRequestHeader("Expect", "100-continue");
3228         }
3229         else
3230         {
3231             p.curl.set(lenOpt, to!curl_off_t(len));
3232         }
3233     }
3234 
3235     /**
3236        Authentication method as specified in $(LREF AuthMethod).
3237     */
authenticationMethodHTTP3238     @property void authenticationMethod(AuthMethod authMethod)
3239     {
3240         p.curl.set(CurlOption.httpauth, cast(long) authMethod);
3241     }
3242 
3243     /**
3244        Set max allowed redirections using the location header.
3245        uint.max for infinite.
3246     */
maxRedirectsHTTP3247     @property void maxRedirects(uint maxRedirs)
3248     {
3249         if (maxRedirs == uint.max)
3250         {
3251             // Disable
3252             p.curl.set(CurlOption.followlocation, 0);
3253         }
3254         else
3255         {
3256             p.curl.set(CurlOption.followlocation, 1);
3257             p.curl.set(CurlOption.maxredirs, maxRedirs);
3258         }
3259     }
3260 
3261     /** <a name="HTTP.Method"/>The standard HTTP methods :
3262      *  $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1, _RFC2616 Section 5.1.1)
3263      */
3264     enum Method
3265     {
3266         undefined,
3267         head, ///
3268         get,  ///
3269         post, ///
3270         put,  ///
3271         del,  ///
3272         options, ///
3273         trace,   ///
3274         connect,  ///
3275         patch, ///
3276     }
3277 
3278     /**
3279        HTTP status line ie. the first line returned in an HTTP response.
3280 
3281        If authentication or redirections are done then the status will be for
3282        the last response received.
3283     */
3284     struct StatusLine
3285     {
3286         ushort majorVersion; /// Major HTTP version ie. 1 in HTTP/1.0.
3287         ushort minorVersion; /// Minor HTTP version ie. 0 in HTTP/1.0.
3288         ushort code;         /// HTTP status line code e.g. 200.
3289         string reason;       /// HTTP status line reason string.
3290 
3291         /// Reset this status line
resetHTTP3292         @safe void reset()
3293         {
3294             majorVersion = 0;
3295             minorVersion = 0;
3296             code = 0;
3297             reason = "";
3298         }
3299 
3300         ///
toStringHTTP3301         string toString() const
3302         {
3303             import std.format : format;
3304             return format("%s %s (%s.%s)",
3305                           code, reason, majorVersion, minorVersion);
3306         }
3307     }
3308 
3309 } // HTTP
3310 
3311 @system unittest // charset/Charset/CHARSET/...
3312 {
3313     import std.meta : AliasSeq;
3314 
3315     foreach (c; AliasSeq!("charset", "Charset", "CHARSET", "CharSet", "charSet",
3316         "ChArSeT", "cHaRsEt"))
3317     {
3318         testServer.handle((s) {
3319             s.send("HTTP/1.1 200 OK\r\n"~
3320                 "Content-Length: 0\r\n"~
3321                 "Content-Type: text/plain; " ~ c ~ "=foo\r\n" ~
3322                 "\r\n");
3323         });
3324 
3325         auto http = HTTP(testServer.addr);
3326         http.perform();
3327         assert(http.p.charset == "foo");
3328 
3329         // Bugzilla 16736
3330         double val;
3331         CurlCode code;
3332 
3333         code = http.getTiming(CurlInfo.total_time, val);
3334         assert(code == CurlError.ok);
3335         code = http.getTiming(CurlInfo.namelookup_time, val);
3336         assert(code == CurlError.ok);
3337         code = http.getTiming(CurlInfo.connect_time, val);
3338         assert(code == CurlError.ok);
3339         code = http.getTiming(CurlInfo.pretransfer_time, val);
3340         assert(code == CurlError.ok);
3341         code = http.getTiming(CurlInfo.starttransfer_time, val);
3342         assert(code == CurlError.ok);
3343         code = http.getTiming(CurlInfo.redirect_time, val);
3344         assert(code == CurlError.ok);
3345         code = http.getTiming(CurlInfo.appconnect_time, val);
3346         assert(code == CurlError.ok);
3347     }
3348 }
3349 
3350 /**
3351    FTP client functionality.
3352 
3353    See_Also: $(HTTP tools.ietf.org/html/rfc959, RFC959)
3354 */
3355 struct FTP
3356 {
3357 
3358     mixin Protocol;
3359 
3360     private struct Impl
3361     {
~thisFTP::Impl3362         ~this()
3363         {
3364             if (commands !is null)
3365                 Curl.curl.slist_free_all(commands);
3366             if (curl.handle !is null) // work around RefCounted/emplace bug
3367                 curl.shutdown();
3368         }
3369         curl_slist* commands;
3370         Curl curl;
3371         string encoding;
3372     }
3373 
3374     private RefCounted!Impl p;
3375 
3376     /**
3377        FTP access to the specified url.
3378     */
opCallFTP3379     static FTP opCall(const(char)[] url)
3380     {
3381         FTP ftp;
3382         ftp.initialize();
3383         ftp.url = url;
3384         return ftp;
3385     }
3386 
3387     ///
opCallFTP3388     static FTP opCall()
3389     {
3390         FTP ftp;
3391         ftp.initialize();
3392         return ftp;
3393     }
3394 
3395     ///
dupFTP3396     FTP dup()
3397     {
3398         FTP copy = FTP();
3399         copy.initialize();
3400         copy.p.encoding = p.encoding;
3401         copy.p.curl = p.curl.dup();
3402         curl_slist* cur = p.commands;
3403         curl_slist* newlist = null;
3404         while (cur)
3405         {
3406             newlist = Curl.curl.slist_append(newlist, cur.data);
3407             cur = cur.next;
3408         }
3409         copy.p.commands = newlist;
3410         copy.p.curl.set(CurlOption.postquote, copy.p.commands);
3411         copy.dataTimeout = _defaultDataTimeout;
3412         return copy;
3413     }
3414 
initializeFTP3415     private void initialize()
3416     {
3417         p.curl.initialize();
3418         p.encoding = "ISO-8859-1";
3419         dataTimeout = _defaultDataTimeout;
3420     }
3421 
3422     /**
3423        Performs the ftp request as it has been configured.
3424 
3425        After a FTP client has been setup and possibly assigned callbacks the $(D
3426        perform()) method will start performing the actual communication with the
3427        server.
3428 
3429        Params:
3430        throwOnError = whether to throw an exception or return a CurlCode on error
3431     */
3432     CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError)
3433     {
3434         return p.curl.perform(throwOnError);
3435     }
3436 
3437     /// The URL to specify the location of the resource.
urlFTP3438     @property void url(const(char)[] url)
3439     {
3440         import std.algorithm.searching : startsWith;
3441         import std.uni : toLower;
3442 
3443         if (!startsWith(url.toLower(), "ftp://", "ftps://"))
3444             url = "ftp://" ~ url;
3445         p.curl.set(CurlOption.url, url);
3446     }
3447 
3448     // This is a workaround for mixed in content not having its
3449     // docs mixed in.
versionFTP3450     version (StdDdoc)
3451     {
3452         /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
3453         /// pause a request
3454         alias requestPause = CurlReadFunc.pause;
3455 
3456         /// Value to return from onSend delegate in order to abort a request
3457         alias requestAbort = CurlReadFunc.abort;
3458 
3459         /**
3460            True if the instance is stopped. A stopped instance is not usable.
3461         */
3462         @property bool isStopped();
3463 
3464         /// Stop and invalidate this instance.
3465         void shutdown();
3466 
3467         /** Set verbose.
3468             This will print request information to stderr.
3469         */
3470         @property void verbose(bool on);
3471 
3472         // Connection settings
3473 
3474         /// Set timeout for activity on connection.
3475         @property void dataTimeout(Duration d);
3476 
3477         /** Set maximum time an operation is allowed to take.
3478             This includes dns resolution, connecting, data transfer, etc.
3479           */
3480         @property void operationTimeout(Duration d);
3481 
3482         /// Set timeout for connecting.
3483         @property void connectTimeout(Duration d);
3484 
3485         // Network settings
3486 
3487         /** Proxy
3488          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
3489          */
3490         @property void proxy(const(char)[] host);
3491 
3492         /** Proxy port
3493          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
3494          */
3495         @property void proxyPort(ushort port);
3496 
3497         /// Type of proxy
3498         alias CurlProxy = etc.c.curl.CurlProxy;
3499 
3500         /** Proxy type
3501          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
3502          */
3503         @property void proxyType(CurlProxy type);
3504 
3505         /// DNS lookup timeout.
3506         @property void dnsTimeout(Duration d);
3507 
3508         /**
3509          * The network interface to use in form of the the IP of the interface.
3510          *
3511          * Example:
3512          * ----
3513          * theprotocol.netInterface = "192.168.1.32";
3514          * theprotocol.netInterface = [ 192, 168, 1, 32 ];
3515          * ----
3516          *
3517          * See: $(REF InternetAddress, std,socket)
3518          */
3519         @property void netInterface(const(char)[] i);
3520 
3521         /// ditto
3522         @property void netInterface(const(ubyte)[4] i);
3523 
3524         /// ditto
3525         @property void netInterface(InternetAddress i);
3526 
3527         /**
3528            Set the local outgoing port to use.
3529            Params:
3530            port = the first outgoing port number to try and use
3531         */
3532         @property void localPort(ushort port);
3533 
3534         /**
3535            Set the local outgoing port range to use.
3536            This can be used together with the localPort property.
3537            Params:
3538            range = if the first port is occupied then try this many
3539            port number forwards
3540         */
3541         @property void localPortRange(ushort range);
3542 
3543         /** Set the tcp no-delay socket option on or off.
3544             See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
3545         */
3546         @property void tcpNoDelay(bool on);
3547 
3548         // Authentication settings
3549 
3550         /**
3551            Set the user name, password and optionally domain for authentication
3552            purposes.
3553 
3554            Some protocols may need authentication in some cases. Use this
3555            function to provide credentials.
3556 
3557            Params:
3558            username = the username
3559            password = the password
3560            domain = used for NTLM authentication only and is set to the NTLM domain
3561            name
3562         */
3563         void setAuthentication(const(char)[] username, const(char)[] password,
3564                                const(char)[] domain = "");
3565 
3566         /**
3567            Set the user name and password for proxy authentication.
3568 
3569            Params:
3570            username = the username
3571            password = the password
3572         */
3573         void setProxyAuthentication(const(char)[] username, const(char)[] password);
3574 
3575         /**
3576          * The event handler that gets called when data is needed for sending. The
3577          * length of the $(D void[]) specifies the maximum number of bytes that can
3578          * be sent.
3579          *
3580          * Returns:
3581          * The callback returns the number of elements in the buffer that have been
3582          * filled and are ready to send.
3583          * The special value $(D .abortRequest) can be returned in order to abort the
3584          * current request.
3585          * The special value $(D .pauseRequest) can be returned in order to pause the
3586          * current request.
3587          *
3588          */
3589         @property void onSend(size_t delegate(void[]) callback);
3590 
3591         /**
3592          * The event handler that receives incoming data. Be sure to copy the
3593          * incoming ubyte[] since it is not guaranteed to be valid after the
3594          * callback returns.
3595          *
3596          * Returns:
3597          * The callback returns the incoming bytes read. If not the entire array is
3598          * the request will abort.
3599          * The special value .pauseRequest can be returned in order to pause the
3600          * current request.
3601          *
3602          */
3603         @property void onReceive(size_t delegate(ubyte[]) callback);
3604 
3605         /**
3606          * The event handler that gets called to inform of upload/download progress.
3607          *
3608          * Callback_parameters:
3609          * $(CALLBACK_PARAMS)
3610          *
3611          * Callback_returns:
3612          * Return 0 from the callback to signal success, return non-zero to
3613          * abort transfer.
3614          */
3615         @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
3616                                                size_t ulTotal, size_t ulNow) callback);
3617     }
3618 
3619     /** Clear all commands send to ftp server.
3620     */
clearCommandsFTP3621     void clearCommands()
3622     {
3623         if (p.commands !is null)
3624             Curl.curl.slist_free_all(p.commands);
3625         p.commands = null;
3626         p.curl.clear(CurlOption.postquote);
3627     }
3628 
3629     /** Add a command to send to ftp server.
3630      *
3631      * There is no remove command functionality. Do a $(LREF clearCommands) and
3632      * set the needed commands instead.
3633      *
3634      * Example:
3635      * ---
3636      * import std.net.curl;
3637      * auto client = FTP();
3638      * client.addCommand("RNFR my_file.txt");
3639      * client.addCommand("RNTO my_renamed_file.txt");
3640      * upload("my_file.txt", "ftp.digitalmars.com", client);
3641      * ---
3642      */
addCommandFTP3643     void addCommand(const(char)[] command)
3644     {
3645         p.commands = Curl.curl.slist_append(p.commands,
3646                                             command.tempCString().buffPtr);
3647         p.curl.set(CurlOption.postquote, p.commands);
3648     }
3649 
3650     /// Connection encoding. Defaults to ISO-8859-1.
encodingFTP3651     @property void encoding(string name)
3652     {
3653         p.encoding = name;
3654     }
3655 
3656     /// ditto
encodingFTP3657     @property string encoding()
3658     {
3659         return p.encoding;
3660     }
3661 
3662     /**
3663        The content length in bytes of the ftp data.
3664     */
contentLengthFTP3665     @property void contentLength(ulong len)
3666     {
3667         import std.conv : to;
3668         p.curl.set(CurlOption.infilesize_large, to!curl_off_t(len));
3669     }
3670 
3671     /**
3672      * Get various timings defined in $(REF CurlInfo, etc, c, curl).
3673      * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok).
3674      *
3675      * Params:
3676      *      timing = one of the timings defined in $(REF CurlInfo, etc, c, curl).
3677      *               The values are:
3678      *               $(D etc.c.curl.CurlInfo.namelookup_time),
3679      *               $(D etc.c.curl.CurlInfo.connect_time),
3680      *               $(D etc.c.curl.CurlInfo.pretransfer_time),
3681      *               $(D etc.c.curl.CurlInfo.starttransfer_time),
3682      *               $(D etc.c.curl.CurlInfo.redirect_time),
3683      *               $(D etc.c.curl.CurlInfo.appconnect_time),
3684      *               $(D etc.c.curl.CurlInfo.total_time).
3685      *      val    = the actual value of the inquired timing.
3686      *
3687      * Returns:
3688      *      The return code of the operation. The value stored in val
3689      *      should be used only if the return value is $(D etc.c.curl.CurlInfo.ok).
3690      *
3691      * Example:
3692      * ---
3693      * import std.net.curl;
3694      * import etc.c.curl : CurlError, CurlInfo;
3695      *
3696      * auto client = FTP();
3697      * client.addCommand("RNFR my_file.txt");
3698      * client.addCommand("RNTO my_renamed_file.txt");
3699      * upload("my_file.txt", "ftp.digitalmars.com", client);
3700      *
3701      * double val;
3702      * CurlCode code;
3703      *
3704      * code = http.getTiming(CurlInfo.namelookup_time, val);
3705      * assert(code == CurlError.ok);
3706      * ---
3707      */
getTimingFTP3708     CurlCode getTiming(CurlInfo timing, ref double val)
3709     {
3710         return p.curl.getTiming(timing, val);
3711     }
3712 
3713     @system unittest
3714     {
3715         auto client = FTP();
3716 
3717         double val;
3718         CurlCode code;
3719 
3720         code = client.getTiming(CurlInfo.total_time, val);
3721         assert(code == CurlError.ok);
3722         code = client.getTiming(CurlInfo.namelookup_time, val);
3723         assert(code == CurlError.ok);
3724         code = client.getTiming(CurlInfo.connect_time, val);
3725         assert(code == CurlError.ok);
3726         code = client.getTiming(CurlInfo.pretransfer_time, val);
3727         assert(code == CurlError.ok);
3728         code = client.getTiming(CurlInfo.starttransfer_time, val);
3729         assert(code == CurlError.ok);
3730         code = client.getTiming(CurlInfo.redirect_time, val);
3731         assert(code == CurlError.ok);
3732         code = client.getTiming(CurlInfo.appconnect_time, val);
3733         assert(code == CurlError.ok);
3734     }
3735 }
3736 
3737 /**
3738   * Basic SMTP protocol support.
3739   *
3740   * Example:
3741   * ---
3742   * import std.net.curl;
3743   *
3744   * // Send an email with SMTPS
3745   * auto smtp = SMTP("smtps://smtp.gmail.com");
3746   * smtp.setAuthentication("from.addr@gmail.com", "password");
3747   * smtp.mailTo = ["<to.addr@gmail.com>"];
3748   * smtp.mailFrom = "<from.addr@gmail.com>";
3749   * smtp.message = "Example Message";
3750   * smtp.perform();
3751   * ---
3752   *
3753   * See_Also: $(HTTP www.ietf.org/rfc/rfc2821.txt, RFC2821)
3754   */
3755 struct SMTP
3756 {
3757     mixin Protocol;
3758 
3759     private struct Impl
3760     {
~thisImpl3761         ~this()
3762         {
3763             if (curl.handle !is null) // work around RefCounted/emplace bug
3764                 curl.shutdown();
3765         }
3766         Curl curl;
3767 
messageImpl3768         @property void message(string msg)
3769         {
3770             import std.algorithm.comparison : min;
3771 
3772             auto _message = msg;
3773             /**
3774                 This delegate reads the message text and copies it.
3775             */
3776             curl.onSend = delegate size_t(void[] data)
3777             {
3778                 if (!msg.length) return 0;
3779                 size_t to_copy = min(data.length, _message.length);
3780                 data[0 .. to_copy] = (cast(void[])_message)[0 .. to_copy];
3781                 _message = _message[to_copy..$];
3782                 return to_copy;
3783             };
3784         }
3785     }
3786 
3787     private RefCounted!Impl p;
3788 
3789     /**
3790         Sets to the URL of the SMTP server.
3791     */
opCall(const (char)[]url)3792     static SMTP opCall(const(char)[] url)
3793     {
3794         SMTP smtp;
3795         smtp.initialize();
3796         smtp.url = url;
3797         return smtp;
3798     }
3799 
3800     ///
opCall()3801     static SMTP opCall()
3802     {
3803         SMTP smtp;
3804         smtp.initialize();
3805         return smtp;
3806     }
3807 
3808     /+ TODO: The other structs have this function.
3809     SMTP dup()
3810     {
3811         SMTP copy = SMTP();
3812         copy.initialize();
3813         copy.p.encoding = p.encoding;
3814         copy.p.curl = p.curl.dup();
3815         curl_slist* cur = p.commands;
3816         curl_slist* newlist = null;
3817         while (cur)
3818         {
3819             newlist = Curl.curl.slist_append(newlist, cur.data);
3820             cur = cur.next;
3821         }
3822         copy.p.commands = newlist;
3823         copy.p.curl.set(CurlOption.postquote, copy.p.commands);
3824         copy.dataTimeout = _defaultDataTimeout;
3825         return copy;
3826     }
3827     +/
3828 
3829     /**
3830         Performs the request as configured.
3831         Params:
3832         throwOnError = whether to throw an exception or return a CurlCode on error
3833     */
3834     CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError)
3835     {
3836         return p.curl.perform(throwOnError);
3837     }
3838 
3839     /// The URL to specify the location of the resource.
url(const (char)[]url)3840     @property void url(const(char)[] url)
3841     {
3842         import std.algorithm.searching : startsWith;
3843         import std.uni : toLower;
3844 
3845         auto lowered = url.toLower();
3846 
3847         if (lowered.startsWith("smtps://"))
3848         {
3849             p.curl.set(CurlOption.use_ssl, CurlUseSSL.all);
3850         }
3851         else
3852         {
3853             enforce!CurlException(lowered.startsWith("smtp://"),
3854                                     "The url must be for the smtp protocol.");
3855         }
3856         p.curl.set(CurlOption.url, url);
3857     }
3858 
initialize()3859     private void initialize()
3860     {
3861         p.curl.initialize();
3862         p.curl.set(CurlOption.upload, 1L);
3863         dataTimeout = _defaultDataTimeout;
3864         verifyPeer = true;
3865         verifyHost = true;
3866     }
3867 
3868     // This is a workaround for mixed in content not having its
3869     // docs mixed in.
version(StdDdoc)3870     version (StdDdoc)
3871     {
3872         /// Value to return from $(D onSend)/$(D onReceive) delegates in order to
3873         /// pause a request
3874         alias requestPause = CurlReadFunc.pause;
3875 
3876         /// Value to return from onSend delegate in order to abort a request
3877         alias requestAbort = CurlReadFunc.abort;
3878 
3879         /**
3880            True if the instance is stopped. A stopped instance is not usable.
3881         */
3882         @property bool isStopped();
3883 
3884         /// Stop and invalidate this instance.
3885         void shutdown();
3886 
3887         /** Set verbose.
3888             This will print request information to stderr.
3889         */
3890         @property void verbose(bool on);
3891 
3892         // Connection settings
3893 
3894         /// Set timeout for activity on connection.
3895         @property void dataTimeout(Duration d);
3896 
3897         /** Set maximum time an operation is allowed to take.
3898             This includes dns resolution, connecting, data transfer, etc.
3899           */
3900         @property void operationTimeout(Duration d);
3901 
3902         /// Set timeout for connecting.
3903         @property void connectTimeout(Duration d);
3904 
3905         // Network settings
3906 
3907         /** Proxy
3908          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy)
3909          */
3910         @property void proxy(const(char)[] host);
3911 
3912         /** Proxy port
3913          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port)
3914          */
3915         @property void proxyPort(ushort port);
3916 
3917         /// Type of proxy
3918         alias CurlProxy = etc.c.curl.CurlProxy;
3919 
3920         /** Proxy type
3921          *  See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type)
3922          */
3923         @property void proxyType(CurlProxy type);
3924 
3925         /// DNS lookup timeout.
3926         @property void dnsTimeout(Duration d);
3927 
3928         /**
3929          * The network interface to use in form of the the IP of the interface.
3930          *
3931          * Example:
3932          * ----
3933          * theprotocol.netInterface = "192.168.1.32";
3934          * theprotocol.netInterface = [ 192, 168, 1, 32 ];
3935          * ----
3936          *
3937          * See: $(REF InternetAddress, std,socket)
3938          */
3939         @property void netInterface(const(char)[] i);
3940 
3941         /// ditto
3942         @property void netInterface(const(ubyte)[4] i);
3943 
3944         /// ditto
3945         @property void netInterface(InternetAddress i);
3946 
3947         /**
3948            Set the local outgoing port to use.
3949            Params:
3950            port = the first outgoing port number to try and use
3951         */
3952         @property void localPort(ushort port);
3953 
3954         /**
3955            Set the local outgoing port range to use.
3956            This can be used together with the localPort property.
3957            Params:
3958            range = if the first port is occupied then try this many
3959            port number forwards
3960         */
3961         @property void localPortRange(ushort range);
3962 
3963         /** Set the tcp no-delay socket option on or off.
3964             See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay)
3965         */
3966         @property void tcpNoDelay(bool on);
3967 
3968         // Authentication settings
3969 
3970         /**
3971            Set the user name, password and optionally domain for authentication
3972            purposes.
3973 
3974            Some protocols may need authentication in some cases. Use this
3975            function to provide credentials.
3976 
3977            Params:
3978            username = the username
3979            password = the password
3980            domain = used for NTLM authentication only and is set to the NTLM domain
3981            name
3982         */
3983         void setAuthentication(const(char)[] username, const(char)[] password,
3984                                const(char)[] domain = "");
3985 
3986         /**
3987            Set the user name and password for proxy authentication.
3988 
3989            Params:
3990            username = the username
3991            password = the password
3992         */
3993         void setProxyAuthentication(const(char)[] username, const(char)[] password);
3994 
3995         /**
3996          * The event handler that gets called when data is needed for sending. The
3997          * length of the $(D void[]) specifies the maximum number of bytes that can
3998          * be sent.
3999          *
4000          * Returns:
4001          * The callback returns the number of elements in the buffer that have been
4002          * filled and are ready to send.
4003          * The special value $(D .abortRequest) can be returned in order to abort the
4004          * current request.
4005          * The special value $(D .pauseRequest) can be returned in order to pause the
4006          * current request.
4007          */
4008         @property void onSend(size_t delegate(void[]) callback);
4009 
4010         /**
4011          * The event handler that receives incoming data. Be sure to copy the
4012          * incoming ubyte[] since it is not guaranteed to be valid after the
4013          * callback returns.
4014          *
4015          * Returns:
4016          * The callback returns the incoming bytes read. If not the entire array is
4017          * the request will abort.
4018          * The special value .pauseRequest can be returned in order to pause the
4019          * current request.
4020          */
4021         @property void onReceive(size_t delegate(ubyte[]) callback);
4022 
4023         /**
4024          * The event handler that gets called to inform of upload/download progress.
4025          *
4026          * Callback_parameters:
4027          * $(CALLBACK_PARAMS)
4028          *
4029          * Callback_returns:
4030          * Return 0 from the callback to signal success, return non-zero to
4031          * abort transfer.
4032          */
4033         @property void onProgress(int delegate(size_t dlTotal, size_t dlNow,
4034                                                size_t ulTotal, size_t ulNow) callback);
4035     }
4036 
4037     /**
4038         Setter for the sender's email address.
4039     */
mailFrom()4040     @property void mailFrom()(const(char)[] sender)
4041     {
4042         assert(!sender.empty, "Sender must not be empty");
4043         p.curl.set(CurlOption.mail_from, sender);
4044     }
4045 
4046     /**
4047         Setter for the recipient email addresses.
4048     */
mailTo()4049     void mailTo()(const(char)[][] recipients...)
4050     {
4051         assert(!recipients.empty, "Recipient must not be empty");
4052         curl_slist* recipients_list = null;
4053         foreach (recipient; recipients)
4054         {
4055             recipients_list =
4056                 Curl.curl.slist_append(recipients_list,
4057                                   recipient.tempCString().buffPtr);
4058         }
4059         p.curl.set(CurlOption.mail_rcpt, recipients_list);
4060     }
4061 
4062     /**
4063         Sets the message body text.
4064     */
4065 
message(string msg)4066     @property void message(string msg)
4067     {
4068         p.message = msg;
4069     }
4070 }
4071 
4072 /++
4073     Exception thrown on errors in std.net.curl functions.
4074 +/
4075 class CurlException : Exception
4076 {
4077     /++
4078         Params:
4079             msg  = The message for the exception.
4080             file = The file where the exception occurred.
4081             line = The line number where the exception occurred.
4082             next = The previous exception in the chain of exceptions, if any.
4083       +/
4084     @safe pure nothrow
4085     this(string msg,
4086          string file = __FILE__,
4087          size_t line = __LINE__,
4088          Throwable next = null)
4089     {
4090         super(msg, file, line, next);
4091     }
4092 }
4093 
4094 /++
4095     Exception thrown on timeout errors in std.net.curl functions.
4096 +/
4097 class CurlTimeoutException : CurlException
4098 {
4099     /++
4100         Params:
4101             msg  = The message for the exception.
4102             file = The file where the exception occurred.
4103             line = The line number where the exception occurred.
4104             next = The previous exception in the chain of exceptions, if any.
4105       +/
4106     @safe pure nothrow
4107     this(string msg,
4108          string file = __FILE__,
4109          size_t line = __LINE__,
4110          Throwable next = null)
4111     {
4112         super(msg, file, line, next);
4113     }
4114 }
4115 
4116 /++
4117     Exception thrown on HTTP request failures, e.g. 404 Not Found.
4118 +/
4119 class HTTPStatusException : CurlException
4120 {
4121     /++
4122         Params:
4123             status = The HTTP status code.
4124             msg  = The message for the exception.
4125             file = The file where the exception occurred.
4126             line = The line number where the exception occurred.
4127             next = The previous exception in the chain of exceptions, if any.
4128       +/
4129     @safe pure nothrow
4130     this(int status,
4131          string msg,
4132          string file = __FILE__,
4133          size_t line = __LINE__,
4134          Throwable next = null)
4135     {
4136         super(msg, file, line, next);
4137         this.status = status;
4138     }
4139 
4140     immutable int status; /// The HTTP status code
4141 }
4142 
4143 /// Equal to $(REF CURLcode, etc,c,curl)
4144 alias CurlCode = CURLcode;
4145 
4146 import std.typecons : Flag, Yes, No;
4147 /// Flag to specify whether or not an exception is thrown on error.
4148 alias ThrowOnError = Flag!"throwOnError";
4149 
4150 private struct CurlAPI
4151 {
4152     static struct API
4153     {
4154     extern(C):
4155         import core.stdc.config : c_long;
4156         CURLcode function(c_long flags) global_init;
4157         void function() global_cleanup;
4158         curl_version_info_data * function(CURLversion) version_info;
4159         CURL* function() easy_init;
4160         CURLcode function(CURL *curl, CURLoption option,...) easy_setopt;
4161         CURLcode function(CURL *curl) easy_perform;
4162         CURLcode function(CURL *curl, CURLINFO info,...) easy_getinfo;
4163         CURL* function(CURL *curl) easy_duphandle;
4164         char* function(CURLcode) easy_strerror;
4165         CURLcode function(CURL *handle, int bitmask) easy_pause;
4166         void function(CURL *curl) easy_cleanup;
4167         curl_slist* function(curl_slist *, char *) slist_append;
4168         void function(curl_slist *) slist_free_all;
4169     }
4170     __gshared API _api;
4171     __gshared void* _handle;
4172 
instanceCurlAPI4173     static ref API instance() @property
4174     {
4175         import std.concurrency : initOnce;
4176         initOnce!_handle(loadAPI());
4177         return _api;
4178     }
4179 
loadAPICurlAPI4180     static void* loadAPI()
4181     {
4182         version (Posix)
4183         {
4184             import core.sys.posix.dlfcn : dlsym, dlopen, dlclose, RTLD_LAZY;
4185             alias loadSym = dlsym;
4186         }
4187         else version (Windows)
4188         {
4189             import core.sys.windows.windows : GetProcAddress, GetModuleHandleA,
4190                 LoadLibraryA;
4191             alias loadSym = GetProcAddress;
4192         }
4193         else
4194             static assert(0, "unimplemented");
4195 
4196         void* handle;
4197         version (Posix)
4198             handle = dlopen(null, RTLD_LAZY);
4199         else version (Windows)
4200             handle = GetModuleHandleA(null);
4201         assert(handle !is null);
4202 
4203         // try to load curl from the executable to allow static linking
4204         if (loadSym(handle, "curl_global_init") is null)
4205         {
4206             import std.format : format;
4207             version (Posix)
4208                 dlclose(handle);
4209 
4210             version (OSX)
4211                 static immutable names = ["libcurl.4.dylib"];
4212             else version (Posix)
4213             {
4214                 static immutable names = ["libcurl.so", "libcurl.so.4",
4215                 "libcurl-gnutls.so.4", "libcurl-nss.so.4", "libcurl.so.3"];
4216             }
4217             else version (Windows)
4218                 static immutable names = ["libcurl.dll", "curl.dll"];
4219 
4220             foreach (name; names)
4221             {
4222                 version (Posix)
4223                     handle = dlopen(name.ptr, RTLD_LAZY);
4224                 else version (Windows)
4225                     handle = LoadLibraryA(name.ptr);
4226                 if (handle !is null) break;
4227             }
4228 
4229             enforce!CurlException(handle !is null, "Failed to load curl, tried %(%s, %).".format(names));
4230         }
4231 
4232         foreach (i, FP; typeof(API.tupleof))
4233         {
4234             enum name = __traits(identifier, _api.tupleof[i]);
4235             auto p = enforce!CurlException(loadSym(handle, "curl_"~name),
4236                                            "Couldn't load curl_"~name~" from libcurl.");
4237             _api.tupleof[i] = cast(FP) p;
4238         }
4239 
4240         enforce!CurlException(!_api.global_init(CurlGlobal.all),
4241                               "Failed to initialize libcurl");
4242 
4243         static extern(C) void cleanup()
4244         {
4245             if (_handle is null) return;
4246             _api.global_cleanup();
4247             version (Posix)
4248             {
4249                 import core.sys.posix.dlfcn : dlclose;
4250                 dlclose(_handle);
4251             }
4252             else version (Windows)
4253             {
4254                 import core.sys.windows.windows : FreeLibrary;
4255                 FreeLibrary(_handle);
4256             }
4257             else
4258                 static assert(0, "unimplemented");
4259             _api = API.init;
4260             _handle = null;
4261         }
4262 
4263         import core.stdc.stdlib : atexit;
4264         atexit(&cleanup);
4265 
4266         return handle;
4267     }
4268 }
4269 
4270 /**
4271   Wrapper to provide a better interface to libcurl than using the plain C API.
4272   It is recommended to use the $(D HTTP)/$(D FTP) etc. structs instead unless
4273   raw access to libcurl is needed.
4274 
4275   Warning: This struct uses interior pointers for callbacks. Only allocate it
4276   on the stack if you never move or copy it. This also means passing by reference
4277   when passing Curl to other functions. Otherwise always allocate on
4278   the heap.
4279 */
4280 struct Curl
4281 {
4282     alias OutData = void[];
4283     alias InData = ubyte[];
4284     private bool _stopped;
4285 
curl()4286     private static auto ref curl() @property { return CurlAPI.instance; }
4287 
4288     // A handle should not be used by two threads simultaneously
4289     private CURL* handle;
4290 
4291     // May also return $(D CURL_READFUNC_ABORT) or $(D CURL_READFUNC_PAUSE)
4292     private size_t delegate(OutData) _onSend;
4293     private size_t delegate(InData) _onReceive;
4294     private void delegate(in char[]) _onReceiveHeader;
4295     private CurlSeek delegate(long,CurlSeekPos) _onSeek;
4296     private int delegate(curl_socket_t,CurlSockType) _onSocketOption;
4297     private int delegate(size_t dltotal, size_t dlnow,
4298                          size_t ultotal, size_t ulnow) _onProgress;
4299 
4300     alias requestPause = CurlReadFunc.pause;
4301     alias requestAbort = CurlReadFunc.abort;
4302 
4303     /**
4304        Initialize the instance by creating a working curl handle.
4305     */
initialize()4306     void initialize()
4307     {
4308         enforce!CurlException(!handle, "Curl instance already initialized");
4309         handle = curl.easy_init();
4310         enforce!CurlException(handle, "Curl instance couldn't be initialized");
4311         _stopped = false;
4312         set(CurlOption.nosignal, 1);
4313     }
4314 
4315     ///
stopped()4316     @property bool stopped() const
4317     {
4318         return _stopped;
4319     }
4320 
4321     /**
4322        Duplicate this handle.
4323 
4324        The new handle will have all options set as the one it was duplicated
4325        from. An exception to this is that all options that cannot be shared
4326        across threads are reset thereby making it safe to use the duplicate
4327        in a new thread.
4328     */
dup()4329     Curl dup()
4330     {
4331         Curl copy;
4332         copy.handle = curl.easy_duphandle(handle);
4333         copy._stopped = false;
4334 
4335         with (CurlOption) {
4336             auto tt = AliasSeq!(file, writefunction, writeheader,
4337                 headerfunction, infile, readfunction, ioctldata, ioctlfunction,
4338                 seekdata, seekfunction, sockoptdata, sockoptfunction,
4339                 opensocketdata, opensocketfunction, progressdata,
4340                 progressfunction, debugdata, debugfunction, interleavedata,
4341                 interleavefunction, chunk_data, chunk_bgn_function,
4342                 chunk_end_function, fnmatch_data, fnmatch_function, cookiejar, postfields);
4343 
4344             foreach (option; tt)
4345                 copy.clear(option);
4346         }
4347 
4348         // The options are only supported by libcurl when it has been built
4349         // against certain versions of OpenSSL - if your libcurl uses an old
4350         // OpenSSL, or uses an entirely different SSL engine, attempting to
4351         // clear these normally will raise an exception
4352         copy.clearIfSupported(CurlOption.ssl_ctx_function);
4353         copy.clearIfSupported(CurlOption.ssh_keydata);
4354 
4355         // Enable for curl version > 7.21.7
4356         static if (LIBCURL_VERSION_MAJOR >= 7 &&
4357                    LIBCURL_VERSION_MINOR >= 21 &&
4358                    LIBCURL_VERSION_PATCH >= 7)
4359         {
4360             copy.clear(CurlOption.closesocketdata);
4361             copy.clear(CurlOption.closesocketfunction);
4362         }
4363 
4364         copy.set(CurlOption.nosignal, 1);
4365 
4366         // copy.clear(CurlOption.ssl_ctx_data); Let ssl function be shared
4367         // copy.clear(CurlOption.ssh_keyfunction); Let key function be shared
4368 
4369         /*
4370           Allow sharing of conv functions
4371           copy.clear(CurlOption.conv_to_network_function);
4372           copy.clear(CurlOption.conv_from_network_function);
4373           copy.clear(CurlOption.conv_from_utf8_function);
4374         */
4375 
4376         return copy;
4377     }
4378 
_check(CurlCode code)4379     private void _check(CurlCode code)
4380     {
4381         enforce!CurlTimeoutException(code != CurlError.operation_timedout,
4382                                        errorString(code));
4383 
4384         enforce!CurlException(code == CurlError.ok,
4385                                 errorString(code));
4386     }
4387 
errorString(CurlCode code)4388     private string errorString(CurlCode code)
4389     {
4390         import core.stdc.string : strlen;
4391         import std.format : format;
4392 
4393         auto msgZ = curl.easy_strerror(code);
4394         // doing the following (instead of just using std.conv.to!string) avoids 1 allocation
4395         return format("%s on handle %s", msgZ[0 .. strlen(msgZ)], handle);
4396     }
4397 
4398     private void throwOnStopped(string message = null)
4399     {
4400         auto def = "Curl instance called after being cleaned up";
4401         enforce!CurlException(!stopped,
4402                                 message == null ? def : message);
4403     }
4404 
4405     /**
4406         Stop and invalidate this curl instance.
4407         Warning: Do not call this from inside a callback handler e.g. $(D onReceive).
4408     */
shutdown()4409     void shutdown()
4410     {
4411         throwOnStopped();
4412         _stopped = true;
4413         curl.easy_cleanup(this.handle);
4414         this.handle = null;
4415     }
4416 
4417     /**
4418        Pausing and continuing transfers.
4419     */
pause(bool sendingPaused,bool receivingPaused)4420     void pause(bool sendingPaused, bool receivingPaused)
4421     {
4422         throwOnStopped();
4423         _check(curl.easy_pause(this.handle,
4424                                (sendingPaused ? CurlPause.send_cont : CurlPause.send) |
4425                                (receivingPaused ? CurlPause.recv_cont : CurlPause.recv)));
4426     }
4427 
4428     /**
4429        Set a string curl option.
4430        Params:
4431        option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation
4432        value = The string
4433     */
set(CurlOption option,const (char)[]value)4434     void set(CurlOption option, const(char)[] value)
4435     {
4436         throwOnStopped();
4437         _check(curl.easy_setopt(this.handle, option, value.tempCString().buffPtr));
4438     }
4439 
4440     /**
4441        Set a long curl option.
4442        Params:
4443        option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation
4444        value = The long
4445     */
set(CurlOption option,long value)4446     void set(CurlOption option, long value)
4447     {
4448         throwOnStopped();
4449         _check(curl.easy_setopt(this.handle, option, value));
4450     }
4451 
4452     /**
4453        Set a void* curl option.
4454        Params:
4455        option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation
4456        value = The pointer
4457     */
set(CurlOption option,void * value)4458     void set(CurlOption option, void* value)
4459     {
4460         throwOnStopped();
4461         _check(curl.easy_setopt(this.handle, option, value));
4462     }
4463 
4464     /**
4465        Clear a pointer option.
4466        Params:
4467        option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation
4468     */
clear(CurlOption option)4469     void clear(CurlOption option)
4470     {
4471         throwOnStopped();
4472         _check(curl.easy_setopt(this.handle, option, null));
4473     }
4474 
4475     /**
4476        Clear a pointer option. Does not raise an exception if the underlying
4477        libcurl does not support the option. Use sparingly.
4478        Params:
4479        option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation
4480     */
clearIfSupported(CurlOption option)4481     void clearIfSupported(CurlOption option)
4482     {
4483         throwOnStopped();
4484         auto rval = curl.easy_setopt(this.handle, option, null);
4485         if (rval != CurlError.unknown_option && rval != CurlError.not_built_in)
4486             _check(rval);
4487     }
4488 
4489     /**
4490        perform the curl request by doing the HTTP,FTP etc. as it has
4491        been setup beforehand.
4492 
4493        Params:
4494        throwOnError = whether to throw an exception or return a CurlCode on error
4495     */
4496     CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError)
4497     {
4498         throwOnStopped();
4499         CurlCode code = curl.easy_perform(this.handle);
4500         if (throwOnError)
4501             _check(code);
4502         return code;
4503     }
4504 
4505     /**
4506        Get the various timings like name lookup time, total time, connect time etc.
4507        The timed category is passed through the timing parameter while the timing
4508        value is stored at val. The value is usable only if res is equal to
4509        $(D etc.c.curl.CurlError.ok).
4510     */
getTiming(CurlInfo timing,ref double val)4511     CurlCode getTiming(CurlInfo timing, ref double val)
4512     {
4513         CurlCode code;
4514         code = curl.easy_getinfo(handle, timing, &val);
4515         return code;
4516     }
4517 
4518     /**
4519       * The event handler that receives incoming data.
4520       *
4521       * Params:
4522       * callback = the callback that receives the $(D ubyte[]) data.
4523       * Be sure to copy the incoming data and not store
4524       * a slice.
4525       *
4526       * Returns:
4527       * The callback returns the incoming bytes read. If not the entire array is
4528       * the request will abort.
4529       * The special value HTTP.pauseRequest can be returned in order to pause the
4530       * current request.
4531       *
4532       * Example:
4533       * ----
4534       * import std.net.curl, std.stdio;
4535       * Curl curl;
4536       * curl.initialize();
4537       * curl.set(CurlOption.url, "http://dlang.org");
4538       * curl.onReceive = (ubyte[] data) { writeln("Got data", to!(const(char)[])(data)); return data.length;};
4539       * curl.perform();
4540       * ----
4541       */
onReceive(size_t delegate (InData)callback)4542     @property void onReceive(size_t delegate(InData) callback)
4543     {
4544         _onReceive = (InData id)
4545         {
4546             throwOnStopped("Receive callback called on cleaned up Curl instance");
4547             return callback(id);
4548         };
4549         set(CurlOption.file, cast(void*) &this);
4550         set(CurlOption.writefunction, cast(void*) &Curl._receiveCallback);
4551     }
4552 
4553     /**
4554       * The event handler that receives incoming headers for protocols
4555       * that uses headers.
4556       *
4557       * Params:
4558       * callback = the callback that receives the header string.
4559       * Make sure the callback copies the incoming params if
4560       * it needs to store it because they are references into
4561       * the backend and may very likely change.
4562       *
4563       * Example:
4564       * ----
4565       * import std.net.curl, std.stdio;
4566       * Curl curl;
4567       * curl.initialize();
4568       * curl.set(CurlOption.url, "http://dlang.org");
4569       * curl.onReceiveHeader = (in char[] header) { writeln(header); };
4570       * curl.perform();
4571       * ----
4572       */
onReceiveHeader(void delegate (in char[])callback)4573     @property void onReceiveHeader(void delegate(in char[]) callback)
4574     {
4575         _onReceiveHeader = (in char[] od)
4576         {
4577             throwOnStopped("Receive header callback called on "~
4578                            "cleaned up Curl instance");
4579             callback(od);
4580         };
4581         set(CurlOption.writeheader, cast(void*) &this);
4582         set(CurlOption.headerfunction,
4583             cast(void*) &Curl._receiveHeaderCallback);
4584     }
4585 
4586     /**
4587       * The event handler that gets called when data is needed for sending.
4588       *
4589       * Params:
4590       * callback = the callback that has a $(D void[]) buffer to be filled
4591       *
4592       * Returns:
4593       * The callback returns the number of elements in the buffer that have been
4594       * filled and are ready to send.
4595       * The special value $(D Curl.abortRequest) can be returned in
4596       * order to abort the current request.
4597       * The special value $(D Curl.pauseRequest) can be returned in order to
4598       * pause the current request.
4599       *
4600       * Example:
4601       * ----
4602       * import std.net.curl;
4603       * Curl curl;
4604       * curl.initialize();
4605       * curl.set(CurlOption.url, "http://dlang.org");
4606       *
4607       * string msg = "Hello world";
4608       * curl.onSend = (void[] data)
4609       * {
4610       *     auto m = cast(void[]) msg;
4611       *     size_t length = m.length > data.length ? data.length : m.length;
4612       *     if (length == 0) return 0;
4613       *     data[0 .. length] = m[0 .. length];
4614       *     msg = msg[length..$];
4615       *     return length;
4616       * };
4617       * curl.perform();
4618       * ----
4619       */
onSend(size_t delegate (OutData)callback)4620     @property void onSend(size_t delegate(OutData) callback)
4621     {
4622         _onSend = (OutData od)
4623         {
4624             throwOnStopped("Send callback called on cleaned up Curl instance");
4625             return callback(od);
4626         };
4627         set(CurlOption.infile, cast(void*) &this);
4628         set(CurlOption.readfunction, cast(void*) &Curl._sendCallback);
4629     }
4630 
4631     /**
4632       * The event handler that gets called when the curl backend needs to seek
4633       * the data to be sent.
4634       *
4635       * Params:
4636       * callback = the callback that receives a seek offset and a seek position
4637       *            $(REF CurlSeekPos, etc,c,curl)
4638       *
4639       * Returns:
4640       * The callback returns the success state of the seeking
4641       * $(REF CurlSeek, etc,c,curl)
4642       *
4643       * Example:
4644       * ----
4645       * import std.net.curl;
4646       * Curl curl;
4647       * curl.initialize();
4648       * curl.set(CurlOption.url, "http://dlang.org");
4649       * curl.onSeek = (long p, CurlSeekPos sp)
4650       * {
4651       *     return CurlSeek.cantseek;
4652       * };
4653       * curl.perform();
4654       * ----
4655       */
onSeek(CurlSeek delegate (long,CurlSeekPos)callback)4656     @property void onSeek(CurlSeek delegate(long, CurlSeekPos) callback)
4657     {
4658         _onSeek = (long ofs, CurlSeekPos sp)
4659         {
4660             throwOnStopped("Seek callback called on cleaned up Curl instance");
4661             return callback(ofs, sp);
4662         };
4663         set(CurlOption.seekdata, cast(void*) &this);
4664         set(CurlOption.seekfunction, cast(void*) &Curl._seekCallback);
4665     }
4666 
4667     /**
4668       * The event handler that gets called when the net socket has been created
4669       * but a $(D connect()) call has not yet been done. This makes it possible to set
4670       * misc. socket options.
4671       *
4672       * Params:
4673       * callback = the callback that receives the socket and socket type
4674       * $(REF CurlSockType, etc,c,curl)
4675       *
4676       * Returns:
4677       * Return 0 from the callback to signal success, return 1 to signal error
4678       * and make curl close the socket
4679       *
4680       * Example:
4681       * ----
4682       * import std.net.curl;
4683       * Curl curl;
4684       * curl.initialize();
4685       * curl.set(CurlOption.url, "http://dlang.org");
4686       * curl.onSocketOption = delegate int(curl_socket_t s, CurlSockType t) { /+ do stuff +/ };
4687       * curl.perform();
4688       * ----
4689       */
onSocketOption(int delegate (curl_socket_t,CurlSockType)callback)4690     @property void onSocketOption(int delegate(curl_socket_t,
4691                                                CurlSockType) callback)
4692     {
4693         _onSocketOption = (curl_socket_t sock, CurlSockType st)
4694         {
4695             throwOnStopped("Socket option callback called on "~
4696                            "cleaned up Curl instance");
4697             return callback(sock, st);
4698         };
4699         set(CurlOption.sockoptdata, cast(void*) &this);
4700         set(CurlOption.sockoptfunction,
4701             cast(void*) &Curl._socketOptionCallback);
4702     }
4703 
4704     /**
4705       * The event handler that gets called to inform of upload/download progress.
4706       *
4707       * Params:
4708       * callback = the callback that receives the (total bytes to download,
4709       * currently downloaded bytes, total bytes to upload, currently uploaded
4710       * bytes).
4711       *
4712       * Returns:
4713       * Return 0 from the callback to signal success, return non-zero to abort
4714       * transfer
4715       *
4716       * Example:
4717       * ----
4718       * import std.net.curl;
4719       * Curl curl;
4720       * curl.initialize();
4721       * curl.set(CurlOption.url, "http://dlang.org");
4722       * curl.onProgress = delegate int(size_t dltotal, size_t dlnow, size_t ultotal, size_t uln)
4723       * {
4724       *     writeln("Progress: downloaded bytes ", dlnow, " of ", dltotal);
4725       *     writeln("Progress: uploaded bytes ", ulnow, " of ", ultotal);
4726       *     curl.perform();
4727       * };
4728       * ----
4729       */
onProgress(int delegate (size_t dlTotal,size_t dlNow,size_t ulTotal,size_t ulNow)callback)4730     @property void onProgress(int delegate(size_t dlTotal,
4731                                            size_t dlNow,
4732                                            size_t ulTotal,
4733                                            size_t ulNow) callback)
4734     {
4735         _onProgress = (size_t dlt, size_t dln, size_t ult, size_t uln)
4736         {
4737             throwOnStopped("Progress callback called on cleaned "~
4738                            "up Curl instance");
4739             return callback(dlt, dln, ult, uln);
4740         };
4741         set(CurlOption.noprogress, 0);
4742         set(CurlOption.progressdata, cast(void*) &this);
4743         set(CurlOption.progressfunction, cast(void*) &Curl._progressCallback);
4744     }
4745 
4746     // Internal C callbacks to register with libcurl
4747     extern (C) private static
_receiveCallback(const char * str,size_t size,size_t nmemb,void * ptr)4748     size_t _receiveCallback(const char* str,
4749                             size_t size, size_t nmemb, void* ptr)
4750     {
4751         auto b = cast(Curl*) ptr;
4752         if (b._onReceive != null)
4753             return b._onReceive(cast(InData)(str[0 .. size*nmemb]));
4754         return size*nmemb;
4755     }
4756 
4757     extern (C) private static
_receiveHeaderCallback(const char * str,size_t size,size_t nmemb,void * ptr)4758     size_t _receiveHeaderCallback(const char* str,
4759                                   size_t size, size_t nmemb, void* ptr)
4760     {
4761         import std.string : chomp;
4762 
4763         auto b = cast(Curl*) ptr;
4764         auto s = str[0 .. size*nmemb].chomp();
4765         if (b._onReceiveHeader != null)
4766             b._onReceiveHeader(s);
4767 
4768         return size*nmemb;
4769     }
4770 
4771     extern (C) private static
_sendCallback(char * str,size_t size,size_t nmemb,void * ptr)4772     size_t _sendCallback(char *str, size_t size, size_t nmemb, void *ptr)
4773     {
4774         Curl* b = cast(Curl*) ptr;
4775         auto a = cast(void[]) str[0 .. size*nmemb];
4776         if (b._onSend == null)
4777             return 0;
4778         return b._onSend(a);
4779     }
4780 
4781     extern (C) private static
_seekCallback(void * ptr,curl_off_t offset,int origin)4782     int _seekCallback(void *ptr, curl_off_t offset, int origin)
4783     {
4784         auto b = cast(Curl*) ptr;
4785         if (b._onSeek == null)
4786             return CurlSeek.cantseek;
4787 
4788         // origin: CurlSeekPos.set/current/end
4789         // return: CurlSeek.ok/fail/cantseek
4790         return b._onSeek(cast(long) offset, cast(CurlSeekPos) origin);
4791     }
4792 
4793     extern (C) private static
_socketOptionCallback(void * ptr,curl_socket_t curlfd,curlsocktype purpose)4794     int _socketOptionCallback(void *ptr,
4795                               curl_socket_t curlfd, curlsocktype purpose)
4796     {
4797         auto b = cast(Curl*) ptr;
4798         if (b._onSocketOption == null)
4799             return 0;
4800 
4801         // return: 0 ok, 1 fail
4802         return b._onSocketOption(curlfd, cast(CurlSockType) purpose);
4803     }
4804 
4805     extern (C) private static
_progressCallback(void * ptr,double dltotal,double dlnow,double ultotal,double ulnow)4806     int _progressCallback(void *ptr,
4807                           double dltotal, double dlnow,
4808                           double ultotal, double ulnow)
4809     {
4810         auto b = cast(Curl*) ptr;
4811         if (b._onProgress == null)
4812             return 0;
4813 
4814         // return: 0 ok, 1 fail
4815         return b._onProgress(cast(size_t) dltotal, cast(size_t) dlnow,
4816                              cast(size_t) ultotal, cast(size_t) ulnow);
4817     }
4818 
4819 }
4820 
4821 // Internal messages send between threads.
4822 // The data is wrapped in this struct in order to ensure that
4823 // other std.concurrency.receive calls does not pick up our messages
4824 // by accident.
CurlMessage(T)4825 private struct CurlMessage(T)
4826 {
4827     public T data;
4828 }
4829 
4830 private static CurlMessage!T curlMessage(T)(T data)
4831 {
4832     return CurlMessage!T(data);
4833 }
4834 
4835 // Pool of to be used for reusing buffers
Pool(Data)4836 private struct Pool(Data)
4837 {
4838     private struct Entry
4839     {
4840         Data data;
4841         Entry* next;
4842     }
4843     private Entry*  root;
4844     private Entry* freeList;
4845 
4846     @safe @property bool empty()
4847     {
4848         return root == null;
4849     }
4850 
4851     @safe nothrow void push(Data d)
4852     {
4853         if (freeList == null)
4854         {
4855             // Allocate new Entry since there is no one
4856             // available in the freeList
4857             freeList = new Entry;
4858         }
4859         freeList.data = d;
4860         Entry* oldroot = root;
4861         root = freeList;
4862         freeList = freeList.next;
4863         root.next = oldroot;
4864     }
4865 
4866     @safe Data pop()
4867     {
4868         enforce!Exception(root != null, "pop() called on empty pool");
4869         auto d = root.data;
4870         auto n = root.next;
4871         root.next = freeList;
4872         freeList = root;
4873         root = n;
4874         return d;
4875     }
4876 }
4877 
4878 // Shared function for reading incoming chunks of data and
4879 // sending the to a parent thread
4880 private static size_t _receiveAsyncChunks(ubyte[] data, ref ubyte[] outdata,
4881                                           Pool!(ubyte[]) freeBuffers,
4882                                           ref ubyte[] buffer, Tid fromTid,
4883                                           ref bool aborted)
4884 {
4885     immutable datalen = data.length;
4886 
4887     // Copy data to fill active buffer
4888     while (!data.empty)
4889     {
4890 
4891         // Make sure a buffer is present
4892         while ( outdata.empty && freeBuffers.empty)
4893         {
4894             // Active buffer is invalid and there are no
4895             // available buffers in the pool. Wait for buffers
4896             // to return from main thread in order to reuse
4897             // them.
4898             receive((immutable(ubyte)[] buf)
4899                     {
4900                         buffer = cast(ubyte[]) buf;
4901                         outdata = buffer[];
4902                     },
4903                     (bool flag) { aborted = true; }
4904                     );
4905             if (aborted) return cast(size_t) 0;
4906         }
4907         if (outdata.empty)
4908         {
4909             buffer = freeBuffers.pop();
4910             outdata = buffer[];
4911         }
4912 
4913         // Copy data
4914         auto copyBytes = outdata.length < data.length ?
4915             outdata.length : data.length;
4916 
4917         outdata[0 .. copyBytes] = data[0 .. copyBytes];
4918         outdata = outdata[copyBytes..$];
4919         data = data[copyBytes..$];
4920 
4921         if (outdata.empty)
4922             fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer));
4923     }
4924 
4925     return datalen;
4926 }
4927 
4928 // ditto
_finalizeAsyncChunks(ubyte[]outdata,ref ubyte[]buffer,Tid fromTid)4929 private static void _finalizeAsyncChunks(ubyte[] outdata, ref ubyte[] buffer,
4930                                          Tid fromTid)
4931 {
4932     if (!outdata.empty)
4933     {
4934         // Resize the last buffer
4935         buffer.length = buffer.length - outdata.length;
4936         fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer));
4937     }
4938 }
4939 
4940 
4941 // Shared function for reading incoming lines of data and sending the to a
4942 // parent thread
_receiveAsyncLines(Terminator,Unit)4943 private static size_t _receiveAsyncLines(Terminator, Unit)
4944     (const(ubyte)[] data, ref EncodingScheme encodingScheme,
4945      bool keepTerminator, Terminator terminator,
4946      ref const(ubyte)[] leftOverBytes, ref bool bufferValid,
4947      ref Pool!(Unit[]) freeBuffers, ref Unit[] buffer,
4948      Tid fromTid, ref bool aborted)
4949 {
4950     import std.format : format;
4951 
4952     immutable datalen = data.length;
4953 
4954     // Terminator is specified and buffers should be resized as determined by
4955     // the terminator
4956 
4957     // Copy data to active buffer until terminator is found.
4958 
4959     // Decode as many lines as possible
4960     while (true)
4961     {
4962 
4963         // Make sure a buffer is present
4964         while (!bufferValid && freeBuffers.empty)
4965         {
4966             // Active buffer is invalid and there are no available buffers in
4967             // the pool. Wait for buffers to return from main thread in order to
4968             // reuse them.
4969             receive((immutable(Unit)[] buf)
4970                     {
4971                         buffer = cast(Unit[]) buf;
4972                         buffer.length = 0;
4973                         buffer.assumeSafeAppend();
4974                         bufferValid = true;
4975                     },
4976                     (bool flag) { aborted = true; }
4977                     );
4978             if (aborted) return cast(size_t) 0;
4979         }
4980         if (!bufferValid)
4981         {
4982             buffer = freeBuffers.pop();
4983             bufferValid = true;
4984         }
4985 
4986         // Try to read a line from left over bytes from last onReceive plus the
4987         // newly received bytes.
4988         try
4989         {
4990             if (decodeLineInto(leftOverBytes, data, buffer,
4991                                encodingScheme, terminator))
4992             {
4993                 if (keepTerminator)
4994                 {
4995                     fromTid.send(thisTid,
4996                                  curlMessage(cast(immutable(Unit)[])buffer));
4997                 }
4998                 else
4999                 {
5000                     static if (isArray!Terminator)
5001                         fromTid.send(thisTid,
5002                                      curlMessage(cast(immutable(Unit)[])
5003                                              buffer[0..$-terminator.length]));
5004                     else
5005                         fromTid.send(thisTid,
5006                                      curlMessage(cast(immutable(Unit)[])
5007                                              buffer[0..$-1]));
5008                 }
5009                 bufferValid = false;
5010             }
5011             else
5012             {
5013                 // Could not decode an entire line. Save
5014                 // bytes left in data for next call to
5015                 // onReceive. Can be up to a max of 4 bytes.
5016                 enforce!CurlException(data.length <= 4,
5017                                         format(
5018                                         "Too many bytes left not decoded %s"~
5019                                         " > 4. Maybe the charset specified in"~
5020                                         " headers does not match "~
5021                                         "the actual content downloaded?",
5022                                         data.length));
5023                 leftOverBytes ~= data;
5024                 break;
5025             }
5026         }
5027         catch (CurlException ex)
5028         {
5029             prioritySend(fromTid, cast(immutable(CurlException))ex);
5030             return cast(size_t) 0;
5031         }
5032     }
5033     return datalen;
5034 }
5035 
5036 // ditto
5037 private static
_finalizeAsyncLines(Unit)5038 void _finalizeAsyncLines(Unit)(bool bufferValid, Unit[] buffer, Tid fromTid)
5039 {
5040     if (bufferValid && buffer.length != 0)
5041         fromTid.send(thisTid, curlMessage(cast(immutable(Unit)[])buffer[0..$]));
5042 }
5043 
5044 
5045 // Spawn a thread for handling the reading of incoming data in the
5046 // background while the delegate is executing.  This will optimize
5047 // throughput by allowing simultaneous input (this struct) and
5048 // output (e.g. AsyncHTTPLineOutputRange).
5049 private static void _spawnAsync(Conn, Unit, Terminator = void)()
5050 {
5051     Tid fromTid = receiveOnly!Tid();
5052 
5053     // Get buffer to read into
5054     Pool!(Unit[]) freeBuffers;  // Free list of buffer objects
5055 
5056     // Number of bytes filled into active buffer
5057     Unit[] buffer;
5058     bool aborted = false;
5059 
5060     EncodingScheme encodingScheme;
5061     static if ( !is(Terminator == void))
5062     {
5063         // Only lines reading will receive a terminator
5064         const terminator = receiveOnly!Terminator();
5065         const keepTerminator = receiveOnly!bool();
5066 
5067         // max number of bytes to carry over from an onReceive
5068         // callback. This is 4 because it is the max code units to
5069         // decode a code point in the supported encodings.
5070         auto leftOverBytes =  new const(ubyte)[4];
5071         leftOverBytes.length = 0;
5072         auto bufferValid = false;
5073     }
5074     else
5075     {
5076         Unit[] outdata;
5077     }
5078 
5079     // no move semantic available in std.concurrency ie. must use casting.
5080     auto connDup = cast(CURL*) receiveOnly!ulong();
5081     auto client = Conn();
5082     client.p.curl.handle = connDup;
5083 
5084     // receive a method for both ftp and http but just use it for http
5085     auto method = receiveOnly!(HTTP.Method)();
5086 
5087     client.onReceive = (ubyte[] data)
5088     {
5089         // If no terminator is specified the chunk size is fixed.
5090         static if ( is(Terminator == void) )
5091             return _receiveAsyncChunks(data, outdata, freeBuffers, buffer,
5092                                        fromTid, aborted);
5093         else
5094             return _receiveAsyncLines(data, encodingScheme,
5095                                       keepTerminator, terminator, leftOverBytes,
5096                                       bufferValid, freeBuffers, buffer,
5097                                       fromTid, aborted);
5098     };
5099 
5100     static if ( is(Conn == HTTP) )
5101     {
5102         client.method = method;
5103         // register dummy header handler
5104         client.onReceiveHeader = (in char[] key, in char[] value)
5105         {
5106             if (key == "content-type")
5107                 encodingScheme = EncodingScheme.create(client.p.charset);
5108         };
5109     }
5110     else
5111     {
5112         encodingScheme = EncodingScheme.create(client.encoding);
5113     }
5114 
5115     // Start the request
5116     CurlCode code;
5117     try
5118     {
5119         code = client.perform(No.throwOnError);
5120     }
catch(Exception ex)5121     catch (Exception ex)
5122     {
5123         prioritySend(fromTid, cast(immutable(Exception)) ex);
5124         fromTid.send(thisTid, curlMessage(true)); // signal done
5125         return;
5126     }
5127 
5128     if (code != CurlError.ok)
5129     {
5130         if (aborted && (code == CurlError.aborted_by_callback ||
5131                         code == CurlError.write_error))
5132         {
5133             fromTid.send(thisTid, curlMessage(true)); // signal done
5134             return;
5135         }
5136         prioritySend(fromTid, cast(immutable(CurlException))
5137                      new CurlException(client.p.curl.errorString(code)));
5138 
5139         fromTid.send(thisTid, curlMessage(true)); // signal done
5140         return;
5141     }
5142 
5143     // Send remaining data that is not a full chunk size
5144     static if ( is(Terminator == void) )
5145         _finalizeAsyncChunks(outdata, buffer, fromTid);
5146     else
5147         _finalizeAsyncLines(bufferValid, buffer, fromTid);
5148 
5149     fromTid.send(thisTid, curlMessage(true)); // signal done
5150 }
5151