// Copyright (C) 2024, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; using System.Collections.Specialized; using Duplicati.Library.Utility; namespace Duplicati.Library.Backend.WebApi { public static class GoogleCloudStorage { // From: https://cloud.google.com/storage/docs/bucket-locations public static readonly KeyValuePair[] KNOWN_GCS_LOCATIONS = { new KeyValuePair("(default)", null), new KeyValuePair("Europe", "EU"), new KeyValuePair("United States", "US"), new KeyValuePair("Asia", "ASIA"), //Regional buckets: https://cloud.google.com/storage/docs/regional-buckets new KeyValuePair("Eastern Asia-Pacific", "ASIA-EAST1"), new KeyValuePair("Central United States 1", "US-CENTRAL1"), new KeyValuePair("Central United States 2", "US-CENTRAL2"), new KeyValuePair("Eastern United States 1", "US-EAST1"), new KeyValuePair("Eastern United States 2", "US-EAST2"), new KeyValuePair("Eastern United States 3", "US-EAST3"), new KeyValuePair("Western United States", "US-WEST1"), }; public static readonly KeyValuePair[] KNOWN_GCS_STORAGE_CLASSES = { new KeyValuePair("(default)", null), new KeyValuePair("Standard", "STANDARD"), new KeyValuePair("Durable Reduced Availability (DRA)", "DURABLE_REDUCED_AVAILABILITY"), new KeyValuePair("Nearline", "NEARLINE"), }; public static string[] Hosts() { return new [] { new System.Uri(Url.UPLOAD).Host, new System.Uri(Url.API).Host }; } public static string DeleteUrl(string bucketId, string objectId) { var path = BucketObjectPath(bucketId, objectId); return Uri.UriBuilder(Url.API, path); } public static string CreateFolderUrl(string projectId) { var queryParams = new NameValueCollection { { QueryParam.Project, projectId } }; return Uri.UriBuilder(Url.API, Path.Bucket, queryParams); } public static string RenameUrl(string bucketId, string objectId) { return Uri.UriBuilder(Url.API, BucketObjectPath(bucketId, objectId)); } public static string ListUrl(string bucketId, string prefix) { return ListUrl(bucketId, prefix, null); } public static string ListUrl(string bucketId, string prefix, string token) { var queryParams = new NameValueCollection { { QueryParam.Prefix, prefix } }; if (token != null) { queryParams.Set(QueryParam.PageToken, token); } return Uri.UriBuilder(Url.API, BucketObjectPath(bucketId), queryParams); } public static string PutUrl(string bucketId) { var queryParams = new NameValueCollection { { QueryParam.UploadType, QueryValue.Resumable } }; var path = UrlPath.Create(Path.Bucket).Append(bucketId).Append(Path.Object).ToString(); return Uri.UriBuilder(Url.UPLOAD, path, queryParams); } public static string GetUrl(string bucketId, string objectId) { var queryParams = new NameValueCollection { { QueryParam.Alt , QueryValue.Media } }; var path = BucketObjectPath(bucketId, objectId); return Uri.UriBuilder(Url.API, path, queryParams); } private static class Url { public const string API = "https://www.googleapis.com/storage/v1"; public const string UPLOAD = "https://www.googleapis.com/upload/storage/v1"; } private static class QueryParam { public const string Project = "project"; public const string Prefix = "prefix"; public const string PageToken = "pageToken"; public const string UploadType = "uploadType"; public const string Alt = "alt"; } private static class QueryValue { public const string Resumable = "resumable"; public const string Media = "media"; } private static class Path { public const string Bucket = "b"; public const string Object = "o"; } private static string BucketObjectPath(string bucketId, string objectId = null) { return UrlPath.Create(Path.Bucket) .Append(bucketId) .Append(Path.Object) .Append(objectId).ToString(); } } public static class GoogleDrive { public static string[] Hosts() { return new [] { new System.Uri(Url.DRIVE).Host, new System.Uri(Url.UPLOAD).Host }; } public static string GetUrl(string fileId) { return FileQueryUrl(fileId, new NameValueCollection{ { QueryParam.Alt, QueryValue.Media } }); } public static string DeleteUrl(string fileId, string teamDriveId) { return FileQueryUrl(Uri.UrlPathEncode(fileId), AddTeamDriveParam(teamDriveId)); } public static string PutUrl(string fileId, bool useTeamDrive) { var queryParams = new NameValueCollection { { QueryParam.UploadType, QueryValue.Resumable } }; if (useTeamDrive) { queryParams.Add(QueryParam.SupportsTeamDrive, QueryValue.True); } return !string.IsNullOrWhiteSpace(fileId) ? FileUploadUrl(Uri.UrlPathEncode(fileId), queryParams) : FileUploadUrl(queryParams); } public static string ListUrl(string fileQuery, string teamDriveId) { return ListUrl(fileQuery, teamDriveId, null); } public static string ListUrl(string fileQuery, string teamDriveId, string token) { var queryParams = new NameValueCollection { { QueryParam.File, fileQuery } }; queryParams.Add(AddTeamDriveParam(teamDriveId)); if (token != null) { queryParams.Set(QueryParam.PageToken, token); } return FileQueryUrl(queryParams); } public static string CreateFolderUrl(string teamDriveId) { return FileQueryUrl(AddTeamDriveParam(teamDriveId)); } public static string AboutInfoUrl() { return Uri.UriBuilder(Url.DRIVE, Path.About); } private static class Url { public const string DRIVE = "https://www.googleapis.com/drive/v2"; public const string UPLOAD = "https://www.googleapis.com/upload/drive/v2"; } private static class Path { public const string File = "files"; public const string About = "about"; } private static class QueryParam { public const string SupportsTeamDrive = "supportsTeamDrives"; public const string IncludeTeamDrive = "includeTeamDriveItems"; public const string TeamDriveId = "teamDriveId"; public const string corpora = "corpora"; public const string File = "q"; public const string PageToken = "pageToken"; public const string UploadType = "uploadType"; public const string Alt = "alt"; } private static class QueryValue { public const string True = "true"; public const string Resumable = "resumable"; public const string Media = "media"; public const string TeamDrive = "teamDrive"; } private static string FileQueryUrl(NameValueCollection values) { return Uri.UriBuilder(Url.DRIVE, Path.File, values); } private static string FileQueryUrl(string fileId, NameValueCollection values = null) { return Uri.UriBuilder(Url.DRIVE, UrlPath.Create(Path.File).Append(fileId).ToString(), values); } private static string FileUploadUrl(string fileId, NameValueCollection values) { return Uri.UriBuilder(Url.UPLOAD, UrlPath.Create(Path.File).Append(fileId).ToString(), values); } private static string FileUploadUrl(NameValueCollection values) { return Uri.UriBuilder(Url.UPLOAD, Path.File, values); } private static NameValueCollection AddTeamDriveParam(string teamDriveId) { return teamDriveId != null ? new NameValueCollection { { WebApi.GoogleDrive.QueryParam.SupportsTeamDrive, WebApi.GoogleDrive.QueryValue.True }, { WebApi.GoogleDrive.QueryParam.TeamDriveId, teamDriveId }, { WebApi.GoogleDrive.QueryParam.IncludeTeamDrive, WebApi.GoogleDrive.QueryValue.True }, { WebApi.GoogleDrive.QueryParam.corpora, WebApi.GoogleDrive.QueryValue.TeamDrive } } : new NameValueCollection( ); } } }