Пример #1
0
        /// <summary>
        /// Given a <see cref="B2UploadUrl"/> returned from the API, attempts to upload a file.
        /// </summary>
        /// <param name="b2UploadUrl">Information returned by the <c>b2_get_upload_url</c> API.</param>
        /// <param name="destinationPath">The remote path to upload to.</param>
        /// <param name="file">The file to upload.</param>
        /// <returns>
        ///     A B2UploadResult(HTTP status, B2Error, B2Upload) that can be decomposed as follows:
        ///
        ///     <ul>
        ///         <li><b>If successful:</b> <c>(200, null, B2Upload)</c></li>
        ///         <li><b>If unsuccessful:</b> <c>(HTTP status, B2Error, null)</c></li>
        ///         <li><b>If the connection failed:</b> <c>(-1, null, null)</c></li>
        ///     </ul>
        /// </returns>
        private B2UploadResult B2ApiUploadFile(B2UploadUrl b2UploadUrl, string destinationPath, Stream file)
        {
            // we want to send 'Content-Disposition: inline; filename="screenshot.png"'
            // this should display the uploaded data inline if possible, but if that fails, present a sensible filename
            // conveniently, this class will handle this for us
            ContentDisposition contentDisposition = new ContentDisposition("inline")
            {
                FileName = URLHelpers.GetFileName(destinationPath)
            };

            DebugHelper.WriteLine($"B2 uploader: Content disposition is '{contentDisposition}'.");

            // compute SHA1 hash without loading the file fully into memory
            string sha1Hash;

            using (SHA1CryptoServiceProvider cryptoProvider = new SHA1CryptoServiceProvider())
            {
                file.Seek(0, SeekOrigin.Begin);
                byte[] bytes = cryptoProvider.ComputeHash(file);
                sha1Hash = BitConverter.ToString(bytes).Replace("-", "").ToLower();
                file.Seek(0, SeekOrigin.Begin);
            }
            DebugHelper.WriteLine($"B2 uploader: SHA1 hash is '{sha1Hash}'.");

            // it's showtime
            // https://www.backblaze.com/b2/docs/b2_upload_file.html
            NameValueCollection headers = new NameValueCollection()
            {
                ["Authorization"]     = b2UploadUrl.authorizationToken,
                ["X-Bz-File-Name"]    = URLHelpers.URLEncode(destinationPath),
                ["Content-Length"]    = file.Length.ToString(),
                ["X-Bz-Content-Sha1"] = sha1Hash,
                ["X-Bz-Info-src_last_modified_millis"] = DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString(),
                ["X-Bz-Info-b2-content-disposition"]   = URLHelpers.URLEncode(contentDisposition.ToString()),
            };

            string contentType = UploadHelpers.GetMimeType(destinationPath);

            using (HttpWebResponse res = GetResponse(HttpMethod.POST, b2UploadUrl.uploadUrl,
                                                     contentType: contentType, headers: headers, data: file, allowNon2xxResponses: true))
            {
                // if connection failed, res will be null, and here we -do- want to check explicitly for this
                // since the server might be down
                if (res == null)
                {
                    return(new B2UploadResult(-1, null, null));
                }

                if (res.StatusCode != HttpStatusCode.OK)
                {
                    return(new B2UploadResult((int)res.StatusCode, ParseB2Error(res), null));
                }

                string body = UploadHelpers.ResponseToString(res);
                DebugHelper.WriteLine($"B2 uploader: B2ApiUploadFile() reports success! '{body}'");

                return(new B2UploadResult((int)res.StatusCode, null, JsonConvert.DeserializeObject <B2Upload>(body)));
            }
        }
Пример #2
0
        /// <summary>
        /// Uploads the locally-stored database <paramref name="localDb" /> to the bucket that <paramref name="client" /> has access to.
        /// </summary>
        /// <param name="client">The <see cref="B2Client" /> created by <see cref="GetClient" /> with access to a bucket to upload to.</param>
        /// <param name="localDb">The local database to upload.</param>
        /// <returns><see langword="true" /> if the upload was successful, or <see langword="false" /> otherwise.</returns>
        public static async Task <bool> UploadDbAsync(B2Client client, PwDatabase localDb)
        {
            if (client == null)
            {
                return(false);
            }

            Interface.UpdateStatus("Uploading database...");

            string localPath = localDb.IOConnectionInfo.Path;

            byte[] fileData;
            using (FileStream fs = File.OpenRead(localPath))
            {
                if (!fs.CanRead)
                {
                    return(false);
                }

                using (MemoryStream ms = new MemoryStream())
                {
                    fs.CopyTo(ms);
                    fileData = ms.ToArray();
                }
            }

            try
            {
                B2UploadUrl uploadUrl = await client.Files.GetUploadUrl(client.Capabilities.BucketId);

                B2File file = await client.Files.Upload(fileData, Path.GetFileName(localPath), uploadUrl, true,
                                                        client.Capabilities.BucketId);
            }
            catch (Exception e)
            {
                if (new [] { typeof(SocketException), typeof(WebException), typeof(HttpRequestException), typeof(AggregateException), typeof(InvalidOperationException) }.Contains(e.GetType()))
                {
                    Interface.UpdateStatus("Unable to upload the database to B2.");
                    return(false);
                }

                throw;
            }

            Interface.UpdateStatus("Database upload successful.");

            return(true);
        }
