1 module dast.net.curl;
2 
3 import dast.net.curlapi : C = Curl, CurlInf, curl = curlAPI;
4 public import etc.c.curl : CurlError, CurlOption, CurlProto, CURL, CURLcode;
5 
6 struct Curl {
7 	C c;
8 	alias c this;
9 
10 	this(CURL* h) {
11 		c = C(h);
12 	}
13 
14 	this(C curl) {
15 		c = curl;
16 	}
17 
18 	@disable this();
19 
20 	@property {
21 		void connTimeout(long secs) => set(CurlOption.connecttimeout, secs);
22 
23 		void timeout(long secs) => set(CurlOption.timeout, secs);
24 
25 		void nobody(bool v) => set(CurlOption.nobody, v);
26 
27 		void maxredirs(long v) => set(CurlOption.maxredirs, v);
28 
29 		long contentLength() => c.get(CurlInf.contentlengthdownload);
30 	}
31 
32 private:
33 	void* ch;
34 }
35 
36 struct CurlSet {
37 	import etc.c.curl : CurlM, CurlMsg, CURLMsg;
38 
39 	this(Curl[] cs) {
40 		mh = curl.multi_init();
41 		_rc = 1;
42 		msgLeft = -1;
43 		if (mh) {
44 			foreach (c; cs)
45 				curl.multi_add_handle(mh, c.handle);
46 		}
47 	}
48 
49 	this(this) {
50 		++_rc;
51 	}
52 
53 	~this() {
54 		if (mh) {
55 			if (--_rc == 0)
56 				curl.multi_cleanup(mh);
57 		}
58 	}
59 
60 	@property {
61 		auto handle() => mh;
62 
63 		bool running() => _running > 0;
64 
65 		bool empty() {
66 			if (msgLeft < 0)
67 				msg = curl.multi_info_read(mh, &msgLeft);
68 			return msgLeft == 0;
69 		}
70 	}
71 
72 	auto tryWait(int timeout_ms = 1000) => curl.multi_wait(mh, null, 0, timeout_ms, null);
73 
74 	auto add(Curl c) => curl.multi_add_handle(mh, c.handle);
75 
76 	auto remove(Curl c) => curl.multi_remove_handle(mh, c.handle);
77 
78 	auto perform() => curl.multi_perform(mh, &_running);
79 
80 	auto run() {
81 		while (_running > 0) {
82 			auto rc = perform();
83 			if (rc != CurlM.call_multi_perform)
84 				return rc;
85 		}
86 		return CurlM.ok;
87 	}
88 
89 private:
90 	void* mh;
91 	CURLMsg* msg;
92 	uint _rc;
93 	int _running;
94 	int msgLeft;
95 }
96 
97 version (unittest) private void testFunc(alias f)(in char[] src, string expected) {
98 	char[256] buf = void;
99 	assert(f(src, buf) == expected.length);
100 	assert(buf[0 .. expected.length] == expected);
101 }
102 
103 // TODO: @nogc nothrow
104 
105 /// URI编码
106 int escapeTo(in char[] src, scope char[] dst) @trusted
107 in (src.length <= int.max) {
108 	import etc.c.curl,
109 	core.stdc.string;
110 
111 	dst[0 .. src.length] = src;
112 	dst[src.length] = 0;
113 	int len = -1;
114 	auto p = curl.easy_escape(null, dst.ptr, cast(int)src.length);
115 	if (p) {
116 		len = cast(int)strlen(p);
117 		if (len <= dst.length)
118 			dst[0 .. len] = p[0 .. len];
119 		else
120 			len = -1;
121 		curl.free(p);
122 	}
123 	return len;
124 }
125 
126 @safe unittest {
127 	alias test = testFunc!escapeTo;
128 	test("ab", "ab");
129 	test("a b", "a%20b");
130 }
131 
132 /// URI解码
133 int unescapeTo(in char[] src, scope char[] dst) @trusted
134 in (src.length <= int.max)
135 in (dst.length > src.length) {
136 	import etc.c.curl;
137 
138 	if (src.ptr !is dst.ptr)
139 		dst[0 .. src.length] = src;
140 	dst[src.length] = 0;
141 	int len = -1;
142 	auto p = curl.easy_unescape(null, dst.ptr, cast(int)src.length, &len);
143 	if (p) {
144 		dst[0 .. len] = p[0 .. len];
145 		curl.free(p);
146 	}
147 	return len;
148 }
149 
150 @safe unittest {
151 	alias test = testFunc!unescapeTo;
152 
153 	test("ab", "ab");
154 	test("a%20b", "a b");
155 }