1 #define COBJMACROS
2 #include <urlmon.h>
3 #include <wininet.h>
4 #include <wchar.h>
5 #include <strsafe.h>
6 #include <conutils.h>
7
8 #include "resource.h"
9
10 /* FIXME: add correct definitions to urlmon.idl */
11 #ifdef UNICODE
12 #define URLDownloadToFile URLDownloadToFileW
13 #else
14 #define URLDownloadToFile URLDownloadToFileA
15 #endif
16
17 #define DWNL_E_LASTERROR 0
18 #define DWNL_E_NEEDTARGETFILENAME -1
19 #define DWNL_E_UNSUPPORTEDSCHEME -2
20
21 typedef struct
22 {
23 const IBindStatusCallbackVtbl* lpIBindStatusCallbackVtbl;
24 LONG ref;
25 WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH + 1];
26 WCHAR szMimeType[128];
27 UINT64 Size;
28 UINT64 Progress;
29 UINT bResolving : 1;
30 UINT bConnecting : 1;
31 UINT bSendingReq : 1;
32 UINT bBeginTransfer : 1;
33 } CBindStatusCallback;
34
35 #define impl_to_interface(impl,iface) (struct iface *)(&(impl)->lp##iface##Vtbl)
36 #define interface_to_impl(instance,iface) ((CBindStatusCallback*)((ULONG_PTR)instance - FIELD_OFFSET(CBindStatusCallback,lp##iface##Vtbl)))
37
38 static void
CBindStatusCallback_Destroy(CBindStatusCallback * This)39 CBindStatusCallback_Destroy(CBindStatusCallback *This)
40 {
41 return;
42 }
43
44 static void
write_status(LPCWSTR lpFmt,...)45 write_status(LPCWSTR lpFmt, ...)
46 {
47 va_list args;
48 WCHAR szTxt[128];
49 CONSOLE_SCREEN_BUFFER_INFO csbi;
50
51 va_start(args, lpFmt);
52 StringCbVPrintfW(szTxt, sizeof(szTxt), lpFmt, args);
53 va_end(args);
54
55 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
56 {
57 ConPrintf(StdOut, L"\r%*.*s", -(csbi.dwSize.X - 1), csbi.dwSize.X - 1, szTxt);
58 }
59 else
60 {
61 ConPuts(StdOut, szTxt);
62 }
63 }
64
65 static void
CBindStatusCallback_UpdateProgress(CBindStatusCallback * This)66 CBindStatusCallback_UpdateProgress(CBindStatusCallback *This)
67 {
68 WCHAR szMessage[MAX_PATH];
69
70 /* FIXME: better output */
71 if (This->Size != 0)
72 {
73 UINT Percentage;
74
75 Percentage = (UINT)((This->Progress * 100) / This->Size);
76 if (Percentage > 99)
77 Percentage = 99;
78
79 LoadStringW(NULL, IDS_BYTES_DOWNLOADED_FULL, szMessage, ARRAYSIZE(szMessage));
80
81 write_status(szMessage, Percentage, This->Progress);
82 }
83 else
84 {
85 LoadStringW(NULL, IDS_BYTES_DOWNLOADED, szMessage, ARRAYSIZE(szMessage));
86
87 /* Unknown size */
88 write_status(szMessage, This->Progress);
89 }
90 }
91
92 static ULONG STDMETHODCALLTYPE
CBindStatusCallback_AddRef(IBindStatusCallback * iface)93 CBindStatusCallback_AddRef(IBindStatusCallback *iface)
94 {
95 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback);
96 ULONG ret;
97
98 ret = InterlockedIncrement((PLONG)&This->ref);
99 return ret;
100 }
101
102 static ULONG STDMETHODCALLTYPE
CBindStatusCallback_Release(IBindStatusCallback * iface)103 CBindStatusCallback_Release(IBindStatusCallback *iface)
104 {
105 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback);
106 ULONG ret;
107
108 ret = InterlockedDecrement((PLONG)&This->ref);
109 if (ret == 0)
110 {
111 CBindStatusCallback_Destroy(This);
112
113 HeapFree(GetProcessHeap(),
114 0,
115 This);
116 }
117
118 return ret;
119 }
120
121 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_QueryInterface(IBindStatusCallback * iface,REFIID iid,PVOID * pvObject)122 CBindStatusCallback_QueryInterface(IBindStatusCallback *iface,
123 REFIID iid,
124 PVOID *pvObject)
125 {
126 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback);
127
128 *pvObject = NULL;
129
130 if (IsEqualIID(iid,
131 &IID_IBindStatusCallback) ||
132 IsEqualIID(iid,
133 &IID_IUnknown))
134 {
135 *pvObject = impl_to_interface(This, IBindStatusCallback);
136 }
137 else
138 return E_NOINTERFACE;
139
140 CBindStatusCallback_AddRef(iface);
141 return S_OK;
142 }
143
144 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnStartBinding(IBindStatusCallback * iface,DWORD dwReserved,IBinding * pib)145 CBindStatusCallback_OnStartBinding(IBindStatusCallback *iface,
146 DWORD dwReserved,
147 IBinding* pib)
148 {
149 return E_NOTIMPL;
150 }
151
152 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_GetPriority(IBindStatusCallback * iface,LONG * pnPriority)153 CBindStatusCallback_GetPriority(IBindStatusCallback *iface,
154 LONG* pnPriority)
155 {
156 return E_NOTIMPL;
157 }
158
159 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnLowResource(IBindStatusCallback * iface,DWORD reserved)160 CBindStatusCallback_OnLowResource(IBindStatusCallback *iface,
161 DWORD reserved)
162 {
163 return E_NOTIMPL;
164 }
165
166 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnProgress(IBindStatusCallback * iface,ULONG ulProgress,ULONG ulProgressMax,ULONG ulStatusCode,LPCWSTR szStatusText)167 CBindStatusCallback_OnProgress(IBindStatusCallback *iface,
168 ULONG ulProgress,
169 ULONG ulProgressMax,
170 ULONG ulStatusCode,
171 LPCWSTR szStatusText)
172 {
173 CBindStatusCallback *This = interface_to_impl(iface, IBindStatusCallback);
174
175 switch (ulStatusCode)
176 {
177 case BINDSTATUS_FINDINGRESOURCE:
178 if (!This->bResolving)
179 {
180 wcscpy(This->szHostName, szStatusText);
181 This->bResolving = TRUE;
182
183 ConResPrintf(StdOut, IDS_RESOLVING, This->szHostName);
184 }
185 break;
186
187 case BINDSTATUS_CONNECTING:
188 This->bConnecting = TRUE;
189 This->bSendingReq = FALSE;
190 This->bBeginTransfer = FALSE;
191 This->szMimeType[0] = L'\0';
192 if (This->bResolving)
193 {
194 ConResPrintf(StdOut, IDS_DONE);
195 ConResPrintf(StdOut, IDS_CONNECTING_TO_FULL, This->szHostName, szStatusText);
196 }
197 else
198 {
199 ConResPrintf(StdOut, IDS_CONNECTING_TO, szStatusText);
200 }
201 break;
202
203 case BINDSTATUS_REDIRECTING:
204 This->bResolving = FALSE;
205 This->bConnecting = FALSE;
206 This->bSendingReq = FALSE;
207 This->bBeginTransfer = FALSE;
208 This->szMimeType[0] = L'\0';
209 ConResPrintf(StdOut, IDS_REDIRECTING_TO, szStatusText);
210 break;
211
212 case BINDSTATUS_SENDINGREQUEST:
213 This->bBeginTransfer = FALSE;
214 This->szMimeType[0] = L'\0';
215 if (This->bResolving || This->bConnecting)
216 ConResPrintf(StdOut, IDS_DONE);
217
218 if (!This->bSendingReq)
219 ConResPrintf(StdOut, IDS_SEND_REQUEST);
220
221 This->bSendingReq = TRUE;
222 break;
223
224 case BINDSTATUS_MIMETYPEAVAILABLE:
225 wcscpy(This->szMimeType, szStatusText);
226 break;
227
228 case BINDSTATUS_BEGINDOWNLOADDATA:
229 This->Progress = (UINT64)ulProgress;
230 This->Size = (UINT64)ulProgressMax;
231
232 if (This->bSendingReq)
233 ConResPrintf(StdOut, IDS_DONE);
234
235 if (!This->bBeginTransfer && This->Size != 0)
236 {
237 if (This->szMimeType[0] != L'\0')
238 ConResPrintf(StdOut, IDS_LENGTH_FULL, This->Size, This->szMimeType);
239 else
240 ConResPrintf(StdOut, IDS_LENGTH, This->Size);
241 }
242
243 ConPuts(StdOut, L"\n");
244
245 This->bBeginTransfer = TRUE;
246 break;
247
248 case BINDSTATUS_ENDDOWNLOADDATA:
249 ConResPrintf(StdOut, IDS_FILE_SAVED);
250 break;
251
252 case BINDSTATUS_DOWNLOADINGDATA:
253 This->Progress = (UINT64)ulProgress;
254 This->Size = (UINT64)ulProgressMax;
255
256 CBindStatusCallback_UpdateProgress(This);
257 break;
258 }
259
260 return S_OK;
261 }
262
263 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnStopBinding(IBindStatusCallback * iface,HRESULT hresult,LPCWSTR szError)264 CBindStatusCallback_OnStopBinding(IBindStatusCallback *iface,
265 HRESULT hresult,
266 LPCWSTR szError)
267 {
268 return E_NOTIMPL;
269 }
270
271 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_GetBindInfo(IBindStatusCallback * iface,DWORD * grfBINDF,BINDINFO * pbindinfo)272 CBindStatusCallback_GetBindInfo(IBindStatusCallback *iface,
273 DWORD* grfBINDF,
274 BINDINFO* pbindinfo)
275 {
276 return E_NOTIMPL;
277 }
278
279 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnDataAvailable(IBindStatusCallback * iface,DWORD grfBSCF,DWORD dwSize,FORMATETC * pformatetc,STGMEDIUM * pstgmed)280 CBindStatusCallback_OnDataAvailable(IBindStatusCallback *iface,
281 DWORD grfBSCF,
282 DWORD dwSize,
283 FORMATETC* pformatetc,
284 STGMEDIUM* pstgmed)
285 {
286 return E_NOTIMPL;
287 }
288
289 static HRESULT STDMETHODCALLTYPE
CBindStatusCallback_OnObjectAvailable(IBindStatusCallback * iface,REFIID riid,IUnknown * punk)290 CBindStatusCallback_OnObjectAvailable(IBindStatusCallback *iface,
291 REFIID riid,
292 IUnknown* punk)
293 {
294 return E_NOTIMPL;
295 }
296
297 static const struct IBindStatusCallbackVtbl vtblIBindStatusCallback =
298 {
299 CBindStatusCallback_QueryInterface,
300 CBindStatusCallback_AddRef,
301 CBindStatusCallback_Release,
302 CBindStatusCallback_OnStartBinding,
303 CBindStatusCallback_GetPriority,
304 CBindStatusCallback_OnLowResource,
305 CBindStatusCallback_OnProgress,
306 CBindStatusCallback_OnStopBinding,
307 CBindStatusCallback_GetBindInfo,
308 CBindStatusCallback_OnDataAvailable,
309 CBindStatusCallback_OnObjectAvailable,
310 };
311
312 static IBindStatusCallback *
CreateBindStatusCallback(void)313 CreateBindStatusCallback(void)
314 {
315 CBindStatusCallback *This;
316
317 This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This));
318 if (This == NULL)
319 return NULL;
320
321 This->lpIBindStatusCallbackVtbl = &vtblIBindStatusCallback;
322 This->ref = 1;
323
324 return impl_to_interface(This, IBindStatusCallback);
325 }
326
327
328 // ToDo: Show status, get file name from webserver, better error reporting
329
330 static int
get_display_url(IN LPURL_COMPONENTS purl,OUT LPWSTR szBuffer,IN PDWORD pdwBufferSize)331 get_display_url(IN LPURL_COMPONENTS purl,
332 OUT LPWSTR szBuffer,
333 IN PDWORD pdwBufferSize)
334 {
335 URL_COMPONENTS urlc;
336
337 /* Hide the password */
338 urlc = *purl;
339 urlc.lpszPassword = NULL;
340 urlc.dwPasswordLength = 0;
341
342 if (!InternetCreateUrl(&urlc, ICU_ESCAPE, szBuffer, pdwBufferSize))
343 return DWNL_E_LASTERROR;
344
345 return 1;
346 }
347
348 static int
download_file(IN LPCWSTR pszUrl,IN LPCWSTR pszFile OPTIONAL)349 download_file(IN LPCWSTR pszUrl,
350 IN LPCWSTR pszFile OPTIONAL)
351 {
352 WCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH + 1];
353 WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH + 1];
354 WCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH + 1];
355 WCHAR szPassWord[INTERNET_MAX_PASSWORD_LENGTH + 1];
356 WCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH + 1];
357 WCHAR szExtraInfo[INTERNET_MAX_PATH_LENGTH + 1];
358 WCHAR szUrl[INTERNET_MAX_URL_LENGTH + 1];
359 DWORD dwUrlLen;
360 LPTSTR pszFilePart;
361 URL_COMPONENTS urlc;
362 IBindStatusCallback *pbsc;
363 int iRet;
364 SYSTEMTIME sysTime;
365
366 if (pszFile != NULL && pszFile[0] == L'\0')
367 pszFile = NULL;
368
369 urlc.dwStructSize = sizeof(urlc);
370 urlc.lpszScheme = szScheme;
371 urlc.dwSchemeLength = sizeof(szScheme) / sizeof(szScheme[0]);
372 urlc.lpszHostName = szHostName;
373 urlc.dwHostNameLength = sizeof(szHostName) / sizeof(szHostName[0]);
374 urlc.lpszUserName = szUserName;
375 urlc.dwUserNameLength = sizeof(szUserName) / sizeof(szUserName[0]);
376 urlc.lpszPassword = szPassWord;
377 urlc.dwPasswordLength = sizeof(szPassWord) / sizeof(szPassWord[0]);
378 urlc.lpszUrlPath = szUrlPath;
379 urlc.dwUrlPathLength = sizeof(szUrlPath) / sizeof(szUrlPath[0]);
380 urlc.lpszExtraInfo = szExtraInfo;
381 urlc.dwExtraInfoLength = sizeof(szExtraInfo) / sizeof(szExtraInfo[0]);
382 if (!InternetCrackUrl(pszUrl, wcslen(pszUrl), ICU_ESCAPE, &urlc))
383 return DWNL_E_LASTERROR;
384
385 if (urlc.nScheme != INTERNET_SCHEME_FTP &&
386 urlc.nScheme != INTERNET_SCHEME_GOPHER &&
387 urlc.nScheme != INTERNET_SCHEME_HTTP &&
388 urlc.nScheme != INTERNET_SCHEME_HTTPS)
389 {
390 return DWNL_E_UNSUPPORTEDSCHEME;
391 }
392
393 if (urlc.nScheme == INTERNET_SCHEME_FTP && urlc.dwUserNameLength == 0 && urlc.dwPasswordLength == 0)
394 {
395 wcscpy(szUserName, L"anonymous");
396 urlc.dwUserNameLength = wcslen(szUserName);
397 }
398
399 /* FIXME: Get file name from server */
400 if (urlc.dwUrlPathLength == 0 && pszFile == NULL)
401 return DWNL_E_NEEDTARGETFILENAME;
402
403 pszFilePart = wcsrchr(szUrlPath, L'/');
404 if (pszFilePart != NULL)
405 pszFilePart++;
406
407 if (pszFilePart == NULL && pszFile == NULL)
408 return DWNL_E_NEEDTARGETFILENAME;
409
410 if (pszFile == NULL)
411 pszFile = pszFilePart;
412
413 if (urlc.dwUserNameLength == 0)
414 urlc.lpszUserName = NULL;
415
416 if (urlc.dwPasswordLength == 0)
417 urlc.lpszPassword = NULL;
418
419 /* Generate the URL to be displayed (without a password) */
420 dwUrlLen = sizeof(szUrl) / sizeof(szUrl[0]);
421 iRet = get_display_url(&urlc, szUrl, &dwUrlLen);
422 if (iRet <= 0)
423 return iRet;
424
425 GetLocalTime(&sysTime);
426
427 ConPrintf(StdOut, L"--%d-%02d-%02d %02d:%02d:%02d-- %s\n\t=> %s\n",
428 sysTime.wYear, sysTime.wMonth, sysTime.wDay,
429 sysTime.wHour, sysTime.wMinute, sysTime.wSecond,
430 szUrl, pszFile);
431
432 /* Generate the URL to download */
433 dwUrlLen = sizeof(szUrl) / sizeof(szUrl[0]);
434 if (!InternetCreateUrl(&urlc, ICU_ESCAPE, szUrl, &dwUrlLen))
435 return DWNL_E_LASTERROR;
436
437 pbsc = CreateBindStatusCallback();
438 if (pbsc == NULL)
439 return DWNL_E_LASTERROR;
440
441 if(!SUCCEEDED(URLDownloadToFile(NULL, szUrl, pszFile, 0, pbsc)))
442 {
443 IBindStatusCallback_Release(pbsc);
444 return DWNL_E_LASTERROR; /* FIXME */
445 }
446
447 IBindStatusCallback_Release(pbsc);
448 return 1;
449 }
450
451 static int
print_err(int iErr)452 print_err(int iErr)
453 {
454 write_status(L"");
455
456 if (iErr == DWNL_E_LASTERROR)
457 {
458 if (GetLastError() == ERROR_SUCCESS)
459 {
460 /* File not found */
461 ConResPrintf(StdErr, IDS_ERROR_DOWNLOAD);
462 }
463 else
464 {
465 /* Display last error code */
466 ConResPrintf(StdErr, IDS_ERROR_CODE, GetLastError());
467 }
468 }
469 else
470 {
471 switch (iErr)
472 {
473 case DWNL_E_NEEDTARGETFILENAME:
474 ConResPrintf(StdErr, IDS_ERROR_FILENAME);
475 break;
476
477 case DWNL_E_UNSUPPORTEDSCHEME:
478 ConResPrintf(StdErr, IDS_ERROR_PROTOCOL);
479 break;
480 }
481 }
482
483 return 1;
484 }
485
wmain(int argc,WCHAR ** argv)486 int wmain(int argc, WCHAR **argv)
487 {
488 int iErr, iRet = 0;
489
490 /* Initialize the Console Standard Streams */
491 ConInitStdStreams();
492
493 if(argc != 2 && argc != 3)
494 {
495 ConResPrintf(StdOut, IDS_USAGE);
496 return 2;
497 }
498
499 iErr = download_file(argv[1], argc == 3 ? argv[2] : NULL);
500 if (iErr <= 0)
501 iRet = print_err(iErr);
502
503 return iRet;
504 }
505