Пример #3
0
        /// <summary>
        /// Uploads one file to B2, returning its unique file ID. Filename will be URL Encoded. If auto retry
        /// is set true it will retry a failed upload once after 1 second.
        /// </summary>
        /// <param name="fileData"></param>
        /// <param name="fileName"></param>
        /// <param name="uploadUrl"></param>
        /// <param name="bucketId"></param>
        /// <param name="autoRetry">Retry a failed upload one time after waiting for 1 second.</param>
        /// <param name="fileInfo"></param>
        /// <param name="cancelToken"></param>
        /// <returns></returns>
        public async Task <B2File> Upload(byte[] fileData, string fileName, B2UploadUrl uploadUrl, bool autoRetry, string bucketId = "", Dictionary <string, string> fileInfo = null, CancellationToken cancelToken = default(CancellationToken))
        {
            // Now we can upload the file
            var requestMessage = FileUploadRequestGenerators.Upload(_options, uploadUrl.UploadUrl, fileData, fileName, fileInfo);

            var response = await _client.SendAsync(requestMessage, cancelToken);

            // Auto retry
            if (autoRetry && (
                    response.StatusCode == (HttpStatusCode)429 ||
                    response.StatusCode == HttpStatusCode.RequestTimeout ||
                    response.StatusCode == HttpStatusCode.ServiceUnavailable))
            {
                Task.Delay(1000, cancelToken).Wait(cancelToken);
                response = await _client.SendAsync(requestMessage, cancelToken);
            }

            return(await ResponseParser.ParseResponse <B2File>(response, _api));
        }
Пример #4
0
        /// <summary>
        /// Uploads a temporary file.
        /// </summary>
        /// <param name="fileData">The file data.</param>
        /// <param name="fileName">Name of the file.</param>
        public static async Task <File> UploadTemporaryFileAsync(byte[] fileData, string fileName)
        {
            await Authorize();

            B2UploadUrl uploadUrl = await _TempBucketClient.Files.GetUploadUrl();

            string sha1Hash = Utilities.GetSHA1Hash(fileData);

            B2File uploadedFile = await _TempBucketClient.Files.Upload(fileData, fileName, uploadUrl);

            if (sha1Hash == uploadedFile.ContentSHA1)
            {
                string fullUrl  = $"{Constants.BackblazeCDN}/file/{SettingsManager.Configuration.BackblazeTempBucket.BucketName}/{uploadedFile.FileName}";
                string shortUrl = await Http.ShortenUrl(fullUrl);

                return(new File(SettingsManager.Configuration.BackblazeTempBucket.BucketName, uploadedFile.FileName, fullUrl, shortUrl));
            }

            return(null);
        }
