xref: /reactos/base/applications/network/dwnl/dwnl.c (revision 0f92924a)
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