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 <ae@cy.md> 13 */ 14 15 module ae.sys.net.wininet; 16 version(Windows): 17 18 import std.algorithm.iteration; 19 import std.algorithm.searching; 20 import std.array; 21 import std.conv : to; 22 import std.exception; 23 import std.range : dropOne; 24 import std.string; 25 import std.traits : OriginalType; 26 import std.typecons : RefCounted; 27 28 import ae.net.http.common : HttpRequest, HttpResponse, HttpStatusCode; 29 import ae.net.ietf.url; 30 import ae.sys.data; 31 import ae.sys.dataset; 32 import ae.sys.net; 33 import ae.sys.windows.dll; 34 import ae.sys.windows.exception; 35 import ae.utils.meta; 36 37 import ae.sys.windows.imports; 38 mixin(importWin32!q{winbase}); 39 mixin(importWin32!q{windef}); 40 mixin(importWin32!q{wininet}); 41 42 /// WinINet-based `Network` implementation. 43 class WinINetNetwork : Network 44 { 45 private: 46 // Don't require wininet.lib 47 mixin DynamicLoadMulti!("wininet.dll", 48 HttpQueryInfoA, 49 HttpOpenRequestA, 50 HttpSendRequestA, 51 InternetCloseHandle, 52 InternetConnectA, 53 InternetOpenA, 54 InternetOpenUrlA, 55 InternetReadFile, 56 ); 57 58 protected: 59 struct HNetImpl 60 { 61 HINTERNET hNet; 62 alias hNet this; 63 @disable this(this); 64 ~this() { if (hNet != hNet.init) InternetCloseHandle(hNet); } 65 } 66 alias HNet = RefCounted!HNetImpl; 67 68 final static HNet open(DWORD flags = 0) 69 { 70 auto hNet = InternetOpenA("ae.sys.net.wininet", INTERNET_OPEN_TYPE_PRECONFIG, null, null, flags) 71 .wenforce("InternetOpen"); 72 return HNet(hNet); 73 } 74 75 final static HNet openUrl(ref HNet hNet, string url) 76 { 77 auto hUrl = InternetOpenUrlA(hNet, url.toStringz(), null, 0xFFFFFFFF, INTERNET_FLAG_RELOAD, 0) 78 .wenforce("InternetOpenUrl"); 79 return HNet(hUrl); 80 } 81 82 final static HNet connect(ref HNet hNet, string serverName, INTERNET_PORT port) 83 { 84 auto hCon = InternetConnectA(hNet, serverName.toStringz(), port, null, null, INTERNET_SERVICE_HTTP, 0, 0) 85 .wenforce("InternetConnect"); 86 return HNet(hCon); 87 } 88 89 final static HNet openRequest(ref HNet hCon, string method, string resource, DWORD flags = 0) 90 { 91 auto hReq = HttpOpenRequestA(hCon, method.toStringz(), resource.toStringz(), null, null, null, flags, 0) 92 .wenforce("HttpOpenRequest"); 93 return HNet(hReq); 94 } 95 96 final static void sendRequest(ref HNet hReq, string headers = null, const(ubyte)[] optionalData = null) 97 { 98 HttpSendRequestA(hReq, headers.ptr, headers.length.to!DWORD, cast(void*)optionalData.ptr, optionalData.length.to!DWORD) 99 .wenforce("HttpSendRequest"); 100 } 101 102 static ubyte[0x10000] buf = void; 103 104 final static void[][] httpQuery(ref HNet hUrl, uint infoLevel) 105 { 106 DWORD index = 0; 107 108 void[][] result; 109 while (true) 110 { 111 DWORD size = buf.sizeof; 112 if (HttpQueryInfoA(hUrl, infoLevel, buf.ptr, &size, &index)) 113 { 114 if (size == buf.sizeof && (infoLevel & HTTP_QUERY_FLAG_NUMBER)) 115 size = DWORD.sizeof; 116 result ~= buf[0..size].dup; 117 } 118 else 119 if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND) 120 return result; 121 else 122 wenforce(false, "HttpQueryInfo"); 123 124 if (index == 0) 125 return result; 126 } 127 } 128 129 final static void[] httpQueryOne(ref HNet hUrl, uint infoLevel) 130 { 131 auto results = hUrl.I!httpQuery(infoLevel); 132 enforce(results.length <= 1, "Multiple results for HTTP info query"); 133 return results.length ? results[0] : null; 134 } 135 136 final static string httpQueryString(ref HNet hUrl, uint infoLevel) 137 { 138 return cast(string)hUrl.I!httpQueryOne(infoLevel); 139 } 140 141 final static uint httpQueryNumber(ref HNet hUrl, uint infoLevel) 142 { 143 DWORD[] results = cast(DWORD[])hUrl.I!httpQueryOne(infoLevel | HTTP_QUERY_FLAG_NUMBER); 144 enforce(results.length, "No result for HTTP info query"); 145 return results[0]; 146 } 147 148 final static void checkOK(HNet hUrl) 149 { 150 auto statusCode = hUrl.I!httpQueryNumber(HTTP_QUERY_STATUS_CODE); 151 if (statusCode != 200) 152 { 153 auto statusText = hUrl.I!httpQueryString(HTTP_QUERY_STATUS_TEXT); 154 throw new Exception("Bad HTTP status code: %d (%s)".format(statusCode, statusText)); 155 } 156 } 157 158 final static void doDownload(HNet hUrl, scope void delegate(in ubyte[]) sink) 159 { 160 // Get total file size 161 DWORD bytesTotal = 0; 162 try 163 bytesTotal = hUrl.I!httpQueryNumber(HTTP_QUERY_CONTENT_LENGTH); 164 catch (Exception e) {} 165 166 DWORD bytesReadTotal; 167 168 while (true) 169 { 170 DWORD bytesRead; 171 InternetReadFile(hUrl, buf.ptr, buf.length, &bytesRead); 172 if (bytesRead==0) 173 break; 174 sink(buf[0..bytesRead]); 175 bytesReadTotal += bytesRead; 176 } 177 178 enforce(!bytesTotal || bytesReadTotal == bytesTotal, 179 "Failed to download the whole object (got %s out of %s bytes)".format(bytesReadTotal, bytesTotal)); 180 } 181 182 final static DWORD urlFlags(string url) 183 { 184 return url.startsWith("https://") ? INTERNET_FLAG_SECURE : 0; 185 } 186 187 public: 188 override void downloadFile(string url, string target) 189 { 190 import std.stdio : File; 191 auto f = File(target, "wb"); 192 auto hNet = open(); 193 auto hReq = hNet.I!openUrl(url); 194 hReq.I!checkOK(); 195 hReq.I!doDownload(&f.rawWrite!ubyte); 196 } /// 197 198 override ubyte[] getFile(string url) 199 { 200 auto result = appender!(ubyte[]); 201 auto hNet = open(); 202 auto hReq = hNet.I!openUrl(url); 203 hReq.I!checkOK(); 204 hReq.I!doDownload(&result.put!(const(ubyte)[])); 205 return result.data; 206 } /// 207 208 override ubyte[] post(string url, const(ubyte)[] data) 209 { 210 auto request = new HttpRequest(url); 211 212 auto hNet = open(); 213 auto hCon = hNet.I!connect(request.host, request.port); 214 auto hReq = hCon.I!openRequest("POST", request.resource, urlFlags(url)); 215 hReq.I!sendRequest(null, data); 216 hReq.I!checkOK(); 217 218 auto result = appender!(ubyte[]); 219 hReq.I!doDownload(&result.put!(const(ubyte)[])); 220 return result.data; 221 } /// 222 223 override bool urlOK(string url) 224 { 225 try 226 { 227 auto request = new HttpRequest(url); 228 229 auto hNet = open(); 230 auto hCon = hNet.I!connect(request.host, request.port); 231 auto hReq = hCon.I!openRequest("HEAD", request.resource, urlFlags(url)); 232 hReq.I!sendRequest(); 233 234 return hReq.I!httpQueryNumber(HTTP_QUERY_STATUS_CODE) == 200; 235 } 236 catch (Exception e) 237 return false; 238 } /// 239 240 override string resolveRedirect(string url) 241 { 242 auto request = new HttpRequest(url); 243 244 auto hNet = open(INTERNET_FLAG_NO_AUTO_REDIRECT); 245 auto hCon = hNet.I!connect(request.host, request.port); 246 auto hReq = hCon.I!openRequest("HEAD", request.resource, INTERNET_FLAG_NO_AUTO_REDIRECT | urlFlags(url)); 247 hReq.I!sendRequest(); 248 249 auto location = hReq.I!httpQueryString(HTTP_QUERY_LOCATION); 250 return location ? url.applyRelativeURL(location) : null; 251 } /// 252 253 override HttpResponse httpRequest(HttpRequest request) 254 { 255 string requestHeaders; 256 foreach (name, value; request.headers) 257 requestHeaders ~= name ~ ": " ~ value ~ "\r\n"; 258 auto hNet = open(INTERNET_FLAG_NO_AUTO_REDIRECT); 259 auto hCon = hNet.I!connect(request.host, request.port); 260 auto hReq = hCon.I!openRequest(request.method, request.resource, 261 INTERNET_FLAG_NO_AUTO_REDIRECT | urlFlags(request.url)); 262 auto requestData = request.data.joinData; 263 hReq.I!sendRequest(requestHeaders, requestData.contents); 264 265 auto response = new HttpResponse; 266 response.status = cast(HttpStatusCode)hReq.I!httpQueryNumber(HTTP_QUERY_STATUS_CODE).to!(OriginalType!HttpStatusCode); 267 response.statusMessage = hReq.I!httpQueryString(HTTP_QUERY_STATUS_TEXT); 268 269 auto responseHeaders = hReq.I!httpQueryString(HTTP_QUERY_RAW_HEADERS); 270 foreach (header; responseHeaders.splitter('\0').dropOne) 271 if (header.length) 272 { 273 auto parts = header.findSplit(":"); 274 response.headers.add(parts[0].strip, parts[2].strip); 275 } 276 277 hReq.I!doDownload((scope const(ubyte)[] bytes) { response.data ~= Data(bytes); }); 278 return response; 279 } /// 280 } 281 282 static this() 283 { 284 net = new WinINetNetwork(); 285 }