2023-10-18 20:31:50 +02:00
// What is this module called?
module onedrive ;
// What does this module require to function?
import core.stdc.stdlib : EXIT_SUCCESS , EXIT_FAILURE , exit ;
import core.memory ;
import core.thread ;
import std.stdio ;
import std.string ;
import std.utf ;
import std.file ;
import std.exception ;
import std.regex ;
import std.json ;
2019-10-21 09:04:08 +02:00
import std.algorithm.searching ;
2023-10-18 20:31:50 +02:00
import std.net.curl ;
2022-04-21 00:03:22 +02:00
import std.datetime ;
2023-10-18 20:31:50 +02:00
import std.path ;
import std.conv ;
import std.math ;
import std.uri ;
2024-01-08 04:53:33 +01:00
import std.array ;
2021-11-23 20:54:28 +01:00
2023-10-18 21:48:25 +02:00
// Required for webhooks
import arsd.cgi ;
import std.concurrency ;
import core.atomic : atomicOp ;
import std.uuid ;
2023-10-18 20:31:50 +02:00
// What other modules that we have created do we need to import?
import config ;
import log ;
import util ;
import curlEngine ;
2018-11-23 20:26:30 +01:00
2023-10-18 21:48:25 +02:00
// Shared variables between classes
shared bool debugHTTPResponseOutput = false ;
2023-10-18 20:31:50 +02:00
class OneDriveException : Exception {
2017-12-28 15:03:15 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/errors
2016-12-14 15:17:20 +01:00
int httpStatusCode ;
2016-12-13 18:04:16 +01:00
JSONValue error ;
2015-10-04 16:24:45 +02:00
2023-10-18 20:31:50 +02:00
@safe pure this ( int httpStatusCode , string reason , string file = __FILE__ , size_t line = __LINE__ ) {
2016-12-14 15:17:20 +01:00
this . httpStatusCode = httpStatusCode ;
2016-12-13 18:04:16 +01:00
this . error = error ;
2016-12-14 15:17:20 +01:00
string msg = format ( "HTTP request returned status code %d (%s)" , httpStatusCode , reason ) ;
2017-12-28 15:03:15 +01:00
super ( msg , file , line ) ;
2015-10-04 16:24:45 +02:00
}
2016-12-13 18:04:16 +01:00
2023-10-18 20:31:50 +02:00
this ( int httpStatusCode , string reason , ref const JSONValue error , string file = __FILE__ , size_t line = __LINE__ ) {
2016-12-14 15:17:20 +01:00
this . httpStatusCode = httpStatusCode ;
2016-12-13 18:04:16 +01:00
this . error = error ;
2018-03-29 23:14:00 +02:00
string msg = format ( "HTTP request returned status code %d (%s)\n%s" , httpStatusCode , reason , toJSON ( error , true ) ) ;
2017-12-28 15:03:15 +01:00
super ( msg , file , line ) ;
2015-10-04 16:24:45 +02:00
}
2015-09-01 20:45:34 +02:00
}
2023-10-18 21:48:25 +02:00
class OneDriveWebhook {
// We need OneDriveWebhook.serve to be a static function, otherwise we would hit the member function
// "requires a dual-context, which is deprecated" warning. The root cause is described here:
// - https://issues.dlang.org/show_bug.cgi?id=5710
// - https://forum.dlang.org/post/fkyppfxzegenniyzztos@forum.dlang.org
// The problem is deemed a bug and should be fixed in the compilers eventually. The singleton stuff
// could be undone when it is fixed.
//
// Following the singleton pattern described here: https://wiki.dlang.org/Low-Lock_Singleton_Pattern
// Cache instantiation flag in thread-local bool
// Thread local
private static bool instantiated_ ;
2023-10-28 23:04:57 +02:00
private RequestServer server ;
2023-10-18 21:48:25 +02:00
// Thread global
private __gshared OneDriveWebhook instance_ ;
private string host ;
private ushort port ;
private Tid parentTid ;
private shared uint count ;
2023-10-27 22:21:54 +02:00
private bool started ;
2023-10-18 21:48:25 +02:00
static OneDriveWebhook getOrCreate ( string host , ushort port , Tid parentTid ) {
if ( ! instantiated_ ) {
synchronized ( OneDriveWebhook . classinfo ) {
if ( ! instance_ ) {
instance_ = new OneDriveWebhook ( host , port , parentTid ) ;
}
instantiated_ = true ;
}
}
return instance_ ;
}
private this ( string host , ushort port , Tid parentTid ) {
this . host = host ;
this . port = port ;
this . parentTid = parentTid ;
this . count = 0 ;
}
2023-10-27 22:21:54 +02:00
void serve ( ) {
spawn ( & serveStatic ) ;
this . started = true ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Started webhook server" ) ;
2023-10-27 22:21:54 +02:00
}
void stop ( ) {
if ( this . started ) {
2023-10-28 23:04:57 +02:00
server . stop ( ) ;
2023-10-27 22:21:54 +02:00
this . started = false ;
}
2023-12-08 23:19:57 +01:00
addLogEntry ( "Stopped webhook server" ) ;
2023-10-28 23:04:57 +02:00
object . destroy ( server ) ;
2023-10-27 22:21:54 +02:00
}
2023-10-18 21:48:25 +02:00
// The static serve() is necessary because spawn() does not like instance methods
2023-10-27 22:21:54 +02:00
private static void serveStatic ( ) {
2023-10-18 21:48:25 +02:00
// we won't create the singleton instance if it hasn't been created already
// such case is a bug which should crash the program and gets fixed
instance_ . serveImpl ( ) ;
}
// The static handle() is necessary to work around the dual-context warning mentioned above
private static void handle ( Cgi cgi ) {
// we won't create the singleton instance if it hasn't been created already
// such case is a bug which should crash the program and gets fixed
instance_ . handleImpl ( cgi ) ;
}
private void serveImpl ( ) {
2023-10-28 23:04:57 +02:00
server = RequestServer ( host , port ) ;
2023-10-18 21:48:25 +02:00
server . serveEmbeddedHttp ! handle ( ) ;
}
private void handleImpl ( Cgi cgi ) {
if ( debugHTTPResponseOutput ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Webhook request: " ~ to ! string ( cgi . requestMethod ) ~ " " ~ to ! string ( cgi . requestUri ) ) ;
2023-10-18 21:48:25 +02:00
if ( ! cgi . postBody . empty ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Webhook post body: " ~ to ! string ( cgi . postBody ) ) ;
2023-10-18 21:48:25 +02:00
}
}
cgi . setResponseContentType ( "text/plain" ) ;
if ( "validationToken" in cgi . get ) {
// For validation requests, respond with the validation token passed in the query string
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/webhook-receiver-validation-request
cgi . write ( cgi . get [ "validationToken" ] ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Webhook: handled validation request" ) ;
2023-10-18 21:48:25 +02:00
} else {
// Notifications don't include any information about the changes that triggered them.
// Put a refresh signal in the queue and let the main monitor loop process it.
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/using-webhooks
count . atomicOp ! "+=" ( 1 ) ;
send ( parentTid , to ! ulong ( count ) ) ;
cgi . write ( "OK" ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Webhook: sent refresh signal #" ~ to ! string ( count ) ) ;
2023-10-18 21:48:25 +02:00
}
}
}
2023-10-18 20:31:50 +02:00
class OneDriveApi {
// Class variables
ApplicationConfig appConfig ;
CurlEngine curlEngine ;
2023-10-18 21:48:25 +02:00
OneDriveWebhook webhook ;
2023-10-18 20:31:50 +02:00
string clientId = "" ;
string companyName = "" ;
string authUrl = "" ;
string redirectUrl = "" ;
string tokenUrl = "" ;
string driveUrl = "" ;
string driveByIdUrl = "" ;
string sharedWithMeUrl = "" ;
string itemByIdUrl = "" ;
string itemByPathUrl = "" ;
string siteSearchUrl = "" ;
string siteDriveUrl = "" ;
string tenantId = "" ;
string authScope = "" ;
2024-01-08 04:53:33 +01:00
const ( char ) [ ] refreshToken = "" ;
2023-10-18 20:31:50 +02:00
bool dryRun = false ;
bool debugResponse = false ;
ulong retryAfterValue = 0 ;
2023-10-18 21:48:25 +02:00
// Webhook Subscriptions
string subscriptionUrl = "" ;
string subscriptionId = "" ;
2023-10-19 07:33:25 +02:00
SysTime subscriptionExpiration , subscriptionLastErrorAt ;
2023-10-19 09:37:14 +02:00
Duration subscriptionExpirationInterval , subscriptionRenewalInterval , subscriptionRetryInterval ;
2023-10-18 21:48:25 +02:00
string notificationUrl = "" ;
2023-10-18 20:31:50 +02:00
this ( ApplicationConfig appConfig ) {
// Configure the class varaible to consume the application configuration
this . appConfig = appConfig ;
// Configure the major API Query URL's, based on using application configuration
// These however can be updated by config option 'azure_ad_endpoint', thus handled differently
// Drive Queries
driveUrl = appConfig . globalGraphEndpoint ~ "/v1.0/me/drive" ;
driveByIdUrl = appConfig . globalGraphEndpoint ~ "/v1.0/drives/" ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// What is 'shared with me' Query
sharedWithMeUrl = appConfig . globalGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe" ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Item Queries
itemByIdUrl = appConfig . globalGraphEndpoint ~ "/v1.0/me/drive/items/" ;
itemByPathUrl = appConfig . globalGraphEndpoint ~ "/v1.0/me/drive/root:/" ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Office 365 / SharePoint Queries
siteSearchUrl = appConfig . globalGraphEndpoint ~ "/v1.0/sites?search" ;
siteDriveUrl = appConfig . globalGraphEndpoint ~ "/v1.0/sites/" ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Subscriptions
subscriptionUrl = appConfig . globalGraphEndpoint ~ "/v1.0/subscriptions" ;
2023-10-18 21:48:25 +02:00
subscriptionExpiration = Clock . currTime ( UTC ( ) ) ;
2023-10-19 07:33:25 +02:00
subscriptionLastErrorAt = SysTime . fromUnixTime ( 0 ) ;
2023-10-18 21:48:25 +02:00
subscriptionExpirationInterval = dur ! "seconds" ( appConfig . getValueLong ( "webhook_expiration_interval" ) ) ;
subscriptionRenewalInterval = dur ! "seconds" ( appConfig . getValueLong ( "webhook_renewal_interval" ) ) ;
2023-10-19 09:37:14 +02:00
subscriptionRetryInterval = dur ! "seconds" ( appConfig . getValueLong ( "webhook_retry_interval" ) ) ;
2023-10-18 21:48:25 +02:00
notificationUrl = appConfig . getValueString ( "webhook_public_url" ) ;
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
// Initialise the OneDrive API class
2023-10-24 22:20:16 +02:00
bool initialise ( bool keepAlive = false ) {
2023-10-18 20:31:50 +02:00
// Initialise the curl engine
curlEngine = new CurlEngine ( ) ;
2023-10-24 22:20:16 +02:00
curlEngine . initialise ( appConfig . getValueLong ( "dns_timeout" ) , appConfig . getValueLong ( "connect_timeout" ) , appConfig . getValueLong ( "data_timeout" ) , appConfig . getValueLong ( "operation_timeout" ) , appConfig . defaultMaxRedirects , appConfig . getValueBool ( "debug_https" ) , appConfig . getValueString ( "user_agent" ) , appConfig . getValueBool ( "force_http_11" ) , appConfig . getValueLong ( "rate_limit" ) , appConfig . getValueLong ( "ip_protocol_version" ) , keepAlive ) ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Authorised value to return
bool authorised = false ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Did the user specify --dry-run
dryRun = appConfig . getValueBool ( "dry_run" ) ;
// Did the user specify --debug-https
debugResponse = appConfig . getValueBool ( "debug_https" ) ;
2023-10-18 21:48:25 +02:00
// Flag this so if webhooks are being used, it can also be consumed
debugHTTPResponseOutput = appConfig . getValueBool ( "debug_https" ) ;
2023-10-18 20:31:50 +02:00
// Set clientId to use the configured 'application_id'
clientId = appConfig . getValueString ( "application_id" ) ;
if ( clientId ! = appConfig . defaultApplicationId ) {
// a custom 'application_id' was set
companyName = "custom_application" ;
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
// Do we have a custom Azure Tenant ID?
if ( ! appConfig . getValueString ( "azure_tenant_id" ) . empty ) {
// Use the value entered by the user
tenantId = appConfig . getValueString ( "azure_tenant_id" ) ;
2021-11-23 20:54:28 +01:00
} else {
2023-10-18 20:31:50 +02:00
// set to common
tenantId = "common" ;
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
// Did the user specify a 'drive_id' ?
if ( ! appConfig . getValueString ( "drive_id" ) . empty ) {
// Update base URL's
driveUrl = driveByIdUrl ~ appConfig . getValueString ( "drive_id" ) ;
itemByIdUrl = driveUrl ~ "/items" ;
itemByPathUrl = driveUrl ~ "/root:/" ;
2020-11-25 19:51:29 +01:00
}
2023-10-18 20:31:50 +02:00
// Configure the authentication scope
if ( appConfig . getValueBool ( "read_only_auth_scope" ) ) {
// read-only authentication scopes has been requested
authScope = "&scope=Files.Read%20Files.Read.All%20Sites.Read.All%20offline_access&response_type=code&prompt=login&redirect_uri=" ;
} else {
// read-write authentication scopes will be used (default)
authScope = "&scope=Files.ReadWrite%20Files.ReadWrite.All%20Sites.ReadWrite.All%20offline_access&response_type=code&prompt=login&redirect_uri=" ;
2020-10-05 23:06:04 +02:00
}
2023-10-18 20:31:50 +02:00
2020-06-16 23:57:14 +02:00
// Configure Azure AD endpoints if 'azure_ad_endpoint' is configured
2023-10-18 20:31:50 +02:00
string azureConfigValue = appConfig . getValueString ( "azure_ad_endpoint" ) ;
2020-06-16 23:57:14 +02:00
switch ( azureConfigValue ) {
case "" :
2020-10-10 06:39:04 +02:00
if ( tenantId = = "common" ) {
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring Global Azure AD Endpoints" ) ;
2020-10-10 06:39:04 +02:00
} else {
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring Global Azure AD Endpoints - Single Tenant Application" ) ;
2020-10-10 06:39:04 +02:00
}
// Authentication
2023-10-18 20:31:50 +02:00
authUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/authorize" ;
redirectUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
tokenUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/token" ;
2020-06-16 23:57:14 +02:00
break ;
case "USL4" :
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring Azure AD for US Government Endpoints" ) ;
2020-06-16 23:57:14 +02:00
// Authentication
2023-10-18 20:31:50 +02:00
authUrl = appConfig . usl4AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/authorize" ;
tokenUrl = appConfig . usl4AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/token" ;
if ( clientId = = appConfig . defaultApplicationId ) {
2020-11-25 19:51:29 +01:00
// application_id == default
2023-12-08 23:19:57 +01:00
addLogEntry ( "USL4 AD Endpoint but default application_id, redirectUrl needs to be aligned to globalAuthEndpoint" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
} else {
// custom application_id
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . usl4AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
}
2021-11-23 20:54:28 +01:00
2020-06-16 23:57:14 +02:00
// Drive Queries
2023-10-18 20:31:50 +02:00
driveUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/me/drive" ;
driveByIdUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/drives/" ;
2020-06-16 23:57:14 +02:00
// Item Queries
2023-10-18 20:31:50 +02:00
itemByIdUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/me/drive/items/" ;
itemByPathUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/me/drive/root:/" ;
2020-06-16 23:57:14 +02:00
// Office 365 / SharePoint Queries
2023-10-18 20:31:50 +02:00
siteSearchUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/sites?search" ;
siteDriveUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/sites/" ;
2020-06-27 11:10:37 +02:00
// Shared With Me
2023-10-18 20:31:50 +02:00
sharedWithMeUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/me/drive/sharedWithMe" ;
2021-11-23 20:54:28 +01:00
// Subscriptions
2023-10-18 20:31:50 +02:00
subscriptionUrl = appConfig . usl4GraphEndpoint ~ "/v1.0/subscriptions" ;
2020-06-16 23:57:14 +02:00
break ;
case "USL5" :
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring Azure AD for US Government Endpoints (DOD)" ) ;
2020-06-16 23:57:14 +02:00
// Authentication
2023-10-18 20:31:50 +02:00
authUrl = appConfig . usl5AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/authorize" ;
tokenUrl = appConfig . usl5AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/token" ;
if ( clientId = = appConfig . defaultApplicationId ) {
2020-11-25 19:51:29 +01:00
// application_id == default
2023-12-08 23:19:57 +01:00
addLogEntry ( "USL5 AD Endpoint but default application_id, redirectUrl needs to be aligned to globalAuthEndpoint" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
} else {
// custom application_id
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . usl5AuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
}
2021-11-23 20:54:28 +01:00
2020-06-16 23:57:14 +02:00
// Drive Queries
2023-10-18 20:31:50 +02:00
driveUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/me/drive" ;
driveByIdUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/drives/" ;
2020-06-16 23:57:14 +02:00
// Item Queries
2023-10-18 20:31:50 +02:00
itemByIdUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/me/drive/items/" ;
itemByPathUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/me/drive/root:/" ;
2020-06-16 23:57:14 +02:00
// Office 365 / SharePoint Queries
2023-10-18 20:31:50 +02:00
siteSearchUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/sites?search" ;
siteDriveUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/sites/" ;
2020-06-27 11:10:37 +02:00
// Shared With Me
2023-10-18 20:31:50 +02:00
sharedWithMeUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/me/drive/sharedWithMe" ;
2021-11-23 20:54:28 +01:00
// Subscriptions
2023-10-18 20:31:50 +02:00
subscriptionUrl = appConfig . usl5GraphEndpoint ~ "/v1.0/subscriptions" ;
2020-06-16 23:57:14 +02:00
break ;
case "DE" :
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring Azure AD Germany" ) ;
2020-06-16 23:57:14 +02:00
// Authentication
2023-10-18 20:31:50 +02:00
authUrl = appConfig . deAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/authorize" ;
tokenUrl = appConfig . deAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/token" ;
if ( clientId = = appConfig . defaultApplicationId ) {
2020-11-25 19:51:29 +01:00
// application_id == default
2023-12-08 23:19:57 +01:00
addLogEntry ( "DE AD Endpoint but default application_id, redirectUrl needs to be aligned to globalAuthEndpoint" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
} else {
// custom application_id
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . deAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
}
2021-11-23 20:54:28 +01:00
2020-06-16 23:57:14 +02:00
// Drive Queries
2023-10-18 20:31:50 +02:00
driveUrl = appConfig . deGraphEndpoint ~ "/v1.0/me/drive" ;
driveByIdUrl = appConfig . deGraphEndpoint ~ "/v1.0/drives/" ;
2020-06-16 23:57:14 +02:00
// Item Queries
2023-10-18 20:31:50 +02:00
itemByIdUrl = appConfig . deGraphEndpoint ~ "/v1.0/me/drive/items/" ;
itemByPathUrl = appConfig . deGraphEndpoint ~ "/v1.0/me/drive/root:/" ;
2020-06-16 23:57:14 +02:00
// Office 365 / SharePoint Queries
2023-10-18 20:31:50 +02:00
siteSearchUrl = appConfig . deGraphEndpoint ~ "/v1.0/sites?search" ;
siteDriveUrl = appConfig . deGraphEndpoint ~ "/v1.0/sites/" ;
2020-06-27 11:10:37 +02:00
// Shared With Me
2023-10-18 20:31:50 +02:00
sharedWithMeUrl = appConfig . deGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe" ;
2021-11-23 20:54:28 +01:00
// Subscriptions
2023-10-18 20:31:50 +02:00
subscriptionUrl = appConfig . deGraphEndpoint ~ "/v1.0/subscriptions" ;
2020-06-16 23:57:14 +02:00
break ;
case "CN" :
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Configuring AD China operated by 21Vianet" ) ;
2020-06-16 23:57:14 +02:00
// Authentication
2023-10-18 20:31:50 +02:00
authUrl = appConfig . cnAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/authorize" ;
tokenUrl = appConfig . cnAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/v2.0/token" ;
if ( clientId = = appConfig . defaultApplicationId ) {
2020-11-25 19:51:29 +01:00
// application_id == default
2023-12-08 23:19:57 +01:00
addLogEntry ( "CN AD Endpoint but default application_id, redirectUrl needs to be aligned to globalAuthEndpoint" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . globalAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
} else {
// custom application_id
2023-10-18 20:31:50 +02:00
redirectUrl = appConfig . cnAuthEndpoint ~ "/" ~ tenantId ~ "/oauth2/nativeclient" ;
2020-11-25 19:51:29 +01:00
}
2021-11-23 20:54:28 +01:00
2020-06-16 23:57:14 +02:00
// Drive Queries
2023-10-18 20:31:50 +02:00
driveUrl = appConfig . cnGraphEndpoint ~ "/v1.0/me/drive" ;
driveByIdUrl = appConfig . cnGraphEndpoint ~ "/v1.0/drives/" ;
2020-06-16 23:57:14 +02:00
// Item Queries
2023-10-18 20:31:50 +02:00
itemByIdUrl = appConfig . cnGraphEndpoint ~ "/v1.0/me/drive/items/" ;
itemByPathUrl = appConfig . cnGraphEndpoint ~ "/v1.0/me/drive/root:/" ;
2020-06-16 23:57:14 +02:00
// Office 365 / SharePoint Queries
2023-10-18 20:31:50 +02:00
siteSearchUrl = appConfig . cnGraphEndpoint ~ "/v1.0/sites?search" ;
siteDriveUrl = appConfig . cnGraphEndpoint ~ "/v1.0/sites/" ;
2020-06-27 11:10:37 +02:00
// Shared With Me
2023-10-18 20:31:50 +02:00
sharedWithMeUrl = appConfig . cnGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe" ;
2021-11-23 20:54:28 +01:00
// Subscriptions
2023-10-18 20:31:50 +02:00
subscriptionUrl = appConfig . cnGraphEndpoint ~ "/v1.0/subscriptions" ;
2020-06-16 23:57:14 +02:00
break ;
2021-11-23 20:54:28 +01:00
// Default - all other entries
2020-06-16 23:57:14 +02:00
default :
2023-12-08 23:19:57 +01:00
if ( ! appConfig . apiWasInitialised ) addLogEntry ( "Unknown Azure AD Endpoint request - using Global Azure AD Endpoints" ) ;
2020-06-16 23:57:14 +02:00
}
2023-10-18 20:31:50 +02:00
// Has the application been authenticated?
if ( ! exists ( appConfig . refreshTokenFilePath ) ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Application has no 'refresh_token' thus needs to be authenticated" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
authorised = authorise ( ) ;
} else {
// Try and read the value from the appConfig if it is set, rather than trying to read the value from disk
if ( ! appConfig . refreshToken . empty ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Read token from appConfig" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
refreshToken = strip ( appConfig . refreshToken ) ;
authorised = true ;
} else {
// Try and read the file from disk
try {
refreshToken = strip ( readText ( appConfig . refreshTokenFilePath ) ) ;
// is the refresh_token empty?
if ( refreshToken . empty ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "RefreshToken exists but is empty: " ~ appConfig . refreshTokenFilePath ) ;
2023-10-18 20:31:50 +02:00
authorised = authorise ( ) ;
} else {
// existing token not empty
authorised = true ;
// update appConfig.refreshToken
appConfig . refreshToken = refreshToken ;
}
} catch ( FileException e ) {
authorised = authorise ( ) ;
} catch ( std . utf . UTFException e ) {
// path contains characters which generate a UTF exception
2023-12-08 23:19:57 +01:00
addLogEntry ( "Cannot read refreshToken from: " ~ appConfig . refreshTokenFilePath ) ;
addLogEntry ( " Error Reason:" ~ e . msg ) ;
2023-10-18 20:31:50 +02:00
authorised = false ;
}
}
if ( refreshToken . empty ) {
2023-12-08 23:19:57 +01:00
// PROBLEM ... CODING TO DO ??????????
addLogEntry ( "refreshToken is empty !!!!!!!!!! This will cause 4xx errors ... CODING TO DO TO HANDLE ?????" ) ;
2023-10-18 20:31:50 +02:00
}
}
// Return if we are authorised
2023-12-08 23:19:57 +01:00
addLogEntry ( "Authorised State: " ~ to ! string ( authorised ) , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
return authorised ;
}
// If the API has been configured correctly, print the items that been configured
void debugOutputConfiguredAPIItems ( ) {
2020-12-10 01:18:13 +01:00
// Debug output of configured URL's
2023-10-18 20:31:50 +02:00
// Application Identification
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured clientId " ~ clientId , [ "debug" ] ) ;
addLogEntry ( "Configured userAgent " ~ appConfig . getValueString ( "user_agent" ) , [ "debug" ] ) ;
2020-12-10 01:18:13 +01:00
// Authentication
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured authScope: " ~ authScope , [ "debug" ] ) ;
addLogEntry ( "Configured authUrl: " ~ authUrl , [ "debug" ] ) ;
addLogEntry ( "Configured redirectUrl: " ~ redirectUrl , [ "debug" ] ) ;
addLogEntry ( "Configured tokenUrl: " ~ tokenUrl , [ "debug" ] ) ;
2020-12-10 01:18:13 +01:00
// Drive Queries
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured driveUrl: " ~ driveUrl , [ "debug" ] ) ;
addLogEntry ( "Configured driveByIdUrl: " ~ driveByIdUrl , [ "debug" ] ) ;
2020-12-10 01:18:13 +01:00
// Shared With Me
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured sharedWithMeUrl: " ~ sharedWithMeUrl , [ "debug" ] ) ;
2020-12-10 01:18:13 +01:00
// Item Queries
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured itemByIdUrl: " ~ itemByIdUrl , [ "debug" ] ) ;
addLogEntry ( "Configured itemByPathUrl: " ~ itemByPathUrl , [ "debug" ] ) ;
2020-12-10 01:18:13 +01:00
// SharePoint Queries
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configured siteSearchUrl: " ~ siteSearchUrl , [ "debug" ] ) ;
addLogEntry ( "Configured siteDriveUrl: " ~ siteDriveUrl , [ "debug" ] ) ;
2020-05-20 03:37:11 +02:00
}
2023-10-18 20:31:50 +02:00
// Shutdown OneDrive API Curl Engine
void shutdown ( ) {
2023-10-27 22:21:54 +02:00
2023-10-18 20:31:50 +02:00
// Delete subscription if there exists any
2023-10-27 22:21:54 +02:00
try {
deleteSubscription ( ) ;
} catch ( OneDriveException e ) {
logSubscriptionError ( e ) ;
}
// Shutdown webhook server if it is running
if ( webhook ! is null ) {
webhook . stop ( ) ;
object . destroy ( webhook ) ;
}
2023-10-18 20:31:50 +02:00
// Reset any values to defaults, freeing any set objects
curlEngine . http . clearRequestHeaders ( ) ;
curlEngine . http . onSend = null ;
curlEngine . http . onReceive = null ;
curlEngine . http . onReceiveHeader = null ;
curlEngine . http . onReceiveStatusLine = null ;
curlEngine . http . contentLength = 0 ;
// Shut down the curl instance & close any open sockets
curlEngine . http . shutdown ( ) ;
// Free object and memory
object . destroy ( curlEngine ) ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
// Authenticate this client against Microsoft OneDrive API
bool authorise ( ) {
2016-06-30 12:51:44 +02:00
char [ ] response ;
2023-10-18 20:31:50 +02:00
// What URL should be presented to the user to access
2022-08-06 00:24:51 +02:00
string url = authUrl ~ "?client_id=" ~ clientId ~ authScope ~ redirectUrl ;
2023-10-18 20:31:50 +02:00
// Configure automated authentication if --auth-files authUrl:responseUrl is being used
string authFilesString = appConfig . getValueString ( "auth_files" ) ;
string authResponseString = appConfig . getValueString ( "auth_response" ) ;
if ( ! authResponseString . empty ) {
// read the response from authResponseString
2021-11-22 21:08:04 +01:00
response = cast ( char [ ] ) authResponseString ;
} else if ( authFilesString ! = "" ) {
2019-06-11 09:12:36 +02:00
string [ ] authFiles = authFilesString . split ( ":" ) ;
string authUrl = authFiles [ 0 ] ;
string responseUrl = authFiles [ 1 ] ;
2023-07-23 02:13:03 +02:00
try {
auto authUrlFile = File ( authUrl , "w" ) ;
authUrlFile . write ( url ) ;
authUrlFile . close ( ) ;
2023-10-18 20:31:50 +02:00
} catch ( FileException e ) {
2023-07-23 02:13:03 +02:00
// There was a file system error
// display the error message
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2023-12-21 12:09:46 +01:00
// Must force exit here, allow logging to be done
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
2023-10-18 20:31:50 +02:00
exit ( - 1 ) ;
} catch ( ErrnoException e ) {
// There was a file system error
// display the error message
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2023-12-21 12:09:46 +01:00
// Must force exit here, allow logging to be done
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
2023-10-18 20:31:50 +02:00
exit ( - 1 ) ;
2023-07-23 02:13:03 +02:00
}
2023-10-18 20:31:50 +02:00
2023-12-08 23:19:57 +01:00
addLogEntry ( "Client requires authentication before proceeding. Waiting for --auth-files elements to be available." ) ;
2023-07-23 02:13:03 +02:00
2019-06-11 09:12:36 +02:00
while ( ! exists ( responseUrl ) ) {
Thread . sleep ( dur ! ( "msecs" ) ( 100 ) ) ;
}
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// read response from provided from OneDrive
2020-03-02 02:54:56 +01:00
try {
response = cast ( char [ ] ) read ( responseUrl ) ;
} catch ( OneDriveException e ) {
// exception generated
2020-12-09 04:18:16 +01:00
displayOneDriveErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-03-02 02:54:56 +01:00
return false ;
2021-11-23 20:54:28 +01:00
}
2020-03-02 02:54:56 +01:00
// try to remove old files
2019-06-11 09:12:36 +02:00
try {
std . file . remove ( authUrl ) ;
std . file . remove ( responseUrl ) ;
} catch ( FileException e ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Cannot remove files " ~ authUrl ~ " " ~ responseUrl ) ;
2020-03-02 02:54:56 +01:00
return false ;
2019-06-11 09:12:36 +02:00
}
2021-11-22 21:08:04 +01:00
} else {
2023-12-21 10:55:33 +01:00
addLogEntry ( "Authorise this application by visiting:\n" , [ "consoleOnly" ] ) ;
addLogEntry ( url ~ "\n\n" , [ "consoleOnly" ] ) ;
addLogEntry ( "Enter the response uri from your browser: " , [ "consoleOnlyNoNewLine" ] ) ;
2021-11-22 21:08:04 +01:00
readln ( response ) ;
2023-10-18 20:31:50 +02:00
appConfig . applicationAuthorizeResponseUri = true ;
2019-06-11 09:12:36 +02:00
}
2016-06-30 12:51:44 +02:00
// match the authorization code
2020-03-20 21:20:19 +01:00
auto c = matchFirst ( response , r"(?:[\?&]code=)([\w\d-.]+)" ) ;
2016-06-30 12:51:44 +02:00
if ( c . empty ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "An empty or invalid response uri was entered" ) ;
2016-06-30 12:51:44 +02:00
return false ;
2015-09-01 20:45:34 +02:00
}
2016-06-30 12:51:44 +02:00
c . popFront ( ) ; // skip the whole match
redeemToken ( c . front ) ;
2023-10-18 20:31:50 +02:00
2016-06-30 12:51:44 +02:00
return true ;
2023-10-18 20:31:50 +02:00
2015-09-01 20:45:34 +02:00
}
2022-12-15 20:08:46 +01:00
2017-12-27 15:12:38 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/drive_get
2023-10-18 20:31:50 +02:00
JSONValue getDefaultDriveDetails ( ) {
2016-12-25 16:38:55 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2020-05-20 03:37:11 +02:00
url = driveUrl ;
2024-01-08 21:34:12 +01:00
return get ( url ) ;
2016-12-25 16:38:55 +01:00
}
2023-10-18 20:31:50 +02:00
2017-12-27 15:12:38 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
2023-10-18 20:31:50 +02:00
JSONValue getDefaultRootDetails ( ) {
2017-12-27 15:12:38 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2020-05-20 03:37:11 +02:00
url = driveUrl ~ "/root" ;
return get ( url ) ;
2017-12-27 15:12:38 +01:00
}
2023-10-18 20:31:50 +02:00
2020-06-27 11:10:37 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
2023-10-18 20:31:50 +02:00
JSONValue getDriveIdRoot ( string driveId ) {
2020-06-27 11:10:37 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2020-06-27 11:10:37 +02:00
url = driveByIdUrl ~ driveId ~ "/root" ;
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
2020-08-08 00:56:00 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/drive_get
2023-10-18 20:31:50 +02:00
JSONValue getDriveQuota ( string driveId ) {
2020-08-08 00:56:00 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2020-08-08 00:56:00 +02:00
url = driveByIdUrl ~ driveId ~ "/" ;
url ~ = "?select=quota" ;
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
// Return the details of the specified path, by giving the path we wish to query
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getPathDetails ( string path ) {
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
if ( ( path = = "." ) | | ( path = = "/" ) ) {
url = driveUrl ~ "/root/" ;
2020-05-20 03:37:11 +02:00
} else {
2023-10-18 20:31:50 +02:00
url = itemByPathUrl ~ encodeComponent ( path ) ~ ":/" ;
2017-12-27 15:12:38 +01:00
}
2015-09-01 20:45:34 +02:00
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
// Return the details of the specified item based on its driveID and itemID
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getPathDetailsById ( string driveId , string id ) {
checkAccessTokenExpired ( ) ;
string url ;
url = driveByIdUrl ~ driveId ~ "/items/" ~ id ;
//url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
return get ( url ) ;
}
// Create a shareable link for an existing file on OneDrive based on the accessScope JSON permissions
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createlink
JSONValue createShareableLink ( string driveId , string id , JSONValue accessScope ) {
checkAccessTokenExpired ( ) ;
string url ;
url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/createLink" ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
return post ( url , accessScope . toString ( ) ) ;
}
// Return the requested details of the specified path on the specified drive id and path
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getPathDetailsByDriveId ( string driveId , string path ) {
checkAccessTokenExpired ( ) ;
string url ;
2023-10-23 00:59:27 +02:00
// https://learn.microsoft.com/en-us/onedrive/developer/rest-api/concepts/addressing-driveitems?view=odsp-graph-online
// Required format: /drives/{drive-id}/root:/{item-path}:
url = driveByIdUrl ~ driveId ~ "/root:/" ~ encodeComponent ( path ) ~ ":" ;
2023-10-18 20:31:50 +02:00
return get ( url ) ;
}
2020-06-27 11:10:37 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta
2023-10-18 20:31:50 +02:00
JSONValue viewChangesByItemId ( string driveId , string id , string deltaLink ) {
2020-06-27 11:10:37 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
// If Business Account add addIncludeFeatureRequestHeader() which should add Prefer: Include-Feature=AddToOneDrive
if ( ( appConfig . accountType ! = "personal" ) & & ( appConfig . getValueBool ( "sync_business_shared_items" ) ) ) {
addIncludeFeatureRequestHeader ( ) ;
}
string url ;
// configure deltaLink to query
if ( deltaLink . empty ) {
url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/delta" ;
} else {
url = deltaLink ;
2020-06-27 11:10:37 +02:00
}
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
2020-06-16 23:57:14 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children
2023-10-18 20:31:50 +02:00
JSONValue listChildren ( string driveId , string id , string nextLink ) {
2020-06-16 23:57:14 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
// If Business Account add addIncludeFeatureRequestHeader() which should add Prefer: Include-Feature=AddToOneDrive
if ( ( appConfig . accountType ! = "personal" ) & & ( appConfig . getValueBool ( "sync_business_shared_items" ) ) ) {
addIncludeFeatureRequestHeader ( ) ;
}
string url ;
2020-06-16 23:57:14 +02:00
// configure URL to query
if ( nextLink . empty ) {
url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/children" ;
2023-10-18 20:31:50 +02:00
//url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
2020-06-16 23:57:14 +02:00
} else {
url = nextLink ;
}
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
// https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search
JSONValue searchDriveForPath ( string driveId , string path ) {
2017-12-31 02:30:31 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
url = "https://graph.microsoft.com/v1.0/drives/" ~ driveId ~ "/root/search(q='" ~ encodeComponent ( path ) ~ "')" ;
return get ( url ) ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
2017-12-27 15:12:38 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_update
2023-10-18 20:31:50 +02:00
JSONValue updateById ( const ( char ) [ ] driveId , const ( char ) [ ] id , JSONValue data , const ( char ) [ ] eTag = null ) {
2015-09-05 21:23:43 +02:00
checkAccessTokenExpired ( ) ;
2017-12-27 15:12:38 +01:00
const ( char ) [ ] url = driveByIdUrl ~ driveId ~ "/items/" ~ id ;
2023-10-18 20:31:50 +02:00
if ( eTag ) curlEngine . http . addRequestHeader ( "If-Match" , eTag ) ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
2015-10-04 16:24:45 +02:00
return patch ( url , data . toString ( ) ) ;
2015-09-05 21:23:43 +02:00
}
2023-10-18 20:31:50 +02:00
2017-12-27 15:12:38 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete
2023-10-18 20:31:50 +02:00
void deleteById ( const ( char ) [ ] driveId , const ( char ) [ ] id , const ( char ) [ ] eTag = null ) {
2015-09-06 10:23:32 +02:00
checkAccessTokenExpired ( ) ;
2017-12-27 15:12:38 +01:00
const ( char ) [ ] url = driveByIdUrl ~ driveId ~ "/items/" ~ id ;
2016-12-28 23:47:29 +01:00
//TODO: investigate why this always fail with 412 (Precondition Failed)
//if (eTag) http.addRequestHeader("If-Match", eTag);
2023-10-18 20:31:50 +02:00
performDelete ( url ) ;
2015-09-06 10:23:32 +02:00
}
2023-10-18 20:31:50 +02:00
2017-12-31 13:18:11 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children
2023-10-18 20:31:50 +02:00
JSONValue createById ( string parentDriveId , string parentId , JSONValue item ) {
2016-12-28 16:29:55 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url = driveByIdUrl ~ parentDriveId ~ "/items/" ~ parentId ~ "/children" ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
2015-10-04 16:24:45 +02:00
return post ( url , item . toString ( ) ) ;
2015-09-06 11:06:13 +02:00
}
2023-10-18 20:31:50 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content
JSONValue simpleUpload ( string localPath , string parentDriveId , string parentId , string filename ) {
2020-09-14 09:49:50 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url = driveByIdUrl ~ parentDriveId ~ "/items/" ~ parentId ~ ":/" ~ encodeComponent ( filename ) ~ ":/content" ;
return upload ( localPath , url ) ;
2020-09-14 09:49:50 +02:00
}
2023-10-18 20:31:50 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content
JSONValue simpleUploadReplace ( string localPath , string driveId , string id ) {
2018-03-14 05:43:40 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/content" ;
return upload ( localPath , url ) ;
2018-03-14 05:43:40 +01:00
}
2023-10-18 20:31:50 +02:00
2017-12-31 16:11:02 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession
2023-10-18 20:31:50 +02:00
//JSONValue createUploadSession(string parentDriveId, string parentId, string filename, string eTag = null, JSONValue item = null) {
JSONValue createUploadSession ( string parentDriveId , string parentId , string filename , const ( char ) [ ] eTag = null , JSONValue item = null ) {
2015-09-27 18:47:41 +02:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url = driveByIdUrl ~ parentDriveId ~ "/items/" ~ parentId ~ ":/" ~ encodeComponent ( filename ) ~ ":/createUploadSession" ;
// eTag If-Match header addition commented out for the moment
// At some point, post the creation of this upload session the eTag is being 'updated' by OneDrive, thus when uploadFragment() is used
// this generates a 412 Precondition Failed and then a 416 Requested Range Not Satisfiable
// This needs to be investigated further as to why this occurs
//if (eTag) curlEngine.http.addRequestHeader("If-Match", eTag);
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
2018-07-02 23:24:57 +02:00
return post ( url , item . toString ( ) ) ;
2015-09-27 18:47:41 +02:00
}
2023-10-18 20:31:50 +02:00
2015-09-27 18:47:41 +02:00
// https://dev.onedrive.com/items/upload_large_files.htm
2023-10-18 20:31:50 +02:00
JSONValue uploadFragment ( string uploadUrl , string filepath , long offset , long offsetSize , long fileSize ) {
2015-09-27 18:47:41 +02:00
checkAccessTokenExpired ( ) ;
2020-05-20 03:37:11 +02:00
// open file as read-only in binary mode
2023-10-18 20:31:50 +02:00
// If we upload a modified file, with the current known online eTag, this gets changed when the session is started - thus, the tail end of uploading
// a fragment fails with a 412 Precondition Failed and then a 416 Requested Range Not Satisfiable
// For the moment, comment out adding the If-Match header in createUploadSession, which then avoids this issue
2020-05-20 03:37:11 +02:00
auto file = File ( filepath , "rb" ) ;
file . seek ( offset ) ;
string contentRange = "bytes " ~ to ! string ( offset ) ~ "-" ~ to ! string ( offset + offsetSize - 1 ) ~ "/" ~ to ! string ( fileSize ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "" , [ "debug" ] ) ; // Add an empty newline before log output
addLogEntry ( "contentRange: " ~ contentRange , [ "debug" ] ) ;
2020-05-20 03:37:11 +02:00
// function scopes
2015-10-04 16:24:45 +02:00
scope ( exit ) {
2023-10-18 20:31:50 +02:00
curlEngine . http . clearRequestHeaders ( ) ;
curlEngine . http . onSend = null ;
curlEngine . http . onReceive = null ;
curlEngine . http . onReceiveHeader = null ;
curlEngine . http . onReceiveStatusLine = null ;
curlEngine . http . contentLength = 0 ;
2020-05-20 03:37:11 +02:00
// close file if open
if ( file . isOpen ( ) ) {
// close open file
file . close ( ) ;
}
2015-10-04 16:24:45 +02:00
}
2021-11-23 20:54:28 +01:00
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . put , uploadUrl ) ;
2023-10-18 20:31:50 +02:00
curlEngine . http . addRequestHeader ( "Content-Range" , contentRange ) ;
curlEngine . http . onSend = data = > file . rawRead ( data ) . length ;
2021-07-11 21:59:32 +02:00
// convert offsetSize to ulong
2023-10-18 20:31:50 +02:00
curlEngine . http . contentLength = to ! ulong ( offsetSize ) ;
auto response = performHTTPOperation ( ) ;
checkHttpResponseCode ( response ) ;
2015-10-04 16:24:45 +02:00
return response ;
2015-09-27 18:47:41 +02:00
}
2023-10-18 20:31:50 +02:00
2023-11-09 02:20:11 +01:00
// https://dev.onedrive.com/items/upload_large_files.htm
JSONValue requestUploadStatus ( string uploadUrl ) {
checkAccessTokenExpired ( ) ;
return get ( uploadUrl , true ) ;
}
2018-12-04 00:59:23 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/site_search?view=odsp-graph-online
2023-10-18 20:31:50 +02:00
JSONValue o365SiteSearch ( string nextLink ) {
2018-12-04 00:59:23 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2021-03-12 19:11:44 +01:00
// configure URL to query
if ( nextLink . empty ) {
url = siteSearchUrl ~ "=*" ;
} else {
url = nextLink ;
}
2018-12-04 00:59:23 +01:00
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
2018-12-04 00:59:23 +01:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/drive_list?view=odsp-graph-online
JSONValue o365SiteDrives ( string site_id ) {
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
string url ;
2018-12-04 00:59:23 +01:00
url = siteDriveUrl ~ site_id ~ "/drives" ;
return get ( url ) ;
}
2023-10-18 20:31:50 +02:00
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get_content
void downloadById ( const ( char ) [ ] driveId , const ( char ) [ ] id , string saveToPath , long fileSize ) {
2021-11-23 20:54:28 +01:00
checkAccessTokenExpired ( ) ;
2023-10-18 20:31:50 +02:00
scope ( failure ) {
if ( exists ( saveToPath ) ) {
// try and remove the file, catch error
try {
remove ( saveToPath ) ;
} catch ( FileException e ) {
// display the error message
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2021-11-23 20:54:28 +01:00
}
}
}
2023-10-18 20:31:50 +02:00
// Create the required local directory
string newPath = dirName ( saveToPath ) ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
// Does the path exist locally?
if ( ! exists ( newPath ) ) {
try {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Requested local path does not exist, creating directory structure: " ~ newPath , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
mkdirRecurse ( newPath ) ;
// Configure the applicable permissions for the folder
2023-12-08 23:19:57 +01:00
addLogEntry ( "Setting directory permissions for: " ~ newPath , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
newPath . setAttributes ( appConfig . returnRequiredDirectoryPermisions ( ) ) ;
} catch ( FileException e ) {
// display the error message
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
}
2023-03-18 00:03:49 +01:00
}
2022-03-06 00:02:14 +01:00
2023-10-18 20:31:50 +02:00
const ( char ) [ ] url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/content?AVOverride=1" ;
// Download file
downloadFile ( url , saveToPath , fileSize ) ;
// Does path exist?
if ( exists ( saveToPath ) ) {
// File was downloaded successfully - configure the applicable permissions for the file
2023-12-08 23:19:57 +01:00
addLogEntry ( "Setting file permissions for: " ~ saveToPath , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
saveToPath . setAttributes ( appConfig . returnRequiredFilePermisions ( ) ) ;
2022-03-06 00:02:14 +01:00
}
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
// Return the actual siteSearchUrl being used and/or requested when performing 'siteQuery = onedrive.o365SiteSearch(nextLink);' call
string getSiteSearchUrl ( ) {
return siteSearchUrl ;
}
// Return the current value of retryAfterValue
ulong getRetryAfterValue ( ) {
return retryAfterValue ;
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
// Reset the current value of retryAfterValue to 0 after it has been used
void resetRetryAfterValue ( ) {
retryAfterValue = 0 ;
2021-11-23 20:54:28 +01:00
}
2023-10-18 20:31:50 +02:00
2023-10-19 07:33:25 +02:00
// Create a new subscription or renew the existing subscription
2023-10-18 21:48:25 +02:00
void createOrRenewSubscription ( ) {
checkAccessTokenExpired ( ) ;
// Kick off the webhook server first
if ( webhook is null ) {
webhook = OneDriveWebhook . getOrCreate (
appConfig . getValueString ( "webhook_listening_host" ) ,
to ! ushort ( appConfig . getValueLong ( "webhook_listening_port" ) ) ,
thisTid
) ;
2023-10-27 22:21:54 +02:00
webhook . serve ( ) ;
2023-10-18 21:48:25 +02:00
}
2023-10-19 07:33:25 +02:00
auto elapsed = Clock . currTime ( UTC ( ) ) - subscriptionLastErrorAt ;
2023-10-19 09:37:14 +02:00
if ( elapsed < subscriptionRetryInterval ) {
2023-10-19 07:33:25 +02:00
return ;
}
try {
if ( ! hasValidSubscription ( ) ) {
createSubscription ( ) ;
} else if ( isSubscriptionUpForRenewal ( ) ) {
2023-10-18 21:48:25 +02:00
renewSubscription ( ) ;
}
2023-10-19 07:33:25 +02:00
} catch ( OneDriveException e ) {
logSubscriptionError ( e ) ;
subscriptionLastErrorAt = Clock . currTime ( UTC ( ) ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Will retry creating or renewing subscription in " ~ to ! string ( subscriptionRetryInterval ) ) ;
2023-10-19 07:33:25 +02:00
} catch ( JSONException e ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: Unexpected JSON error when attempting to validate subscription: " ~ e . msg ) ;
2023-10-19 07:33:25 +02:00
subscriptionLastErrorAt = Clock . currTime ( UTC ( ) ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Will retry creating or renewing subscription in " ~ to ! string ( subscriptionRetryInterval ) ) ;
2023-10-18 21:48:25 +02:00
}
}
2023-10-19 07:33:25 +02:00
2023-10-18 21:48:25 +02:00
// Private functions
private bool hasValidSubscription ( ) {
return ! subscriptionId . empty & & subscriptionExpiration > Clock . currTime ( UTC ( ) ) ;
}
private bool isSubscriptionUpForRenewal ( ) {
return subscriptionExpiration < Clock . currTime ( UTC ( ) ) + subscriptionRenewalInterval ;
}
private void createSubscription ( ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Initializing subscription for updates ..." ) ;
2023-10-18 21:48:25 +02:00
auto expirationDateTime = Clock . currTime ( UTC ( ) ) + subscriptionExpirationInterval ;
string driveId = appConfig . getValueString ( "drive_id" ) ;
string url = subscriptionUrl ;
// Create a resource item based on if we have a driveId
string resourceItem ;
if ( driveId . length ) {
resourceItem = "/drives/" ~ driveId ~ "/root" ;
} else {
resourceItem = "/me/drive/root" ;
}
2023-10-19 07:33:25 +02:00
2023-10-18 21:48:25 +02:00
// create JSON request to create webhook subscription
const JSONValue request = [
"changeType" : "updated" ,
"notificationUrl" : notificationUrl ,
"resource" : resourceItem ,
"expirationDateTime" : expirationDateTime . toISOExtString ( ) ,
"clientState" : randomUUID ( ) . toString ( )
] ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
try {
2023-10-19 07:33:25 +02:00
JSONValue response = post ( url , request . toString ( ) ) ;
// Save important subscription metadata including id and expiration
subscriptionId = response [ "id" ] . str ;
subscriptionExpiration = SysTime . fromISOExtString ( response [ "expirationDateTime" ] . str ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Created new subscription " ~ subscriptionId ~ " with expiration: " ~ to ! string ( subscriptionExpiration . toISOExtString ( ) ) ) ;
2023-10-18 21:48:25 +02:00
} catch ( OneDriveException e ) {
2023-10-19 07:33:25 +02:00
if ( e . httpStatusCode = = 409 ) {
// Take over an existing subscription on HTTP 409.
//
// Sample 409 error:
// {
// "error": {
// "code": "ObjectIdentifierInUse",
// "innerError": {
// "client-request-id": "615af209-467a-4ab7-8eff-27c1d1efbc2d",
// "date": "2023-09-26T09:27:45",
// "request-id": "615af209-467a-4ab7-8eff-27c1d1efbc2d"
// },
// "message": "Subscription Id c0bba80e-57a3-43a7-bac2-e6f525a76e7c already exists for the requested combination"
// }
// }
2023-10-18 21:48:25 +02:00
2023-10-19 07:33:25 +02:00
// Make sure the error code is "ObjectIdentifierInUse"
try {
if ( e . error [ "error" ] [ "code" ] . str ! = "ObjectIdentifierInUse" ) {
throw e ;
}
} catch ( JSONException jsonEx ) {
throw e ;
}
// Extract the existing subscription id from the error message
import std.regex ;
auto idReg = ctRegex ! ( r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" , "i" ) ;
auto m = matchFirst ( e . error [ "error" ] [ "message" ] . str , idReg ) ;
if ( ! m ) {
throw e ;
}
// Save the subscription id and renew it immediately since we don't know the expiration timestamp
subscriptionId = m [ 0 ] ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Found existing subscription " ~ subscriptionId ) ;
2023-10-19 07:33:25 +02:00
renewSubscription ( ) ;
} else {
throw e ;
}
}
2023-10-18 21:48:25 +02:00
}
private void renewSubscription ( ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Renewing subscription for updates ..." ) ;
2023-10-18 21:48:25 +02:00
auto expirationDateTime = Clock . currTime ( UTC ( ) ) + subscriptionExpirationInterval ;
string url ;
url = subscriptionUrl ~ "/" ~ subscriptionId ;
const JSONValue request = [
"expirationDateTime" : expirationDateTime . toISOExtString ( )
] ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/json" ) ;
2023-10-19 07:33:25 +02:00
try {
JSONValue response = patch ( url , request . toString ( ) ) ;
// Update subscription expiration from the response
subscriptionExpiration = SysTime . fromISOExtString ( response [ "expirationDateTime" ] . str ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Renewed subscription " ~ subscriptionId ~ " with expiration: " ~ to ! string ( subscriptionExpiration . toISOExtString ( ) ) ) ;
2023-10-19 07:33:25 +02:00
} catch ( OneDriveException e ) {
if ( e . httpStatusCode = = 404 ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "The subscription is not found on the server. Recreating subscription ..." ) ;
2023-10-19 07:33:25 +02:00
subscriptionId = null ;
subscriptionExpiration = Clock . currTime ( UTC ( ) ) ;
createSubscription ( ) ;
} else {
throw e ;
}
}
2023-10-18 21:48:25 +02:00
}
private void deleteSubscription ( ) {
if ( ! hasValidSubscription ( ) ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "No valid Microsoft OneDrive webhook subscription to delete" , [ "debug" ] ) ;
2023-10-18 21:48:25 +02:00
return ;
}
string url ;
url = subscriptionUrl ~ "/" ~ subscriptionId ;
2023-10-27 22:21:54 +02:00
performDelete ( url ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Deleted Microsoft OneDrive webhook subscription" , [ "debug" ] ) ;
2023-10-18 21:48:25 +02:00
}
2023-10-19 07:33:25 +02:00
private void logSubscriptionError ( OneDriveException e ) {
if ( e . httpStatusCode = = 400 ) {
// Log known 400 error where Microsoft cannot get a 200 OK from the webhook endpoint
//
// Sample 400 error:
// {
// "error": {
// "code": "InvalidRequest",
// "innerError": {
// "client-request-id": "<uuid>",
// "date": "<timestamp>",
// "request-id": "<uuid>"
// },
// "message": "Subscription validation request failed. Notification endpoint must respond with 200 OK to validation request."
// }
// }
try {
if ( e . error [ "error" ] [ "code" ] . str = = "InvalidRequest" ) {
import std.regex ;
auto msgReg = ctRegex ! ( r"Subscription validation request failed" , "i" ) ;
auto m = matchFirst ( e . error [ "error" ] [ "message" ] . str , msgReg ) ;
if ( m ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: Cannot create or renew subscription: Microsoft did not get 200 OK from the webhook endpoint." ) ;
2023-10-19 07:33:25 +02:00
return ;
}
}
} catch ( JSONException ) {
// fallthrough
}
} else if ( e . httpStatusCode = = 401 ) {
// Log known 401 error where authentication failed
//
// Sample 401 error:
// {
// "error": {
// "code": "ExtensionError",
// "innerError": {
// "client-request-id": "<uuid>",
// "date": "<timestamp>",
// "request-id": "<uuid>"
// },
// "message": "Operation: Create; Exception: [Status Code: Unauthorized; Reason: Authentication failed]"
// }
// }
try {
if ( e . error [ "error" ] [ "code" ] . str = = "ExtensionError" ) {
import std.regex ;
auto msgReg = ctRegex ! ( r"Authentication failed" , "i" ) ;
auto m = matchFirst ( e . error [ "error" ] [ "message" ] . str , msgReg ) ;
if ( m ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: Cannot create or renew subscription: Authentication failed." ) ;
2023-10-19 07:33:25 +02:00
return ;
}
}
} catch ( JSONException ) {
// fallthrough
}
} else if ( e . httpStatusCode = = 403 ) {
// Log known 403 error where the number of subscriptions on item has exceeded limit
//
// Sample 403 error:
// {
// "error": {
// "code": "ExtensionError",
// "innerError": {
// "client-request-id": "<uuid>",
// "date": "<timestamp>",
// "request-id": "<uuid>"
// },
// "message": "Operation: Create; Exception: [Status Code: Forbidden; Reason: Number of subscriptions on item has exceeded limit]"
// }
// }
try {
if ( e . error [ "error" ] [ "code" ] . str = = "ExtensionError" ) {
import std.regex ;
auto msgReg = ctRegex ! ( r"Number of subscriptions on item has exceeded limit" , "i" ) ;
auto m = matchFirst ( e . error [ "error" ] [ "message" ] . str , msgReg ) ;
if ( m ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: Cannot create or renew subscription: Number of subscriptions has exceeded limit." ) ;
2023-10-19 07:33:25 +02:00
return ;
}
}
} catch ( JSONException ) {
// fallthrough
}
}
// Log detailed message for unknown errors
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: Cannot create or renew subscription." ) ;
2023-10-19 07:33:25 +02:00
displayOneDriveErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
}
2023-10-18 20:31:50 +02:00
private void addAccessTokenHeader ( ) {
curlEngine . http . addRequestHeader ( "Authorization" , appConfig . accessToken ) ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
private void addIncludeFeatureRequestHeader ( ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Adding 'Include-Feature=AddToOneDrive' API request header as 'sync_business_shared_items' config option is enabled" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
curlEngine . http . addRequestHeader ( "Prefer" , "Include-Feature=AddToOneDrive" ) ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
private void acquireToken ( char [ ] postData ) {
2020-03-02 02:54:56 +01:00
JSONValue response ;
2021-11-23 20:54:28 +01:00
2020-03-02 02:54:56 +01:00
try {
response = post ( tokenUrl , postData ) ;
} catch ( OneDriveException e ) {
// an error was generated
2023-10-18 20:31:50 +02:00
if ( ( e . httpStatusCode = = 400 ) | | ( e . httpStatusCode = = 401 ) ) {
// Handle an unauthorised client
handleClientUnauthorised ( e . httpStatusCode , e . msg ) ;
} else {
if ( e . httpStatusCode > = 500 ) {
// There was a HTTP 5xx Server Side Error - retry
acquireToken ( postData ) ;
} else {
displayOneDriveErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
}
}
2020-03-02 02:54:56 +01:00
}
2021-11-23 20:54:28 +01:00
2019-09-06 05:12:13 +02:00
if ( response . type ( ) = = JSONType . object ) {
2022-08-06 00:24:51 +02:00
// Has the client been configured to use read_only_auth_scope
2023-10-18 20:31:50 +02:00
if ( appConfig . getValueBool ( "read_only_auth_scope" ) ) {
2022-08-06 00:24:51 +02:00
// read_only_auth_scope has been configured
if ( "scope" in response ) {
string effectiveScopes = response [ "scope" ] . str ( ) ;
// Display the effective authentication scopes
2023-12-08 23:19:57 +01:00
addLogEntry ( ) ;
addLogEntry ( "Effective API Authentication Scopes: " ~ effectiveScopes , [ "verbose" ] ) ;
2022-08-06 00:24:51 +02:00
// if we have any write scopes, we need to tell the user to update an remove online prior authentication and exit application
if ( canFind ( effectiveScopes , "Write" ) ) {
// effective scopes contain write scopes .. so not a read-only configuration
2023-12-08 23:19:57 +01:00
addLogEntry ( ) ;
addLogEntry ( "ERROR: You have authentication scopes that allow write operations. You need to remove your existing application access consent" ) ;
addLogEntry ( ) ;
addLogEntry ( "Please login to https://account.live.com/consent/Manage and remove your existing application access consent" ) ;
addLogEntry ( ) ;
2022-08-06 00:24:51 +02:00
// force exit
shutdown ( ) ;
2023-12-21 12:09:46 +01:00
// Must force exit here, allow logging to be done
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
2022-08-06 00:24:51 +02:00
exit ( - 1 ) ;
}
}
}
2019-09-06 05:12:13 +02:00
if ( "access_token" in response ) {
2023-10-18 20:31:50 +02:00
appConfig . accessToken = "bearer " ~ strip ( response [ "access_token" ] . str ) ;
// Do we print the current access token
2023-12-08 23:19:57 +01:00
if ( appConfig . verbosityCount > 1 ) {
2023-10-18 20:31:50 +02:00
if ( appConfig . getValueBool ( "debug_https" ) ) {
if ( appConfig . getValueBool ( "print_token" ) ) {
// This needs to be highly restricted in output ....
2024-01-08 04:53:33 +01:00
addLogEntry ( "CAUTION - KEEP THIS SAFE: Current access token: " ~ to ! string ( appConfig . accessToken ) , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
}
}
}
refreshToken = strip ( response [ "refresh_token" ] . str ) ;
appConfig . accessTokenExpiration = Clock . currTime ( ) + dur ! "seconds" ( response [ "expires_in" ] . integer ( ) ) ;
if ( ! dryRun ) {
// Update the refreshToken in appConfig so that we can reuse it
if ( appConfig . refreshToken . empty ) {
// The access token is empty
2023-12-08 23:19:57 +01:00
addLogEntry ( "Updating appConfig.refreshToken with new refreshToken as appConfig.refreshToken is empty" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
appConfig . refreshToken = refreshToken ;
} else {
// Is the access token different?
if ( appConfig . refreshToken ! = refreshToken ) {
// Update the memory version
2023-12-08 23:19:57 +01:00
addLogEntry ( "Updating appConfig.refreshToken with updated refreshToken" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
appConfig . refreshToken = refreshToken ;
}
}
// try and update the refresh_token file on disk
2020-11-08 22:06:48 +01:00
try {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Updating refreshToken on disk" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
std . file . write ( appConfig . refreshTokenFilePath , refreshToken ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Setting file permissions for: " ~ appConfig . refreshTokenFilePath , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
appConfig . refreshTokenFilePath . setAttributes ( appConfig . returnRequiredFilePermisions ( ) ) ;
2020-11-08 22:06:48 +01:00
} catch ( FileException e ) {
// display the error message
2020-12-09 04:18:16 +01:00
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-11-08 22:06:48 +01:00
}
2019-09-06 05:12:13 +02:00
}
} else {
2023-12-08 23:19:57 +01:00
addLogEntry ( "\nInvalid authentication response from OneDrive. Please check the response uri\n" ) ;
2019-09-06 05:12:13 +02:00
// re-authorize
2023-10-18 20:31:50 +02:00
authorise ( ) ;
2019-03-12 01:50:21 +01:00
}
} else {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Invalid response from the OneDrive API. Unable to initialise OneDrive API instance." ) ;
2023-12-21 12:09:46 +01:00
// Must force exit here, allow logging to be done
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
2023-10-18 20:31:50 +02:00
exit ( - 1 ) ;
2019-03-11 07:57:47 +01:00
}
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
private void checkAccessTokenExpired ( ) {
2017-06-15 13:02:04 +02:00
try {
2023-10-18 20:31:50 +02:00
if ( Clock . currTime ( ) > = appConfig . accessTokenExpiration ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Microsoft OneDrive Access Token has EXPIRED. Must generate a new Microsoft OneDrive Access Token" , [ "debug" ] ) ;
2017-06-15 13:02:04 +02:00
newToken ( ) ;
2023-10-18 20:31:50 +02:00
} else {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Existing Microsoft OneDrive Access Token Expires: " ~ to ! string ( appConfig . accessTokenExpiration ) , [ "debug" ] ) ;
2017-06-15 13:02:04 +02:00
}
} catch ( OneDriveException e ) {
if ( e . httpStatusCode = = 400 | | e . httpStatusCode = = 401 ) {
2020-09-24 22:42:44 +02:00
// flag error and notify
2023-12-08 23:19:57 +01:00
addLogEntry ( ) ;
addLogEntry ( "ERROR: Refresh token invalid, use --reauth to authorize the client again." , [ "info" , "notify" ] ) ;
addLogEntry ( ) ;
2020-09-24 22:42:44 +02:00
// set error message
2022-03-07 19:35:00 +01:00
e . msg ~ = "\nRefresh token invalid, use --reauth to authorize the client again" ;
2017-06-15 13:02:04 +02:00
}
2015-09-01 22:23:42 +02:00
}
}
2023-10-18 20:31:50 +02:00
private void performDelete ( const ( char ) [ ] url ) {
scope ( exit ) curlEngine . http . clearRequestHeaders ( ) ;
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . del , url ) ;
2015-10-04 16:24:45 +02:00
addAccessTokenHeader ( ) ;
2023-10-18 20:31:50 +02:00
auto response = performHTTPOperation ( ) ;
checkHttpResponseCode ( response ) ;
2015-10-04 16:24:45 +02:00
}
2023-10-18 20:31:50 +02:00
private void downloadFile ( const ( char ) [ ] url , string filename , long fileSize ) {
2018-08-09 23:46:27 +02:00
// Threshold for displaying download bar
long thresholdFileSize = 4 * 2 ^ ^ 20 ; // 4 MiB
2022-03-09 21:01:08 +01:00
// To support marking of partially-downloaded files,
string originalFilename = filename ;
string downloadFilename = filename ~ ".partial" ;
// open downloadFilename as write in binary mode
auto file = File ( downloadFilename , "wb" ) ;
2021-11-23 20:54:28 +01:00
2020-05-20 03:37:11 +02:00
// function scopes
scope ( exit ) {
2023-10-18 20:31:50 +02:00
curlEngine . http . clearRequestHeaders ( ) ;
curlEngine . http . onSend = null ;
curlEngine . http . onReceive = null ;
curlEngine . http . onReceiveHeader = null ;
curlEngine . http . onReceiveStatusLine = null ;
curlEngine . http . contentLength = 0 ;
2020-05-20 03:37:11 +02:00
// Reset onProgress to not display anything for next download
2023-10-18 20:31:50 +02:00
curlEngine . http . onProgress = delegate int ( size_t dltotal , size_t dlnow , size_t ultotal , size_t ulnow )
2020-05-20 03:37:11 +02:00
{
return 0 ;
} ;
// close file if open
if ( file . isOpen ( ) ) {
// close open file
file . close ( ) ;
}
}
2021-11-23 20:54:28 +01:00
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . get , url ) ;
2015-10-04 16:24:45 +02:00
addAccessTokenHeader ( ) ;
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
curlEngine . http . onReceive = ( ubyte [ ] data ) {
2020-05-20 03:37:11 +02:00
file . rawWrite ( data ) ;
2015-10-04 16:24:45 +02:00
return data . length ;
} ;
2021-11-23 20:54:28 +01:00
2018-08-09 23:46:27 +02:00
if ( fileSize > = thresholdFileSize ) {
2023-12-21 08:13:56 +01:00
// Download Progress variables
size_t expected_total_segments = 20 ;
ulong start_unix_time = Clock . currTime . toUnixTime ( ) ;
int h , m , s ;
string etaString ;
2020-04-25 22:52:29 +02:00
bool barInit = false ;
2022-07-28 23:07:08 +02:00
real previousProgressPercent = - 1.0 ;
2018-08-09 23:46:27 +02:00
real percentCheck = 5.0 ;
2023-12-21 08:13:56 +01:00
long segmentCount = - 1 ;
2018-08-09 23:46:27 +02:00
// Setup progress bar to display
2023-12-21 08:13:56 +01:00
curlEngine . http . onProgress = delegate int ( size_t dltotal , size_t dlnow , size_t ultotal , size_t ulnow ) {
2018-08-09 23:46:27 +02:00
// For each onProgress, what is the % of dlnow to dltotal
2020-04-25 22:52:29 +02:00
// floor - rounds down to nearest whole number
real currentDLPercent = floor ( double ( dlnow ) / dltotal * 100 ) ;
2024-01-07 03:19:55 +01:00
string downloadLogEntry = "Downloading: " ~ filename ~ " ... " ;
2023-12-21 08:13:56 +01:00
2022-07-28 23:07:08 +02:00
// Have we started downloading?
2020-04-25 22:52:29 +02:00
if ( currentDLPercent > 0 ) {
// We have started downloading
2023-12-08 23:19:57 +01:00
addLogEntry ( "" , [ "debug" ] ) ; // Debug new line only
addLogEntry ( "Data Received = " ~ to ! string ( dlnow ) , [ "debug" ] ) ;
addLogEntry ( "Expected Total = " ~ to ! string ( dltotal ) , [ "debug" ] ) ;
addLogEntry ( "Percent Complete = " ~ to ! string ( currentDLPercent ) , [ "debug" ] ) ;
2022-07-28 23:07:08 +02:00
// Every 5% download we need to increment the download bar
// Has the user set a data rate limit?
// when using rate_limit, we will get odd download rates, for example:
// Percent Complete = 24
// Data Received = 13080163
// Expected Total = 52428800
// Percent Complete = 24
// Data Received = 13685777
// Expected Total = 52428800
// Percent Complete = 26 <---- jumps to 26% missing 25%, thus fmod misses incrementing progress bar
// Data Received = 13685777
// Expected Total = 52428800
// Percent Complete = 26
2023-10-18 20:31:50 +02:00
if ( appConfig . getValueLong ( "rate_limit" ) > 0 ) {
2022-07-28 23:07:08 +02:00
// User configured rate limit
// How much data should be in each segment to qualify for 5%
2023-12-21 08:13:56 +01:00
ulong dataPerSegment = to ! ulong ( floor ( double ( dltotal ) / expected_total_segments ) ) ;
2022-07-28 23:07:08 +02:00
// How much data received do we need to validate against
2023-10-18 20:31:50 +02:00
ulong thisSegmentData = dataPerSegment * segmentCount ;
ulong nextSegmentData = dataPerSegment * ( segmentCount + 1 ) ;
2023-12-21 08:13:56 +01:00
2022-07-28 23:07:08 +02:00
// Has the data that has been received in a 5% window that we need to increment the progress bar at
if ( ( dlnow > thisSegmentData ) & & ( dlnow < nextSegmentData ) & & ( previousProgressPercent ! = currentDLPercent ) | | ( dlnow = = dltotal ) ) {
// Downloaded data equals approx 5%
2023-12-08 23:19:57 +01:00
addLogEntry ( "Incrementing Progress Bar using calculated 5% of data received" , [ "debug" ] ) ;
2023-12-21 08:13:56 +01:00
// 100% check
if ( currentDLPercent ! = 100 ) {
// Not 100% yet
// Calculate the output
segmentCount + + ;
auto eta = calc_eta ( segmentCount , expected_total_segments , start_unix_time ) ;
dur ! "seconds" ( eta ) . split ! ( "hours" , "minutes" , "seconds" ) ( h , m , s ) ;
2024-01-07 03:19:55 +01:00
etaString = format ! "| ETA %02d:%02d:%02d" ( h , m , s ) ;
string percentage = leftJustify ( to ! string ( currentDLPercent ) ~ "%" , 5 , ' ' ) ;
2023-12-21 08:13:56 +01:00
addLogEntry ( downloadLogEntry ~ percentage ~ etaString , [ "consoleOnly" ] ) ;
} else {
// 100% done
ulong end_unix_time = Clock . currTime . toUnixTime ( ) ;
auto upload_duration = cast ( int ) ( end_unix_time - start_unix_time ) ;
dur ! "seconds" ( upload_duration ) . split ! ( "hours" , "minutes" , "seconds" ) ( h , m , s ) ;
etaString = format ! "| DONE in %02d:%02d:%02d" ( h , m , s ) ;
2024-01-07 03:19:55 +01:00
string percentage = leftJustify ( to ! string ( currentDLPercent ) ~ "%" , 5 , ' ' ) ;
2023-12-21 08:13:56 +01:00
addLogEntry ( downloadLogEntry ~ percentage ~ etaString , [ "consoleOnly" ] ) ;
}
2022-07-28 23:07:08 +02:00
// update values
2023-12-08 23:19:57 +01:00
addLogEntry ( "Setting previousProgressPercent to " ~ to ! string ( currentDLPercent ) , [ "debug" ] ) ;
2022-07-28 23:07:08 +02:00
previousProgressPercent = currentDLPercent ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Incrementing segmentCount" , [ "debug" ] ) ;
2022-07-28 23:07:08 +02:00
segmentCount + + ;
}
} else {
// Is currentDLPercent divisible by 5 leaving remainder 0 and does previousProgressPercent not equal currentDLPercent
if ( ( isIdentical ( fmod ( currentDLPercent , percentCheck ) , 0.0 ) ) & & ( previousProgressPercent ! = currentDLPercent ) ) {
// currentDLPercent matches a new increment
2023-12-08 23:19:57 +01:00
addLogEntry ( "Incrementing Progress Bar using fmod match" , [ "debug" ] ) ;
2023-12-21 08:13:56 +01:00
// 100% check
if ( currentDLPercent ! = 100 ) {
// Not 100% yet
// Calculate the output
segmentCount + + ;
auto eta = calc_eta ( segmentCount , expected_total_segments , start_unix_time ) ;
dur ! "seconds" ( eta ) . split ! ( "hours" , "minutes" , "seconds" ) ( h , m , s ) ;
2024-01-07 03:19:55 +01:00
etaString = format ! "| ETA %02d:%02d:%02d" ( h , m , s ) ;
string percentage = leftJustify ( to ! string ( currentDLPercent ) ~ "%" , 5 , ' ' ) ;
2023-12-21 08:13:56 +01:00
addLogEntry ( downloadLogEntry ~ percentage ~ etaString , [ "consoleOnly" ] ) ;
} else {
// 100% done
ulong end_unix_time = Clock . currTime . toUnixTime ( ) ;
auto upload_duration = cast ( int ) ( end_unix_time - start_unix_time ) ;
dur ! "seconds" ( upload_duration ) . split ! ( "hours" , "minutes" , "seconds" ) ( h , m , s ) ;
etaString = format ! "| DONE in %02d:%02d:%02d" ( h , m , s ) ;
2024-01-07 03:19:55 +01:00
string percentage = leftJustify ( to ! string ( currentDLPercent ) ~ "%" , 5 , ' ' ) ;
2023-12-21 08:13:56 +01:00
addLogEntry ( downloadLogEntry ~ percentage ~ etaString , [ "consoleOnly" ] ) ;
}
2022-07-28 23:07:08 +02:00
// update values
previousProgressPercent = currentDLPercent ;
}
2020-04-25 22:52:29 +02:00
}
} else {
if ( ( currentDLPercent = = 0 ) & & ( ! barInit ) ) {
2023-12-21 08:13:56 +01:00
// Calculate the output
segmentCount + + ;
2024-01-07 03:19:55 +01:00
etaString = "| ETA --:--:--" ;
string percentage = leftJustify ( to ! string ( currentDLPercent ) ~ "%" , 5 , ' ' ) ;
2023-12-21 08:13:56 +01:00
addLogEntry ( downloadLogEntry ~ percentage ~ etaString , [ "consoleOnly" ] ) ;
2020-04-25 22:52:29 +02:00
barInit = true ;
}
2018-08-09 23:46:27 +02:00
}
return 0 ;
} ;
2021-11-23 20:54:28 +01:00
2023-12-21 08:13:56 +01:00
// Perform download
2020-04-23 02:25:41 +02:00
try {
// try and catch any curl error
2023-10-18 20:31:50 +02:00
curlEngine . http . perform ( ) ;
2021-03-03 01:12:27 +01:00
// Check the HTTP Response headers - needed for correct 429 handling
// check will be performed in checkHttpCode()
2020-05-20 03:37:11 +02:00
// Reset onProgress to not display anything for next download done using exit scope
2020-04-23 02:25:41 +02:00
} catch ( CurlException e ) {
2020-12-09 04:18:16 +01:00
displayOneDriveErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-04-23 02:25:41 +02:00
}
2018-08-09 23:46:27 +02:00
} else {
// No progress bar
2020-04-23 02:25:41 +02:00
try {
// try and catch any curl error
2023-10-18 20:31:50 +02:00
curlEngine . http . perform ( ) ;
2021-03-03 01:12:27 +01:00
// Check the HTTP Response headers - needed for correct 429 handling
// check will be performed in checkHttpCode()
2020-04-23 02:25:41 +02:00
} catch ( CurlException e ) {
2020-12-09 04:18:16 +01:00
displayOneDriveErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-04-23 02:25:41 +02:00
}
2018-08-09 23:46:27 +02:00
}
2021-11-23 20:54:28 +01:00
2022-03-09 21:01:08 +01:00
// Rename downloaded file
rename ( downloadFilename , originalFilename ) ;
2021-03-03 01:12:27 +01:00
// Check the HTTP response code, which, if a 429, will also check response headers
2015-10-04 16:24:45 +02:00
checkHttpCode ( ) ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
private JSONValue get ( string url , bool skipToken = false ) {
scope ( exit ) curlEngine . http . clearRequestHeaders ( ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( "Request URL = " ~ url , [ "debug" ] ) ;
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . get , url ) ;
2023-10-18 20:31:50 +02:00
if ( ! skipToken ) addAccessTokenHeader ( ) ; // HACK: requestUploadStatus
JSONValue response ;
response = performHTTPOperation ( ) ;
checkHttpResponseCode ( response ) ;
// OneDrive API Response Debugging if --https-debug is being used
if ( debugResponse ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive API Response: " ~ to ! string ( response ) , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
}
2015-10-04 16:24:45 +02:00
return response ;
2015-09-05 21:23:43 +02:00
}
2023-10-18 20:31:50 +02:00
private void newToken ( ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "Need to generate a new access token for Microsoft OneDrive" , [ "debug" ] ) ;
2024-01-08 04:53:33 +01:00
auto postData = appender ! ( string ) ( ) ;
postData ~ = "client_id=" ~ clientId ;
postData ~ = "&redirect_uri=" ~ redirectUrl ;
postData ~ = "&refresh_token=" ~ to ! string ( refreshToken ) ;
postData ~ = "&grant_type=refresh_token" ;
acquireToken ( postData . data . dup ) ;
2015-10-04 16:24:45 +02:00
}
2024-01-08 04:53:33 +01:00
2023-10-18 20:31:50 +02:00
private auto patch ( T ) ( const ( char ) [ ] url , const ( T ) [ ] patchData ) {
2023-10-23 00:40:53 +02:00
scope ( exit ) curlEngine . http . clearRequestHeaders ( ) ;
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . patch , url ) ;
2018-03-14 05:43:40 +01:00
addAccessTokenHeader ( ) ;
2023-10-18 20:31:50 +02:00
auto response = perform ( patchData ) ;
checkHttpResponseCode ( response ) ;
2018-03-14 05:43:40 +01:00
return response ;
}
2023-10-18 20:31:50 +02:00
private auto post ( T ) ( string url , const ( T ) [ ] postData ) {
2023-10-23 00:40:53 +02:00
scope ( exit ) curlEngine . http . clearRequestHeaders ( ) ;
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . post , url ) ;
2015-10-04 16:24:45 +02:00
addAccessTokenHeader ( ) ;
2023-10-18 20:31:50 +02:00
auto response = perform ( postData ) ;
checkHttpResponseCode ( response ) ;
2015-10-04 16:24:45 +02:00
return response ;
}
2023-10-18 20:31:50 +02:00
private JSONValue perform ( const ( void ) [ ] sendData ) {
2015-10-04 16:24:45 +02:00
scope ( exit ) {
2023-10-18 20:31:50 +02:00
curlEngine . http . onSend = null ;
curlEngine . http . contentLength = 0 ;
2015-10-04 16:24:45 +02:00
}
if ( sendData ) {
2023-10-18 20:31:50 +02:00
curlEngine . http . contentLength = sendData . length ;
curlEngine . http . onSend = ( void [ ] buf ) {
2015-10-04 16:24:45 +02:00
import std.algorithm : min ;
size_t minLen = min ( buf . length , sendData . length ) ;
if ( minLen = = 0 ) return 0 ;
buf [ 0 . . minLen ] = sendData [ 0 . . minLen ] ;
sendData = sendData [ minLen . . $ ] ;
return minLen ;
} ;
} else {
2023-10-18 20:31:50 +02:00
curlEngine . http . onSend = buf = > 0 ;
2015-10-04 16:24:45 +02:00
}
2023-10-18 20:31:50 +02:00
auto response = performHTTPOperation ( ) ;
2020-09-25 04:55:21 +02:00
return response ;
2015-10-04 16:24:45 +02:00
}
2023-10-18 20:31:50 +02:00
private JSONValue performHTTPOperation ( ) {
scope ( exit ) curlEngine . http . onReceive = null ;
2015-10-04 17:33:48 +02:00
char [ ] content ;
2020-05-20 03:37:11 +02:00
JSONValue json ;
2023-10-18 20:31:50 +02:00
curlEngine . http . onReceive = ( ubyte [ ] data ) {
2015-10-04 16:24:45 +02:00
content ~ = data ;
2020-03-19 20:12:47 +01:00
// HTTP Server Response Code Debugging if --https-debug is being used
2023-10-18 20:31:50 +02:00
if ( debugResponse ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "onedrive.performHTTPOperation() => OneDrive HTTP Server Response: " ~ to ! string ( curlEngine . http . statusLine . code ) , [ "debug" ] ) ;
2018-11-14 20:08:55 +01:00
}
2015-10-04 16:24:45 +02:00
return data . length ;
} ;
2021-11-23 20:54:28 +01:00
2018-08-02 00:25:37 +02:00
try {
2023-10-18 20:31:50 +02:00
curlEngine . http . perform ( ) ;
2021-03-03 01:12:27 +01:00
// Check the HTTP Response headers - needed for correct 429 handling
checkHTTPResponseHeaders ( ) ;
2018-08-02 00:25:37 +02:00
} catch ( CurlException e ) {
2019-10-21 09:04:08 +02:00
// Parse and display error message received from OneDrive
2023-12-08 23:19:57 +01:00
addLogEntry ( "onedrive.performHTTPOperation() Generated a OneDrive CurlException" , [ "debug" ] ) ;
2019-10-21 09:04:08 +02:00
auto errorArray = splitLines ( e . msg ) ;
string errorMessage = errorArray [ 0 ] ;
2022-04-21 00:03:22 +02:00
// what is contained in the curl error message?
2020-12-09 04:18:16 +01:00
if ( canFind ( errorMessage , "Couldn't connect to server on handle" ) | | canFind ( errorMessage , "Couldn't resolve host name on handle" ) | | canFind ( errorMessage , "Timeout was reached on handle" ) ) {
2019-10-21 09:04:08 +02:00
// This is a curl timeout
2022-04-21 00:03:22 +02:00
// or is this a 408 request timeout
2019-10-21 09:04:08 +02:00
// https://github.com/abraunegg/onedrive/issues/694
// Back off & retry with incremental delay
int retryCount = 10000 ;
2022-04-21 00:03:22 +02:00
int retryAttempts = 0 ;
int backoffInterval = 0 ;
2019-10-21 09:04:08 +02:00
int maxBackoffInterval = 3600 ;
2022-04-21 00:03:22 +02:00
int timestampAlign = 0 ;
2019-10-21 09:53:00 +02:00
bool retrySuccess = false ;
2022-04-21 00:03:22 +02:00
SysTime currentTime ;
2023-12-08 23:19:57 +01:00
// Connectivity to Microsoft OneDrive was lost
2023-12-17 01:10:29 +01:00
addLogEntry ( "Internet connectivity to Microsoft OneDrive service has been lost .. re-trying in the background" ) ;
2023-12-08 23:19:57 +01:00
2022-04-21 00:03:22 +02:00
// what caused the initial curl exception?
2023-12-08 23:19:57 +01:00
if ( canFind ( errorMessage , "Couldn't connect to server on handle" ) ) addLogEntry ( "Unable to connect to server - HTTPS access blocked?" , [ "debug" ] ) ;
if ( canFind ( errorMessage , "Couldn't resolve host name on handle" ) ) addLogEntry ( "Unable to resolve server - DNS access blocked?" , [ "debug" ] ) ;
if ( canFind ( errorMessage , "Timeout was reached on handle" ) ) addLogEntry ( "A timeout was triggered - data too slow, no response ... use --debug-https to diagnose further" , [ "debug" ] ) ;
2022-04-21 00:03:22 +02:00
2019-10-21 09:53:00 +02:00
while ( ! retrySuccess ) {
2019-10-21 09:04:08 +02:00
try {
2022-04-21 00:03:22 +02:00
// configure libcurl to perform a fresh connection
2023-12-08 23:19:57 +01:00
addLogEntry ( "Configuring libcurl to use a fresh connection for re-try" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
curlEngine . http . handle . set ( CurlOption . fresh_connect , 1 ) ;
2022-04-21 00:03:22 +02:00
// try the access
2023-10-18 20:31:50 +02:00
curlEngine . http . perform ( ) ;
2021-03-03 01:12:27 +01:00
// Check the HTTP Response headers - needed for correct 429 handling
checkHTTPResponseHeaders ( ) ;
2019-10-21 09:04:08 +02:00
// no error from http.perform() on re-try
2023-12-08 23:19:57 +01:00
addLogEntry ( "Internet connectivity to Microsoft OneDrive service has been restored" ) ;
2022-04-21 00:03:22 +02:00
// unset the fresh connect option as this then creates performance issues if left enabled
2023-12-08 23:19:57 +01:00
addLogEntry ( "Unsetting libcurl to use a fresh connection as this causes a performance impact if left enabled" , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
curlEngine . http . handle . set ( CurlOption . fresh_connect , 0 ) ;
2022-04-21 00:03:22 +02:00
// connectivity restored
2019-10-21 09:53:00 +02:00
retrySuccess = true ;
2019-10-21 09:04:08 +02:00
} catch ( CurlException e ) {
2022-04-21 00:03:22 +02:00
// when was the exception generated
currentTime = Clock . currTime ( ) ;
// Increment retry attempts
retryAttempts + + ;
2020-12-09 04:18:16 +01:00
if ( canFind ( e . msg , "Couldn't connect to server on handle" ) | | canFind ( e . msg , "Couldn't resolve host name on handle" ) | | canFind ( errorMessage , "Timeout was reached on handle" ) ) {
2022-04-21 00:03:22 +02:00
// no access to Internet
2023-12-08 23:19:57 +01:00
addLogEntry ( ) ;
addLogEntry ( "ERROR: There was a timeout in accessing the Microsoft OneDrive service - Internet connectivity issue?" ) ;
2022-04-21 00:03:22 +02:00
// what is the error reason to assis the user as what to check
if ( canFind ( e . msg , "Couldn't connect to server on handle" ) ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( " - Check HTTPS access or Firewall Rules" ) ;
2022-04-21 00:03:22 +02:00
timestampAlign = 9 ;
}
if ( canFind ( e . msg , "Couldn't resolve host name on handle" ) ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( " - Check DNS resolution or Firewall Rules" ) ;
2022-04-21 00:03:22 +02:00
timestampAlign = 0 ;
}
// increment backoff interval
backoffInterval + + ;
int thisBackOffInterval = retryAttempts * backoffInterval ;
// display retry information
currentTime . fracSecs = Duration . zero ;
auto timeString = currentTime . toString ( ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( " Retry attempt: " ~ to ! string ( retryAttempts ) , [ "verbose" ] ) ;
addLogEntry ( " This attempt timestamp: " ~ timeString , [ "verbose" ] ) ;
2022-04-21 00:03:22 +02:00
if ( thisBackOffInterval > maxBackoffInterval ) {
thisBackOffInterval = maxBackoffInterval ;
}
// detail when the next attempt will be tried
// factor in the delay for curl to generate the exception - otherwise the next timestamp appears to be 'out' even though technically correct
auto nextRetry = currentTime + dur ! "seconds" ( thisBackOffInterval ) + dur ! "seconds" ( timestampAlign ) ;
2023-12-08 23:19:57 +01:00
addLogEntry ( " Next retry in approx: " ~ to ! string ( ( thisBackOffInterval + timestampAlign ) ) ~ " seconds" , [ "verbose" ] ) ;
addLogEntry ( " Next retry approx: " ~ to ! string ( nextRetry ) , [ "verbose" ] ) ;
2022-04-21 00:03:22 +02:00
// thread sleep
Thread . sleep ( dur ! "seconds" ( thisBackOffInterval ) ) ;
2019-10-21 09:04:08 +02:00
}
if ( retryAttempts = = retryCount ) {
// we have attempted to re-connect X number of times
// false set this to true to break out of while loop
2019-10-21 09:53:00 +02:00
retrySuccess = true ;
2019-10-21 09:04:08 +02:00
}
}
}
if ( retryAttempts > = retryCount ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( " ERROR: Unable to reconnect to the Microsoft OneDrive service after " ~ to ! string ( retryCount ) ~ " attempts lasting over 1.2 years!" ) ;
2019-10-21 09:04:08 +02:00
throw new OneDriveException ( 408 , "Request Timeout - HTTP 408 or Internet down?" ) ;
}
} else {
2023-10-18 20:31:50 +02:00
// what error was returned?
if ( canFind ( errorMessage , "Problem with the SSL CA cert (path? access rights?) on handle" ) ) {
// error setting certificate verify locations:
// CAfile: /etc/pki/tls/certs/ca-bundle.crt
// CApath: none
//
// Tell the Curl Engine to bypass SSL check - essentially SSL is passing back a bad value due to 'stdio' compile time option
// Further reading:
// https://github.com/curl/curl/issues/6090
// https://github.com/openssl/openssl/issues/7536
// https://stackoverflow.com/questions/45829588/brew-install-fails-curl77-error-setting-certificate-verify
// https://forum.dlang.org/post/vwvkbubufexgeuaxhqfl@forum.dlang.org
2024-01-07 22:04:55 +01:00
addLogEntry ( "Problem with reading the SSL CA cert via libcurl - please repair your system SSL CA Certificates" ) ;
// Must force exit here, allow logging to be done. If needed later, we could re-use setDisableSSLVerifyPeer()
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
exit ( - 1 ) ;
2023-10-18 20:31:50 +02:00
} else {
// Log that an error was returned
2023-12-08 23:19:57 +01:00
addLogEntry ( "ERROR: OneDrive returned an error with the following message:" ) ;
2023-10-18 20:31:50 +02:00
// Some other error was returned
2023-12-08 23:19:57 +01:00
addLogEntry ( " Error Message: " ~ errorMessage ) ;
addLogEntry ( " Calling Function: " ~ getFunctionName ! ( { } ) ) ;
2022-05-31 21:57:05 +02:00
2023-10-18 20:31:50 +02:00
// Was this a curl initialization error?
if ( canFind ( errorMessage , "Failed initialization on handle" ) ) {
// initialization error ... prevent a run-away process if we have zero disk space
ulong localActualFreeSpace = getAvailableDiskSpace ( "." ) ;
if ( localActualFreeSpace = = 0 ) {
// force exit
shutdown ( ) ;
2023-12-21 12:09:46 +01:00
// Must force exit here, allow logging to be done
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
2023-10-18 20:31:50 +02:00
exit ( - 1 ) ;
}
2022-05-31 21:57:05 +02:00
}
}
2019-10-21 09:04:08 +02:00
}
// return an empty JSON for handling
2019-09-06 05:12:13 +02:00
return json ;
2018-08-02 00:25:37 +02:00
}
2021-11-23 20:54:28 +01:00
2017-03-11 11:40:19 +01:00
try {
json = content . parseJSON ( ) ;
} catch ( JSONException e ) {
2019-07-26 21:24:59 +02:00
// Log that a JSON Exception was caught, dont output the HTML response from OneDrive
2023-12-08 23:19:57 +01:00
addLogEntry ( "JSON Exception caught when performing HTTP operations - use --debug-https to diagnose further" , [ "debug" ] ) ;
2017-03-11 11:40:19 +01:00
}
return json ;
2015-09-01 20:45:34 +02:00
}
2023-10-18 20:31:50 +02:00
private void redeemToken ( char [ ] authCode ) {
char [ ] postData =
"client_id=" ~ clientId ~
"&redirect_uri=" ~ redirectUrl ~
"&code=" ~ authCode ~
"&grant_type=authorization_code" ;
acquireToken ( postData ) ;
}
private JSONValue upload ( string filepath , string url ) {
checkAccessTokenExpired ( ) ;
// open file as read-only in binary mode
auto file = File ( filepath , "rb" ) ;
// function scopes
scope ( exit ) {
curlEngine . http . clearRequestHeaders ( ) ;
curlEngine . http . onSend = null ;
curlEngine . http . onReceive = null ;
curlEngine . http . onReceiveHeader = null ;
curlEngine . http . onReceiveStatusLine = null ;
curlEngine . http . contentLength = 0 ;
// close file if open
if ( file . isOpen ( ) ) {
// close open file
file . close ( ) ;
}
}
2021-11-23 20:54:28 +01:00
2023-10-24 22:20:16 +02:00
curlEngine . connect ( HTTP . Method . put , url ) ;
2023-10-18 20:31:50 +02:00
addAccessTokenHeader ( ) ;
curlEngine . http . addRequestHeader ( "Content-Type" , "application/octet-stream" ) ;
curlEngine . http . onSend = data = > file . rawRead ( data ) . length ;
curlEngine . http . contentLength = file . size ;
auto response = performHTTPOperation ( ) ;
checkHttpResponseCode ( response ) ;
return response ;
}
private void checkHTTPResponseHeaders ( ) {
2021-03-03 01:12:27 +01:00
// Get the HTTP Response headers - needed for correct 429 handling
2023-10-18 20:31:50 +02:00
auto responseHeaders = curlEngine . http . responseHeaders ( ) ;
if ( debugResponse ) {
2023-12-08 23:19:57 +01:00
addLogEntry ( "curlEngine.http.perform() => HTTP Response Headers: " ~ to ! string ( responseHeaders ) , [ "debug" ] ) ;
2021-03-03 01:12:27 +01:00
}
2021-11-23 20:54:28 +01:00
2021-03-03 01:12:27 +01:00
// is retry-after in the response headers
2023-10-18 20:31:50 +02:00
if ( "retry-after" in curlEngine . http . responseHeaders ) {
2021-03-03 01:12:27 +01:00
// Set the retry-after value
2023-12-08 23:19:57 +01:00
addLogEntry ( "curlEngine.http.perform() => Received a 'Retry-After' Header Response with the following value: " ~ to ! string ( curlEngine . http . responseHeaders [ "retry-after" ] ) , [ "debug" ] ) ;
addLogEntry ( "curlEngine.http.perform() => Setting retryAfterValue to: " ~ to ! string ( curlEngine . http . responseHeaders [ "retry-after" ] ) , [ "debug" ] ) ;
2023-10-18 20:31:50 +02:00
retryAfterValue = to ! ulong ( curlEngine . http . responseHeaders [ "retry-after" ] ) ;
2021-03-03 01:12:27 +01:00
}
}
2015-09-01 20:45:34 +02:00
2023-10-18 20:31:50 +02:00
private void checkHttpResponseCode ( JSONValue response ) {
switch ( curlEngine . http . statusLine . code ) {
// 0 - OK ... HTTP2 version of 200 OK
case 0 :
break ;
// 100 - Continue
case 100 :
break ;
// 200 - OK
case 200 :
// No Log ..
break ;
// 201 - Created OK
// 202 - Accepted
// 204 - Deleted OK
case 201 , 202 , 204 :
2023-12-08 23:19:57 +01:00
// Log if --debug-https logging is used
if ( debugHTTPResponseOutput ) {
addLogEntry ( "OneDrive Response: '" ~ to ! string ( curlEngine . http . statusLine . code ) ~ " - " ~ to ! string ( curlEngine . http . statusLine . reason ) ~ "'" , [ "debug" ] ) ;
}
2023-10-18 20:31:50 +02:00
break ;
// 302 - resource found and available at another location, redirect
case 302 :
2023-12-08 23:19:57 +01:00
// Log if --debug-https logging is used
if ( debugHTTPResponseOutput ) {
addLogEntry ( "OneDrive Response: '" ~ to ! string ( curlEngine . http . statusLine . code ) ~ " - " ~ to ! string ( curlEngine . http . statusLine . reason ) ~ "'" , [ "debug" ] ) ;
}
2023-10-18 20:31:50 +02:00
break ;
// 400 - Bad Request
case 400 :
// Bad Request .. how should we act?
// make sure this is thrown so that it is caught
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
// 403 - Forbidden
case 403 :
// OneDrive responded that the user is forbidden
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 403 - Forbidden' - gracefully handling error" , [ "verbose" ] ) ;
2023-10-18 20:31:50 +02:00
// Throw this as a specific exception so this is caught when performing 'siteQuery = onedrive.o365SiteSearch(nextLink);' call
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
// 412 - Precondition Failed
case 412 :
// Throw this as a specific exception so this is caught when performing sync.uploadLastModifiedTime
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
// Server side (OneDrive) Errors
// 500 - Internal Server Error
// 502 - Bad Gateway
// 503 - Service Unavailable
// 504 - Gateway Timeout (Issue #320)
case 500 :
// Throw this as a specific exception so this is caught
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
case 502 :
// Throw this as a specific exception so this is caught
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
case 503 :
// Throw this as a specific exception so this is caught
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
case 504 :
// Throw this as a specific exception so this is caught
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
// Default - all other errors that are not a 2xx or a 302
default :
if ( curlEngine . http . statusLine . code / 100 ! = 2 & & curlEngine . http . statusLine . code ! = 302 ) {
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason , response ) ;
}
}
}
private void checkHttpCode ( ) {
2018-03-14 05:43:40 +01:00
// https://dev.onedrive.com/misc/errors.htm
// https://developer.overdrive.com/docs/reference-guide
2021-11-23 20:54:28 +01:00
2018-03-14 05:43:40 +01:00
/ *
2018-11-14 20:08:55 +01:00
HTTP / 1.1 Response handling
2018-03-14 05:43:40 +01:00
Errors in the OneDrive API are returned using standard HTTP status codes , as well as a JSON error response object . The following HTTP status codes should be expected .
Status code Status message Description
2021-11-23 20:54:28 +01:00
100 Continue Continue
2018-03-14 05:43:40 +01:00
200 OK Request was handled OK
201 Created This means you ' ve made a successful POST to checkout , lock in a format , or place a hold
204 No Content This means you ' ve made a successful DELETE to remove a hold or return a title
2021-11-23 20:54:28 +01:00
2018-03-14 05:43:40 +01:00
400 Bad Request Cannot process the request because it is malformed or incorrect .
401 Unauthorized Required authentication information is either missing or not valid for the resource .
403 Forbidden Access is denied to the requested resource . The user might not have enough permission .
404 Not Found The requested resource doesn ’ t exist .
405 Method Not Allowed The HTTP method in the request is not allowed on the resource .
406 Not Acceptable This service doesn ’ t support the format requested in the Accept header .
2019-10-21 09:04:08 +02:00
408 Request Time out Not expected from OneDrive , but can be used to handle Internet connection failures the same ( fallback and try again )
2018-03-14 05:43:40 +01:00
409 Conflict The current state conflicts with what the request expects . For example , the specified parent folder might not exist .
410 Gone The requested resource is no longer available at the server .
411 Length Required A Content - Length header is required on the request .
412 Precondition Failed A precondition provided in the request ( such as an if - match header ) does not match the resource ' s current state .
413 Request Entity Too Large The request size exceeds the maximum limit .
415 Unsupported Media Type The content type of the request is a format that is not supported by the service .
416 Requested Range Not Satisfiable The specified byte range is invalid or unavailable .
422 Unprocessable Entity Cannot process the request because it is semantically incorrect .
429 Too Many Requests Client application has been throttled and should not attempt to repeat the request until an amount of time has elapsed .
2021-11-23 20:54:28 +01:00
2018-03-14 05:43:40 +01:00
500 Internal Server Error There was an internal server error while processing the request .
501 Not Implemented The requested feature isn ’ t implemented .
502 Bad Gateway The service was unreachable
503 Service Unavailable The service is temporarily unavailable . You may repeat the request after a delay . There may be a Retry - After header .
507 Insufficient Storage The maximum storage quota has been reached .
509 Bandwidth Limit Exceeded Your app has been throttled for exceeding the maximum bandwidth cap . Your app can retry the request again after more time has elapsed .
2021-11-23 20:54:28 +01:00
HTTP / 2 Response handling
2018-11-14 20:08:55 +01:00
0 OK
2021-11-23 20:54:28 +01:00
2018-03-14 05:43:40 +01:00
* /
2021-11-23 20:54:28 +01:00
2023-10-18 20:31:50 +02:00
switch ( curlEngine . http . statusLine . code )
2018-03-14 05:43:40 +01:00
{
2019-08-30 21:27:49 +02:00
// 0 - OK ... HTTP2 version of 200 OK
2018-11-14 20:08:55 +01:00
case 0 :
break ;
2020-05-05 23:20:13 +02:00
// 100 - Continue
case 100 :
break ;
2018-05-03 08:21:53 +02:00
// 200 - OK
case 200 :
2021-11-23 20:54:28 +01:00
// No Log ..
2018-05-03 08:21:53 +02:00
break ;
// 201 - Created OK
// 202 - Accepted
// 204 - Deleted OK
case 201 , 202 , 204 :
2023-12-08 23:19:57 +01:00
// Log if --debug-https logging is used
if ( debugHTTPResponseOutput ) {
addLogEntry ( "OneDrive Response: '" ~ to ! string ( curlEngine . http . statusLine . code ) ~ " - " ~ to ! string ( curlEngine . http . statusLine . reason ) ~ "'" , [ "debug" ] ) ;
}
2018-05-03 08:21:53 +02:00
break ;
2021-11-23 20:54:28 +01:00
2018-11-11 20:01:57 +01:00
// 302 - resource found and available at another location, redirect
case 302 :
2023-12-08 23:19:57 +01:00
// Log if --debug-https logging is used
if ( debugHTTPResponseOutput ) {
addLogEntry ( "OneDrive Response: '" ~ to ! string ( curlEngine . http . statusLine . code ) ~ " - " ~ to ! string ( curlEngine . http . statusLine . reason ) ~ "'" , [ "debug" ] ) ;
}
2018-11-11 20:01:57 +01:00
break ;
2021-11-23 20:54:28 +01:00
2018-05-03 08:21:53 +02:00
// 400 - Bad Request
case 400 :
// Bad Request .. how should we act?
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 400 - Bad Request' - gracefully handling error" , [ "verbose" ] ) ;
2019-08-09 10:14:10 +02:00
break ;
2023-12-08 23:19:57 +01:00
2019-08-09 10:14:10 +02:00
// 403 - Forbidden
case 403 :
// OneDrive responded that the user is forbidden
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 403 - Forbidden' - gracefully handling error" , [ "verbose" ] ) ;
2019-08-09 10:14:10 +02:00
break ;
2021-11-23 20:54:28 +01:00
2019-08-09 10:14:10 +02:00
// 404 - Item not found
2018-05-03 08:21:53 +02:00
case 404 :
// Item was not found - do not throw an exception
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 404 - Item not found' - gracefully handling error" , [ "verbose" ] ) ;
2018-05-03 08:21:53 +02:00
break ;
2021-11-23 20:54:28 +01:00
2019-10-21 09:04:08 +02:00
// 408 - Request Timeout
case 408 :
// Request to connect to OneDrive service timed out
2023-12-08 23:19:57 +01:00
addLogEntry ( "Request Timeout - gracefully handling error" , [ "verbose" ] ) ;
2021-11-23 20:54:28 +01:00
throw new OneDriveException ( 408 , "Request Timeout - HTTP 408 or Internet down?" ) ;
2019-10-21 09:04:08 +02:00
2018-05-03 08:21:53 +02:00
// 409 - Conflict
case 409 :
// Conflict handling .. how should we act? This only really gets triggered if we are using --local-first & we remove items.db as the DB thinks the file is not uploaded but it is
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 409 - Conflict' - gracefully handling error" , [ "verbose" ] ) ;
2021-11-23 20:54:28 +01:00
break ;
2018-05-03 08:21:53 +02:00
// 412 - Precondition Failed
case 412 :
// A precondition provided in the request (such as an if-match header) does not match the resource's current state.
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 412 - Precondition Failed' - gracefully handling error" , [ "verbose" ] ) ;
2021-11-23 20:54:28 +01:00
break ;
2018-05-03 08:21:53 +02:00
// 415 - Unsupported Media Type
case 415 :
// Unsupported Media Type ... sometimes triggered on image files, especially PNG
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 415 - Unsupported Media Type' - gracefully handling error" , [ "verbose" ] ) ;
2018-05-03 08:21:53 +02:00
break ;
2021-11-23 20:54:28 +01:00
2018-08-27 02:45:26 +02:00
// 429 - Too Many Requests
case 429 :
// Too many requests in a certain time window
2021-03-03 01:12:27 +01:00
// Check the HTTP Response headers - needed for correct 429 handling
checkHTTPResponseHeaders ( ) ;
2018-08-27 02:45:26 +02:00
// https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 429 - Too Many Requests' - gracefully handling error" , [ "verbose" ] ) ;
2023-10-18 20:31:50 +02:00
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason ) ;
2021-11-23 20:54:28 +01:00
2018-05-03 08:21:53 +02:00
// Server side (OneDrive) Errors
// 500 - Internal Server Error
// 502 - Bad Gateway
// 503 - Service Unavailable
// 504 - Gateway Timeout (Issue #320)
2019-04-04 08:13:15 +02:00
case 500 :
2018-05-03 08:21:53 +02:00
// No actions
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 500 Internal Server Error' - gracefully handling error" , [ "verbose" ] ) ;
2019-04-04 08:13:15 +02:00
break ;
2021-11-23 20:54:28 +01:00
2019-04-04 08:13:15 +02:00
case 502 :
// No actions
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 502 Bad Gateway Error' - gracefully handling error" , [ "verbose" ] ) ;
2019-04-04 08:13:15 +02:00
break ;
2021-11-23 20:54:28 +01:00
2019-04-04 08:13:15 +02:00
case 503 :
// No actions
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 503 Service Unavailable Error' - gracefully handling error" , [ "verbose" ] ) ;
2019-04-04 08:13:15 +02:00
break ;
2021-11-23 20:54:28 +01:00
2019-04-04 08:13:15 +02:00
case 504 :
// No actions
2023-12-08 23:19:57 +01:00
addLogEntry ( "OneDrive returned a 'HTTP 504 Gateway Timeout Error' - gracefully handling error" , [ "verbose" ] ) ;
2019-04-04 08:13:15 +02:00
break ;
2018-05-03 08:21:53 +02:00
// "else"
default :
2023-10-18 20:31:50 +02:00
throw new OneDriveException ( curlEngine . http . statusLine . code , curlEngine . http . statusLine . reason ) ;
2016-12-13 18:04:16 +01:00
}
}
2023-10-18 20:31:50 +02:00
}