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