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