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