gilbertchen / duplicacy

A new generation cloud backup tool
https://duplicacy.com
Other
5.26k stars 340 forks source link

Google Drive backend should respond to malware/spam warning #433

Open s4y opened 6 years ago

s4y commented 6 years ago

My Google Drive restore was failing with this error:

Failed to download the chunk [snip]: googleapi: got HTTP response code 403 with body: {
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "cannotDownloadAbusiveFile",
    "message": "This file has been identified as malware or spam and cannot be downloaded."
   }
  ],
  "code": 403,
  "message": "This file has been identified as malware or spam and cannot be downloaded."
 }
}

Since chunks are encrypted, it seems like this one just happened to match some pattern deep in Google's databases.

It turns out that a parameter, acknowledgeAbuse=true, can be passed to ACK this condition (but it's an error to pass it for non-blocked files).

I hacked up my copy of Duplicacy to respond to it:

index c3efc0a..f368aa5 100644
--- a/src/duplicacy_gcdstorage.go
+++ b/src/duplicacy_gcdstorage.go
@@ -624,13 +624,22 @@ func (storage *GCDStorage) DownloadFile(threadIndex int, filePath string, chunk
        var response *http.Response

        for {
-               response, err = storage.service.Files.Get(fileID).Download()
-               if retry, err := storage.shouldRetry(threadIndex, err); err == nil && !retry {
+               // AcknowledgeAbuse(true) lets the download proceed even if GCD thinks that it contains malware.
+               // TODO: Should this prompt the user or log a warning?
+               req := storage.service.Files.Get(fileID)
+               if e, ok := err.(*googleapi.Error); ok {
+                       if strings.Contains(err.Error(), "cannotDownloadAbusiveFile") || len(e.Errors) > 0 && e.Errors[0].Reason == "cannotDownloadAbusiveFile" {
+                               LOG_WARN("GCD_STORAGE", "%s is marked as abusive, will download anyway.", filePath)
+                               req = req.AcknowledgeAbuse(true)
+                       }
+               }
+               response, err = req.Download()
+               if retry, retry_err := storage.shouldRetry(threadIndex, err); retry_err == nil && !retry {
                        break
                } else if retry {
                        continue
                } else {
-                       return err
+                       return retry_err
                }
        }

It worked, and I'm unblocked. I thought that the googleapi.Error would contain the underlying error and I could just check its value, but Errors was empty, hence the quick and dirty string matching.

Just a heads up that this is a potential issue and that supporting sending this response could help some people out 🙂.

gilbertchen commented 6 years ago

Interesting. Can you create a PR for your changes?

s4y commented 6 years ago

I can if it'd be helpful, but I see the change as super hacky as-is (because the library doesn't seem to be parsing this error, or I'm not holding it right). So, I was hoping that you'd have a better idea.

s4y commented 6 years ago

Uploaded #447.