1 /**
2  * ae.sys.net implementation using WinINet
3  * Note: Requires Windows.
4  *
5  * License:
6  *   This Source Code Form is subject to the terms of
7  *   the Mozilla Public License, v. 2.0. If a copy of
8  *   the MPL was not distributed with this file, You
9  *   can obtain one at http://mozilla.org/MPL/2.0/.
10  *
11  * Authors:
12  *   Vladimir Panteleev <vladimir@thecybershadow.net>
13  */
14 
15 module ae.sys.net.wininet;
16 version(Windows):
17 
18 import std.array;
19 import std.conv : to;
20 import std.exception;
21 import std.string;
22 import std.typecons : RefCounted;
23 
24 import ae.net.http.common : HttpRequest;
25 import ae.net.ietf.url;
26 import ae.sys.net;
27 import ae.sys.windows.dll;
28 import ae.sys.windows.exception;
29 import ae.utils.meta;
30 
31 import ae.sys.windows.imports;
32 mixin(importWin32!q{winbase});
33 mixin(importWin32!q{windef});
34 mixin(importWin32!q{wininet});
35 
36 class WinINetNetwork : Network
37 {
38 private:
39 	// Don't require wininet.lib
40 	mixin DynamicLoadMulti!("wininet.dll",
41 		HttpQueryInfoA,
42 		HttpOpenRequestA,
43 		HttpSendRequestA,
44 		InternetCloseHandle,
45 		InternetConnectA,
46 		InternetOpenA,
47 		InternetOpenUrlA,
48 		InternetReadFile,
49 	);
50 
51 protected:
52 	struct HNetImpl
53 	{
54 		HINTERNET hNet;
55 		alias hNet this;
56 		~this() { if (hNet != hNet.init) InternetCloseHandle(hNet); }
57 	}
58 	alias HNet = RefCounted!HNetImpl;
59 
60 	final static HNet open(DWORD flags = 0)
61 	{
62 		auto hNet = InternetOpenA("ae.sys.net.wininet", INTERNET_OPEN_TYPE_PRECONFIG, null, null, flags)
63 			.wenforce("InternetOpen");
64 		return HNet(hNet);
65 	}
66 
67 	final static HNet openUrl(ref HNet hNet, string url)
68 	{
69 		auto hUrl = InternetOpenUrlA(hNet, url.toStringz(), null, 0xFFFFFFFF, INTERNET_FLAG_RELOAD, 0)
70 			.wenforce("InternetOpenUrl");
71 		return HNet(hUrl);
72 	}
73 
74 	final static HNet connect(ref HNet hNet, string serverName, INTERNET_PORT port)
75 	{
76 		auto hCon = InternetConnectA(hNet, serverName.toStringz(), port, null, null, INTERNET_SERVICE_HTTP, 0, 0)
77 			.wenforce("InternetConnect");
78 		return HNet(hCon);
79 	}
80 
81 	final static HNet openRequest(ref HNet hCon, string method, string resource, DWORD flags = 0)
82 	{
83 		auto hReq = HttpOpenRequestA(hCon, method.toStringz(), resource.toStringz(), null, null, null, flags, 0)
84 			.wenforce("HttpOpenRequest");
85 		return HNet(hReq);
86 	}
87 
88 	final static void sendRequest(ref HNet hReq, string headers = null, in void[] optionalData = null)
89 	{
90 		HttpSendRequestA(hReq, headers.ptr, headers.length.to!DWORD, cast(void*)optionalData.ptr, optionalData.length.to!DWORD)
91 			.wenforce("HttpSendRequest");
92 	}
93 
94 	static ubyte[0x10000] buf = void;
95 
96 	final static void[][] httpQuery(ref HNet hUrl, uint infoLevel)
97 	{
98 		DWORD index = 0;
99 
100 		void[][] result;
101 		while (true)
102 		{
103 			DWORD size = buf.sizeof;
104 			if (HttpQueryInfoA(hUrl, infoLevel, buf.ptr, &size, &index))
105 			{
106 				if (size == buf.sizeof && (infoLevel & HTTP_QUERY_FLAG_NUMBER))
107 					size = DWORD.sizeof;
108 				result ~= buf[0..size].dup;
109 			}
110 			else
111 			if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
112 				return result;
113 			else
114 				wenforce(false, "HttpQueryInfo");
115 
116 			if (index == 0)
117 				return result;
118 		}
119 	}
120 
121 	final static void[] httpQueryOne(ref HNet hUrl, uint infoLevel)
122 	{
123 		auto results = hUrl.I!httpQuery(infoLevel);
124 		enforce(results.length <= 1, "Multiple results for HTTP info query");
125 		return results.length ? results[0] : null;
126 	}
127 
128 	final static string httpQueryString(ref HNet hUrl, uint infoLevel)
129 	{
130 		return cast(string)hUrl.I!httpQueryOne(infoLevel);
131 	}
132 
133 	final static uint httpQueryNumber(ref HNet hUrl, uint infoLevel)
134 	{
135 		DWORD[] results = cast(DWORD[])hUrl.I!httpQueryOne(infoLevel | HTTP_QUERY_FLAG_NUMBER);
136 		enforce(results.length, "No result for HTTP info query");
137 		return results[0];
138 	}
139 
140 	final static void doDownload(HNet hUrl, void delegate(ubyte[]) sink)
141 	{
142 		doDownload(hUrl, cast(void delegate(const ubyte[])) sink);
143 	}
144 
145 	final static void doDownload(HNet hUrl, void delegate(const ubyte[]) sink)
146 	{
147 		// Check HTTP status code
148 		auto statusCode = hUrl.I!httpQueryNumber(HTTP_QUERY_STATUS_CODE);
149 		if (statusCode != 200)
150 		{
151 			auto statusText = hUrl.I!httpQueryString(HTTP_QUERY_STATUS_TEXT);
152 			throw new Exception("Bad HTTP status code: %d (%s)".format(statusCode, statusText));
153 		}
154 
155 		// Get total file size
156 		DWORD bytesTotal = 0;
157 		try
158 			bytesTotal = hUrl.I!httpQueryNumber(HTTP_QUERY_CONTENT_LENGTH);
159 		catch (Exception e) {}
160 
161 		DWORD bytesReadTotal;
162 
163 		while (true)
164 		{
165 			DWORD bytesRead;
166 			InternetReadFile(hUrl, buf.ptr, buf.length, &bytesRead);
167 			if (bytesRead==0)
168 				break;
169 			sink(buf[0..bytesRead]);
170 			bytesReadTotal += bytesRead;
171 		}
172 
173 		enforce(!bytesTotal || bytesReadTotal == bytesTotal,
174 			"Failed to download the whole object (got %s out of %s bytes)".format(bytesReadTotal, bytesTotal));
175 	}
176 
177 	final static DWORD urlFlags(string url)
178 	{
179 		return url.startsWith("https://") ? INTERNET_FLAG_SECURE : 0;
180 	}
181 
182 public:
183 	override void downloadFile(string url, string target)
184 	{
185 		import std.stdio;
186 		auto f = File(target, "wb");
187 		auto hNet = open();
188 		doDownload(hNet.I!openUrl(url), &f.rawWrite!ubyte);
189 	}
190 
191 	override void[] getFile(string url)
192 	{
193 		auto result = appender!(ubyte[]);
194 		auto hNet = open();
195 		doDownload(hNet.I!openUrl(url), &result.put!(ubyte[]));
196 		return result.data;
197 	}
198 
199 	override void[] post(string url, in void[] data)
200 	{
201 		auto request = new HttpRequest(url);
202 
203 		auto hNet = open();
204 		auto hCon = hNet.I!connect(request.host, request.port);
205 		auto hReq = hCon.I!openRequest("POST", request.resource, urlFlags(url));
206 		hReq.I!sendRequest(null, data);
207 
208 		auto result = appender!(ubyte[]);
209 		doDownload(hReq, &result.put!(ubyte[]));
210 		return result.data;
211 	}
212 
213 	override bool urlOK(string url)
214 	{
215 		try
216 		{
217 			auto request = new HttpRequest(url);
218 
219 			auto hNet = open();
220 			auto hCon = hNet.I!connect(request.host, request.port);
221 			auto hReq = hCon.I!openRequest("HEAD", request.resource, urlFlags(url));
222 			hReq.I!sendRequest();
223 
224 			return hReq.I!httpQueryNumber(HTTP_QUERY_STATUS_CODE) == 200;
225 		}
226 		catch (Exception e)
227 			return false;
228 	}
229 
230 	override string resolveRedirect(string url)
231 	{
232 		auto request = new HttpRequest(url);
233 
234 		auto hNet = open(INTERNET_FLAG_NO_AUTO_REDIRECT);
235 		auto hCon = hNet.I!connect(request.host, request.port);
236 		auto hReq = hCon.I!openRequest("HEAD", request.resource, INTERNET_FLAG_NO_AUTO_REDIRECT | urlFlags(url));
237 		hReq.I!sendRequest();
238 
239 		auto location = hReq.I!httpQueryString(HTTP_QUERY_LOCATION);
240 		return location ? url.applyRelativeURL(location) : null;
241 	}
242 }
243 
244 static this()
245 {
246 	net = new WinINetNetwork();
247 }