VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp@ 85002

Last change on this file since 85002 was 85002, checked in by vboxsync, 4 years ago

DnD: More work on path conversion / validation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.7 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 85002 2020-06-30 09:34:16Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag and drop source.
4 */
5
6/*
7 * Copyright (C) 2014-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#include "GuestDnDSourceImpl.h"
27#include "GuestDnDPrivate.h"
28#include "ConsoleImpl.h"
29
30#include "Global.h"
31#include "AutoCaller.h"
32#include "ThreadTask.h"
33
34#include <iprt/asm.h>
35#include <iprt/dir.h>
36#include <iprt/file.h>
37#include <iprt/path.h>
38#include <iprt/uri.h>
39
40#include <iprt/cpp/utils.h> /* For unconst(). */
41
42#include <VBox/com/array.h>
43
44
45/**
46 * Base class for a source task.
47 */
48class GuestDnDSourceTask : public ThreadTask
49{
50public:
51
52 GuestDnDSourceTask(GuestDnDSource *pSource)
53 : ThreadTask("GenericGuestDnDSourceTask")
54 , mSource(pSource)
55 , mRC(VINF_SUCCESS) { }
56
57 virtual ~GuestDnDSourceTask(void) { }
58
59 int getRC(void) const { return mRC; }
60 bool isOk(void) const { return RT_SUCCESS(mRC); }
61 const ComObjPtr<GuestDnDSource> &getSource(void) const { return mSource; }
62
63protected:
64
65 const ComObjPtr<GuestDnDSource> mSource;
66 int mRC;
67};
68
69/**
70 * Task structure for receiving data from a source using
71 * a worker thread.
72 */
73class RecvDataTask : public GuestDnDSourceTask
74{
75public:
76
77 RecvDataTask(GuestDnDSource *pSource, PRECVDATACTX pCtx)
78 : GuestDnDSourceTask(pSource)
79 , mpCtx(pCtx)
80 {
81 m_strTaskName = "dndSrcRcvData";
82 }
83
84 void handler()
85 {
86 GuestDnDSource::i_receiveDataThreadTask(this);
87 }
88
89 virtual ~RecvDataTask(void) { }
90
91 PRECVDATACTX getCtx(void) { return mpCtx; }
92
93protected:
94
95 /** Pointer to receive data context. */
96 PRECVDATACTX mpCtx;
97};
98
99// constructor / destructor
100/////////////////////////////////////////////////////////////////////////////
101
102DEFINE_EMPTY_CTOR_DTOR(GuestDnDSource)
103
104HRESULT GuestDnDSource::FinalConstruct(void)
105{
106 /*
107 * Set the maximum block size this source can handle to 64K. This always has
108 * been hardcoded until now.
109 *
110 * Note: Never ever rely on information from the guest; the host dictates what and
111 * how to do something, so try to negogiate a sensible value here later.
112 */
113 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
114
115 LogFlowThisFunc(("\n"));
116 return BaseFinalConstruct();
117}
118
119void GuestDnDSource::FinalRelease(void)
120{
121 LogFlowThisFuncEnter();
122 uninit();
123 BaseFinalRelease();
124 LogFlowThisFuncLeave();
125}
126
127// public initializer/uninitializer for internal purposes only
128/////////////////////////////////////////////////////////////////////////////
129
130int GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
131{
132 LogFlowThisFuncEnter();
133
134 /* Enclose the state transition NotReady->InInit->Ready. */
135 AutoInitSpan autoInitSpan(this);
136 AssertReturn(autoInitSpan.isOk(), E_FAIL);
137
138 unconst(m_pGuest) = pGuest;
139
140 /* Confirm a successful initialization when it's the case. */
141 autoInitSpan.setSucceeded();
142
143 return VINF_SUCCESS;
144}
145
146/**
147 * Uninitializes the instance.
148 * Called from FinalRelease().
149 */
150void GuestDnDSource::uninit(void)
151{
152 LogFlowThisFunc(("\n"));
153
154 /* Enclose the state transition Ready->InUninit->NotReady. */
155 AutoUninitSpan autoUninitSpan(this);
156 if (autoUninitSpan.uninitDone())
157 return;
158}
159
160// implementation of wrapped IDnDBase methods.
161/////////////////////////////////////////////////////////////////////////////
162
163HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
164{
165#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
166 ReturnComNotImplemented();
167#else /* VBOX_WITH_DRAG_AND_DROP */
168
169 AutoCaller autoCaller(this);
170 if (FAILED(autoCaller.rc())) return autoCaller.rc();
171
172 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
173
174 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
175#endif /* VBOX_WITH_DRAG_AND_DROP */
176}
177
178HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats)
179{
180#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
181 ReturnComNotImplemented();
182#else /* VBOX_WITH_DRAG_AND_DROP */
183
184 AutoCaller autoCaller(this);
185 if (FAILED(autoCaller.rc())) return autoCaller.rc();
186
187 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
188
189 return GuestDnDBase::i_getFormats(aFormats);
190#endif /* VBOX_WITH_DRAG_AND_DROP */
191}
192
193HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats)
194{
195#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
196 ReturnComNotImplemented();
197#else /* VBOX_WITH_DRAG_AND_DROP */
198
199 AutoCaller autoCaller(this);
200 if (FAILED(autoCaller.rc())) return autoCaller.rc();
201
202 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
203
204 return GuestDnDBase::i_addFormats(aFormats);
205#endif /* VBOX_WITH_DRAG_AND_DROP */
206}
207
208HRESULT GuestDnDSource::removeFormats(const GuestDnDMIMEList &aFormats)
209{
210#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
211 ReturnComNotImplemented();
212#else /* VBOX_WITH_DRAG_AND_DROP */
213
214 AutoCaller autoCaller(this);
215 if (FAILED(autoCaller.rc())) return autoCaller.rc();
216
217 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
218
219 return GuestDnDBase::i_removeFormats(aFormats);
220#endif /* VBOX_WITH_DRAG_AND_DROP */
221}
222
223HRESULT GuestDnDSource::getProtocolVersion(ULONG *aProtocolVersion)
224{
225#if !defined(VBOX_WITH_DRAG_AND_DROP)
226 ReturnComNotImplemented();
227#else /* VBOX_WITH_DRAG_AND_DROP */
228
229 AutoCaller autoCaller(this);
230 if (FAILED(autoCaller.rc())) return autoCaller.rc();
231
232 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
233
234 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
235#endif /* VBOX_WITH_DRAG_AND_DROP */
236}
237
238// implementation of wrapped IDnDSource methods.
239/////////////////////////////////////////////////////////////////////////////
240
241HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats,
242 std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
243{
244#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
245 ReturnComNotImplemented();
246#else /* VBOX_WITH_DRAG_AND_DROP */
247
248 /* aDefaultAction is optional. */
249
250 AutoCaller autoCaller(this);
251 if (FAILED(autoCaller.rc())) return autoCaller.rc();
252
253 /* Determine guest DnD protocol to use. */
254 GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion);
255
256 /* Default is ignoring the action. */
257 if (aDefaultAction)
258 *aDefaultAction = DnDAction_Ignore;
259
260 HRESULT hr = S_OK;
261
262 GuestDnDMsg Msg;
263 Msg.setType(HOST_DND_GH_REQ_PENDING);
264 if (mDataBase.m_uProtocolVersion >= 3)
265 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
266 Msg.setNextUInt32(uScreenId);
267
268 int rc = GUESTDNDINST()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
269 if (RT_SUCCESS(rc))
270 {
271 GuestDnDResponse *pResp = GUESTDNDINST()->response();
272 AssertPtr(pResp);
273
274 bool fFetchResult = true;
275
276 rc = pResp->waitForGuestResponse(100 /* Timeout in ms */);
277 if (RT_FAILURE(rc))
278 fFetchResult = false;
279
280 if ( fFetchResult
281 && isDnDIgnoreAction(pResp->getActionDefault()))
282 fFetchResult = false;
283
284 /* Fetch the default action to use. */
285 if (fFetchResult)
286 {
287 /*
288 * In the GuestDnDSource case the source formats are from the guest,
289 * as GuestDnDSource acts as a target for the guest. The host always
290 * dictates what's supported and what's not, so filter out all formats
291 * which are not supported by the host.
292 */
293 GuestDnDMIMEList lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, pResp->formats());
294 if (lstFiltered.size())
295 {
296 LogRel3(("DnD: Host offered the following formats:\n"));
297 for (size_t i = 0; i < lstFiltered.size(); i++)
298 LogRel3(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str()));
299
300 aFormats = lstFiltered;
301 aAllowedActions = GuestDnD::toMainActions(pResp->getActionsAllowed());
302 if (aDefaultAction)
303 *aDefaultAction = GuestDnD::toMainAction(pResp->getActionDefault());
304
305 /* Apply the (filtered) formats list. */
306 m_lstFmtOffered = lstFiltered;
307 }
308 else
309 LogRel2(("DnD: Negotiation of formats between guest and host failed, drag and drop to host not possible\n"));
310 }
311
312 LogFlowFunc(("fFetchResult=%RTbool, lstActionsAllowed=0x%x\n", fFetchResult, pResp->getActionsAllowed()));
313 }
314
315 LogFlowFunc(("hr=%Rhrc\n", hr));
316 return hr;
317#endif /* VBOX_WITH_DRAG_AND_DROP */
318}
319
320HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
321{
322#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
323 ReturnComNotImplemented();
324#else /* VBOX_WITH_DRAG_AND_DROP */
325
326 AutoCaller autoCaller(this);
327 if (FAILED(autoCaller.rc())) return autoCaller.rc();
328
329 LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction));
330
331 /* Input validation. */
332 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
333 return setError(E_INVALIDARG, tr("No drop format specified"));
334
335 /* Is the specified format in our list of (left over) offered formats? */
336 if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered))
337 return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str());
338
339 VBOXDNDACTION dndAction = GuestDnD::toHGCMAction(aAction);
340 if (isDnDIgnoreAction(dndAction)) /* If there is no usable action, ignore this request. */
341 return S_OK;
342
343 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
344
345 /* At the moment we only support one transfer at a time. */
346 if (mDataBase.m_cTransfersPending)
347 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
348
349 /* Dito. */
350 GuestDnDResponse *pResp = GUESTDNDINST()->response();
351 AssertPtr(pResp);
352
353 HRESULT hr = pResp->resetProgress(m_pGuest);
354 if (FAILED(hr))
355 return hr;
356
357 RecvDataTask *pTask = NULL;
358
359 try
360 {
361 mData.mRecvCtx.mIsActive = false;
362 mData.mRecvCtx.mpSource = this;
363 mData.mRecvCtx.mpResp = pResp;
364 mData.mRecvCtx.mFmtReq = aFormat;
365 mData.mRecvCtx.mFmtOffered = m_lstFmtOffered;
366
367 LogRel2(("DnD: Requesting data from guest in format: %s\n", aFormat.c_str()));
368
369 pTask = new RecvDataTask(this, &mData.mRecvCtx);
370 if (!pTask->isOk())
371 {
372 delete pTask;
373 LogRel2(("DnD: Could not create RecvDataTask object \n"));
374 throw hr = E_FAIL;
375 }
376
377 /* This function delete pTask in case of exceptions,
378 * so there is no need in the call of delete operator. */
379 hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
380 pTask = NULL; /* Note: pTask is now owned by the worker thread. */
381 }
382 catch (std::bad_alloc &)
383 {
384 hr = setError(E_OUTOFMEMORY);
385 }
386 catch (...)
387 {
388 LogRel2(("DnD: Could not create thread for data receiving task\n"));
389 hr = E_FAIL;
390 }
391
392 if (SUCCEEDED(hr))
393 {
394 mDataBase.m_cTransfersPending++;
395
396 hr = pResp->queryProgressTo(aProgress.asOutParam());
397 ComAssertComRC(hr);
398
399 }
400 else
401 hr = setError(hr, tr("Starting thread for GuestDnDSource::i_receiveDataThread failed (%Rhrc)"), hr);
402 /* Note: mDataBase.mfTransferIsPending will be set to false again by i_receiveDataThread. */
403
404 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
405 return hr;
406#endif /* VBOX_WITH_DRAG_AND_DROP */
407}
408
409HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
410{
411#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
412 ReturnComNotImplemented();
413#else /* VBOX_WITH_DRAG_AND_DROP */
414
415 AutoCaller autoCaller(this);
416 if (FAILED(autoCaller.rc())) return autoCaller.rc();
417
418 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
419
420 LogFlowThisFunc(("cTransfersPending=%RU32\n", mDataBase.m_cTransfersPending));
421
422 /* Don't allow receiving the actual data until our transfer actually is complete. */
423 if (mDataBase.m_cTransfersPending)
424 return setError(E_FAIL, tr("Current drop operation still in progress"));
425
426 PRECVDATACTX pCtx = &mData.mRecvCtx;
427 HRESULT hr = S_OK;
428
429 try
430 {
431 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
432 if (fHasURIList)
433 {
434 LogRel2(("DnD: Drop directory is: %s\n", pCtx->mURI.getDroppedFiles().GetDirAbs()));
435 int rc2 = pCtx->mURI.toMetaData(aData);
436 if (RT_FAILURE(rc2))
437 hr = E_OUTOFMEMORY;
438 }
439 else
440 {
441 const size_t cbData = pCtx->mData.getMeta().getSize();
442 LogFlowFunc(("cbData=%zu\n", cbData));
443 if (cbData)
444 {
445 /* Copy the data into a safe array of bytes. */
446 aData.resize(cbData);
447 memcpy(&aData.front(), pCtx->mData.getMeta().getData(), cbData);
448 }
449 else
450 aData.resize(0);
451 }
452 }
453 catch (std::bad_alloc &)
454 {
455 hr = E_OUTOFMEMORY;
456 }
457
458 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
459 return hr;
460#endif /* VBOX_WITH_DRAG_AND_DROP */
461}
462
463// implementation of internal methods.
464/////////////////////////////////////////////////////////////////////////////
465
466/* static */
467Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc)
468{
469 Utf8Str strError;
470
471 switch (guestRc)
472 {
473 case VERR_ACCESS_DENIED:
474 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
475 "user does not have the appropriate access rights for. Please make sure that all selected "
476 "elements can be accessed and that your guest user has the appropriate rights"));
477 break;
478
479 case VERR_NOT_FOUND:
480 /* Should not happen due to file locking on the guest, but anyway ... */
481 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
482 "found on the guest anymore. This can be the case if the guest files were moved and/or"
483 "altered while the drag and drop operation was in progress"));
484 break;
485
486 case VERR_SHARING_VIOLATION:
487 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
488 "Please make sure that all selected elements can be accessed and that your guest user has "
489 "the appropriate rights"));
490 break;
491
492 case VERR_TIMEOUT:
493 strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time"));
494 break;
495
496 default:
497 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
498 break;
499 }
500
501 return strError;
502}
503
504/* static */
505Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc)
506{
507 Utf8Str strError;
508
509 switch (hostRc)
510 {
511 case VERR_ACCESS_DENIED:
512 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
513 "user does not have the appropriate access rights for. Please make sure that all selected "
514 "elements can be accessed and that your host user has the appropriate rights."));
515 break;
516
517 case VERR_DISK_FULL:
518 strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full)."));
519 break;
520
521 case VERR_NOT_FOUND:
522 /* Should not happen due to file locking on the host, but anyway ... */
523 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
524 "found on the host anymore. This can be the case if the host files were moved and/or"
525 "altered while the drag and drop operation was in progress."));
526 break;
527
528 case VERR_SHARING_VIOLATION:
529 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
530 "Please make sure that all selected elements can be accessed and that your host user has "
531 "the appropriate rights."));
532 break;
533
534 default:
535 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
536 break;
537 }
538
539 return strError;
540}
541
542#ifdef VBOX_WITH_DRAG_AND_DROP_GH
543int GuestDnDSource::i_onReceiveDataHdr(PRECVDATACTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
544{
545 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
546 AssertReturn(pDataHdr, VERR_INVALID_POINTER);
547
548 pCtx->mData.setEstimatedSize(pDataHdr->cbTotal, pDataHdr->cbMeta);
549
550 Assert(pCtx->mURI.getObjToProcess() == 0);
551 pCtx->mURI.reset();
552 pCtx->mURI.setEstimatedObjects(pDataHdr->cObjects);
553
554 /** @todo Handle compression type. */
555 /** @todo Handle checksum type. */
556
557 LogFlowFuncLeave();
558 return VINF_SUCCESS;
559}
560
561int GuestDnDSource::i_onReceiveData(PRECVDATACTX pCtx, PVBOXDNDSNDDATA pSndData)
562{
563 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
564 AssertPtrReturn(pSndData, VERR_INVALID_POINTER);
565
566 int rc = VINF_SUCCESS;
567
568 try
569 {
570 GuestDnDData *pData = &pCtx->mData;
571 GuestDnDURIData *pURI = &pCtx->mURI;
572
573 uint32_t cbData;
574 void *pvData;
575 uint64_t cbTotal;
576 uint32_t cbMeta;
577
578 if (mDataBase.m_uProtocolVersion < 3)
579 {
580 cbData = pSndData->u.v1.cbData;
581 pvData = pSndData->u.v1.pvData;
582
583 /* Sends the total data size to receive for every data chunk. */
584 cbTotal = pSndData->u.v1.cbTotalSize;
585
586 /* Meta data size always is cbData, meaning there cannot be an
587 * extended data chunk transfer by sending further data. */
588 cbMeta = cbData;
589 }
590 else
591 {
592 cbData = pSndData->u.v3.cbData;
593 pvData = pSndData->u.v3.pvData;
594
595 /* Note: Data sizes get updated in i_onReceiveDataHdr(). */
596 cbTotal = pData->getTotal();
597 cbMeta = pData->getMeta().getSize();
598 }
599 Assert(cbTotal);
600
601 if ( cbData == 0
602 || cbData > cbTotal /* Paranoia */)
603 {
604 LogFlowFunc(("Incoming data size invalid: cbData=%RU32, cbToProcess=%RU64\n", cbData, pData->getTotal()));
605 rc = VERR_INVALID_PARAMETER;
606 }
607 else if (cbTotal < cbMeta)
608 {
609 AssertMsgFailed(("cbTotal (%RU64) is smaller than cbMeta (%RU32)\n", cbTotal, cbMeta));
610 rc = VERR_INVALID_PARAMETER;
611 }
612
613 if (RT_SUCCESS(rc))
614 {
615 cbMeta = pData->getMeta().add(pvData, cbData);
616 LogFlowThisFunc(("cbMetaSize=%zu, cbData=%RU32, cbMeta=%RU32, cbTotal=%RU64\n",
617 pData->getMeta().getSize(), cbData, cbMeta, cbTotal));
618 }
619
620 if (RT_SUCCESS(rc))
621 {
622 /*
623 * (Meta) Data transfer complete?
624 */
625 Assert(cbMeta <= pData->getMeta().getSize());
626 if (cbMeta == pData->getMeta().getSize())
627 {
628 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
629 LogFlowThisFunc(("fHasURIList=%RTbool\n", fHasURIList));
630 if (fHasURIList)
631 {
632 /* Try parsing the data as URI list. */
633 rc = pURI->fromRemoteMetaData(pData->getMeta());
634 if (RT_SUCCESS(rc))
635 {
636 if (mDataBase.m_uProtocolVersion < 3)
637 pData->setEstimatedSize(cbTotal, cbMeta);
638
639 /*
640 * Update our process with the data we already received.
641 * Note: The total size will consist of the meta data (in pVecData) and
642 * the actual accumulated file/directory data from the guest.
643 */
644 rc = updateProgress(pData, pCtx->mpResp, (uint32_t)pData->getMeta().getSize());
645 }
646 }
647 else /* Raw data. */
648 rc = updateProgress(pData, pCtx->mpResp, cbData);
649 }
650 }
651 }
652 catch (std::bad_alloc &)
653 {
654 rc = VERR_NO_MEMORY;
655 }
656
657 LogFlowFuncLeaveRC(rc);
658 return rc;
659}
660
661int GuestDnDSource::i_onReceiveDir(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
662{
663 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
664 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
665 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
666
667 LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
668
669 /*
670 * Sanity checking.
671 */
672 if ( !cbPath
673 || cbPath > RTPATH_MAX)
674 {
675 LogFlowFunc(("Path length invalid, bailing out\n"));
676 return VERR_INVALID_PARAMETER;
677 }
678
679 int rc = RTStrValidateEncodingEx(pszPath, RTSTR_MAX, 0);
680 if (RT_FAILURE(rc))
681 {
682 LogFlowFunc(("Path validation failed with %Rrc, bailing out\n", rc));
683 return VERR_INVALID_PARAMETER;
684 }
685
686 if (pCtx->mURI.isComplete())
687 {
688 LogFlowFunc(("Data transfer already complete, bailing out\n"));
689 return VERR_INVALID_PARAMETER;
690 }
691
692 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
693
694 rc = objCtx.createIntermediate(DnDURIObject::Type_Directory);
695 if (RT_FAILURE(rc))
696 return rc;
697
698 DnDURIObject *pObj = objCtx.getObj();
699 AssertPtr(pObj);
700
701 const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs();
702 char *pszDir = RTPathJoinA(pszDroppedFilesDir, pszPath);
703 if (pszDir)
704 {
705#ifdef RT_OS_WINDOWS
706 RTPathChangeToDosSlashes(pszDir, true /* fForce */);
707#else
708 RTPathChangeToDosSlashes(pszDir, true /* fForce */);
709#endif
710 rc = RTDirCreateFullPath(pszDir, fMode);
711 if (RT_SUCCESS(rc))
712 {
713 pCtx->mURI.processObject(*pObj);
714
715 /* Add for having a proper rollback. */
716 int rc2 = pCtx->mURI.getDroppedFiles().AddDir(pszDir);
717 AssertRC(rc2);
718
719 objCtx.reset();
720 LogRel2(("DnD: Created guest directory '%s' on host\n", pszDir));
721 }
722 else
723 LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pszDir, rc));
724
725 RTStrFree(pszDir);
726 }
727 else
728 rc = VERR_NO_MEMORY;
729
730 LogFlowFuncLeaveRC(rc);
731 return rc;
732}
733
734int GuestDnDSource::i_onReceiveFileHdr(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath,
735 uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
736{
737 RT_NOREF(fFlags);
738 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
739 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
740 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
741 AssertReturn(fMode, VERR_INVALID_PARAMETER);
742 /* fFlags are optional. */
743
744 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
745
746 /*
747 * Sanity checking.
748 */
749 if ( !cbPath
750 || cbPath > RTPATH_MAX)
751 {
752 return VERR_INVALID_PARAMETER;
753 }
754
755 if (!RTStrIsValidEncoding(pszPath))
756 return VERR_INVALID_PARAMETER;
757
758 if (cbSize > pCtx->mData.getTotal())
759 {
760 AssertMsgFailed(("File size (%RU64) exceeds total size to transfer (%RU64)\n", cbSize, pCtx->mData.getTotal()));
761 return VERR_INVALID_PARAMETER;
762 }
763
764 if (pCtx->mURI.getObjToProcess() && pCtx->mURI.isComplete())
765 return VERR_INVALID_PARAMETER;
766
767 int rc = VINF_SUCCESS;
768
769 do
770 {
771 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
772 DnDURIObject *pObj = objCtx.getObj();
773
774 /*
775 * Sanity checking.
776 */
777 if (pObj)
778 {
779 if ( pObj->IsOpen()
780 && !pObj->IsComplete())
781 {
782 AssertMsgFailed(("Object '%s' not complete yet\n", pObj->GetPath().c_str()));
783 rc = VERR_WRONG_ORDER;
784 break;
785 }
786
787 if (pObj->IsOpen()) /* File already opened? */
788 {
789 AssertMsgFailed(("Current opened object is '%s', close this first\n", pObj->GetPath().c_str()));
790 rc = VERR_WRONG_ORDER;
791 break;
792 }
793 }
794 else
795 {
796 /*
797 * Create new intermediate object to work with.
798 */
799 rc = objCtx.createIntermediate(DnDURIObject::Type_File);
800 }
801
802 if (RT_SUCCESS(rc))
803 {
804 pObj = objCtx.getObj();
805 AssertPtr(pObj);
806
807 const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs();
808 AssertPtr(pszDroppedFilesDir);
809
810 char pszPathAbs[RTPATH_MAX];
811 rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), pszDroppedFilesDir, pszPath);
812 if (RT_FAILURE(rc))
813 {
814 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
815 break;
816 }
817
818 rc = pObj->Init(DnDURIObject::Type_File, pszPathAbs);
819 if (RT_SUCCESS(rc))
820 {
821 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
822 rc = pObj->Open(RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
823 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
824 if (RT_SUCCESS(rc))
825 {
826 /* Add for having a proper rollback. */
827 int rc2 = pCtx->mURI.getDroppedFiles().AddFile(pszPathAbs);
828 AssertRC(rc2);
829 }
830 }
831
832 if (RT_FAILURE(rc))
833 LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pszPathAbs, rc));
834 }
835
836 if (RT_SUCCESS(rc))
837 {
838 /* Note: Protocol v1 does not send any file sizes, so always 0. */
839 if (mDataBase.m_uProtocolVersion >= 2)
840 rc = pObj->SetSize(cbSize);
841
842 /** @todo Unescape path before printing. */
843 LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode 0x%x)\n",
844 pObj->GetPath().c_str(), pObj->GetSize(), pObj->GetMode()));
845
846 /** @todo Set progress object title to current file being transferred? */
847
848 if (pObj->IsComplete()) /* 0-byte file? We're done already. */
849 {
850 LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pObj->GetPath().c_str()));
851
852 pCtx->mURI.processObject(*pObj);
853 pObj->Close();
854
855 objCtx.reset();
856 }
857 }
858
859 } while (0);
860
861 if (RT_FAILURE(rc))
862 LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc));
863
864 LogFlowFuncLeaveRC(rc);
865 return rc;
866}
867
868int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
869{
870 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
871 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
872 AssertReturn(cbData, VERR_INVALID_PARAMETER);
873
874 int rc = VINF_SUCCESS;
875
876 LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
877
878 /*
879 * Sanity checking.
880 */
881 if (cbData > mData.mcbBlockSize)
882 return VERR_INVALID_PARAMETER;
883
884 do
885 {
886 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */
887 DnDURIObject *pObj = objCtx.getObj();
888
889 if (!pObj)
890 {
891 LogFlowFunc(("Warning: No current object set\n"));
892 rc = VERR_WRONG_ORDER;
893 break;
894 }
895
896 if (pObj->IsComplete())
897 {
898 LogFlowFunc(("Warning: Object '%s' already completed\n", pObj->GetPath().c_str()));
899 rc = VERR_WRONG_ORDER;
900 break;
901 }
902
903 if (!pObj->IsOpen()) /* File opened on host? */
904 {
905 LogFlowFunc(("Warning: Object '%s' not opened\n", pObj->GetPath().c_str()));
906 rc = VERR_WRONG_ORDER;
907 break;
908 }
909
910 uint32_t cbWritten;
911 rc = pObj->Write(pvData, cbData, &cbWritten);
912 if (RT_SUCCESS(rc))
913 {
914 Assert(cbWritten <= cbData);
915 if (cbWritten < cbData)
916 {
917 LogRel(("DnD: Only written %RU32 of %RU32 bytes of guest file '%s' -- disk full?\n",
918 cbWritten, cbData, pObj->GetPath().c_str()));
919 rc = VERR_IO_GEN_FAILURE; /** @todo Find a better rc. */
920 }
921
922 if (RT_SUCCESS(rc))
923 rc = updateProgress(&pCtx->mData, pCtx->mpResp, cbWritten);
924 }
925 else
926 LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pObj->GetPath().c_str(), rc));
927
928 if (RT_SUCCESS(rc))
929 {
930 if (pObj->IsComplete())
931 {
932 /** @todo Sanitize path. */
933 LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pObj->GetPath().c_str()));
934 pCtx->mURI.processObject(*pObj);
935 objCtx.reset();
936 }
937 }
938
939 } while (0);
940
941 if (RT_FAILURE(rc))
942 LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc));
943
944 LogFlowFuncLeaveRC(rc);
945 return rc;
946}
947#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
948
949/**
950 * @returns VBox status code that the caller ignores. Not sure if that's
951 * intentional or not.
952 */
953int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
954{
955 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
956
957 GuestDnD *pInst = GUESTDNDINST();
958 if (!pInst)
959 return VERR_INVALID_POINTER;
960
961 GuestDnDResponse *pResp = pCtx->mpResp;
962 AssertPtr(pCtx->mpResp);
963
964 int rc = pCtx->mCBEvent.Reset();
965 if (RT_FAILURE(rc))
966 return rc;
967
968 /* Is this context already in receiving state? */
969 if (ASMAtomicReadBool(&pCtx->mIsActive))
970 return VERR_WRONG_ORDER;
971 ASMAtomicWriteBool(&pCtx->mIsActive, true);
972
973 /*
974 * Reset any old data.
975 */
976 pCtx->mData.reset();
977 pCtx->mURI.reset();
978 pResp->reset();
979
980 /*
981 * Do we need to receive a different format than initially requested?
982 *
983 * For example, receiving a file link as "text/plain" requires still to receive
984 * the file from the guest as "text/uri-list" first, then pointing to
985 * the file path on the host in the "text/plain" data returned.
986 */
987
988 bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
989
990 LogFlowFunc(("mFmtReq=%s, mFmtRecv=%s, mAction=0x%x\n",
991 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str(), pCtx->mAction));
992
993 /* Plain text wanted? */
994 if ( pCtx->mFmtReq.equalsIgnoreCase("text/plain")
995 || pCtx->mFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
996 {
997 /* Did the guest offer a file? Receive a file instead. */
998 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
999 pCtx->mFmtRecv = "text/uri-list";
1000 /* Guest only offers (plain) text. */
1001 else
1002 pCtx->mFmtRecv = "text/plain;charset=utf-8";
1003
1004 /** @todo Add more conversions here. */
1005 }
1006 /* File(s) wanted? */
1007 else if (pCtx->mFmtReq.equalsIgnoreCase("text/uri-list"))
1008 {
1009 /* Does the guest support sending files? */
1010 if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered))
1011 pCtx->mFmtRecv = "text/uri-list";
1012 else /* Bail out. */
1013 fFoundFormat = false;
1014 }
1015
1016 if (fFoundFormat)
1017 {
1018 Assert(!pCtx->mFmtReq.isEmpty());
1019 Assert(!pCtx->mFmtRecv.isEmpty());
1020
1021 if (!pCtx->mFmtRecv.equals(pCtx->mFmtReq))
1022 LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
1023 pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str()));
1024
1025 /*
1026 * Call the appropriate receive handler based on the data format to handle.
1027 */
1028 bool fURIData = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length());
1029 if (fURIData)
1030 {
1031 rc = i_receiveURIData(pCtx, msTimeout);
1032 }
1033 else
1034 {
1035 rc = i_receiveRawData(pCtx, msTimeout);
1036 }
1037 }
1038 else /* Just inform the user (if verbose release logging is enabled). */
1039 {
1040 LogRel(("DnD: The guest does not support format '%s':\n", pCtx->mFmtReq.c_str()));
1041 LogRel(("DnD: Guest offered the following formats:\n"));
1042 for (size_t i = 0; i < pCtx->mFmtOffered.size(); i++)
1043 LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->mFmtOffered.at(i).c_str()));
1044 }
1045
1046 ASMAtomicWriteBool(&pCtx->mIsActive, false);
1047
1048 LogFlowFuncLeaveRC(rc);
1049 return rc;
1050}
1051
1052/* static */
1053void GuestDnDSource::i_receiveDataThreadTask(RecvDataTask *pTask)
1054{
1055 LogFlowFunc(("pTask=%p\n", pTask));
1056 AssertPtrReturnVoid(pTask);
1057
1058 const ComObjPtr<GuestDnDSource> pThis(pTask->getSource());
1059 Assert(!pThis.isNull());
1060
1061 AutoCaller autoCaller(pThis);
1062 if (FAILED(autoCaller.rc()))
1063 return;
1064
1065 int vrc = pThis->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
1066 if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
1067 {
1068 AssertFailed();
1069 LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
1070 }
1071
1072 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
1073
1074 Assert(pThis->mDataBase.m_cTransfersPending);
1075 if (pThis->mDataBase.m_cTransfersPending)
1076 pThis->mDataBase.m_cTransfersPending--;
1077
1078 LogFlowFunc(("pSource=%p, vrc=%Rrc (ignored)\n", (GuestDnDSource *)pThis, vrc));
1079}
1080
1081int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1082{
1083 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1084
1085 int rc;
1086
1087 LogFlowFuncEnter();
1088
1089 GuestDnDResponse *pResp = pCtx->mpResp;
1090 AssertPtr(pCtx->mpResp);
1091
1092 GuestDnD *pInst = GUESTDNDINST();
1093 if (!pInst)
1094 return VERR_INVALID_POINTER;
1095
1096#define REGISTER_CALLBACK(x) \
1097 do { \
1098 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
1099 if (RT_FAILURE(rc)) \
1100 return rc; \
1101 } while (0)
1102
1103#define UNREGISTER_CALLBACK(x) \
1104 do { \
1105 int rc2 = pResp->setCallback(x, NULL); \
1106 AssertRC(rc2); \
1107 } while (0)
1108
1109 /*
1110 * Register callbacks.
1111 */
1112 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1113 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1114 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1115 if (mDataBase.m_uProtocolVersion >= 3)
1116 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1117 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1118
1119 do
1120 {
1121 /*
1122 * Receive the raw data.
1123 */
1124 GuestDnDMsg Msg;
1125 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1126 if (mDataBase.m_uProtocolVersion >= 3)
1127 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1128 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1129 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1130 Msg.setNextUInt32(pCtx->mAction);
1131
1132 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1133 * the host and therefore now waiting for the actual raw data. */
1134 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1135 if (RT_SUCCESS(rc))
1136 {
1137 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1138 if (RT_SUCCESS(rc))
1139 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1140 }
1141
1142 } while (0);
1143
1144 /*
1145 * Unregister callbacks.
1146 */
1147 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1148 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1149 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1150 if (mDataBase.m_uProtocolVersion >= 3)
1151 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1152 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1153
1154#undef REGISTER_CALLBACK
1155#undef UNREGISTER_CALLBACK
1156
1157 if (RT_FAILURE(rc))
1158 {
1159 if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1160 {
1161 /*
1162 * Now that we've cleaned up tell the guest side to cancel.
1163 * This does not imply we're waiting for the guest to react, as the
1164 * host side never must depend on anything from the guest.
1165 */
1166 int rc2 = sendCancel();
1167 AssertRC(rc2);
1168
1169 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1170 AssertRC(rc2);
1171 }
1172 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1173 {
1174 int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1175 rc, GuestDnDSource::i_hostErrorToString(rc));
1176 AssertRC(rc2);
1177 }
1178
1179 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1180 }
1181
1182 LogFlowFuncLeaveRC(rc);
1183 return rc;
1184}
1185
1186int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
1187{
1188 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1189
1190 int rc;
1191
1192 LogFlowFuncEnter();
1193
1194 GuestDnDResponse *pResp = pCtx->mpResp;
1195 AssertPtr(pCtx->mpResp);
1196
1197 GuestDnD *pInst = GUESTDNDINST();
1198 if (!pInst)
1199 return VERR_INVALID_POINTER;
1200
1201#define REGISTER_CALLBACK(x) \
1202 do { \
1203 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
1204 if (RT_FAILURE(rc)) \
1205 return rc; \
1206 } while (0)
1207
1208#define UNREGISTER_CALLBACK(x) \
1209 do { \
1210 int rc2 = pResp->setCallback(x, NULL); \
1211 AssertRC(rc2); \
1212 } while (0)
1213
1214 /*
1215 * Register callbacks.
1216 */
1217 /* Guest callbacks. */
1218 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1219 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1220 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1221 if (mDataBase.m_uProtocolVersion >= 3)
1222 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1223 REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1224 REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1225 if (mDataBase.m_uProtocolVersion >= 2)
1226 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1227 REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1228
1229 DnDDroppedFiles &droppedFiles = pCtx->mURI.getDroppedFiles();
1230
1231 do
1232 {
1233 rc = droppedFiles.OpenTemp(0 /* fFlags */);
1234 if (RT_FAILURE(rc))
1235 {
1236 LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n", droppedFiles.GetDirAbs(), rc));
1237 break;
1238 }
1239
1240 /*
1241 * Receive the URI list.
1242 */
1243 GuestDnDMsg Msg;
1244 Msg.setType(HOST_DND_GH_EVT_DROPPED);
1245 if (mDataBase.m_uProtocolVersion >= 3)
1246 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
1247 Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1);
1248 Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1);
1249 Msg.setNextUInt32(pCtx->mAction);
1250
1251 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
1252 * the host and therefore now waiting for the actual URI data. */
1253 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1254 if (RT_SUCCESS(rc))
1255 {
1256 LogFlowFunc(("Waiting ...\n"));
1257
1258 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1259 if (RT_SUCCESS(rc))
1260 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1261
1262 LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
1263 }
1264
1265 } while (0);
1266
1267 /*
1268 * Unregister callbacks.
1269 */
1270 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1271 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1272 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1273 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR);
1274 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA);
1275 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR);
1276 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR);
1277 UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA);
1278
1279#undef REGISTER_CALLBACK
1280#undef UNREGISTER_CALLBACK
1281
1282 if (RT_FAILURE(rc))
1283 {
1284 int rc2 = droppedFiles.Rollback();
1285 if (RT_FAILURE(rc2))
1286 LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
1287 rc2, droppedFiles.GetDirAbs()));
1288
1289 if (rc == VERR_CANCELLED)
1290 {
1291 /*
1292 * Now that we've cleaned up tell the guest side to cancel.
1293 * This does not imply we're waiting for the guest to react, as the
1294 * host side never must depend on anything from the guest.
1295 */
1296 rc2 = sendCancel();
1297 AssertRC(rc2);
1298
1299 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1300 AssertRC(rc2);
1301 }
1302 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1303 {
1304 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR,
1305 rc, GuestDnDSource::i_hostErrorToString(rc));
1306 AssertRC(rc2);
1307 }
1308
1309 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1310 }
1311
1312 droppedFiles.Close();
1313
1314 LogFlowFuncLeaveRC(rc);
1315 return rc;
1316}
1317
1318/* static */
1319DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1320{
1321 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1322 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1323
1324 GuestDnDSource *pThis = pCtx->mpSource;
1325 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1326
1327 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1328
1329 int rc = VINF_SUCCESS;
1330
1331 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1332 bool fNotify = false;
1333
1334 switch (uMsg)
1335 {
1336 case GUEST_DND_CONNECT:
1337 /* Nothing to do here (yet). */
1338 break;
1339
1340 case GUEST_DND_DISCONNECT:
1341 rc = VERR_CANCELLED;
1342 break;
1343
1344#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1345 case GUEST_DND_GH_SND_DATA_HDR:
1346 {
1347 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1348 AssertPtr(pCBData);
1349 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1350 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1351
1352 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1353 break;
1354 }
1355 case GUEST_DND_GH_SND_DATA:
1356 {
1357 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1358 AssertPtr(pCBData);
1359 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1360 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1361
1362 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1363 break;
1364 }
1365 case GUEST_DND_GH_EVT_ERROR:
1366 {
1367 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1368 AssertPtr(pCBData);
1369 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1370 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1371
1372 pCtx->mpResp->reset();
1373
1374 if (RT_SUCCESS(pCBData->rc))
1375 {
1376 AssertMsgFailed(("Received guest error with no error code set\n"));
1377 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1378 }
1379 else if (pCBData->rc == VERR_WRONG_ORDER)
1380 {
1381 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1382 }
1383 else
1384 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1385 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1386
1387 LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
1388
1389 if (RT_SUCCESS(rc))
1390 rcCallback = VERR_GSTDND_GUEST_ERROR;
1391 break;
1392 }
1393#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1394 default:
1395 rc = VERR_NOT_SUPPORTED;
1396 break;
1397 }
1398
1399 if ( RT_FAILURE(rc)
1400 || RT_FAILURE(rcCallback))
1401 {
1402 fNotify = true;
1403 if (RT_SUCCESS(rcCallback))
1404 rcCallback = rc;
1405 }
1406
1407 if (RT_FAILURE(rc))
1408 {
1409 switch (rc)
1410 {
1411 case VERR_NO_DATA:
1412 LogRel2(("DnD: Data transfer to host complete\n"));
1413 break;
1414
1415 case VERR_CANCELLED:
1416 LogRel2(("DnD: Data transfer to host canceled\n"));
1417 break;
1418
1419 default:
1420 LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
1421 break;
1422 }
1423
1424 /* Unregister this callback. */
1425 AssertPtr(pCtx->mpResp);
1426 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1427 AssertRC(rc2);
1428 }
1429
1430 /* All data processed? */
1431 if (pCtx->mData.isComplete())
1432 fNotify = true;
1433
1434 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1435 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1436
1437 if (fNotify)
1438 {
1439 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1440 AssertRC(rc2);
1441 }
1442
1443 LogFlowFuncLeaveRC(rc);
1444 return rc; /* Tell the guest. */
1445}
1446
1447/* static */
1448DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1449{
1450 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1451 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1452
1453 GuestDnDSource *pThis = pCtx->mpSource;
1454 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1455
1456 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1457
1458 int rc = VINF_SUCCESS;
1459
1460 int rcCallback = VINF_SUCCESS; /* rc for the callback. */
1461 bool fNotify = false;
1462
1463 switch (uMsg)
1464 {
1465 case GUEST_DND_CONNECT:
1466 /* Nothing to do here (yet). */
1467 break;
1468
1469 case GUEST_DND_DISCONNECT:
1470 rc = VERR_CANCELLED;
1471 break;
1472
1473#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1474 case GUEST_DND_GH_SND_DATA_HDR:
1475 {
1476 PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
1477 AssertPtr(pCBData);
1478 AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1479 AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1480
1481 rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
1482 break;
1483 }
1484 case GUEST_DND_GH_SND_DATA:
1485 {
1486 PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
1487 AssertPtr(pCBData);
1488 AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1489 AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1490
1491 rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
1492 break;
1493 }
1494 case GUEST_DND_GH_SND_DIR:
1495 {
1496 PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
1497 AssertPtr(pCBData);
1498 AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1499 AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1500
1501 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1502 break;
1503 }
1504 case GUEST_DND_GH_SND_FILE_HDR:
1505 {
1506 PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1507 AssertPtr(pCBData);
1508 AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1509 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1510
1511 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1512 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1513 break;
1514 }
1515 case GUEST_DND_GH_SND_FILE_DATA:
1516 {
1517 PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1518 AssertPtr(pCBData);
1519 AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1520 AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1521
1522 if (pThis->mDataBase.m_uProtocolVersion <= 1)
1523 {
1524 /**
1525 * Notes for protocol v1 (< VBox 5.0):
1526 * - Every time this command is being sent it includes the file header,
1527 * so just process both calls here.
1528 * - There was no information whatsoever about the total file size; the old code only
1529 * appended data to the desired file. So just pass 0 as cbSize.
1530 */
1531 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1532 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1533 if (RT_SUCCESS(rc))
1534 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1535 }
1536 else /* Protocol v2 and up. */
1537 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1538 break;
1539 }
1540 case GUEST_DND_GH_EVT_ERROR:
1541 {
1542 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1543 AssertPtr(pCBData);
1544 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1545 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1546
1547 pCtx->mpResp->reset();
1548
1549 if (RT_SUCCESS(pCBData->rc))
1550 {
1551 AssertMsgFailed(("Received guest error with no error code set\n"));
1552 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1553 }
1554 else if (pCBData->rc == VERR_WRONG_ORDER)
1555 {
1556 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED);
1557 }
1558 else
1559 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1560 GuestDnDSource::i_guestErrorToString(pCBData->rc));
1561
1562 LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
1563
1564 if (RT_SUCCESS(rc))
1565 rcCallback = VERR_GSTDND_GUEST_ERROR;
1566 break;
1567 }
1568#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1569 default:
1570 rc = VERR_NOT_SUPPORTED;
1571 break;
1572 }
1573
1574 if ( RT_FAILURE(rc)
1575 || RT_FAILURE(rcCallback))
1576 {
1577 fNotify = true;
1578 if (RT_SUCCESS(rcCallback))
1579 rcCallback = rc;
1580 }
1581
1582 if (RT_FAILURE(rc))
1583 {
1584 switch (rc)
1585 {
1586 case VERR_NO_DATA:
1587 LogRel2(("DnD: File transfer to host complete\n"));
1588 break;
1589
1590 case VERR_CANCELLED:
1591 LogRel2(("DnD: File transfer to host canceled\n"));
1592 break;
1593
1594 default:
1595 LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
1596 break;
1597 }
1598
1599 /* Unregister this callback. */
1600 AssertPtr(pCtx->mpResp);
1601 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1602 AssertRC(rc2);
1603 }
1604
1605 /* All data processed? */
1606 if ( pCtx->mURI.isComplete()
1607 && pCtx->mData.isComplete())
1608 {
1609 fNotify = true;
1610 }
1611
1612 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
1613 pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc));
1614
1615 if (fNotify)
1616 {
1617 int rc2 = pCtx->mCBEvent.Notify(rcCallback);
1618 AssertRC(rc2);
1619 }
1620
1621 LogFlowFuncLeaveRC(rc);
1622 return rc; /* Tell the guest. */
1623}
1624
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette