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