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 }