[QMGR] Sync with Wine Staging 3.3. CORE-14434
[reactos.git] / dll / win32 / qmgr / file.c
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 }