files.com 1.1.813 → 1.1.814
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/_VERSION +1 -1
- data/doc/Files/APIConnectionError.html +1 -1
- data/doc/Files/APIError.html +1 -1
- data/doc/Files/AccountAlreadyExistsError.html +1 -1
- data/doc/Files/AccountLineItem.html +1 -1
- data/doc/Files/AccountOverdueError.html +1 -1
- data/doc/Files/Action.html +1 -1
- data/doc/Files/ActionLog.html +1 -1
- data/doc/Files/ActionNotificationExport.html +1 -1
- data/doc/Files/ActionNotificationExportResult.html +1 -1
- data/doc/Files/AdditionalAuthenticationRequiredError.html +1 -1
- data/doc/Files/AgentNode.html +1 -1
- data/doc/Files/AgentNodeConnection.html +1 -1
- data/doc/Files/AgentNodeInstance.html +1 -1
- data/doc/Files/AgentPushUpdate.html +1 -1
- data/doc/Files/AgentPushUpdateBlockedError.html +1 -1
- data/doc/Files/AgentUnavailableError.html +1 -1
- data/doc/Files/AgentUpgradeRequiredError.html +1 -1
- data/doc/Files/AiAssistantPersonality.html +1 -1
- data/doc/Files/AiTask.html +1 -1
- data/doc/Files/AiTaskCannotBeRunManuallyError.html +1 -1
- data/doc/Files/AlreadyCompletedError.html +1 -1
- data/doc/Files/Api.html +20 -13
- data/doc/Files/ApiClient/RequestLogContext.html +29 -29
- data/doc/Files/ApiClient/TransferSettings.html +721 -0
- data/doc/Files/ApiClient.html +756 -204
- data/doc/Files/ApiKey.html +1 -1
- data/doc/Files/ApiKeyIsDisabledError.html +1 -1
- data/doc/Files/ApiKeyIsPathRestrictedError.html +1 -1
- data/doc/Files/ApiKeyNotFoundError.html +1 -1
- data/doc/Files/ApiKeyOnlyForDesktopAppError.html +1 -1
- data/doc/Files/ApiKeyOnlyForFileOperationsError.html +1 -1
- data/doc/Files/ApiKeyOnlyForMobileAppError.html +1 -1
- data/doc/Files/ApiKeyOnlyForOfficeIntegrationError.html +1 -1
- data/doc/Files/ApiKeySessionsNotSupportedError.html +1 -1
- data/doc/Files/ApiRequestLog.html +1 -1
- data/doc/Files/App.html +1 -1
- data/doc/Files/As2IncomingMessage.html +1 -1
- data/doc/Files/As2OutgoingMessage.html +1 -1
- data/doc/Files/As2Partner.html +1 -1
- data/doc/Files/As2Station.html +1 -1
- data/doc/Files/AttachmentTooLargeError.html +1 -1
- data/doc/Files/AuthenticationError.html +1 -1
- data/doc/Files/AuthenticationRequiredError.html +1 -1
- data/doc/Files/Auto.html +1 -1
- data/doc/Files/Automation.html +1 -1
- data/doc/Files/AutomationAuthoringSchema.html +1 -1
- data/doc/Files/AutomationCannotBeRunManuallyError.html +1 -1
- data/doc/Files/AutomationExecutionNode.html +1 -1
- data/doc/Files/AutomationLog.html +1 -1
- data/doc/Files/AutomationRun.html +1 -1
- data/doc/Files/AutomationsUnavailableError.html +1 -1
- data/doc/Files/BadRequestError.html +1 -1
- data/doc/Files/BandwidthSnapshot.html +1 -1
- data/doc/Files/Behavior.html +1 -1
- data/doc/Files/BehaviorNotAllowedOnRemoteServerError.html +1 -1
- data/doc/Files/BillingInformationHiddenError.html +1 -1
- data/doc/Files/BillingPermissionRequiredError.html +1 -1
- data/doc/Files/BufferedUploadDisabledForThisDestinationError.html +1 -1
- data/doc/Files/Bundle.html +1 -1
- data/doc/Files/BundleAction.html +1 -1
- data/doc/Files/BundleDownload.html +1 -1
- data/doc/Files/BundleMaximumUsesReachedError.html +1 -1
- data/doc/Files/BundleNotification.html +1 -1
- data/doc/Files/BundleOnlyAllowsPreviewsError.html +1 -1
- data/doc/Files/BundleOperationRequiresSubfolderError.html +1 -1
- data/doc/Files/BundlePath.html +1 -1
- data/doc/Files/BundlePathNotFoundError.html +1 -1
- data/doc/Files/BundlePermissionRequiredError.html +1 -1
- data/doc/Files/BundleRecipient.html +1 -1
- data/doc/Files/BundleRegistration.html +1 -1
- data/doc/Files/BundleRegistrationCodeFailedError.html +1 -1
- data/doc/Files/BundleRegistrationNotFoundError.html +1 -1
- data/doc/Files/CannotAdministerHigherLevelUserError.html +1 -1
- data/doc/Files/CannotDownloadDirectoryError.html +1 -1
- data/doc/Files/CannotLoginWhileUsingKeyError.html +1 -1
- data/doc/Files/CantActForOtherUserError.html +1 -1
- data/doc/Files/CantMoveWithMultipleLocationsError.html +1 -1
- data/doc/Files/ChatMessage.html +1 -1
- data/doc/Files/ChatSession.html +1 -1
- data/doc/Files/ChildSiteManagementPolicy.html +1 -1
- data/doc/Files/Clickwrap.html +1 -1
- data/doc/Files/CodeNotFoundError.html +1 -1
- data/doc/Files/ConfigurationLockedPathError.html +1 -1
- data/doc/Files/ContactAdminForPasswordChangeHelpError.html +1 -1
- data/doc/Files/CouldNotCreateParentError.html +1 -1
- data/doc/Files/CustomDomain.html +1 -1
- data/doc/Files/DatetimeParseError.html +1 -1
- data/doc/Files/DesktopConfigurationProfile.html +1 -1
- data/doc/Files/DestinationExistsError.html +1 -1
- data/doc/Files/DestinationFolderLimitedError.html +1 -1
- data/doc/Files/DestinationParentConflictError.html +1 -1
- data/doc/Files/DestinationParentDoesNotExistError.html +1 -1
- data/doc/Files/DestinationSameError.html +1 -1
- data/doc/Files/DestinationSiteMismatchError.html +1 -1
- data/doc/Files/DirectConnectionInfo.html +1 -1
- data/doc/Files/DnsRecord.html +1 -1
- data/doc/Files/DoesNotSupportSortingError.html +1 -1
- data/doc/Files/DuplicateShareRecipientError.html +1 -1
- data/doc/Files/EmailIncomingMessage.html +1 -1
- data/doc/Files/EmailLog.html +1 -1
- data/doc/Files/Error.html +2 -2
- data/doc/Files/Errors.html +1 -1
- data/doc/Files/EventChannel.html +1 -1
- data/doc/Files/EventDeliveryAttempt.html +1 -1
- data/doc/Files/EventRecord.html +1 -1
- data/doc/Files/EventSubscription.html +1 -1
- data/doc/Files/EventTarget.html +1 -1
- data/doc/Files/ExavaultApiRequestLog.html +1 -1
- data/doc/Files/ExceededRuntimeLimitError.html +1 -1
- data/doc/Files/Expectation.html +1 -1
- data/doc/Files/ExpectationAlreadyHasOpenWindowError.html +1 -1
- data/doc/Files/ExpectationEvaluation.html +1 -1
- data/doc/Files/ExpectationIncident.html +1 -1
- data/doc/Files/ExpectationNotManualTriggerError.html +1 -1
- data/doc/Files/ExpiredPrivateKeyError.html +1 -1
- data/doc/Files/ExpiredPublicKeyError.html +1 -1
- data/doc/Files/ExportFailureError.html +1 -1
- data/doc/Files/ExportNotReadyError.html +1 -1
- data/doc/Files/ExternalEvent.html +1 -1
- data/doc/Files/FailedToChangePasswordError.html +1 -1
- data/doc/Files/File.html +2019 -1515
- data/doc/Files/FileAction.html +1 -1
- data/doc/Files/FileActionError.html +1 -1
- data/doc/Files/FileComment.html +1 -1
- data/doc/Files/FileCommentReaction.html +1 -1
- data/doc/Files/FileLockedError.html +1 -1
- data/doc/Files/FileMigration.html +1 -1
- data/doc/Files/FileMigrationLog.html +1 -1
- data/doc/Files/FileNotFoundError.html +1 -1
- data/doc/Files/FileNotUploadedError.html +1 -1
- data/doc/Files/FilePendingProcessingError.html +1 -1
- data/doc/Files/FileProcessingErrorError.html +1 -1
- data/doc/Files/FileTooBigToDecryptError.html +1 -1
- data/doc/Files/FileTooBigToEncryptError.html +1 -1
- data/doc/Files/FileUploadNotFoundError.html +1 -1
- data/doc/Files/FileUploadPart.html +1 -1
- data/doc/Files/FileUploadedToWrongRegionError.html +1 -1
- data/doc/Files/FileUtils.html +1 -1
- data/doc/Files/FilenameTooLongError.html +1 -1
- data/doc/Files/FilesAgentFailedAuthorizationError.html +1 -1
- data/doc/Files/FilesNativeStorageOverloadedError.html +1 -1
- data/doc/Files/Folder.html +1 -1
- data/doc/Files/FolderAdminOrBillingPermissionRequiredError.html +1 -1
- data/doc/Files/FolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/FolderLockedError.html +1 -1
- data/doc/Files/FolderMustNotBeAFileError.html +1 -1
- data/doc/Files/FolderNotEmptyError.html +1 -1
- data/doc/Files/FoldersNotAllowedError.html +1 -1
- data/doc/Files/FormField.html +1 -1
- data/doc/Files/FormFieldSet.html +1 -1
- data/doc/Files/FtpActionLog.html +1 -1
- data/doc/Files/FullPermissionRequiredError.html +1 -1
- data/doc/Files/GpgKey.html +1 -1
- data/doc/Files/Group.html +1 -1
- data/doc/Files/GroupNotFoundError.html +1 -1
- data/doc/Files/GroupUser.html +1 -1
- data/doc/Files/History.html +1 -1
- data/doc/Files/HistoryExport.html +1 -1
- data/doc/Files/HistoryExportResult.html +1 -1
- data/doc/Files/HistoryPermissionRequiredError.html +1 -1
- data/doc/Files/HistoryUnavailableError.html +1 -1
- data/doc/Files/HolidayCalendar.html +1 -1
- data/doc/Files/HolidayRegion.html +1 -1
- data/doc/Files/Image.html +1 -1
- data/doc/Files/InAppAiAssistantUnavailableError.html +1 -1
- data/doc/Files/InboundS3Log.html +1 -1
- data/doc/Files/InboxNotFoundError.html +1 -1
- data/doc/Files/InboxRecipient.html +1 -1
- data/doc/Files/InboxRegistration.html +1 -1
- data/doc/Files/InboxRegistrationCodeFailedError.html +1 -1
- data/doc/Files/InboxUpload.html +1 -1
- data/doc/Files/InsufficientPermissionForParamsError.html +1 -1
- data/doc/Files/InsufficientPermissionForSiteError.html +1 -1
- data/doc/Files/IntegrationCentricProfile.html +1 -1
- data/doc/Files/InternalGeneralErrorError.html +1 -1
- data/doc/Files/InvalidBodyError.html +1 -1
- data/doc/Files/InvalidBundleCodeError.html +1 -1
- data/doc/Files/InvalidCredentialsError.html +1 -1
- data/doc/Files/InvalidCursorError.html +1 -1
- data/doc/Files/InvalidCursorTypeForSortError.html +1 -1
- data/doc/Files/InvalidEtagsError.html +1 -1
- data/doc/Files/InvalidFileTypeError.html +1 -1
- data/doc/Files/InvalidFilenameError.html +1 -1
- data/doc/Files/InvalidFilterAliasCombinationError.html +1 -1
- data/doc/Files/InvalidFilterFieldError.html +1 -1
- data/doc/Files/InvalidFilterParamError.html +1 -1
- data/doc/Files/InvalidFilterParamFormatError.html +1 -1
- data/doc/Files/InvalidFilterParamValueError.html +1 -1
- data/doc/Files/InvalidInputEncodingError.html +1 -1
- data/doc/Files/InvalidInterfaceError.html +1 -1
- data/doc/Files/InvalidOauthError.html +1 -1
- data/doc/Files/InvalidOauthProviderError.html +1 -1
- data/doc/Files/InvalidOrExpiredCodeError.html +1 -1
- data/doc/Files/InvalidParameterError.html +5 -1
- data/doc/Files/InvalidPathError.html +1 -1
- data/doc/Files/InvalidPriorityColorError.html +1 -1
- data/doc/Files/InvalidRangeError.html +1 -1
- data/doc/Files/InvalidReturnToUrlError.html +1 -1
- data/doc/Files/InvalidSearchQueryError.html +1 -1
- data/doc/Files/InvalidSessionError.html +1 -1
- data/doc/Files/InvalidSiteError.html +1 -1
- data/doc/Files/InvalidSortFieldError.html +1 -1
- data/doc/Files/InvalidSortFilterCombinationError.html +1 -1
- data/doc/Files/InvalidUploadOffsetError.html +1 -1
- data/doc/Files/InvalidUploadPartGapError.html +1 -1
- data/doc/Files/InvalidUploadPartSizeError.html +1 -1
- data/doc/Files/InvalidUsernameOrPasswordError.html +1 -1
- data/doc/Files/InvalidWorkspaceIdHeaderError.html +1 -1
- data/doc/Files/InvalidZipFileError.html +1 -1
- data/doc/Files/Invoice.html +1 -1
- data/doc/Files/InvoiceLineItem.html +1 -1
- data/doc/Files/IpAddress.html +1 -1
- data/doc/Files/KeyLifecycleRule.html +1 -1
- data/doc/Files/List.html +1 -1
- data/doc/Files/Lock.html +1 -1
- data/doc/Files/LockOperationBusyError.html +1 -1
- data/doc/Files/LockedOutError.html +1 -1
- data/doc/Files/LockoutRegionMismatchError.html +1 -1
- data/doc/Files/MetadataCategory.html +1 -1
- data/doc/Files/MetadataNotSupportedOnRemotesError.html +1 -1
- data/doc/Files/MethodNotAllowedError.html +1 -1
- data/doc/Files/MigrationInProgressError.html +1 -1
- data/doc/Files/MissingParameterError.html +1 -1
- data/doc/Files/ModelSaveErrorError.html +1 -1
- data/doc/Files/MoverAccessDeniedError.html +1 -1
- data/doc/Files/MoverPackageRequiredError.html +1 -1
- data/doc/Files/MultipleProcessingErrorsError.html +1 -1
- data/doc/Files/MultipleSortParamsNotAllowedError.html +1 -1
- data/doc/Files/MustAuthenticateWithApiKeyError.html +1 -1
- data/doc/Files/NeedAdminPermissionForInboxError.html +1 -1
- data/doc/Files/NestedNotFoundError.html +1 -1
- data/doc/Files/NoAccountForSiteError.html +1 -1
- data/doc/Files/NoValidInputParamsError.html +1 -1
- data/doc/Files/NonAdminsMustQueryByFolderOrPathError.html +1 -1
- data/doc/Files/NotAllowedToCreateBundleError.html +1 -1
- data/doc/Files/NotAuthenticatedError.html +1 -1
- data/doc/Files/NotAuthorizedError.html +1 -1
- data/doc/Files/NotEnqueuableSyncError.html +1 -1
- data/doc/Files/NotFoundError.html +1 -1
- data/doc/Files/NotImplementedError.html +1 -1
- data/doc/Files/Notification.html +1 -1
- data/doc/Files/OffsetUploadNotAllowedWithMalwareScanningError.html +1 -1
- data/doc/Files/OneTimePasswordIncorrectError.html +1 -1
- data/doc/Files/OutboundConnectionLog.html +1 -1
- data/doc/Files/PartNumberTooLargeError.html +1 -1
- data/doc/Files/Partner.html +1 -1
- data/doc/Files/PartnerChannel.html +1 -1
- data/doc/Files/PartnerChannelTemplate.html +1 -1
- data/doc/Files/PartnerConnection.html +1 -1
- data/doc/Files/PartnerSite.html +1 -1
- data/doc/Files/PartnerSiteRequest.html +1 -1
- data/doc/Files/PasswordChangeNotRequiredError.html +1 -1
- data/doc/Files/PasswordChangeRequiredError.html +1 -1
- data/doc/Files/PathCannotHaveTrailingWhitespaceError.html +1 -1
- data/doc/Files/PathTooLongError.html +1 -1
- data/doc/Files/PathUtil.html +1 -1
- data/doc/Files/Payment.html +1 -1
- data/doc/Files/PaymentLineItem.html +1 -1
- data/doc/Files/PaymentMethodErrorError.html +1 -1
- data/doc/Files/PendingWorkEvent.html +1 -1
- data/doc/Files/Permission.html +1 -1
- data/doc/Files/PlanNotFoundError.html +1 -1
- data/doc/Files/Preview.html +1 -1
- data/doc/Files/PreviewOnlyPermissionCannotDownloadError.html +1 -1
- data/doc/Files/ProcessingFailureError.html +1 -1
- data/doc/Files/PublicHostingRequestLog.html +1 -1
- data/doc/Files/PublicIpAddress.html +1 -1
- data/doc/Files/PublicKey.html +1 -1
- data/doc/Files/RateLimitedError.html +1 -1
- data/doc/Files/ReadOnlySessionError.html +1 -1
- data/doc/Files/ReadPermissionRequiredError.html +1 -1
- data/doc/Files/ReauthenticationFailedError.html +1 -1
- data/doc/Files/ReauthenticationFailedFinalError.html +1 -1
- data/doc/Files/ReauthenticationNeededActionError.html +1 -1
- data/doc/Files/ReauthenticationNeededFieldsError.html +1 -1
- data/doc/Files/ReauthenticationRateLimitedError.html +1 -1
- data/doc/Files/RecaptchaFailedError.html +1 -1
- data/doc/Files/RecipientAlreadySharedError.html +1 -1
- data/doc/Files/RemoteBandwidthSnapshot.html +1 -1
- data/doc/Files/RemoteDesktopDebugLoggingDisabledError.html +1 -1
- data/doc/Files/RemoteEntryReadOnlyError.html +1 -1
- data/doc/Files/RemoteMountBackend.html +1 -1
- data/doc/Files/RemoteServer.html +1 -1
- data/doc/Files/RemoteServerConfigurationFile.html +1 -1
- data/doc/Files/RemoteServerCredential.html +1 -1
- data/doc/Files/RemoteServerErrorError.html +1 -1
- data/doc/Files/Request.html +1 -1
- data/doc/Files/RequestBodyTooLargeError.html +1 -1
- data/doc/Files/RequestParamsContainInvalidCharacterError.html +1 -1
- data/doc/Files/RequestParamsInvalidError.html +1 -1
- data/doc/Files/RequestParamsRequiredError.html +1 -1
- data/doc/Files/ResourceBelongsToParentSiteError.html +1 -1
- data/doc/Files/ResourceLockedError.html +1 -1
- data/doc/Files/Response.html +1 -1
- data/doc/Files/Restore.html +1 -1
- data/doc/Files/RootFolderBehaviorSiteAdminRequiredError.html +1 -1
- data/doc/Files/RootFolderBehaviorSkipSiteAdminRequiredError.html +1 -1
- data/doc/Files/Schedule.html +1 -1
- data/doc/Files/ScheduledExport.html +1 -1
- data/doc/Files/ScimLog.html +1 -1
- data/doc/Files/SearchAllOnChildPathError.html +1 -1
- data/doc/Files/SearchUnavailableError.html +1 -1
- data/doc/Files/Secret.html +1 -1
- data/doc/Files/SelfManagedRequiredError.html +1 -1
- data/doc/Files/ServiceUnavailableError.html +1 -1
- data/doc/Files/Session.html +1 -1
- data/doc/Files/SettingsChange.html +1 -1
- data/doc/Files/SftpActionLog.html +1 -1
- data/doc/Files/SftpHostKey.html +1 -1
- data/doc/Files/ShareGroup.html +1 -1
- data/doc/Files/ShareGroupMember.html +1 -1
- data/doc/Files/SiemHttpDestination.html +1 -1
- data/doc/Files/SiemHttpDestinationEvent.html +1 -1
- data/doc/Files/Site.html +1 -1
- data/doc/Files/SiteAdminOrPartnerAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrFolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminOrFolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminRequiredError.html +1 -1
- data/doc/Files/SiteConfigurationError.html +1 -1
- data/doc/Files/SiteDisabledError.html +1 -1
- data/doc/Files/SiteFilesAreImmutableError.html +1 -1
- data/doc/Files/SiteNotFoundError.html +1 -1
- data/doc/Files/SiteSubdomainRedirect.html +1 -1
- data/doc/Files/SiteWasRemovedError.html +1 -1
- data/doc/Files/SizableIO.html +1 -1
- data/doc/Files/Snapshot.html +1 -1
- data/doc/Files/SsoEvent.html +1 -1
- data/doc/Files/SsoStrategy.html +1 -1
- data/doc/Files/Status.html +1 -1
- data/doc/Files/Style.html +1 -1
- data/doc/Files/SubfolderLockedError.html +1 -1
- data/doc/Files/Sync.html +1 -1
- data/doc/Files/SyncInProgressError.html +1 -1
- data/doc/Files/SyncLog.html +1 -1
- data/doc/Files/SyncRun.html +1 -1
- data/doc/Files/SyncRunLiveTransfer.html +1 -1
- data/doc/Files/SystemProfiler.html +1 -1
- data/doc/Files/TooManyConcurrentLoginsError.html +1 -1
- data/doc/Files/TooManyConcurrentRequestsError.html +1 -1
- data/doc/Files/TooManyLoginAttemptsError.html +1 -1
- data/doc/Files/TooManyRequestsError.html +1 -1
- data/doc/Files/TooManySharesError.html +1 -1
- data/doc/Files/Transfer/BufferLimitError.html +175 -0
- data/doc/Files/Transfer/ByteBudget/Reservation.html +301 -0
- data/doc/Files/Transfer/ByteBudget.html +748 -0
- data/doc/Files/Transfer/CancelledError.html +235 -0
- data/doc/Files/Transfer/Checkpoint.html +1371 -0
- data/doc/Files/Transfer/CheckpointError.html +173 -0
- data/doc/Files/Transfer/ClientTransport/ClientResponse.html +661 -0
- data/doc/Files/Transfer/ClientTransport/Reader.html +530 -0
- data/doc/Files/Transfer/ClientTransport.html +675 -0
- data/doc/Files/Transfer/Clock.html +189 -0
- data/doc/Files/Transfer/Control.html +667 -0
- data/doc/Files/Transfer/Controller/Attempt.html +465 -0
- data/doc/Files/Transfer/Controller.html +1166 -0
- data/doc/Files/Transfer/Coordinator/Timing.html +568 -0
- data/doc/Files/Transfer/Coordinator.html +534 -0
- data/doc/Files/Transfer/DownloadCoordinator/FileSink.html +976 -0
- data/doc/Files/Transfer/DownloadCoordinator/IoSink.html +1168 -0
- data/doc/Files/Transfer/DownloadCoordinator/Piece.html +875 -0
- data/doc/Files/Transfer/DownloadCoordinator.html +728 -0
- data/doc/Files/Transfer/FileSlots.html +480 -0
- data/doc/Files/Transfer/FrontendResponseError.html +235 -0
- data/doc/Files/Transfer/Holds.html +505 -0
- data/doc/Files/Transfer/HttpTransport.html +579 -0
- data/doc/Files/Transfer/Job.html +2338 -0
- data/doc/Files/Transfer/Options.html +924 -0
- data/doc/Files/Transfer/PausedError.html +319 -0
- data/doc/Files/Transfer/Policy.html +1642 -0
- data/doc/Files/Transfer/RequestPermits.html +160 -0
- data/doc/Files/Transfer/SourceChangedError.html +235 -0
- data/doc/Files/Transfer/StorageResponse.html +546 -0
- data/doc/Files/Transfer/StorageRetry.html +1098 -0
- data/doc/Files/Transfer/UploadCoordinator/Grant.html +1048 -0
- data/doc/Files/Transfer/UploadCoordinator/Part.html +1048 -0
- data/doc/Files/Transfer/UploadCoordinator/Region.html +295 -0
- data/doc/Files/Transfer/UploadCoordinator/Segments.html +299 -0
- data/doc/Files/Transfer/UploadCoordinator/Source.html +725 -0
- data/doc/Files/Transfer/UploadCoordinator/StreamSource.html +664 -0
- data/doc/Files/Transfer/UploadCoordinator.html +546 -0
- data/doc/Files/Transfer/UploadPlan.html +522 -0
- data/doc/Files/Transfer/VersionChangedError.html +235 -0
- data/doc/Files/Transfer.html +134 -0
- data/doc/Files/TrialExpiredError.html +1 -1
- data/doc/Files/TrialLockedError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationCodeAlreadySentError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationCountryBlacklistedError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationGeneralErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationMethodUnsupportedErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationRequiredError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationSetupExpiredError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationUnsubscribedRecipientError.html +1 -1
- data/doc/Files/URI.html +1 -1
- data/doc/Files/UnrecognizedSortIndexError.html +1 -1
- data/doc/Files/UnsupportedCurrencyError.html +1 -1
- data/doc/Files/UnsupportedHttpResponseFormatError.html +1 -1
- data/doc/Files/UnsupportedMediaTypeError.html +1 -1
- data/doc/Files/UpdatesNotAllowedForRemotesError.html +1 -1
- data/doc/Files/UploadsUnavailableError.html +1 -1
- data/doc/Files/UsageByTopLevelDir.html +1 -1
- data/doc/Files/UsageDailySnapshot.html +1 -1
- data/doc/Files/UsageSnapshot.html +1 -1
- data/doc/Files/User.html +1 -1
- data/doc/Files/UserAdditionalEmailRecipient.html +1 -1
- data/doc/Files/UserCipherUse.html +1 -1
- data/doc/Files/UserIdInvalidError.html +1 -1
- data/doc/Files/UserIdOnUserEndpointError.html +1 -1
- data/doc/Files/UserIdWithoutSiteAdminError.html +1 -1
- data/doc/Files/UserLifecycleRule.html +1 -1
- data/doc/Files/UserNotFoundError.html +1 -1
- data/doc/Files/UserRequest.html +1 -1
- data/doc/Files/UserRequestsEnabledRequiredError.html +1 -1
- data/doc/Files/UserRequiredError.html +1 -1
- data/doc/Files/UserSecurityEvent.html +1 -1
- data/doc/Files/UserSftpClientUse.html +1 -1
- data/doc/Files/Util/LogSettings.html +1 -1
- data/doc/Files/Util.html +1 -1
- data/doc/Files/WebDavActionLog.html +1 -1
- data/doc/Files/WebhookTest.html +1 -1
- data/doc/Files/Workspace.html +1 -1
- data/doc/Files/WriteAndBundlePermissionRequiredError.html +1 -1
- data/doc/Files/WritePermissionRequiredError.html +1 -1
- data/doc/Files/ZipListEntry.html +1 -1
- data/doc/Files.html +116 -116
- data/doc/_index.html +314 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +1 -1
- data/doc/index.html +1 -1
- data/doc/method_list.html +4703 -2415
- data/doc/top-level-namespace.html +1 -1
- data/docs/file.md +346 -0
- data/lib/files.com/api.rb +6 -1
- data/lib/files.com/api_client.rb +220 -32
- data/lib/files.com/models/file.rb +124 -17
- data/lib/files.com/transfer/budgets.rb +123 -0
- data/lib/files.com/transfer/checkpoint.rb +113 -0
- data/lib/files.com/transfer/clock.rb +14 -0
- data/lib/files.com/transfer/controller.rb +549 -0
- data/lib/files.com/transfer/download_coordinator.rb +906 -0
- data/lib/files.com/transfer/errors.rb +100 -0
- data/lib/files.com/transfer/http_transport.rb +337 -0
- data/lib/files.com/transfer/job.rb +778 -0
- data/lib/files.com/transfer/options.rb +126 -0
- data/lib/files.com/transfer/policy.rb +91 -0
- data/lib/files.com/transfer/storage_retry.rb +169 -0
- data/lib/files.com/transfer/upload_coordinator.rb +657 -0
- data/lib/files.com/transfer/upload_plan.rb +157 -0
- data/lib/files.com.rb +13 -0
- data/spec/lib/api_client_spec.rb +125 -3
- data/spec/models/file_transfer_spec.rb +53 -4
- data/spec/support/transfer_server.rb +113 -14
- data/spec/transfer/adaptive_download_spec.rb +1102 -0
- data/spec/transfer/adaptive_upload_spec.rb +1602 -0
- data/spec/transfer/concurrency_controller_spec.rb +382 -0
- data/spec/workspace_id_spec.rb +19 -14
- metadata +59 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Files
|
|
4
|
+
module Transfer
|
|
5
|
+
# Lets another thread stop an adaptive transfer. cancel and pause stop new requests at once; the transfer then
|
|
6
|
+
# waits for the requests already in flight, releases the memory and connections it holds and raises
|
|
7
|
+
# CancelledError or PausedError. The first of the two wins, and a Control stays stopped: resume with a new one.
|
|
8
|
+
class Control
|
|
9
|
+
def initialize
|
|
10
|
+
@lock = Mutex.new
|
|
11
|
+
@state = :running
|
|
12
|
+
@listeners = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cancel
|
|
16
|
+
change(:cancelled)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pause
|
|
20
|
+
change(:paused)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def cancelled?
|
|
24
|
+
@state == :cancelled
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def paused?
|
|
28
|
+
@state == :paused
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# :running, :cancelled or :paused.
|
|
32
|
+
attr_reader :state
|
|
33
|
+
|
|
34
|
+
# A listener's wake is called after each change, so jobs waiting on this control notice it at once.
|
|
35
|
+
def subscribe(listener)
|
|
36
|
+
@lock.synchronize { @listeners << listener }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unsubscribe(listener)
|
|
40
|
+
@lock.synchronize { @listeners.delete(listener) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private def change(state)
|
|
44
|
+
listeners = @lock.synchronize do
|
|
45
|
+
return false unless @state == :running
|
|
46
|
+
|
|
47
|
+
@state = state
|
|
48
|
+
@listeners.dup
|
|
49
|
+
end
|
|
50
|
+
listeners.each(&:wake)
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# What an adaptive transfer may use. The transfer: option takes these as a Hash, true for all the defaults, an
|
|
56
|
+
# Options or a Job that several transfers share.
|
|
57
|
+
#
|
|
58
|
+
# concurrency:: :automatic (the default) tunes the requests in flight from what the link sustains; an Integer from
|
|
59
|
+
# 1 to 256 keeps exactly that many.
|
|
60
|
+
# startup:: With :automatic, :default starts an upload to cloud storage with one request and grows while each
|
|
61
|
+
# step pays off; :aggressive starts at 32. Downloads and direct destinations ignore it.
|
|
62
|
+
# max_concurrency:: With :automatic, the most requests in flight, from 1 to 256. The default is 64 for cloud
|
|
63
|
+
# storage and downloads and 16 for direct destinations, which it can only lower.
|
|
64
|
+
# buffer_bytes:: The most bytes held in memory at once for parts waiting to be sent or written, at least 65,536.
|
|
65
|
+
# The default is 256 MiB.
|
|
66
|
+
# max_files:: How many files a shared Job transfers at once; more wait their turn. The default is 4.
|
|
67
|
+
# control:: A Control that cancels or pauses the transfer.
|
|
68
|
+
class Options
|
|
69
|
+
MAX_CONCURRENCY = 256
|
|
70
|
+
DEFAULT_BUFFER_BYTES = 256 * 1024 * 1024
|
|
71
|
+
MIN_BUFFER_BYTES = 64 * 1024
|
|
72
|
+
DEFAULT_MAX_FILES = 4
|
|
73
|
+
KEYS = %i[concurrency startup max_concurrency buffer_bytes max_files control].freeze
|
|
74
|
+
STARTUPS = %i[default aggressive].freeze
|
|
75
|
+
|
|
76
|
+
attr_reader :concurrency, :startup, :max_concurrency, :buffer_bytes, :max_files, :control
|
|
77
|
+
|
|
78
|
+
def self.from(value)
|
|
79
|
+
case value
|
|
80
|
+
when Options then value
|
|
81
|
+
when true then new
|
|
82
|
+
when Hash
|
|
83
|
+
unknown = value.keys - KEYS
|
|
84
|
+
raise InvalidParameterError, "Bad parameter: transfer does not take #{unknown.map(&:inspect).join(', ')}" unless unknown.empty?
|
|
85
|
+
|
|
86
|
+
new(**value)
|
|
87
|
+
else
|
|
88
|
+
raise InvalidParameterError, "Bad parameter: transfer must be true, a Hash of transfer options, a Files::Transfer::Options or a Files::Transfer::Job"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def initialize(concurrency: :automatic, startup: :default, max_concurrency: nil, buffer_bytes: DEFAULT_BUFFER_BYTES, max_files: DEFAULT_MAX_FILES, control: nil)
|
|
93
|
+
@concurrency = concurrency
|
|
94
|
+
@startup = startup
|
|
95
|
+
@max_concurrency = max_concurrency
|
|
96
|
+
@buffer_bytes = buffer_bytes
|
|
97
|
+
@max_files = max_files
|
|
98
|
+
@control = control
|
|
99
|
+
validate
|
|
100
|
+
freeze
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def automatic?
|
|
104
|
+
@concurrency == :automatic
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_h
|
|
108
|
+
KEYS.to_h { |key| [ key, public_send(key) ] }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private def validate
|
|
112
|
+
raise InvalidParameterError, "Bad parameter: transfer concurrency must be :automatic or an Integer from 1 to #{MAX_CONCURRENCY}" unless automatic? || concurrency_count?(@concurrency)
|
|
113
|
+
raise InvalidParameterError, "Bad parameter: transfer startup must be :default or :aggressive" unless STARTUPS.include?(@startup)
|
|
114
|
+
raise InvalidParameterError, "Bad parameter: transfer startup applies only to automatic concurrency" if !automatic? && @startup != :default
|
|
115
|
+
raise InvalidParameterError, "Bad parameter: transfer max_concurrency must be an Integer from 1 to #{MAX_CONCURRENCY}, with automatic concurrency" unless @max_concurrency.nil? || (automatic? && concurrency_count?(@max_concurrency))
|
|
116
|
+
raise InvalidParameterError, "Bad parameter: transfer buffer_bytes must be an Integer of at least #{MIN_BUFFER_BYTES}" unless @buffer_bytes.is_a?(Integer) && @buffer_bytes >= MIN_BUFFER_BYTES
|
|
117
|
+
raise InvalidParameterError, "Bad parameter: transfer max_files must be a positive Integer" unless @max_files.is_a?(Integer) && @max_files.positive?
|
|
118
|
+
raise InvalidParameterError, "Bad parameter: transfer control must be a Files::Transfer::Control" unless @control.nil? || @control.is_a?(Control)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private def concurrency_count?(value)
|
|
122
|
+
value.is_a?(Integer) && value.between?(1, MAX_CONCURRENCY)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Files
|
|
4
|
+
module Transfer
|
|
5
|
+
# The numbers a Controller works within for one kind of destination. Every count is capped at max, so a caller's
|
|
6
|
+
# max_concurrency, or a fixed concurrency, always dominates.
|
|
7
|
+
class Policy
|
|
8
|
+
MIB = 1024 * 1024
|
|
9
|
+
# A warm-start window at least this fast ends an upload's warm start at the start concurrency.
|
|
10
|
+
FAST_LINK_BYTES_PER_SECOND = 96 * MIB
|
|
11
|
+
# A window is judged only when its requests in flight averaged at least this share of the limit.
|
|
12
|
+
ELIGIBLE_OCCUPANCY = 0.75
|
|
13
|
+
# A failure, a throttle and a growing queue shrink the limit by these shares, once per epoch.
|
|
14
|
+
FAILURE_SHRINK = 0.35
|
|
15
|
+
THROTTLE_SHRINK = 0.10
|
|
16
|
+
LATENCY_SHRINK = 0.08
|
|
17
|
+
# A probe is kept when a window's throughput beats the one before it by this share, and given up after this many
|
|
18
|
+
# windows that do not; the limit then holds for HOLD_WINDOWS windows.
|
|
19
|
+
PROBE_GAIN = 0.01
|
|
20
|
+
PROBE_WINDOWS = 2
|
|
21
|
+
HOLD_WINDOWS = 1
|
|
22
|
+
# After a rejected warm-start probe, the windows to rest before probing again by one.
|
|
23
|
+
GROWTH_INTERVAL_WINDOWS = 1
|
|
24
|
+
WARM_REJECTIONS = 2
|
|
25
|
+
# Seconds after a throttle, or its Retry-After when longer, during which completions are not measured.
|
|
26
|
+
THROTTLE_COOLDOWN = 1.0
|
|
27
|
+
DOWNLOAD_RANGE_BYTES = 8 * MIB
|
|
28
|
+
# Parts asked for in one begin_upload.
|
|
29
|
+
UPLOAD_BATCH_PARTS = 5
|
|
30
|
+
# The most requests cloud storage and downloads have in flight, and a job across all its transfers, unless the
|
|
31
|
+
# caller's max_concurrency or fixed concurrency says otherwise.
|
|
32
|
+
DEFAULT_MAX = 64
|
|
33
|
+
|
|
34
|
+
attr_reader :name, :initial, :start, :min, :soft, :max, :window_attempts, :growth_step, :queue_suppress, :queue_backoff, :latency_floor, :rejected_floor
|
|
35
|
+
|
|
36
|
+
# Cloud storage, such as S3. :default starts at one request and warms up toward 32; :aggressive starts at 32.
|
|
37
|
+
def self.storage_upload(startup: :default, max_concurrency: nil)
|
|
38
|
+
new(name: :storage, initial: startup == :aggressive ? 32 : 1, start: 32, min: 4, soft: 32, max: max_concurrency || DEFAULT_MAX,
|
|
39
|
+
growth_step: 2, queue_suppress: 20, queue_backoff: 34, latency_floor: 12, rejected_floor: 12, warm: startup == :default
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A destination the API reports a direct connection for, such as an agent. Its queue thresholds are cloud
|
|
44
|
+
# storage's 20 and 34, scaled to its maximum of 16.
|
|
45
|
+
def self.direct_upload(max_concurrency: nil)
|
|
46
|
+
new(name: :direct, initial: 4, start: 4, min: 1, soft: 16, max: [ 16, max_concurrency || 16 ].min,
|
|
47
|
+
growth_step: 1, queue_suppress: 5, queue_backoff: 9, latency_floor: 1, rejected_floor: 1
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.download(max_concurrency: nil)
|
|
52
|
+
new(name: :download, initial: 32, start: 32, min: 4, soft: 32, max: max_concurrency || DEFAULT_MAX,
|
|
53
|
+
growth_step: 2, queue_suppress: 20, queue_backoff: 34, latency_floor: 12, rejected_floor: 12
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.fixed(concurrency)
|
|
58
|
+
new(name: :fixed, initial: concurrency, start: concurrency, min: concurrency, soft: concurrency, max: concurrency,
|
|
59
|
+
growth_step: 0, queue_suppress: concurrency, queue_backoff: concurrency, latency_floor: concurrency, rejected_floor: concurrency, fixed: true
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize(name:, initial:, start:, min:, soft:, max:, growth_step:, queue_suppress:, queue_backoff:, latency_floor:, rejected_floor:,
|
|
64
|
+
window_attempts: 16, warm: false, fixed: false)
|
|
65
|
+
@name = name
|
|
66
|
+
@max = max
|
|
67
|
+
@initial = [ initial, max ].min
|
|
68
|
+
@start = [ start, max ].min
|
|
69
|
+
@min = [ min, max ].min
|
|
70
|
+
@soft = [ soft, max ].min
|
|
71
|
+
@window_attempts = window_attempts
|
|
72
|
+
@growth_step = growth_step
|
|
73
|
+
@queue_suppress = queue_suppress
|
|
74
|
+
@queue_backoff = queue_backoff
|
|
75
|
+
@latency_floor = [ latency_floor, max ].min
|
|
76
|
+
@rejected_floor = [ rejected_floor, max ].min
|
|
77
|
+
@warm = warm && @initial < @start
|
|
78
|
+
@fixed = fixed
|
|
79
|
+
freeze
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def warm?
|
|
83
|
+
@warm
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fixed?
|
|
87
|
+
@fixed
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Files
|
|
6
|
+
module Transfer
|
|
7
|
+
# What a storage response means for the request that got it, and when a signed URL stops working.
|
|
8
|
+
#
|
|
9
|
+
# 429 and 503 are throttles, retried after their whole Retry-After (seconds or an HTTP date) or at once without
|
|
10
|
+
# one; a Retry-After too large to schedule refuses the request instead. 500, 502, 504, a network failure and S3's
|
|
11
|
+
# RequestTimeout error (a 400 whose whole body, of at most 64 KiB, is a complete, DTD-free XML document whose
|
|
12
|
+
# unnamespaced root <Error> has a direct <Code>RequestTimeout</Code>) are retried after 100 ms. Throttles hold the
|
|
13
|
+
# destination back for every request of the job (Holds). Anything else is refused: 501, the other 4xx,
|
|
14
|
+
# 3xx (redirects are never followed) and a success that does not acknowledge the part. Each retry is a new
|
|
15
|
+
# admission, within the caller's Files.max_network_retries, and never for a part whose instructions say
|
|
16
|
+
# retry_parts: false.
|
|
17
|
+
module StorageRetry
|
|
18
|
+
THROTTLE_STATUSES = [ 429, 503 ].freeze
|
|
19
|
+
RETRY_STATUSES = [ 500, 502, 504 ].freeze
|
|
20
|
+
RETRY_DELAY = 0.1
|
|
21
|
+
# Seconds before a signed URL's expiry at which it is renewed rather than used.
|
|
22
|
+
EXPIRY_MARGIN = 5
|
|
23
|
+
FRONTEND_HEADER = "x-files-frontend-app"
|
|
24
|
+
# The most of a 400's body judged, which bounds the XML checked.
|
|
25
|
+
XML_MAX_BYTES = 64 * 1024
|
|
26
|
+
# An XML declaration, accepted only at the very start of the body.
|
|
27
|
+
XML_DECLARATION = /\A<\?xml\s+version\s*=\s*(["'])1\.[0-9]+\1(?:\s+encoding\s*=\s*(["'])[A-Za-z][A-Za-z0-9._-]*\2)?(?:\s+standalone\s*=\s*(["'])(?:yes|no)\3)?\s*\?>/
|
|
28
|
+
# A predefined entity or a character reference: no other entity can be defined, since a DTD is never accepted.
|
|
29
|
+
XML_REFERENCE = '&(?:amp|lt|gt|quot|apos|#[0-9]+|#x\h+);'
|
|
30
|
+
XML_VALUE = %{(?:"(?:[^"<&]|#{XML_REFERENCE})*"|'(?:[^'<&]|#{XML_REFERENCE})*')}.freeze
|
|
31
|
+
# One attribute: its name, with any prefix, and its quoted value.
|
|
32
|
+
XML_ATTRIBUTE = /\s+([A-Za-z_][\w.:-]*)\s*=\s*(#{XML_VALUE})/
|
|
33
|
+
# A start, end or empty-element tag, with its attributes, or character data. A comment, processing instruction,
|
|
34
|
+
# CDATA section or DTD matches nothing, so a body holding one is refused, as is a prefixed element name.
|
|
35
|
+
XML_TOKEN = /\G(?:<(\/?)([A-Za-z_][\w.-]*)((?:\s+[A-Za-z_][\w.:-]*\s*=\s*#{XML_VALUE})*)\s*(\/?)>|((?:[^<&]|#{XML_REFERENCE})+))/
|
|
36
|
+
XML_CHARACTER_REFERENCE = /&#(x\h+|[0-9]+);/
|
|
37
|
+
# Characters XML never allows: the C0 controls other than tab, newline and carriage return.
|
|
38
|
+
XML_FORBIDDEN = /[\x00-\x08\x0B\x0C\x0E-\x1F]/n
|
|
39
|
+
SIGNATURE_PARAMS = [ %w[x-amz-date x-amz-expires], %w[x-files-date x-files-expires], %w[x-goog-date x-goog-expires] ].freeze
|
|
40
|
+
|
|
41
|
+
# [:throttle, seconds], [:retry, seconds] or [:refuse] for a response that is not a success. headers are
|
|
42
|
+
# lowercase names, each with its values. truncated says the body is only the first part of a longer one, which a
|
|
43
|
+
# transport keeps no more of: such a 400 is never taken for RequestTimeout, since its whole document was not seen.
|
|
44
|
+
def self.classify(status, headers, body, truncated: false)
|
|
45
|
+
if THROTTLE_STATUSES.include?(status)
|
|
46
|
+
seconds = retry_after(headers.fetch("retry-after", []).first)
|
|
47
|
+
return seconds.finite? ? [ :throttle, seconds ] : [ :refuse ]
|
|
48
|
+
end
|
|
49
|
+
return [ :retry, RETRY_DELAY ] if RETRY_STATUSES.include?(status)
|
|
50
|
+
return [ :retry, RETRY_DELAY ] if status == 400 && !truncated && request_timeout?(body)
|
|
51
|
+
|
|
52
|
+
[ :refuse ]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Seconds to wait, never negative and never shortened. Infinity when the value cannot be scheduled.
|
|
56
|
+
def self.retry_after(value, now: Time.now)
|
|
57
|
+
return 0.0 if value.nil?
|
|
58
|
+
|
|
59
|
+
text = value.strip
|
|
60
|
+
seconds = text.match?(/\A\d+\z/) ? text.to_f : Time.httpdate(text) - now
|
|
61
|
+
seconds.nan? ? Float::INFINITY : [ seconds, 0.0 ].max
|
|
62
|
+
rescue ArgumentError
|
|
63
|
+
0.0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Whether body is exactly S3's RequestTimeout error: a well-formed XML document of at most 64 KiB in UTF-8, with
|
|
67
|
+
# no DTD, comment, processing instruction (but an XML declaration at its start), CDATA section or entity but the
|
|
68
|
+
# predefined ones, no namespace (neither an xmlns declaration nor a prefix on any element or attribute), no
|
|
69
|
+
# repeated attribute and no character XML disallows, whose root is <Error> with a single direct <Code> child
|
|
70
|
+
# holding only RequestTimeout. A nested or repeated Code, another code, or anything else is not.
|
|
71
|
+
def self.request_timeout?(body)
|
|
72
|
+
return false unless body.is_a?(String) && body.bytesize <= XML_MAX_BYTES
|
|
73
|
+
|
|
74
|
+
text = body.b
|
|
75
|
+
utf8 = text.dup.force_encoding(Encoding::UTF_8)
|
|
76
|
+
return false unless utf8.valid_encoding? && !text.match?(XML_FORBIDDEN) && !utf8.match?(/[]/)
|
|
77
|
+
|
|
78
|
+
text = text.sub(XML_DECLARATION, "").strip
|
|
79
|
+
stack = []
|
|
80
|
+
codes = []
|
|
81
|
+
rooted = false
|
|
82
|
+
position = 0
|
|
83
|
+
while position < text.bytesize
|
|
84
|
+
token = XML_TOKEN.match(text, position)
|
|
85
|
+
return false unless token&.begin(0) == position
|
|
86
|
+
|
|
87
|
+
position = token.end(0)
|
|
88
|
+
closing, name, attributes, empty, content = token.captures
|
|
89
|
+
if content
|
|
90
|
+
return false unless (stack.any? || content.strip.empty?) && valid_xml_text?(content)
|
|
91
|
+
|
|
92
|
+
codes[-1] << content if stack.size == 2 && stack.last == "Code" && codes[-1]
|
|
93
|
+
elsif closing == "/"
|
|
94
|
+
return false unless attributes.empty? && empty.empty? && stack.pop == name
|
|
95
|
+
else
|
|
96
|
+
return false unless valid_xml_attributes?(attributes)
|
|
97
|
+
return false if stack.empty? && (rooted || name != "Error")
|
|
98
|
+
|
|
99
|
+
rooted = true if stack.empty?
|
|
100
|
+
codes[-1] = nil if stack.size == 2 && stack.last == "Code"
|
|
101
|
+
codes << +"" if stack.size == 1 && name == "Code"
|
|
102
|
+
stack << name unless empty == "/"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
rooted && stack.empty? && codes == [ "RequestTimeout" ]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# A tag's attributes: none in a namespace (no xmlns declaration, no prefix), none named twice, and each value well
|
|
109
|
+
# formed.
|
|
110
|
+
def self.valid_xml_attributes?(attributes)
|
|
111
|
+
names = []
|
|
112
|
+
attributes.scan(XML_ATTRIBUTE) do |name, value|
|
|
113
|
+
return false if name == "xmlns" || name.include?(":") || names.include?(name) || !valid_xml_text?(value)
|
|
114
|
+
|
|
115
|
+
names << name
|
|
116
|
+
end
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Character data or an attribute value: without "]]>", and with character references only to characters XML
|
|
121
|
+
# allows.
|
|
122
|
+
def self.valid_xml_text?(text)
|
|
123
|
+
!text.include?("]]>") && text.scan(XML_CHARACTER_REFERENCE).all? do |(digits)|
|
|
124
|
+
code = digits.start_with?("x") ? digits[1..].to_i(16) : digits.to_i
|
|
125
|
+
[ 0x9, 0xA, 0xD ].include?(code) || code.between?(0x20, 0xD7FF) || code.between?(0xE000, 0xFFFD) || code.between?(0x10000, 0x10FFFF)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.frontend?(headers)
|
|
130
|
+
headers.key?(FRONTEND_HEADER)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The earliest of the API's expires and the expiry the signed URL states: S3's X-Amz-Date plus X-Amz-Expires,
|
|
134
|
+
# the same X-Files- and X-Goog- parameters, or an Azure SAS's se. nil when neither says.
|
|
135
|
+
def self.expires_at(url, api_expires = nil)
|
|
136
|
+
times = [ api_time(api_expires) ]
|
|
137
|
+
# Names compare without case, and the last of the parameters with a name wins, in the order the URL gives them.
|
|
138
|
+
query = {}
|
|
139
|
+
::URI.decode_www_form(::URI.parse(url).query.to_s).each { |name, value| query[name.downcase] = value }
|
|
140
|
+
SIGNATURE_PARAMS.each do |date, lifetime|
|
|
141
|
+
signed = compact_time(query[date])
|
|
142
|
+
times << (signed + Integer(query[lifetime], 10)) if signed && query[lifetime].to_s.match?(/\A\d+\z/)
|
|
143
|
+
end
|
|
144
|
+
times << api_time(query["se"])
|
|
145
|
+
times.compact.min
|
|
146
|
+
rescue ArgumentError, ::URI::InvalidURIError
|
|
147
|
+
api_time(api_expires)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.expired?(expires_at, now: Time.now)
|
|
151
|
+
!expires_at.nil? && now >= expires_at - EXPIRY_MARGIN
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.compact_time(text)
|
|
155
|
+
match = text.to_s.match(/\A(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z\z/)
|
|
156
|
+
match && Time.utc(*match.captures.map(&:to_i))
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def self.api_time(value)
|
|
160
|
+
case value
|
|
161
|
+
when Time then value
|
|
162
|
+
when String then Time.iso8601(value)
|
|
163
|
+
end
|
|
164
|
+
rescue ArgumentError
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|