Пример #5
0
        public override UploadResult Upload(Stream stream, string fileName)
        {
            string parsedUploadPath = NameParser.Parse(NameParserType.FolderPath, UploadPath);
            string destinationPath  = parsedUploadPath + fileName;

            // docs: https://www.backblaze.com/b2/docs/

            // STEP 1: authorize, get auth token, api url, download url
            DebugHelper.WriteLine($"B2 uploader: Attempting to authorize as '{ApplicationKeyId}'.");
            B2Authorization auth = B2ApiAuthorize(ApplicationKeyId, ApplicationKey, out string authError);

            if (authError != null)
            {
                DebugHelper.WriteLine("B2 uploader: Failed to authorize.");
                Errors.Add($"Could not authenticate with B2: {authError}");
                return(null);
            }

            DebugHelper.WriteLine($"B2 uploader: Authorized, using API server {auth.apiUrl}, download URL {auth.downloadUrl}");

            // STEP 1.25: if we have an application key, there will be a bucketId present here, but if
            //            not, we have an account key and need to find our bucket id ourselves
            string bucketId = auth.allowed?.bucketId;

            if (bucketId == null)
            {
                DebugHelper.WriteLine("B2 uploader: Key doesn't have a bucket ID set, so I'm looking for a bucket ID.");

                string newBucketId = B2ApiGetBucketId(auth, BucketName, out string getBucketError);
                if (getBucketError != null)
                {
                    DebugHelper.WriteLine($"B2 uploader: It's {newBucketId}.");
                    bucketId = newBucketId;
                }
            }

            // STEP 1.5: verify whether we can write to the bucket user wants to write to, with the given prefix
            DebugHelper.WriteLine("B2 uploader: Checking clientside whether we have permission to upload.");
            bool authCheckOk = IsAuthorizedForUpload(auth, bucketId, destinationPath, out string authCheckError);

            if (!authCheckOk)
            {
                DebugHelper.WriteLine("B2 uploader: Key is not suitable for this upload.");
                Errors.Add($"B2 upload failed: {authCheckError}");
                return(null);
            }

            // STEP 1.75: start upload attempt loop
            const int   maxTries = 5;
            B2UploadUrl url      = null;

            for (int tries = 1; tries <= maxTries; tries++)
            {
                string newOrSameUrl = url == null ? "New URL." : "Same URL.";
                DebugHelper.WriteLine($"B2 uploader: Upload attempt {tries} of {maxTries}. {newOrSameUrl}");

                // sloppy, but we need exponential backoff somehow and we are not in async code
                // since B2Uploader should have the thread to itself, and this just occurs on rare failures,
                // this should be OK
                if (tries > 1)
                {
                    int delay = (int)Math.Pow(2, tries - 1) * 1000;
                    DebugHelper.WriteLine($"Waiting ${delay} ms for backoff.");
                    Thread.Sleep(delay);
                }

                // STEP 2: get upload url that we need to POST to in step 3
                if (url == null)
                {
                    DebugHelper.WriteLine("B2 uploader: Getting new upload URL.");
                    url = B2ApiGetUploadUrl(auth, bucketId, out string getUrlError);
                    if (getUrlError != null)
                    {
                        // this is guaranteed to be unrecoverable, so bail out
                        DebugHelper.WriteLine("B2 uploader: Got error trying to get upload URL.");
                        Errors.Add("Could not get B2 upload URL: " + getUrlError);
                        return(null);
                    }
                }

                // STEP 3: upload file and see if anything went wrong
                DebugHelper.WriteLine($"B2 uploader: Uploading to URL {url.uploadUrl}");
                B2UploadResult   uploadResult      = B2ApiUploadFile(url, destinationPath, stream);
                HashSet <string> expiredTokenCodes = new HashSet <string>(new List <string> {
                    "expired_auth_token", "bad_auth_token"
                });

                if (uploadResult.RC == -1)
                {
                    // magic number for "connection failed", should also happen when upload
                    // caps are exceeded
                    DebugHelper.WriteLine("B2 uploader: Connection failed, trying with new URL.");
                    url = null;
                    continue;
                }
                else if (uploadResult.RC == 401 && expiredTokenCodes.Contains(uploadResult.Error.code))
                {
                    // Unauthorized, our token expired
                    DebugHelper.WriteLine("B2 uploader: Upload auth token expired, trying with new URL.");
                    url = null;
                    continue;
                }
                else if (uploadResult.RC == 408)
                {
                    DebugHelper.WriteLine("B2 uploader: Request Timeout, trying with same URL.");
                    continue;
                }
                else if (uploadResult.RC == 429)
                {
                    DebugHelper.WriteLine("B2 uploader: Too Many Requests, trying with same URL.");
                    continue;
                }
                else if (uploadResult.RC != 200)
                {
                    // something else happened that wasn't a success, so bail out
                    DebugHelper.WriteLine("B2 uploader: Unknown error, upload failure.");
                    Errors.Add("B2 uploader: Unknown error occurred while calling b2_upload_file().");
                    return(null);
                }

                // success!
                // STEP 4: compose:
                //           the download url (e.g. "https://f567.backblazeb2.com")
                //           /file/$bucket/$uploadPath
                //         or
                //           $customUrl/$uploadPath

                string remoteLocation = URLHelpers.CombineURL(auth.downloadUrl, "file", URLHelpers.URLEncode(BucketName), uploadResult.Upload.fileName);

                DebugHelper.WriteLine($"B2 uploader: Successful upload! File should be at: {remoteLocation}");

                if (UseCustomUrl)
                {
                    string parsedCustomUrl = NameParser.Parse(NameParserType.FolderPath, CustomUrl);
                    remoteLocation = parsedCustomUrl + uploadResult.Upload.fileName;
                    DebugHelper.WriteLine($"B2 uploader: But user requested custom URL, which will be: {remoteLocation}");
                }

                return(new UploadResult()
                {
                    IsSuccess = true,
                    URL = remoteLocation
                });
            }

            DebugHelper.WriteLine("B2 uploader: Ran out of attempts, aborting.");
            Errors.Add($"B2 upload failed: Could not upload file after {maxTries} attempts.");
            return(null);
        }
Пример #6
0
 /// <summary>
 /// Uploads one file to B2, returning its unique file ID. Filename will be URL Encoded.
 /// </summary>
 /// <param name="fileData"></param>
 /// <param name="fileName"></param>
 /// <param name="bucketId"></param>
 /// <param name="cancelToken"></param>
 /// <returns></returns>
 public async Task <B2File> Upload(byte[] fileData, string fileName, B2UploadUrl uploadUrl, string bucketId = "", Dictionary <string, string> fileInfo = null, CancellationToken cancelToken = default(CancellationToken))
 {
     return(await Upload(fileData, fileName, uploadUrl, false, bucketId, fileInfo, cancelToken));
 }