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