xref: /reactos/dll/win32/qmgr/file.c (revision aad80191)
1 /*
2  * Queue Manager (BITS) File
3  *
4  * Copyright 2007, 2008 Google (Roy Shea, Dan Hipschman)
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <stdarg.h>
22 
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "winreg.h"
27 #include "winhttp.h"
28 #define COBJMACROS
29 #include "qmgr.h"
30 #include "wine/debug.h"
31 
32 WINE_DEFAULT_DEBUG_CHANNEL(qmgr);
33 
34 static inline BackgroundCopyFileImpl *impl_from_IBackgroundCopyFile2(
35     IBackgroundCopyFile2 *iface)
36 {
37     return CONTAINING_RECORD(iface, BackgroundCopyFileImpl, IBackgroundCopyFile2_iface);
38 }
39 
40 static HRESULT WINAPI BackgroundCopyFile_QueryInterface(
41     IBackgroundCopyFile2 *iface,
42     REFIID riid,
43     void **obj)
44 {
45     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
46 
47     TRACE("(%p)->(%s %p)\n", file, debugstr_guid(riid), obj);
48 
49     if (IsEqualGUID(riid, &IID_IUnknown) ||
50         IsEqualGUID(riid, &IID_IBackgroundCopyFile) ||
51         IsEqualGUID(riid, &IID_IBackgroundCopyFile2))
52     {
53         *obj = iface;
54     }
55     else
56     {
57         *obj = NULL;
58         return E_NOINTERFACE;
59     }
60 
61     IBackgroundCopyFile2_AddRef(iface);
62     return S_OK;
63 }
64 
65 static ULONG WINAPI BackgroundCopyFile_AddRef(
66     IBackgroundCopyFile2 *iface)
67 {
68     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
69     ULONG ref = InterlockedIncrement(&file->ref);
70     TRACE("(%p)->(%d)\n", file, ref);
71     return ref;
72 }
73 
74 static ULONG WINAPI BackgroundCopyFile_Release(
75     IBackgroundCopyFile2 *iface)
76 {
77     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
78     ULONG ref = InterlockedDecrement(&file->ref);
79 
80     TRACE("(%p)->(%d)\n", file, ref);
81 
82     if (ref == 0)
83     {
84         IBackgroundCopyJob3_Release(&file->owner->IBackgroundCopyJob3_iface);
85         HeapFree(GetProcessHeap(), 0, file->info.LocalName);
86         HeapFree(GetProcessHeap(), 0, file->info.RemoteName);
87         HeapFree(GetProcessHeap(), 0, file);
88     }
89 
90     return ref;
91 }
92 
93 /* Get the remote name of a background copy file */
94 static HRESULT WINAPI BackgroundCopyFile_GetRemoteName(
95     IBackgroundCopyFile2 *iface,
96     LPWSTR *pVal)
97 {
98     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
99 
100     TRACE("(%p)->(%p)\n", file, pVal);
101 
102     return return_strval(file->info.RemoteName, pVal);
103 }
104 
105 static HRESULT WINAPI BackgroundCopyFile_GetLocalName(
106     IBackgroundCopyFile2 *iface,
107     LPWSTR *pVal)
108 {
109     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
110 
111     TRACE("(%p)->(%p)\n", file, pVal);
112 
113     return return_strval(file->info.LocalName, pVal);
114 }
115 
116 static HRESULT WINAPI BackgroundCopyFile_GetProgress(
117     IBackgroundCopyFile2 *iface,
118     BG_FILE_PROGRESS *pVal)
119 {
120     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
121 
122     TRACE("(%p)->(%p)\n", file, pVal);
123 
124     EnterCriticalSection(&file->owner->cs);
125     *pVal = file->fileProgress;
126     LeaveCriticalSection(&file->owner->cs);
127 
128     return S_OK;
129 }
130 
131 static HRESULT WINAPI BackgroundCopyFile_GetFileRanges(
132     IBackgroundCopyFile2 *iface,
133     DWORD *RangeCount,
134     BG_FILE_RANGE **Ranges)
135 {
136     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
137     FIXME("(%p)->(%p %p)\n", file, RangeCount, Ranges);
138     return E_NOTIMPL;
139 }
140 
141 static HRESULT WINAPI BackgroundCopyFile_SetRemoteName(
142     IBackgroundCopyFile2 *iface,
143     LPCWSTR Val)
144 {
145     BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
146     FIXME("(%p)->(%s)\n", file, debugstr_w(Val));
147     return E_NOTIMPL;
148 }
149 
150 static const IBackgroundCopyFile2Vtbl BackgroundCopyFile2Vtbl =
151 {
152     BackgroundCopyFile_QueryInterface,
153     BackgroundCopyFile_AddRef,
154     BackgroundCopyFile_Release,
155     BackgroundCopyFile_GetRemoteName,
156     BackgroundCopyFile_GetLocalName,
157     BackgroundCopyFile_GetProgress,
158     BackgroundCopyFile_GetFileRanges,
159     BackgroundCopyFile_SetRemoteName
160 };
161 
162 HRESULT BackgroundCopyFileConstructor(BackgroundCopyJobImpl *owner,
163                                       LPCWSTR remoteName, LPCWSTR localName,
164                                       BackgroundCopyFileImpl **file)
165 {
166     BackgroundCopyFileImpl *This;
167 
168     TRACE("(%s, %s, %p)\n", debugstr_w(remoteName), debugstr_w(localName), file);
169 
170     This = HeapAlloc(GetProcessHeap(), 0, sizeof *This);
171     if (!This)
172         return E_OUTOFMEMORY;
173 
174     This->info.RemoteName = strdupW(remoteName);
175     if (!This->info.RemoteName)
176     {
177         HeapFree(GetProcessHeap(), 0, This);
178         return E_OUTOFMEMORY;
179     }
180 
181     This->info.LocalName = strdupW(localName);
182     if (!This->info.LocalName)
183     {
184         HeapFree(GetProcessHeap(), 0, This->info.RemoteName);
185         HeapFree(GetProcessHeap(), 0, This);
186         return E_OUTOFMEMORY;
187     }
188 
189     This->IBackgroundCopyFile2_iface.lpVtbl = &BackgroundCopyFile2Vtbl;
190     This->ref = 1;
191 
192     This->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
193     This->fileProgress.BytesTransferred = 0;
194     This->fileProgress.Completed = FALSE;
195     This->owner = owner;
196     This->read_size = 0;
197     This->tempFileName[0] = 0;
198     IBackgroundCopyJob3_AddRef(&owner->IBackgroundCopyJob3_iface);
199 
200     *file = This;
201     return S_OK;
202 }
203 
204 static HRESULT hresult_from_http_response(DWORD code)
205 {
206     switch (code)
207     {
208     case 200: return S_OK;
209     case 400: return BG_E_HTTP_ERROR_400;
210     case 401: return BG_E_HTTP_ERROR_401;
211     case 404: return BG_E_HTTP_ERROR_404;
212     case 407: return BG_E_HTTP_ERROR_407;
213     case 414: return BG_E_HTTP_ERROR_414;
214     case 501: return BG_E_HTTP_ERROR_501;
215     case 503: return BG_E_HTTP_ERROR_503;
216     case 504: return BG_E_HTTP_ERROR_504;
217     case 505: return BG_E_HTTP_ERROR_505;
218     default:
219         FIXME("unhandled response code %u\n", code);
220         return S_OK;
221     }
222 }
223 
224 static void CALLBACK progress_callback_http(HINTERNET handle, DWORD_PTR context, DWORD status,
225                                             LPVOID buf, DWORD buflen)
226 {
227     BackgroundCopyFileImpl *file = (BackgroundCopyFileImpl *)context;
228     BackgroundCopyJobImpl *job = file->owner;
229 
230     TRACE("%p, %p, %x, %p, %u\n", handle, file, status, buf, buflen);
231 
232     switch (status)
233     {
234     case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
235     {
236         DWORD code, len, size;
237 
238         size = sizeof(code);
239         if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER,
240                                 NULL, &code, &size, NULL))
241         {
242             if ((job->error.code = hresult_from_http_response(code)))
243             {
244                 EnterCriticalSection(&job->cs);
245 
246                 job->error.context = BG_ERROR_CONTEXT_REMOTE_FILE;
247                 if (job->error.file) IBackgroundCopyFile2_Release(job->error.file);
248                 job->error.file = &file->IBackgroundCopyFile2_iface;
249                 IBackgroundCopyFile2_AddRef(job->error.file);
250 
251                 LeaveCriticalSection(&job->cs);
252                 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
253             }
254             else
255             {
256                 EnterCriticalSection(&job->cs);
257 
258                 job->error.context = 0;
259                 if (job->error.file)
260                 {
261                     IBackgroundCopyFile2_Release(job->error.file);
262                     job->error.file = NULL;
263                 }
264 
265                 LeaveCriticalSection(&job->cs);
266             }
267         }
268         size = sizeof(len);
269         if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER,
270                                 NULL, &len, &size, NULL))
271         {
272             file->fileProgress.BytesTotal = len;
273         }
274         break;
275     }
276     case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
277     {
278         file->read_size = buflen;
279         break;
280     }
281     case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
282     {
283         WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *)buf;
284         job->error.code = HRESULT_FROM_WIN32(result->dwError);
285         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
286         break;
287     }
288     default: break;
289     }
290 
291     SetEvent(job->wait);
292 }
293 
294 static DWORD wait_for_completion(BackgroundCopyJobImpl *job)
295 {
296     HANDLE handles[2] = {job->wait, job->cancel};
297     DWORD error = ERROR_SUCCESS;
298 
299     switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE))
300     {
301     case WAIT_OBJECT_0:
302         break;
303 
304     case WAIT_OBJECT_0 + 1:
305         error = ERROR_CANCELLED;
306         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_CANCELLED);
307         break;
308 
309     default:
310         error = GetLastError();
311         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
312         break;
313     }
314 
315     return error;
316 }
317 
318 static UINT target_from_index(UINT index)
319 {
320     switch (index)
321     {
322     case 0: return WINHTTP_AUTH_TARGET_SERVER;
323     case 1: return WINHTTP_AUTH_TARGET_PROXY;
324     default:
325         ERR("unhandled index %u\n", index);
326         break;
327     }
328     return 0;
329 }
330 
331 static UINT scheme_from_index(UINT index)
332 {
333     switch (index)
334     {
335     case 0: return WINHTTP_AUTH_SCHEME_BASIC;
336     case 1: return WINHTTP_AUTH_SCHEME_NTLM;
337     case 2: return WINHTTP_AUTH_SCHEME_PASSPORT;
338     case 3: return WINHTTP_AUTH_SCHEME_DIGEST;
339     case 4: return WINHTTP_AUTH_SCHEME_NEGOTIATE;
340     default:
341         ERR("unhandled index %u\n", index);
342         break;
343     }
344     return 0;
345 }
346 
347 static BOOL set_request_credentials(HINTERNET req, BackgroundCopyJobImpl *job)
348 {
349     UINT i, j;
350 
351     for (i = 0; i < BG_AUTH_TARGET_PROXY; i++)
352     {
353         UINT target = target_from_index(i);
354         for (j = 0; j < BG_AUTH_SCHEME_PASSPORT; j++)
355         {
356             UINT scheme = scheme_from_index(j);
357             const WCHAR *username = job->http_options.creds[i][j].Credentials.Basic.UserName;
358             const WCHAR *password = job->http_options.creds[i][j].Credentials.Basic.Password;
359 
360             if (!username) continue;
361             if (!WinHttpSetCredentials(req, target, scheme, username, password, NULL)) return FALSE;
362         }
363     }
364     return TRUE;
365 }
366 
367 static BOOL transfer_file_http(BackgroundCopyFileImpl *file, URL_COMPONENTSW *uc,
368                                const WCHAR *tmpfile)
369 {
370     BackgroundCopyJobImpl *job = file->owner;
371     HANDLE handle;
372     HINTERNET ses, con = NULL, req = NULL;
373     DWORD flags = (uc->nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
374     char buf[4096];
375     BOOL ret = FALSE;
376     DWORD written;
377 
378     transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_CONNECTING);
379 
380     if (!(ses = WinHttpOpen(NULL, 0, NULL, NULL, WINHTTP_FLAG_ASYNC))) return FALSE;
381     WinHttpSetStatusCallback(ses, progress_callback_http, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
382     if (!WinHttpSetOption(ses, WINHTTP_OPTION_CONTEXT_VALUE, &file, sizeof(file))) goto done;
383 
384     if (!(con = WinHttpConnect(ses, uc->lpszHostName, uc->nPort, 0))) goto done;
385     if (!(req = WinHttpOpenRequest(con, NULL, uc->lpszUrlPath, NULL, NULL, NULL, flags))) goto done;
386     if (!set_request_credentials(req, job)) goto done;
387 
388     if (!(WinHttpSendRequest(req, job->http_options.headers, ~0u, NULL, 0, 0, (DWORD_PTR)file))) goto done;
389     if (wait_for_completion(job) || FAILED(job->error.code)) goto done;
390 
391     if (!(WinHttpReceiveResponse(req, NULL))) goto done;
392     if (wait_for_completion(job) || FAILED(job->error.code)) goto done;
393 
394     transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_TRANSFERRING);
395 
396     handle = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
397     if (handle == INVALID_HANDLE_VALUE) goto done;
398 
399     for (;;)
400     {
401         file->read_size = 0;
402         if (!(ret = WinHttpReadData(req, buf, sizeof(buf), NULL))) break;
403         if (wait_for_completion(job) || FAILED(job->error.code))
404         {
405             ret = FALSE;
406             break;
407         }
408         if (!file->read_size) break;
409         if (!(ret = WriteFile(handle, buf, file->read_size, &written, NULL))) break;
410 
411         EnterCriticalSection(&job->cs);
412         file->fileProgress.BytesTransferred += file->read_size;
413         job->jobProgress.BytesTransferred += file->read_size;
414         LeaveCriticalSection(&job->cs);
415     }
416 
417     CloseHandle(handle);
418 
419 done:
420     WinHttpCloseHandle(req);
421     WinHttpCloseHandle(con);
422     WinHttpCloseHandle(ses);
423     if (!ret && !transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_ERROR))
424         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
425 
426     SetEvent(job->done);
427     return ret;
428 }
429 
430 static DWORD CALLBACK progress_callback_local(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred,
431                                               LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred,
432                                               DWORD streamNum, DWORD reason, HANDLE srcFile,
433                                               HANDLE dstFile, LPVOID obj)
434 {
435     BackgroundCopyFileImpl *file = obj;
436     BackgroundCopyJobImpl *job = file->owner;
437     ULONG64 diff;
438 
439     EnterCriticalSection(&job->cs);
440     diff = (file->fileProgress.BytesTotal == BG_SIZE_UNKNOWN
441             ? totalTransferred.QuadPart
442             : totalTransferred.QuadPart - file->fileProgress.BytesTransferred);
443     file->fileProgress.BytesTotal = totalSize.QuadPart;
444     file->fileProgress.BytesTransferred = totalTransferred.QuadPart;
445     job->jobProgress.BytesTransferred += diff;
446     LeaveCriticalSection(&job->cs);
447 
448     return (job->state == BG_JOB_STATE_TRANSFERRING
449             ? PROGRESS_CONTINUE
450             : PROGRESS_CANCEL);
451 }
452 
453 static BOOL transfer_file_local(BackgroundCopyFileImpl *file, const WCHAR *tmpname)
454 {
455     static const WCHAR fileW[] = {'f','i','l','e',':','/','/',0};
456     BackgroundCopyJobImpl *job = file->owner;
457     const WCHAR *ptr;
458     BOOL ret;
459 
460     transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSFERRING);
461 
462     if (strlenW(file->info.RemoteName) > 7 && !memicmpW(file->info.RemoteName, fileW, 7))
463         ptr = file->info.RemoteName + 7;
464     else
465         ptr = file->info.RemoteName;
466 
467     if (!(ret = CopyFileExW(ptr, tmpname, progress_callback_local, file, NULL, 0)))
468     {
469         WARN("Local file copy failed: error %u\n", GetLastError());
470         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
471     }
472 
473     SetEvent(job->done);
474     return ret;
475 }
476 
477 BOOL processFile(BackgroundCopyFileImpl *file, BackgroundCopyJobImpl *job)
478 {
479     static const WCHAR prefix[] = {'B','I','T', 0};
480     WCHAR tmpDir[MAX_PATH], tmpName[MAX_PATH];
481     WCHAR host[MAX_PATH];
482     URL_COMPONENTSW uc;
483     BOOL ret;
484 
485     if (!GetTempPathW(MAX_PATH, tmpDir))
486     {
487         ERR("Couldn't create temp file name: %d\n", GetLastError());
488         /* Guessing on what state this should give us */
489         transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
490         return FALSE;
491     }
492 
493     if (!GetTempFileNameW(tmpDir, prefix, 0, tmpName))
494     {
495         ERR("Couldn't create temp file: %d\n", GetLastError());
496         /* Guessing on what state this should give us */
497         transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
498         return FALSE;
499     }
500 
501     EnterCriticalSection(&job->cs);
502     file->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
503     file->fileProgress.BytesTransferred = 0;
504     file->fileProgress.Completed = FALSE;
505     LeaveCriticalSection(&job->cs);
506 
507     TRACE("Transferring: %s -> %s -> %s\n",
508           debugstr_w(file->info.RemoteName),
509           debugstr_w(tmpName),
510           debugstr_w(file->info.LocalName));
511 
512     uc.dwStructSize      = sizeof(uc);
513     uc.nScheme           = 0;
514     uc.lpszScheme        = NULL;
515     uc.dwSchemeLength    = 0;
516     uc.lpszUserName      = NULL;
517     uc.dwUserNameLength  = 0;
518     uc.lpszPassword      = NULL;
519     uc.dwPasswordLength  = 0;
520     uc.lpszHostName      = host;
521     uc.dwHostNameLength  = sizeof(host)/sizeof(host[0]);
522     uc.nPort             = 0;
523     uc.lpszUrlPath       = NULL;
524     uc.dwUrlPathLength   = ~0u;
525     uc.lpszExtraInfo     = NULL;
526     uc.dwExtraInfoLength = 0;
527     ret = WinHttpCrackUrl(file->info.RemoteName, 0, 0, &uc);
528     if (!ret)
529     {
530         TRACE("WinHttpCrackUrl failed, trying local file copy\n");
531         if (!transfer_file_local(file, tmpName)) WARN("local transfer failed\n");
532     }
533     else if (!transfer_file_http(file, &uc, tmpName)) WARN("HTTP transfer failed\n");
534 
535     if (transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_QUEUED) ||
536         transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_QUEUED))
537     {
538         lstrcpyW(file->tempFileName, tmpName);
539 
540         EnterCriticalSection(&job->cs);
541         file->fileProgress.Completed = TRUE;
542         job->jobProgress.FilesTransferred++;
543         LeaveCriticalSection(&job->cs);
544 
545         return TRUE;
546     }
547     else
548     {
549         DeleteFileW(tmpName);
550         return FALSE;
551     }
552 }
553