From f9bbfad3e561c52cd3a7a9002ed9708a87237dc5 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Thu, 17 Oct 2013 16:45:11 +0200 Subject: [PATCH 01/36] Fix sharing error message - id -> file name fixe #2827 --- lib/public/share.php | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/public/share.php b/lib/public/share.php index 1b6f5d05f1..814c02499f 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -439,22 +439,31 @@ class Share { public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions) { $uidOwner = \OC_User::getUser(); $sharingPolicy = \OC_Appconfig::getValue('core', 'shareapi_share_policy', 'global'); + + //retrieve name of file + $fileData = \OC\Files\Filesystem::getFileInfo(\OC\Files\Filesystem::getPath($itemSource)); + if(!is_null($fileData)) { + $itemSourceName = $fileData['name']; + } else { + $itemSourceName = $itemSource; + } + // Verify share type and sharing conditions are met if ($shareType === self::SHARE_TYPE_USER) { if ($shareWith == $uidOwner) { - $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' is the item owner'; + $message = 'Sharing '.$itemSourceName.' failed, because the user '.$shareWith.' is the item owner'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } if (!\OC_User::userExists($shareWith)) { - $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' does not exist'; + $message = 'Sharing '.$itemSourceName.' failed, because the user '.$shareWith.' does not exist'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } if ($sharingPolicy == 'groups_only') { $inGroup = array_intersect(\OC_Group::getUserGroups($uidOwner), \OC_Group::getUserGroups($shareWith)); if (empty($inGroup)) { - $message = 'Sharing '.$itemSource.' failed, because the user ' + $message = 'Sharing '.$itemSourceName.' failed, because the user ' .$shareWith.' is not a member of any groups that '.$uidOwner.' is a member of'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); @@ -467,19 +476,19 @@ class Share { // owner and is not a user share, this use case is for increasing // permissions for a specific user if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) { - $message = 'Sharing '.$itemSource.' failed, because this item is already shared with '.$shareWith; + $message = 'Sharing '.$itemSourceName.' failed, because this item is already shared with '.$shareWith; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } } } else if ($shareType === self::SHARE_TYPE_GROUP) { if (!\OC_Group::groupExists($shareWith)) { - $message = 'Sharing '.$itemSource.' failed, because the group '.$shareWith.' does not exist'; + $message = 'Sharing '.$itemSourceName.' failed, because the group '.$shareWith.' does not exist'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } if ($sharingPolicy == 'groups_only' && !\OC_Group::inGroup($uidOwner, $shareWith)) { - $message = 'Sharing '.$itemSource.' failed, because ' + $message = 'Sharing '.$itemSourceName.' failed, because ' .$uidOwner.' is not a member of the group '.$shareWith; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); @@ -492,7 +501,7 @@ class Share { // owner and is not a group share, this use case is for increasing // permissions for a specific user if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) { - $message = 'Sharing '.$itemSource.' failed, because this item is already shared with '.$shareWith; + $message = 'Sharing '.$itemSourceName.' failed, because this item is already shared with '.$shareWith; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } @@ -541,7 +550,7 @@ class Share { return false; } } - $message = 'Sharing '.$itemSource.' failed, because sharing with links is not allowed'; + $message = 'Sharing '.$itemSourceName.' failed, because sharing with links is not allowed'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); return false; @@ -1318,18 +1327,27 @@ class Share { private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $parentFolder = null, $token = null) { $backend = self::getBackend($itemType); + // Check if this is a reshare if ($checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true)) { + //retrieve name of file + $fileData = \OC\Files\Filesystem::getFileInfo(\OC\Files\Filesystem::getPath($itemSource)); + if(!is_null($fileData)) { + $itemSourceName = $fileData['name']; + } else { + $itemSourceName = $itemSource; + } + // Check if attempting to share back to owner if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) { - $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' is the original sharer'; + $message = 'Sharing '.$itemSourceName.' failed, because the user '.$shareWith.' is the original sharer'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } // Check if share permissions is granted if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & PERMISSION_SHARE) { if (~(int)$checkReshare['permissions'] & $permissions) { - $message = 'Sharing '.$itemSource + $message = 'Sharing '.$itemSourceName .' failed, because the permissions exceed permissions granted to '.$uidOwner; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); @@ -1343,7 +1361,7 @@ class Share { $filePath = $checkReshare['file_target']; } } else { - $message = 'Sharing '.$itemSource.' failed, because resharing is not allowed'; + $message = 'Sharing '.$itemSourceName.' failed, because resharing is not allowed'; \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR); throw new \Exception($message); } From 67779680a67f88c9ff4017ca91fa952dab0946b2 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Tue, 22 Oct 2013 13:22:41 +0200 Subject: [PATCH 02/36] added stripped library php-opencloud (version 1.6.0) --- .../3rdparty/php-opencloud/LICENSE | 16 + .../3rdparty/php-opencloud/lib/Autoload.php | 296 ++++ .../lib/OpenCloud/Common/Base.php | 301 +++++ .../lib/OpenCloud/Common/Collection.php | 320 +++++ .../Common/Exceptions/AsyncError.php | 5 + .../Common/Exceptions/AsyncHttpError.php | 5 + .../Common/Exceptions/AsyncTimeoutError.php | 5 + .../Common/Exceptions/AttributeError.php | 5 + .../Common/Exceptions/AuthenticationError.php | 5 + .../Common/Exceptions/BaseException.php | 7 + .../OpenCloud/Common/Exceptions/CdnError.php | 5 + .../Common/Exceptions/CdnHttpError.php | 5 + .../Exceptions/CdnNotAvailableError.php | 5 + .../Common/Exceptions/CdnTtlError.php | 5 + .../Common/Exceptions/CollectionError.php | 5 + .../Exceptions/ContainerCreateError.php | 5 + .../Exceptions/ContainerDeleteError.php | 5 + .../Common/Exceptions/ContainerError.php | 5 + .../Common/Exceptions/ContainerNameError.php | 5 + .../Exceptions/ContainerNotEmptyError.php | 5 + .../Exceptions/ContainerNotFoundError.php | 5 + .../Common/Exceptions/CreateError.php | 5 + .../Common/Exceptions/CreateUpdateError.php | 5 + .../Common/Exceptions/CredentialError.php | 5 + .../Common/Exceptions/DatabaseCreateError.php | 5 + .../Common/Exceptions/DatabaseDeleteError.php | 5 + .../Common/Exceptions/DatabaseListError.php | 5 + .../Common/Exceptions/DatabaseNameError.php | 5 + .../Common/Exceptions/DatabaseUpdateError.php | 5 + .../Common/Exceptions/DeleteError.php | 5 + .../Common/Exceptions/DocumentError.php | 5 + .../Common/Exceptions/DomainError.php | 5 + .../Common/Exceptions/EmptyResponseError.php | 5 + .../Common/Exceptions/EndpointError.php | 5 + .../Common/Exceptions/FlavorError.php | 5 + .../OpenCloud/Common/Exceptions/HttpError.php | 5 + .../Common/Exceptions/HttpForbiddenError.php | 5 + .../Common/Exceptions/HttpOverLimitError.php | 5 + .../Common/Exceptions/HttpRetryError.php | 5 + .../Common/Exceptions/HttpTimeoutError.php | 5 + .../Exceptions/HttpUnauthorizedError.php | 5 + .../Common/Exceptions/HttpUrlError.php | 5 + .../OpenCloud/Common/Exceptions/IOError.php | 5 + .../Common/Exceptions/IdRequiredError.php | 5 + .../Common/Exceptions/ImageError.php | 5 + .../Common/Exceptions/InstanceCreateError.php | 5 + .../Common/Exceptions/InstanceDeleteError.php | 5 + .../Common/Exceptions/InstanceError.php | 5 + .../Common/Exceptions/InstanceFlavorError.php | 5 + .../Common/Exceptions/InstanceNotFound.php | 5 + .../Common/Exceptions/InstanceUpdateError.php | 5 + .../Exceptions/InvalidArgumentError.php | 5 + .../Common/Exceptions/InvalidIdTypeError.php | 5 + .../Common/Exceptions/InvalidIpTypeError.php | 5 + .../Exceptions/InvalidParameterError.php | 5 + .../Common/Exceptions/InvalidRequestError.php | 5 + .../OpenCloud/Common/Exceptions/JsonError.php | 5 + .../Common/Exceptions/LoggingException.php | 16 + .../Common/Exceptions/MetadataCreateError.php | 5 + .../Common/Exceptions/MetadataDeleteError.php | 5 + .../Common/Exceptions/MetadataError.php | 5 + .../Common/Exceptions/MetadataJsonError.php | 5 + .../Common/Exceptions/MetadataKeyError.php | 5 + .../Common/Exceptions/MetadataPrefixError.php | 5 + .../Common/Exceptions/MetadataUpdateError.php | 5 + .../Exceptions/MisMatchedChecksumError.php | 5 + .../Common/Exceptions/MissingValueError.php | 5 + .../OpenCloud/Common/Exceptions/NameError.php | 5 + .../Common/Exceptions/NetworkCreateError.php | 5 + .../Common/Exceptions/NetworkDeleteError.php | 5 + .../Common/Exceptions/NetworkError.php | 5 + .../Common/Exceptions/NetworkUpdateError.php | 5 + .../Common/Exceptions/NetworkUrlError.php | 5 + .../Common/Exceptions/NoContentTypeError.php | 5 + .../Common/Exceptions/NoNameError.php | 5 + .../Common/Exceptions/ObjFetchError.php | 5 + .../Common/Exceptions/ObjectCopyError.php | 5 + .../Common/Exceptions/ObjectError.php | 5 + .../Common/Exceptions/RebuildError.php | 5 + .../Common/Exceptions/RecordTypeError.php | 5 + .../Common/Exceptions/ServerActionError.php | 5 + .../Common/Exceptions/ServerCreateError.php | 5 + .../Common/Exceptions/ServerDeleteError.php | 5 + .../Exceptions/ServerImageScheduleError.php | 5 + .../Common/Exceptions/ServerIpsError.php | 5 + .../Common/Exceptions/ServerJsonError.php | 5 + .../Common/Exceptions/ServerUpdateError.php | 5 + .../Common/Exceptions/ServerUrlError.php | 5 + .../Common/Exceptions/ServiceValueError.php | 5 + .../Common/Exceptions/SnapshotError.php | 5 + .../Common/Exceptions/TempUrlMethodError.php | 5 + .../Common/Exceptions/UnknownError.php | 5 + .../Exceptions/UnknownParameterError.php | 5 + .../Exceptions/UnrecognizedServiceError.php | 5 + .../Exceptions/UnsupportedExtensionError.php | 5 + .../UnsupportedFeatureExtension.php | 5 + .../Exceptions/UnsupportedVersionError.php | 5 + .../Common/Exceptions/UpdateError.php | 5 + .../OpenCloud/Common/Exceptions/UrlError.php | 5 + .../Common/Exceptions/UserCreateError.php | 5 + .../Common/Exceptions/UserDeleteError.php | 5 + .../Common/Exceptions/UserListError.php | 5 + .../Common/Exceptions/UserNameError.php | 5 + .../Common/Exceptions/UserUpdateError.php | 5 + .../Common/Exceptions/VolumeError.php | 5 + .../Common/Exceptions/VolumeTypeError.php | 5 + .../lib/OpenCloud/Common/Identity/Role.php | 21 + .../lib/OpenCloud/Common/Identity/Tenant.php | 22 + .../lib/OpenCloud/Common/Identity/User.php | 73 + .../lib/OpenCloud/Common/Lang.php | 21 + .../OpenCloud/Common/Log/AbstractLogger.php | 140 ++ .../lib/OpenCloud/Common/Log/LogLevel.php | 38 + .../lib/OpenCloud/Common/Log/Logger.php | 220 +++ .../OpenCloud/Common/Log/LoggerInterface.php | 134 ++ .../lib/OpenCloud/Common/Metadata.php | 92 ++ .../lib/OpenCloud/Common/Nova.php | 140 ++ .../lib/OpenCloud/Common/PersistentObject.php | 939 +++++++++++++ .../lib/OpenCloud/Common/Request/Curl.php | 308 +++++ .../Common/Request/HttpRequestInterface.php | 23 + .../Common/Request/Response/Blank.php | 27 + .../Common/Request/Response/Http.php | 140 ++ .../lib/OpenCloud/Common/Service.php | 489 +++++++ .../OpenCloud/Common/ServiceCatalogItem.php | 18 + .../php-opencloud/lib/OpenCloud/Globals.php | 252 ++++ .../OpenCloud/ObjectStore/AbstractService.php | 57 + .../lib/OpenCloud/ObjectStore/CDNService.php | 62 + .../Resource/AbstractStorageObject.php | 170 +++ .../ObjectStore/Resource/CDNContainer.php | 298 ++++ .../ObjectStore/Resource/Container.php | 401 ++++++ .../ObjectStore/Resource/DataObject.php | 941 +++++++++++++ .../lib/OpenCloud/ObjectStore/Service.php | 115 ++ .../php-opencloud/lib/OpenCloud/OpenStack.php | 1198 +++++++++++++++++ .../php-opencloud/lib/OpenCloud/Rackspace.php | 132 ++ .../3rdparty/php-opencloud/lib/openstack.php | 8 + .../php-opencloud/lib/php-opencloud.php | 15 + .../3rdparty/php-opencloud/lib/rackspace.php | 8 + 136 files changed, 7958 insertions(+) create mode 100644 apps/files_external/3rdparty/php-opencloud/LICENSE create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/Autoload.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Base.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Collection.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncHttpError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncTimeoutError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AttributeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AuthenticationError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/BaseException.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CdnError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CdnHttpError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CdnNotAvailableError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CdnTtlError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CollectionError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerNameError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerNotEmptyError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ContainerNotFoundError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CreateUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/CredentialError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DatabaseCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DatabaseDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DatabaseListError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DatabaseNameError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DatabaseUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DocumentError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/DomainError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/EmptyResponseError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/EndpointError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/FlavorError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpForbiddenError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpOverLimitError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpRetryError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpTimeoutError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpUnauthorizedError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/HttpUrlError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/IOError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/IdRequiredError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ImageError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceFlavorError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceNotFound.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InstanceUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InvalidArgumentError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InvalidIdTypeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InvalidIpTypeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InvalidParameterError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/InvalidRequestError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/JsonError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/LoggingException.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataJsonError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataKeyError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataPrefixError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MisMatchedChecksumError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MissingValueError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NameError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NetworkCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NetworkDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NetworkError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NetworkUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NetworkUrlError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NoContentTypeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/NoNameError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ObjFetchError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ObjectCopyError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ObjectError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/RebuildError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/RecordTypeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerActionError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerImageScheduleError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerIpsError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerJsonError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServerUrlError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/ServiceValueError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/SnapshotError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/TempUrlMethodError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnknownError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnknownParameterError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnrecognizedServiceError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnsupportedExtensionError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnsupportedFeatureExtension.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UnsupportedVersionError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UrlError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UserCreateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UserDeleteError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UserListError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UserNameError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/UserUpdateError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/VolumeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/VolumeTypeError.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/Role.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/Tenant.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/User.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Lang.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/AbstractLogger.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LogLevel.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/Logger.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LoggerInterface.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Metadata.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Nova.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/PersistentObject.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Curl.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/HttpRequestInterface.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Response/Blank.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Response/Http.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/ServiceCatalogItem.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Globals.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/AbstractService.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/CDNService.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/AbstractStorageObject.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/CDNContainer.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/Container.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/DataObject.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Service.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/OpenStack.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Rackspace.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/openstack.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/php-opencloud.php create mode 100644 apps/files_external/3rdparty/php-opencloud/lib/rackspace.php diff --git a/apps/files_external/3rdparty/php-opencloud/LICENSE b/apps/files_external/3rdparty/php-opencloud/LICENSE new file mode 100644 index 0000000000..f7c56967e6 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/LICENSE @@ -0,0 +1,16 @@ + Copyright 2012-2013 Rackspace US, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + All contributions to this repository are covered under the same license, + terms, and conditions. \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/Autoload.php b/apps/files_external/3rdparty/php-opencloud/lib/Autoload.php new file mode 100644 index 0000000000..32e9dc24b7 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/Autoload.php @@ -0,0 +1,296 @@ +useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return Boolean + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Gets the configured namespaces. + * + * @return array A hash with namespaces as keys and directories as values + */ + public function getNamespaces() + { + return $this->namespaces; + } + + /** + * Gets the configured class prefixes. + * + * @return array A hash with class prefixes as keys and directories as values + */ + public function getPrefixes() + { + return $this->prefixes; + } + + /** + * Gets the directory(ies) to use as a fallback for namespaces. + * + * @return array An array of directories + */ + public function getNamespaceFallbacks() + { + return $this->namespaceFallbacks; + } + + /** + * Gets the directory(ies) to use as a fallback for class prefixes. + * + * @return array An array of directories + */ + public function getPrefixFallbacks() + { + return $this->prefixFallbacks; + } + + /** + * Registers the directory to use as a fallback for namespaces. + * + * @param array $dirs An array of directories + * + * @api + */ + public function registerNamespaceFallbacks(array $dirs) + { + $this->namespaceFallbacks = $dirs; + } + + /** + * Registers a directory to use as a fallback for namespaces. + * + * @param string $dir A directory + */ + public function registerNamespaceFallback($dir) + { + $this->namespaceFallbacks[] = $dir; + } + + /** + * Registers directories to use as a fallback for class prefixes. + * + * @param array $dirs An array of directories + * + * @api + */ + public function registerPrefixFallbacks(array $dirs) + { + $this->prefixFallbacks = $dirs; + } + + /** + * Registers a directory to use as a fallback for class prefixes. + * + * @param string $dir A directory + */ + public function registerPrefixFallback($dir) + { + $this->prefixFallbacks[] = $dir; + } + + /** + * Registers an array of namespaces + * + * @param array $namespaces An array of namespaces (namespaces as keys and locations as values) + * + * @api + */ + public function registerNamespaces(array $namespaces) + { + foreach ($namespaces as $namespace => $locations) { + $this->namespaces[$namespace] = (array) $locations; + } + } + + /** + * Registers a namespace. + * + * @param string $namespace The namespace + * @param array|string $paths The location(s) of the namespace + * + * @api + */ + public function registerNamespace($namespace, $paths) + { + $this->namespaces[$namespace] = (array) $paths; + } + + /** + * Registers an array of classes using the PEAR naming convention. + * + * @param array $classes An array of classes (prefixes as keys and locations as values) + * + * @api + */ + public function registerPrefixes(array $classes) + { + foreach ($classes as $prefix => $locations) { + $this->prefixes[$prefix] = (array) $locations; + } + } + + /** + * Registers a set of classes using the PEAR naming convention. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + * + * @api + */ + public function registerPrefix($prefix, $paths) + { + $this->prefixes[$prefix] = (array) $paths; + } + + /** + * Registers this instance as an autoloader. + * + * @param Boolean $prepend Whether to prepend the autoloader or not + * + * @api + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Fix for certain versions of PHP that have trouble with + * namespaces with leading separators. + * + * @access private + * @param mixed $className + * @return void + */ + private function makeBackwardsCompatible($className) + { + return (phpversion() < '5.3.3') ? ltrim($className, '\\') : $className; + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return Boolean|null True, if loaded + */ + public function loadClass($class) + { + $class = $this->makeBackwardsCompatible($class); + + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $namespace = substr($class, 0, $pos); + $className = substr($class, $pos + 1); + $normalizedClass = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; + foreach ($this->namespaces as $ns => $dirs) { + if (0 !== strpos($namespace, $ns)) { + continue; + } + + foreach ($dirs as $dir) { + $file = $dir.DIRECTORY_SEPARATOR.$normalizedClass; + if (is_file($file)) { + return $file; + } + } + } + + foreach ($this->namespaceFallbacks as $dir) { + $file = $dir.DIRECTORY_SEPARATOR.$normalizedClass; + if (is_file($file)) { + return $file; + } + } + + } else { + // PEAR-like class name + $normalizedClass = str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; + foreach ($this->prefixes as $prefix => $dirs) { + if (0 !== strpos($class, $prefix)) { + continue; + } + + foreach ($dirs as $dir) { + $file = $dir.DIRECTORY_SEPARATOR.$normalizedClass; + if (is_file($file)) { + return $file; + } + } + } + + foreach ($this->prefixFallbacks as $dir) { + $file = $dir.DIRECTORY_SEPARATOR.$normalizedClass; + if (is_file($file)) { + return $file; + } + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($normalizedClass)) { + return $file; + } + } +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Base.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Base.php new file mode 100644 index 0000000000..f80c9320e2 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Base.php @@ -0,0 +1,301 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\Common; + +use OpenCloud\Common\Lang; +use OpenCloud\Common\Exceptions\AttributeError; +use OpenCloud\Common\Exceptions\JsonError; +use OpenCloud\Common\Exceptions\UrlError; + +/** + * The root class for all other objects used or defined by this SDK. + * + * It contains common code for error handling as well as service functions that + * are useful. Because it is an abstract class, it cannot be called directly, + * and it has no publicly-visible properties. + */ +abstract class Base +{ + + private $http_headers = array(); + private $_errors = array(); + + /** + * Debug status. + * + * @var LoggerInterface + * @access private + */ + private $logger; + + /** + * Sets the Logger object. + * + * @param \OpenCloud\Common\Log\LoggerInterface $logger + */ + public function setLogger(Log\LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Returns the Logger object. + * + * @return \OpenCloud\Common\Log\AbstractLogger + */ + public function getLogger() + { + if (null === $this->logger) { + $this->setLogger(new Log\Logger); + } + return $this->logger; + } + + /** + * Returns the URL of the service/object + * + * The assumption is that nearly all objects will have a URL; at this + * base level, it simply throws an exception to enforce the idea that + * subclasses need to define this method. + * + * @throws UrlError + */ + public function url($subresource = '') + { + throw new UrlError(Lang::translate( + 'URL method must be overridden in class definition' + )); + } + +/** + * Populates the current object based on an unknown data type. + * + * @param array|object|string|integer $info + * @throws Exceptions\InvalidArgumentError + */ + public function populate($info, $setObjects = true) + { + if (is_string($info) || is_integer($info)) { + + // If the data type represents an ID, the primary key is set + // and we retrieve the full resource from the API + $this->{$this->primaryKeyField()} = (string) $info; + $this->refresh($info); + + } elseif (is_object($info) || is_array($info)) { + + foreach($info as $key => $value) { + + if ($key == 'metadata' || $key == 'meta') { + + if (empty($this->metadata) || !$this->metadata instanceof Metadata) { + $this->metadata = new Metadata; + } + + // Metadata + $this->$key->setArray($value); + + } elseif (!empty($this->associatedResources[$key]) && $setObjects === true) { + + // Associated resource + try { + $resource = $this->service()->resource($this->associatedResources[$key], $value); + $resource->setParent($this); + $this->$key = $resource; + } catch (Exception\ServiceException $e) {} + + } elseif (!empty($this->associatedCollections[$key]) && $setObjects === true) { + + // Associated collection + try { + $this->$key = $this->service()->resourceList($this->associatedCollections[$key], null, $this); + } catch (Exception\ServiceException $e) {} + + } else { + + // Normal key/value pair + $this->$key = $value; + } + } + } elseif (null !== $info) { + throw new Exceptions\InvalidArgumentError(sprintf( + Lang::translate('Argument for [%s] must be string or object'), + get_class() + )); + } + } + + /** + * Sets extended attributes on an object and validates them + * + * This function is provided to ensure that attributes cannot + * arbitrarily added to an object. If this function is called, it + * means that the attribute is not defined on the object, and thus + * an exception is thrown. + * + * @codeCoverageIgnore + * + * @param string $property the name of the attribute + * @param mixed $value the value of the attribute + * @return void + */ + public function __set($property, $value) + { + $this->setProperty($property, $value); + } + + /** + * Sets an extended (unrecognized) property on the current object + * + * If RAXSDK_STRICT_PROPERTY_CHECKS is TRUE, then the prefix of the + * property name must appear in the $prefixes array, or else an + * exception is thrown. + * + * @param string $property the property name + * @param mixed $value the value of the property + * @param array $prefixes optional list of supported prefixes + * @throws \OpenCloud\AttributeError if strict checks are on and + * the property prefix is not in the list of prefixes. + */ + public function setProperty($property, $value, array $prefixes = array()) + { + // if strict checks are off, go ahead and set it + if (!RAXSDK_STRICT_PROPERTY_CHECKS + || $this->checkAttributePrefix($property, $prefixes) + ) { + $this->$property = $value; + } else { + // if that fails, then throw the exception + throw new AttributeError(sprintf( + Lang::translate('Unrecognized attribute [%s] for [%s]'), + $property, + get_class($this) + )); + } + } + + /** + * Converts an array of key/value pairs into a single query string + * + * For example, array('A'=>1,'B'=>2) would become 'A=1&B=2'. + * + * @param array $arr array of key/value pairs + * @return string + */ + public function makeQueryString($array) + { + $queryString = ''; + + foreach($array as $key => $value) { + if ($queryString) { + $queryString .= '&'; + } + $queryString .= urlencode($key) . '=' . urlencode($this->to_string($value)); + } + + return $queryString; + } + + /** + * Checks the most recent JSON operation for errors + * + * This function should be called after any `json_*()` function call. + * This ensures that nasty JSON errors are detected and the proper + * exception thrown. + * + * Example: + * `$obj = json_decode($string);` + * `if (check_json_error()) do something ...` + * + * @return boolean TRUE if an error occurred, FALSE if none + * @throws JsonError + * + * @codeCoverageIgnore + */ + public function checkJsonError() + { + switch (json_last_error()) { + case JSON_ERROR_NONE: + return; + case JSON_ERROR_DEPTH: + $jsonError = 'JSON error: The maximum stack depth has been exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $jsonError = 'JSON error: Invalid or malformed JSON'; + break; + case JSON_ERROR_CTRL_CHAR: + $jsonError = 'JSON error: Control character error, possibly incorrectly encoded'; + break; + case JSON_ERROR_SYNTAX: + $jsonError = 'JSON error: Syntax error'; + break; + case JSON_ERROR_UTF8: + $jsonError = 'JSON error: Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $jsonError = 'Unexpected JSON error'; + break; + } + + if (isset($jsonError)) { + throw new JsonError(Lang::translate($jsonError)); + } + } + + /** + * Returns a class that implements the HttpRequest interface. + * + * This can be stubbed out for unit testing and avoid making live calls. + */ + public function getHttpRequestObject($url, $method = 'GET', array $options = array()) + { + return new Request\Curl($url, $method, $options); + } + + /** + * Checks the attribute $property and only permits it if the prefix is + * in the specified $prefixes array + * + * This is to support extension namespaces in some services. + * + * @param string $property the name of the attribute + * @param array $prefixes a list of prefixes + * @return boolean TRUE if valid; FALSE if not + */ + private function checkAttributePrefix($property, array $prefixes = array()) + { + $prefix = strstr($property, ':', true); + + if (in_array($prefix, $prefixes)) { + return true; + } else { + return false; + } + } + + /** + * Converts a value to an HTTP-displayable string form + * + * @param mixed $x a value to convert + * @return string + */ + private function to_string($x) + { + if (is_bool($x) && $x) { + return 'True'; + } elseif (is_bool($x)) { + return 'False'; + } else { + return (string) $x; + } + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Collection.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Collection.php new file mode 100644 index 0000000000..e1bf80376e --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Collection.php @@ -0,0 +1,320 @@ + + * @author Jamie Hannaford + */ +class Collection extends Base +{ + + private $service; + private $itemclass; + private $itemlist = array(); + private $pointer = 0; + private $sortkey; + private $next_page_class; + private $next_page_callback; + private $next_page_url; + + /** + * A Collection is an array of objects + * + * Some assumptions: + * * The `Collection` class assumes that there exists on its service + * a factory method with the same name of the class. For example, if + * you create a Collection of class `Foobar`, it will attempt to call + * the method `parent::Foobar()` to create instances of that class. + * * It assumes that the factory method can take an array of values, and + * it passes that to the method. + * + * @param Service $service - the service associated with the collection + * @param string $itemclass - the Class of each item in the collection + * (assumed to be the name of the factory method) + * @param array $arr - the input array + */ + public function __construct($service, $itemclass, $array) + { + $this->service = $service; + + $this->getLogger()->info( + 'Collection:service={class}, class={itemClass}, array={array}', + array( + 'class' => get_class($service), + 'itemClass' => $itemclass, + 'array' => print_r($array, true) + ) + ); + + $this->next_page_class = $itemclass; + + if (false !== ($classNamePos = strrpos($itemclass, '\\'))) { + $this->itemclass = substr($itemclass, $classNamePos + 1); + } else { + $this->itemclass = $itemclass; + } + + if (!is_array($array)) { + throw new Exceptions\CollectionError( + Lang::translate('Cannot create a Collection without an array') + ); + } + + // save the array of items + $this->setItemList($array); + } + + /** + * Set the entire data array. + * + * @param array $array + */ + public function setItemList(array $array) + { + $this->itemlist = $array; + } + + /** + * Retrieve the entire data array. + * + * @return array + */ + public function getItemList() + { + return $this->itemlist; + } + + /** + * Returns the number of items in the collection + * + * For most services, this is the total number of items. If the Collection + * is paginated, however, this only returns the count of items in the + * current page of data. + * + * @return int + */ + public function count() + { + return count($this->itemlist); + } + + /** + * Pseudonym for count() + * + * @codeCoverageIgnore + */ + public function size() + { + return $this->count(); + } + + /** + * Retrieves the service associated with the Collection + * + * @return Service + */ + public function service() + { + return $this->service; + } + + /** + * Resets the pointer to the beginning, but does NOT return the first item + * + * @api + * @return void + */ + public function reset() + { + $this->pointer = 0; + } + + /** + * Resets the collection pointer back to the first item in the page + * and returns it + * + * This is useful if you're only interested in the first item in the page. + * + * @api + * @return Base the first item in the set + */ + public function first() + { + $this->reset(); + return $this->next(); + } + + /** + * Returns the next item in the page + * + * @api + * @return Base the next item or FALSE if at the end of the page + */ + public function next() + { + if ($this->pointer >= $this->count()) { + return false; + } + + $service = $this->service(); + + if (method_exists($service, $this->itemclass)) { + return $service->{$this->itemclass}($this->itemlist[$this->pointer++]); + } elseif (method_exists($service, 'resource')) { + return $service->resource($this->itemclass, $this->itemlist[$this->pointer++]); + } + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + /** + * sorts the collection on a specified key + * + * Note: only top-level keys can be used as the sort key. Note that this + * only sorts the data in the current page of the Collection (for + * multi-page data). + * + * @api + * @param string $keyname the name of the field to use as the sort key + * @return void + */ + public function sort($keyname = 'id') + { + $this->sortkey = $keyname; + usort($this->itemlist, array($this, 'sortCompare')); + } + + /** + * selects only specified items from the Collection + * + * This provides a simple form of filtering on Collections. For each item + * in the collection, it calls the callback function, passing it the item. + * If the callback returns `TRUE`, then the item is retained; if it returns + * `FALSE`, then the item is deleted from the collection. + * + * Note that this should not supersede server-side filtering; the + * `Collection::Select()` method requires that *all* of the data for the + * Collection be retrieved from the server before the filtering is + * performed; this can be very inefficient, especially for large data + * sets. This method is mostly useful on smaller-sized sets. + * + * Example: + * + * $services = $connection->ServiceList(); + * $services->Select(function($item){ return $item->region=='ORD';}); + * // now the $services Collection only has items from the ORD region + * + * + * `Select()` is *destructive*; that is, it actually removes entries from + * the collection. For example, if you use `Select()` to find items with + * the ID > 10, then use it again to find items that are <= 10, it will + * return an empty list. + * + * @api + * @param callable $testfunc a callback function that is passed each item + * in turn. Note that `Select()` performs an explicit test for + * `FALSE`, so functions like `strpos()` need to be cast into a + * boolean value (and not just return the integer). + * @returns void + * @throws DomainError if callback doesn't return a boolean value + */ + public function select($testfunc) + { + foreach ($this->getItemList() as $index => $item) { + $test = call_user_func($testfunc, $item); + if (!is_bool($test)) { + throw new Exceptions\DomainError( + Lang::translate('Callback function for Collection::Select() did not return boolean') + ); + } + if ($test === false) { + unset($this->itemlist[$index]); + } + } + } + + /** + * returns the Collection object for the next page of results, or + * FALSE if there are no more pages + * + * Generally, the structure for a multi-page collection will look like + * this: + * + * $coll = $obj->Collection(); + * do { + * while($item = $coll->Next()) { + * // do something with the item + * } + * } while ($coll = $coll->NextPage()); + * + * @api + * @return Collection if there are more pages of results, otherwise FALSE + */ + public function nextPage() + { + if (isset($this->next_page_url)) { + return call_user_func( + $this->next_page_callback, + $this->next_page_class, + $this->next_page_url + ); + } + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + /** + * for paginated collection, sets the callback function and URL for + * the next page + * + * The callback function should have the signature: + * + * function Whatever($class, $url, $parent) + * + * and the `$url` should be the URL of the next page of results + * + * @param callable $callback the name of the function (or array of + * object, function name) + * @param string $url the URL of the next page of results + * @return void + */ + public function setNextPageCallback($callback, $url) + { + $this->next_page_callback = $callback; + $this->next_page_url = $url; + } + + /** + * Compares two values of sort keys + */ + private function sortCompare($a, $b) + { + $key = $this->sortkey; + + // handle strings with strcmp() + if (is_string($a->$key)) { + return strcmp($a->$key, $b->$key); + } + + // handle others with logical comparisons + if ($a->$key == $b->$key) { + return 0; + } + + if ($a->$key < $b->$key) { + return -1; + } else { + return 1; + } + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncError.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncError.php new file mode 100644 index 0000000000..cbbacff38b --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/AsyncError.php @@ -0,0 +1,5 @@ + + */ + +namespace OpenCloud\Common\Exceptions; + +use Exception; + +class LoggingException extends Exception +{ +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataCreateError.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataCreateError.php new file mode 100644 index 0000000000..a119397392 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Exceptions/MetadataCreateError.php @@ -0,0 +1,5 @@ + + * @version 2.0.0 + * @copyright Copyright 2012-2013 Rackspace US, Inc. + * @license https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0 + */ + +/** + * Description of Role + * + * @link + * + * @codeCoverageIgnore + */ +class Role +{ +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/Tenant.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/Tenant.php new file mode 100644 index 0000000000..62783613c2 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/Tenant.php @@ -0,0 +1,22 @@ + + * @version 2.0.0 + * @copyright Copyright 2012-2013 Rackspace US, Inc. + * @license https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0 + */ + +/** + * Description of Tenant + * + * @link + * + * @codeCoverageIgnore + */ +class Tenant +{ + +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/User.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/User.php new file mode 100644 index 0000000000..9e3862d175 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Identity/User.php @@ -0,0 +1,73 @@ + + * @author Jamie Hannaford + */ + +/** + * Represents a sub-user in Keystone. + * + * @link http://docs.rackspace.com/auth/api/v2.0/auth-client-devguide/content/User_Calls.html + * + * @codeCoverageIgnore + */ +class User extends PersistentObject +{ + + public static function factory($info) + { + $user = new self; + } + + /** + * Return detailed information about a specific user, by either user name or user ID. + * @param int|string $info + */ + public function get($info) + { + if (is_integer($info)) { + + } elseif (is_string($info)) { + + } else { + throw new Exception\IdentityException(sprintf( + 'A string-based username or an integer-based user ID is valid' + )); + } + } + + public function create() + { + + } + + public function update() + { + + } + + public function delete() + { + + } + + public function listAllCredentials() + { + + } + + public function getCredentials() + { + + } + + public function resetApiKey() + { + + } + +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Lang.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Lang.php new file mode 100644 index 0000000000..7bb1285973 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Lang.php @@ -0,0 +1,21 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LogLevel.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LogLevel.php new file mode 100644 index 0000000000..64b0169b50 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LogLevel.php @@ -0,0 +1,38 @@ + + */ + +namespace OpenCloud\Common\Log; + +use OpenCloud\Common\Exceptions\LoggingException; + +/** + * Basic logger for OpenCloud which extends FIG's PSR-3 standard logger. + * + * @link https://github.com/php-fig/log + */ +class Logger extends AbstractLogger +{ + /** + * Is this debug class enabled or not? + * + * @var bool + */ + private $enabled = false; + + /** + * These are the levels which will always be outputted - regardless of + * user-imposed settings. + * + * @var array + */ + private $urgentLevels = array( + LogLevel::EMERGENCY, + LogLevel::ALERT, + LogLevel::CRITICAL + ); + + /** + * Logging options. + * + * @var array + */ + private $options = array( + 'outputToFile' => false, + 'logFile' => null, + 'dateFormat' => 'd/m/y H:I', + 'delimeter' => ' - ' + ); + + /** + * Determines whether a log level needs to be outputted. + * + * @param string $logLevel + * @return bool + */ + private function outputIsUrgent($logLevel) + { + return in_array($logLevel, $this->urgentLevels); + } + + /** + * Interpolates context values into the message placeholders. + * + * @param string $message + * @param array $context + * @return type + */ + private function interpolate($message, array $context = array()) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } + + /** + * Enable or disable the debug class. + * + * @param bool $enabled + * @return self + */ + public function setEnabled($enabled) + { + $this->enabled = $enabled; + return $this; + } + + /** + * Is the debug class enabled? + * + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } + + /** + * Set an array of options. + * + * @param array $options + */ + public function setOptions(array $options = array()) + { + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + /** + * Get all options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set an individual option. + * + * @param string $key + * @param string $value + */ + public function setOption($key, $value) + { + if ($this->optionExists($key)) { + $this->options[$key] = $value; + } + } + + /** + * Get an individual option. + * + * @param string $key + * @return string|null + */ + public function getOption($key) + { + if ($this->optionExists($key)) { + return $this->options[$key]; + } + } + + /** + * Check whether an individual option exists. + * + * @param string $key + * @return bool + */ + private function optionExists($key) + { + return array_key_exists($key, $this->getOptions()); + } + + /** + * Outputs a log message if necessary. + * + * @param string $logLevel + * @param string $message + * @param string $context + */ + public function log($level, $message, array $context = array()) + { + if ($this->outputIsUrgent($level) + || $this->getEnabled() === true + || RAXSDK_DEBUG === true + ) { + $this->dispatch($message, $context); + } + } + + /** + * Used to format the line outputted in the log file. + * + * @param string $string + * @return string + */ + private function formatFileLine($string) + { + $format = $this->getOption('dateFormat') . $this->getOption('delimeter'); + return date($format) . $string; + } + + /** + * Dispatch a log output message. + * + * @param string $message + * @param array $context + * @throws LoggingException + */ + private function dispatch($message, $context) + { + $output = $this->interpolate($message, $context) . PHP_EOL; + + if ($this->getOption('outputToFile') === true) { + $file = $this->getOption('logFile'); + + if (!is_writable($file)) { + throw new LoggingException( + 'The log file either does not exist or is not writeable' + ); + } + + // Output to file + file_put_contents($file, $this->formatFileLine($output)); + } else { + + echo $output; + } + } + +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LoggerInterface.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LoggerInterface.php new file mode 100644 index 0000000000..daef1b04da --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Log/LoggerInterface.php @@ -0,0 +1,134 @@ + + */ + +namespace OpenCloud\Common; + +/** + * The Metadata class represents either Server or Image metadata + * + * @api + * @author Glen Campbell + */ +class Metadata extends Base +{ + + // array holding the names of keys that were set + private $_keylist = array(); + + /** + * This setter overrides the base one, since the metadata key can be + * anything + * + * @param string $key + * @param string $value + * @return void + */ + public function __set($key, $value) + { + // set the value and track the keys + if (!in_array($key, $this->_keylist)) { + $this->_keylist[] = $key; + } + + $this->$key = $value; + } + + /** + * Returns the list of keys defined + * + * @return array + */ + public function Keylist() + { + return $this->_keylist; + } + + /** + * Sets metadata values from an array, with optional prefix + * + * If $prefix is provided, then only array keys that match the prefix + * are set as metadata values, and $prefix is stripped from the key name. + * + * @param array $values an array of key/value pairs to set + * @param string $prefix if provided, a prefix that is used to identify + * metadata values. For example, you can set values from headers + * for a Container by using $prefix='X-Container-Meta-'. + * @return void + */ + public function setArray($values, $prefix = null) + { + if (empty($values)) { + return false; + } + + foreach ($values as $key => $value) { + if ($prefix) { + if (strpos($key, $prefix) === 0) { + $name = substr($key, strlen($prefix)); + $this->getLogger()->info( + Lang::translate('Setting [{name}] to [{value}]'), + array( + 'name' => $name, + 'value' => $value + ) + ); + $this->$name = $value; + } + } else { + $this->$key = $value; + } + } + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Nova.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Nova.php new file mode 100644 index 0000000000..fe4dcccc73 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Nova.php @@ -0,0 +1,140 @@ + + */ + +namespace OpenCloud\Common; + +use OpenCloud\OpenStack; +use OpenCloud\Common\Lang; +use OpenCloud\Compute\Flavor; + +/** + * Nova is an abstraction layer for the OpenStack compute service. + * + * Nova is used as a basis for several products, including Compute services + * as well as Rackspace's Cloud Databases. This class is, in essence, a vehicle + * for sharing common code between those other classes. + */ +abstract class Nova extends Service +{ + + private $_url; + + /** + * Called when creating a new Compute service object + * + * _NOTE_ that the order of parameters for this is *different* from the + * parent Service class. This is because the earlier parameters are the + * ones that most typically change, whereas the later ones are not + * modified as often. + * + * @param \OpenCloud\Identity $conn - a connection object + * @param string $serviceRegion - identifies the region of this Compute + * service + * @param string $urltype - identifies the URL type ("publicURL", + * "privateURL") + * @param string $serviceName - identifies the name of the service in the + * catalog + */ + public function __construct( + OpenStack $conn, + $serviceType, + $serviceName, + $serviceRegion, + $urltype + ) { + parent::__construct( + $conn, + $serviceType, + $serviceName, + $serviceRegion, + $urltype + ); + + $this->_url = Lang::noslash(parent::Url()); + + $this->getLogger()->info(Lang::translate('Initializing Nova...')); + } + + /** + * Returns a flavor from the service + * + * This is a factory method and should generally be called instead of + * creating a Flavor object directly. + * + * @api + * @param string $id - if supplied, the Flavor identified by this is + * retrieved + * @return Compute\Flavor object + */ + public function Flavor($id = null) + { + return new Flavor($this, $id); + } + + /** + * Returns a list of Flavor objects + * + * This is a factory method and should generally be called instead of + * creating a FlavorList object directly. + * + * @api + * @param boolean $details - if TRUE (the default), returns full details. + * Set to FALSE to retrieve minimal details and possibly improve + * performance. + * @param array $filter - optional key/value pairs for creating query + * strings + * @return Collection (or FALSE on an error) + */ + public function FlavorList($details = true, array $filter = array()) + { + if ($details) { + $url = $this->Url(Flavor::ResourceName().'/detail', $filter); + } else { + $url = $this->Url(Flavor::ResourceName(), $filter); + } + return $this->Collection('\OpenCloud\Compute\Flavor', $url); + } + + /** + * Gets a request from an HTTP source and ensures that the + * content type is always "application/json" + * + * This is a simple subclass of the parent::Request() method that ensures + * that all Compute requests use application/json as the Content-Type: + * + * @param string $url - the URL of the request + * @param string $method - the HTTP method ("GET" by default) + * @param array $headers - an associative array of headers to pass to + * the request + * @param string $body - optional body for POST or PUT requests + * @return \Rackspace\HttpResult object + */ + public function Request($url, $method = 'GET', array $headers = array(), $body = null) + { + $headers['Content-Type'] = RAXSDK_CONTENT_TYPE_JSON; + return parent::Request($url, $method, $headers, $body); + } + + /** + * Loads the available namespaces from the /extensions resource + */ + protected function load_namespaces() + { + $ext = $this->Extensions(); + foreach($ext as $obj) { + $this->_namespaces[] = $obj->alias; + } + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/PersistentObject.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/PersistentObject.php new file mode 100644 index 0000000000..0257526d70 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/PersistentObject.php @@ -0,0 +1,939 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\Common; + +/** + * Represents an object that can be retrieved, created, updated and deleted. + * + * This class abstracts much of the common functionality between: + * + * * Nova servers; + * * Swift containers and objects; + * * DBAAS instances; + * * Cinder volumes; + * * and various other objects that: + * * have a URL; + * * can be created, updated, deleted, or retrieved; + * * use a standard JSON format with a top-level element followed by + * a child object with attributes. + * + * In general, you can create a persistent object class by subclassing this + * class and defining some protected, static variables: + * + * * $url_resource - the sub-resource value in the URL of the parent. For + * example, if the parent URL is `http://something/parent`, then setting this + * value to "another" would result in a URL for the persistent object of + * `http://something/parent/another`. + * + * * $json_name - the top-level JSON object name. For example, if the + * persistent object is represented by `{"foo": {"attr":value, ...}}`, then + * set $json_name to "foo". + * + * * $json_collection_name - optional; this value is the name of a collection + * of the persistent objects. If not provided, it defaults to `json_name` + * with an appended "s" (e.g., if `json_name` is "foo", then + * `json_collection_name` would be "foos"). Set this value if the collection + * name doesn't follow this pattern. + * + * * $json_collection_element - the common pattern for a collection is: + * `{"collection": [{"attr":"value",...}, {"attr":"value",...}, ...]}` + * That is, each element of the array is a \stdClass object containing the + * object's attributes. In rare instances, the objects in the array + * are named, and `json_collection_element` contains the name of the + * collection objects. For example, in this JSON response: + * `{"allowedDomain":[{"allowedDomain":{"name":"foo"}}]}`, + * `json_collection_element` would be set to "allowedDomain". + * + * The PersistentObject class supports the standard CRUD methods; if these are + * not needed (i.e. not supported by the service), the subclass should redefine + * these to call the `noCreate`, `noUpdate`, or `noDelete` methods, which will + * trigger an appropriate exception. For example, if an object cannot be created: + * + * function create($params = array()) + * { + * $this->noCreate(); + * } + */ +abstract class PersistentObject extends Base +{ + + private $service; + + private $parent; + + protected $id; + + /** + * Retrieves the instance from persistent storage + * + * @param mixed $service The service object for this resource + * @param mixed $info The ID or array/object of data + */ + public function __construct($service = null, $info = null) + { + if ($service instanceof Service) { + $this->setService($service); + } + + if (property_exists($this, 'metadata')) { + $this->metadata = new Metadata; + } + + $this->populate($info); + } + + /** + * Validates properties that have a namespace: prefix + * + * If the property prefix: appears in the list of supported extension + * namespaces, then the property is applied to the object. Otherwise, + * an exception is thrown. + * + * @param string $name the name of the property + * @param mixed $value the property's value + * @return void + * @throws AttributeError + */ + public function __set($name, $value) + { + $this->setProperty($name, $value, $this->getService()->namespaces()); + } + + /** + * Sets the service associated with this resource object. + * + * @param \OpenCloud\Common\Service $service + */ + public function setService(Service $service) + { + $this->service = $service; + return $this; + } + + /** + * Returns the service object for this resource; required for making + * requests, etc. because it has direct access to the Connection. + * + * @return \OpenCloud\Common\Service + */ + public function getService() + { + if (null === $this->service) { + throw new Exceptions\ServiceValueError( + 'No service defined' + ); + } + return $this->service; + } + + /** + * Legacy shortcut to getService + * + * @return \OpenCloud\Common\Service + */ + public function service() + { + return $this->getService(); + } + + /** + * Set the parent object for this resource. + * + * @param \OpenCloud\Common\PersistentObject $parent + */ + public function setParent(PersistentObject $parent) + { + $this->parent = $parent; + return $this; + } + + /** + * Returns the parent. + * + * @return \OpenCloud\Common\PersistentObject + */ + public function getParent() + { + if (null === $this->parent) { + $this->parent = $this->getService(); + } + return $this->parent; + } + + /** + * Legacy shortcut to getParent + * + * @return \OpenCloud\Common\PersistentObject + */ + public function parent() + { + return $this->getParent(); + } + + + + + /** + * API OPERATIONS (CRUD & CUSTOM) + */ + + /** + * Creates a new object + * + * @api + * @param array $params array of values to set when creating the object + * @return HttpResponse + * @throws VolumeCreateError if HTTP status is not Success + */ + public function create($params = array()) + { + // set parameters + if (!empty($params)) { + $this->populate($params, false); + } + + // debug + $this->getLogger()->info('{class}::Create({name})', array( + 'class' => get_class($this), + 'name' => $this->Name() + )); + + // construct the JSON + $object = $this->createJson(); + $json = json_encode($object); + $this->checkJsonError(); + + $this->getLogger()->info('{class}::Create JSON [{json}]', array( + 'class' => get_class($this), + 'json' => $json + )); + + // send the request + $response = $this->getService()->request( + $this->createUrl(), + 'POST', + array('Content-Type' => 'application/json'), + $json + ); + + // check the return code + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 204) { + throw new Exceptions\CreateError(sprintf( + Lang::translate('Error creating [%s] [%s], status [%d] response [%s]'), + get_class($this), + $this->Name(), + $response->HttpStatus(), + $response->HttpBody() + )); + } + + if ($response->HttpStatus() == "201" && ($location = $response->Header('Location'))) { + // follow Location header + $this->refresh(null, $location); + } else { + // set values from response + $object = json_decode($response->httpBody()); + + if (!$this->checkJsonError()) { + $top = $this->jsonName(); + if (isset($object->$top)) { + $this->populate($object->$top); + } + } + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Updates an existing object + * + * @api + * @param array $params array of values to set when updating the object + * @return HttpResponse + * @throws VolumeCreateError if HTTP status is not Success + */ + public function update($params = array()) + { + // set parameters + if (!empty($params)) { + $this->populate($params); + } + + // debug + $this->getLogger()->info('{class}::Update({name})', array( + 'class' => get_class($this), + 'name' => $this->Name() + )); + + // construct the JSON + $obj = $this->updateJson($params); + $json = json_encode($obj); + + $this->checkJsonError(); + + $this->getLogger()->info('{class}::Update JSON [{json}]', array( + 'class' => get_class($this), + 'json' => $json + )); + + // send the request + $response = $this->getService()->Request( + $this->url(), + 'PUT', + array(), + $json + ); + + // check the return code + // @codeCoverageIgnoreStart + if ($response->HttpStatus() > 204) { + throw new Exceptions\UpdateError(sprintf( + Lang::translate('Error updating [%s] with [%s], status [%d] response [%s]'), + get_class($this), + $json, + $response->HttpStatus(), + $response->HttpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Deletes an object + * + * @api + * @return HttpResponse + * @throws DeleteError if HTTP status is not Success + */ + public function delete() + { + $this->getLogger()->info('{class}::Delete()', array('class' => get_class($this))); + + // send the request + $response = $this->getService()->request($this->url(), 'DELETE'); + + // check the return code + // @codeCoverageIgnoreStart + if ($response->HttpStatus() > 204) { + throw new Exceptions\DeleteError(sprintf( + Lang::translate('Error deleting [%s] [%s], status [%d] response [%s]'), + get_class(), + $this->Name(), + $response->HttpStatus(), + $response->HttpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Returns an object for the Create() method JSON + * Must be overridden in a child class. + * + * @throws CreateError if not overridden + */ + protected function createJson() + { + throw new Exceptions\CreateError(sprintf( + Lang::translate('[%s] CreateJson() must be overridden'), + get_class($this) + )); + } + + /** + * Returns an object for the Update() method JSON + * Must be overridden in a child class. + * + * @throws UpdateError if not overridden + */ + protected function updateJson($params = array()) + { + throw new Exceptions\UpdateError(sprintf( + Lang::translate('[%s] UpdateJson() must be overridden'), + get_class($this) + )); + } + + /** + * throws a CreateError for subclasses that don't support Create + * + * @throws CreateError + */ + protected function noCreate() + { + throw new Exceptions\CreateError(sprintf( + Lang::translate('[%s] does not support Create()'), + get_class() + )); + } + + /** + * throws a DeleteError for subclasses that don't support Delete + * + * @throws DeleteError + */ + protected function noDelete() + { + throw new Exceptions\DeleteError(sprintf( + Lang::translate('[%s] does not support Delete()'), + get_class() + )); + } + + /** + * throws a UpdateError for subclasses that don't support Update + * + * @throws UpdateError + */ + protected function noUpdate() + { + throw new Exceptions\UpdateError(sprintf( + Lang::translate('[%s] does not support Update()'), + get_class() + )); + } + + /** + * Returns the default URL of the object + * + * This may have to be overridden in subclasses. + * + * @param string $subresource optional sub-resource string + * @param array $qstr optional k/v pairs for query strings + * @return string + * @throws UrlError if URL is not defined + */ + public function url($subresource = null, $queryString = array()) + { + // find the primary key attribute name + $primaryKey = $this->primaryKeyField(); + + // first, see if we have a [self] link + $url = $this->findLink('self'); + + /** + * Next, check to see if we have an ID + * Note that we use Parent() instead of Service(), since the parent + * object might not be a service. + */ + if (!$url && $this->$primaryKey) { + $url = Lang::noslash($this->getParent()->url($this->resourceName())) . '/' . $this->$primaryKey; + } + + // add the subresource + if ($url) { + $url .= $subresource ? "/$subresource" : ''; + if (count($queryString)) { + $url .= '?' . $this->makeQueryString($queryString); + } + return $url; + } + + // otherwise, we don't have a URL yet + throw new Exceptions\UrlError(sprintf( + Lang::translate('%s does not have a URL yet'), + get_class($this) + )); + } + + /** + * Waits for the server/instance status to change + * + * This function repeatedly polls the system for a change in server + * status. Once the status reaches the `$terminal` value (or 'ERROR'), + * then the function returns. + * + * The polling interval is set by the constant RAXSDK_POLL_INTERVAL. + * + * The function will automatically terminate after RAXSDK_SERVER_MAXTIMEOUT + * seconds elapse. + * + * @api + * @param string $terminal the terminal state to wait for + * @param integer $timeout the max time (in seconds) to wait + * @param callable $callback a callback function that is invoked with + * each repetition of the polling sequence. This can be used, for + * example, to update a status display or to permit other operations + * to continue + * @return void + */ + public function waitFor( + $terminal = 'ACTIVE', + $timeout = RAXSDK_SERVER_MAXTIMEOUT, + $callback = NULL, + $sleep = RAXSDK_POLL_INTERVAL + ) { + // find the primary key field + $primaryKey = $this->PrimaryKeyField(); + + // save stats + $startTime = time(); + + $states = array('ERROR', $terminal); + + while (true) { + + $this->refresh($this->$primaryKey); + + if ($callback) { + call_user_func($callback, $this); + } + + if (in_array($this->status(), $states) || (time() - $startTime) > $timeout) { + return; + } + // @codeCoverageIgnoreStart + sleep($sleep); + } + } + // @codeCoverageIgnoreEnd + + /** + * Refreshes the object from the origin (useful when the server is + * changing states) + * + * @return void + * @throws IdRequiredError + */ + public function refresh($id = null, $url = null) + { + $primaryKey = $this->PrimaryKeyField(); + + if (!$url) { + if ($id === null) { + $id = $this->$primaryKey; + } + + if (!$id) { + throw new Exceptions\IdRequiredError(sprintf( + Lang::translate('%s has no ID; cannot be refreshed'), + get_class()) + ); + } + + // retrieve it + $this->getLogger()->info(Lang::translate('{class} id [{id}]'), array( + 'class' => get_class($this), + 'id' => $id + )); + + $this->$primaryKey = $id; + $url = $this->url(); + } + + // reset status, if available + if (property_exists($this, 'status')) { + $this->status = null; + } + + // perform a GET on the URL + $response = $this->getService()->Request($url); + + // check status codes + // @codeCoverageIgnoreStart + if ($response->HttpStatus() == 404) { + throw new Exceptions\InstanceNotFound( + sprintf(Lang::translate('%s [%s] not found [%s]'), + get_class($this), + $this->$primaryKey, + $url + )); + } + + if ($response->HttpStatus() >= 300) { + throw new Exceptions\UnknownError( + sprintf(Lang::translate('Unexpected %s error [%d] [%s]'), + get_class($this), + $response->HttpStatus(), + $response->HttpBody() + )); + } + + // check for empty response + if (!$response->HttpBody()) { + throw new Exceptions\EmptyResponseError( + sprintf(Lang::translate('%s::Refresh() unexpected empty response, URL [%s]'), + get_class($this), + $url + )); + } + + // we're ok, reload the response + if ($json = $response->HttpBody()) { + + $this->getLogger()->info('refresh() JSON [{json}]', array('json' => $json)); + + $response = json_decode($json); + + if ($this->CheckJsonError()) { + throw new Exceptions\ServerJsonError(sprintf( + Lang::translate('JSON parse error on %s refresh'), + get_class($this) + )); + } + + $top = $this->JsonName(); + + if ($top && isset($response->$top)) { + $content = $response->$top; + } else { + $content = $response; + } + + $this->populate($content); + + } + // @codeCoverageIgnoreEnd + } + + + /** + * OBJECT INFORMATION + */ + + /** + * Returns the displayable name of the object + * + * Can be overridden by child objects; *must* be overridden by child + * objects if the object does not have a `name` attribute defined. + * + * @api + * @return string + * @throws NameError if attribute 'name' is not defined + */ + public function name() + { + if (property_exists($this, 'name')) { + return $this->name; + } else { + throw new Exceptions\NameError(sprintf( + Lang::translate('Name attribute does not exist for [%s]'), + get_class($this) + )); + } + } + + /** + * Sends the json string to the /action resource + * + * This is used for many purposes, such as rebooting the server, + * setting the root password, creating images, etc. + * Since it can only be used on a live server, it checks for a valid ID. + * + * @param $object - this will be encoded as json, and we handle all the JSON + * error-checking in one place + * @throws ServerIdError if server ID is not defined + * @throws ServerActionError on other errors + * @returns boolean; TRUE if successful, FALSE otherwise + */ + protected function action($object) + { + $primaryKey = $this->primaryKeyField(); + + if (!$this->$primaryKey) { + throw new Exceptions\IdRequiredError(sprintf( + Lang::translate('%s is not defined'), + get_class($this) + )); + } + + if (!is_object($object)) { + throw new Exceptions\ServerActionError(sprintf( + Lang::translate('%s::Action() requires an object as its parameter'), + get_class($this) + )); + } + + // convert the object to json + $json = json_encode($object); + $this->getLogger()->info('JSON [{string}]', array('json' => $json)); + + $this->checkJsonError(); + + // debug - save the request + $this->getLogger()->info(Lang::translate('{class}::action [{json}]'), array( + 'class' => get_class($this), + 'json' => $json + )); + + // get the URL for the POST message + $url = $this->url('action'); + + // POST the message + $response = $this->getService()->request($url, 'POST', array(), $json); + + // @codeCoverageIgnoreStart + if (!is_object($response)) { + throw new Exceptions\HttpError(sprintf( + Lang::translate('Invalid response for %s::Action() request'), + get_class($this) + )); + } + + // check for errors + if ($response->HttpStatus() >= 300) { + throw new Exceptions\ServerActionError(sprintf( + Lang::translate('%s::Action() [%s] failed; response [%s]'), + get_class($this), + $url, + $response->HttpBody() + )); + } + // @codeCoverageIgnoreStart + + return $response; + } + + /** + * Execute a custom resource request. + * + * @param string $path + * @param string $method + * @param string|array|object $body + * @return boolean + * @throws Exceptions\InvalidArgumentError + * @throws Exceptions\HttpError + * @throws Exceptions\ServerActionError + */ + public function customAction($url, $method = 'GET', $body = null) + { + if (is_string($body) && (json_decode($body) === null)) { + throw new Exceptions\InvalidArgumentError( + 'Please provide either a well-formed JSON string, or an object ' + . 'for JSON serialization' + ); + } else { + $body = json_encode($body); + } + + // POST the message + $response = $this->service()->request($url, $method, array(), $body); + + if (!is_object($response)) { + throw new Exceptions\HttpError(sprintf( + Lang::translate('Invalid response for %s::customAction() request'), + get_class($this) + )); + } + + // check for errors + // @codeCoverageIgnoreStart + if ($response->HttpStatus() >= 300) { + throw new Exceptions\ServerActionError(sprintf( + Lang::translate('%s::customAction() [%s] failed; response [%s]'), + get_class($this), + $url, + $response->HttpBody() + )); + } + // @codeCoverageIgnoreEnd + + $object = json_decode($response->httpBody()); + + $this->checkJsonError(); + + return $object; + } + + /** + * returns the object's status or `N/A` if not available + * + * @api + * @return string + */ + public function status() + { + return (isset($this->status)) ? $this->status : 'N/A'; + } + + /** + * returns the object's identifier + * + * Can be overridden by a child class if the identifier is not in the + * `$id` property. Use of this function permits the `$id` attribute to + * be protected or private to prevent unauthorized overwriting for + * security. + * + * @api + * @return string + */ + public function id() + { + return $this->id; + } + + /** + * checks for `$alias` in extensions and throws an error if not present + * + * @throws UnsupportedExtensionError + */ + public function checkExtension($alias) + { + if (!in_array($alias, $this->getService()->namespaces())) { + throw new Exceptions\UnsupportedExtensionError(sprintf( + Lang::translate('Extension [%s] is not installed'), + $alias + )); + } + + return true; + } + + /** + * returns the region associated with the object + * + * navigates to the parent service to determine the region. + * + * @api + */ + public function region() + { + return $this->getService()->Region(); + } + + /** + * Since each server can have multiple links, this returns the desired one + * + * @param string $type - 'self' is most common; use 'bookmark' for + * the version-independent one + * @return string the URL from the links block + */ + public function findLink($type = 'self') + { + if (empty($this->links)) { + return false; + } + + foreach ($this->links as $link) { + if ($link->rel == $type) { + return $link->href; + } + } + + return false; + } + + /** + * returns the URL used for Create + * + * @return string + */ + protected function createUrl() + { + return $this->getParent()->Url($this->ResourceName()); + } + + /** + * Returns the primary key field for the object + * + * The primary key is usually 'id', but this function is provided so that + * (in rare cases where it is not 'id'), it can be overridden. + * + * @return string + */ + protected function primaryKeyField() + { + return 'id'; + } + + /** + * Returns the top-level document identifier for the returned response + * JSON document; must be overridden in child classes + * + * For example, a server document is (JSON) `{"server": ...}` and an + * Instance document is `{"instance": ...}` - this function must return + * the top level document name (either "server" or "instance", in + * these examples). + * + * @throws DocumentError if not overridden + */ + public static function jsonName() + { + if (isset(static::$json_name)) { + return static::$json_name; + } + + throw new Exceptions\DocumentError(sprintf( + Lang::translate('No JSON object defined for class [%s] in JsonName()'), + get_class() + )); + } + + /** + * returns the collection JSON element name + * + * When an object is returned in a collection, it usually has a top-level + * object that is an array holding child objects of the object types. + * This static function returns the name of the top-level element. Usually, + * that top-level element is simply the JSON name of the resource.'s'; + * however, it can be overridden by specifying the $json_collection_name + * attribute. + * + * @return string + */ + public static function jsonCollectionName() + { + if (isset(static::$json_collection_name)) { + return static::$json_collection_name; + } else { + return static::$json_name . 's'; + } + } + + /** + * returns the JSON name for each element in a collection + * + * Usually, elements in a collection are anonymous; this function, however, + * provides for an element level name: + * + * `{ "collection" : [ { "element" : ... } ] }` + * + * @return string + */ + public static function jsonCollectionElement() + { + if (isset(static::$json_collection_element)) { + return static::$json_collection_element; + } + } + + /** + * Returns the resource name for the URL of the object; must be overridden + * in child classes + * + * For example, a server is `/servers/`, a database instance is + * `/instances/`. Must be overridden in child classes. + * + * @throws UrlError + */ + public static function resourceName() + { + if (isset(static::$url_resource)) { + return static::$url_resource; + } + + throw new Exceptions\UrlError(sprintf( + Lang::translate('No URL resource defined for class [%s] in ResourceName()'), + get_class() + )); + } + +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Curl.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Curl.php new file mode 100644 index 0000000000..bb829afc5f --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Curl.php @@ -0,0 +1,308 @@ + + */ +class Curl extends Base implements HttpRequestInterface +{ + + private $url; + private $method; + private $handle; + private $retries = 0; + private $headers = array(); + private $returnheaders = array(); + + /** + * Initializes the CURL handle and HTTP method + * + * The constructor also sets a number of default values for options. + * + * @param string $url the URL to connect to + * @param string $method the HTTP method (default "GET") + * @param array $options optional hashed array of options => value pairs + */ + public function __construct($url, $method = 'GET', array $options = array()) + { + $this->url = $url; + $this->method = $method; + $this->handle = curl_init($url); + + // set our options + $this->setOption(CURLOPT_CUSTOMREQUEST, $method); + + foreach($options as $opt => $value) { + $this->getLogger()->info(Lang::translate('Setting option {key}={val}'), array( + 'key' => $opt, + 'val' => $value + )); + $this->setOption($opt, $value); + } + + // @codeCoverageIgnoreStart + if (RAXSDK_SSL_VERIFYHOST != 2) { + $this->getLogger()->warning("WARNING: RAXSDK_SSL_VERIFYHOST has reduced security, value [{value}]", array( + 'value' => RAXSDK_SSL_VERIFYHOST + )); + } + + if (RAXSDK_SSL_VERIFYPEER !== true) { + $this->getLogger()->warning("WARNING: RAXSDK_SSL_VERIFYPEER has reduced security"); + } + // @codeCoverageIgnoreEnd + + $this->setOption(CURLOPT_SSL_VERIFYHOST, RAXSDK_SSL_VERIFYHOST); + $this->setOption(CURLOPT_SSL_VERIFYPEER, RAXSDK_SSL_VERIFYPEER); + + if (defined('RAXSDK_CACERTPEM') && file_exists(RAXSDK_CACERTPEM)) { + $this->setOption(CURLOPT_CAINFO, RAXSDK_CACERTPEM); + } + + // curl code [18] + // message [transfer closed with x bytes remaining to read] + if ($method === 'HEAD') { + $this->setOption(CURLOPT_NOBODY, true); + } + + // follow redirects + $this->setOption(CURLOPT_FOLLOWLOCATION, true); + + // don't return the headers in the request + $this->setOption(CURLOPT_HEADER, false); + + // retrieve headers via callback + $this->setOption(CURLOPT_HEADERFUNCTION, array($this, '_get_header_cb')); + + // return the entire request on curl_exec() + $this->setOption(CURLOPT_RETURNTRANSFER, true); + + // set default timeouts + $this->setConnectTimeout(RAXSDK_CONNECTTIMEOUT); + $this->setHttpTimeout(RAXSDK_TIMEOUT); + } + + /** + * Sets a CURL option + * + * @param const $name - a CURL named constant; e.g. CURLOPT_TIMEOUT + * @param mixed $value - the value for the option + */ + public function setOption($name, $value) + { + return curl_setopt($this->handle, $name, $value); + } + + /** + * Explicit method for setting the connect timeout + * + * The connect timeout is the time it takes for the initial connection + * request to be established. It is different than the HTTP timeout, which + * is the time for the entire request to be serviced. + * + * @param integer $value The connection timeout in seconds. + * Use 0 to wait indefinitely (NOT recommended) + */ + public function setConnectTimeout($value) + { + $this->setOption(CURLOPT_CONNECTTIMEOUT, $value); + } + + /** + * Explicit method for setting the HTTP timeout + * + * The HTTP timeout is the time it takes for the HTTP request to be + * serviced. This value is usually larger than the connect timeout + * value. + * + * @param integer $value - the number of seconds to wait before timing out + * the HTTP request. + */ + public function setHttpTimeout($value) + { + $this->setOption(CURLOPT_TIMEOUT, $value); + } + + /** + * Sets the number of retries + * + * If you set this to a non-zero value, then it will repeat the request + * up to that number. + */ + public function setRetries($value) + { + $this->retries = $value; + } + + /** + * Simplified method for setting lots of headers at once + * + * This method takes an associative array of header/value pairs and calls + * the setheader() method on each of them. + * + * @param array $arr an associative array of headers + */ + public function setheaders($array) + { + if (!is_array($array)) { + throw new HttpError(Lang::translate( + 'Value passed to CurlRequest::setheaders() must be array' + )); + } + + foreach ($array as $name => $value) { + $this->setHeader($name, $value); + } + } + + /** + * Sets a single header + * + * For example, to set the content type to JSON: + * `$request->SetHeader('Content-Type','application/json');` + * + * @param string $name The name of the header + * @param mixed $value The value of the header + */ + public function setHeader($name, $value) + { + $this->headers[$name] = $value; + } + + /** + * Executes the current request + * + * This method actually performs the request using the values set + * previously. It throws a OpenCloud\HttpError exception on + * any CURL error. + * + * @return OpenCloud\HttpResponse + * @throws OpenCloud\HttpError + * + * @codeCoverageIgnore + */ + public function execute() + { + // set all the headers + $headarr = array(); + + foreach ($this->headers as $name => $value) { + $headarr[] = $name.': '.$value; + } + + $this->setOption(CURLOPT_HTTPHEADER, $headarr); + + // set up to retry if necessary + $try_counter = 0; + + do { + $data = curl_exec($this->handle); + if (curl_errno($this->handle) && ($try_counter<$this->retries)) { + $this->getLogger()->info(Lang::translate('Curl error [%d]; retrying [%s]'), array( + 'error' => curl_errno($this->handle), + 'url' => $this->url + )); + } + + } while((++$try_counter <= $this->retries) && (curl_errno($this->handle) != 0)); + + // log retries error + if ($this->retries && curl_errno($this->handle)) { + throw new HttpRetryError(sprintf( + Lang::translate('No more retries available, last error [%d]'), + curl_errno($this->handle) + )); + } + + // check for CURL errors + switch(curl_errno($this->handle)) { + case 0: + // everything's ok + break; + case 3: + throw new HttpUrlError(sprintf(Lang::translate('Malformed URL [%s]'), $this->url)); + break; + case 28: + // timeout + throw new HttpTimeoutError(Lang::translate('Operation timed out; check RAXSDK_TIMEOUT value')); + break; + default: + throw new HttpError(sprintf( + Lang::translate('HTTP error on [%s], curl code [%d] message [%s]'), + $this->url, + curl_errno($this->handle), + curl_error($this->handle) + )); + } + + // otherwise, return the HttpResponse + return new Response\Http($this, $data); + } + + /** + * returns an array of information about the request + */ + public function info() + { + return curl_getinfo($this->handle); + } + + /** + * returns the most recent CURL error number + */ + public function errno() + { + return curl_errno($this->handle); + } + + /** + * returns the most recent CURL error string + */ + public function error() + { + return curl_error($this->handle); + } + + /** + * Closes the HTTP request + */ + public function close() + { + return curl_close($this->handle); + } + + /** + * Returns the headers as an array + */ + public function returnHeaders() + { + return $this->returnheaders; + } + + /** + * This is a callback method used to handle the returned HTTP headers + * + * @param mixed $ch a CURL handle + * @param string $header the header string in its entirety + */ + public function _get_header_cb($ch, $header) + { + $this->returnheaders[] = $header; + return strlen($header); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/HttpRequestInterface.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/HttpRequestInterface.php new file mode 100644 index 0000000000..cbe3b5412a --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/HttpRequestInterface.php @@ -0,0 +1,23 @@ + $value) { + $this->$name = $value; + } + } + + public function httpStatus() + { + return $this->status; + } + +} \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Response/Http.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Response/Http.php new file mode 100644 index 0000000000..a7cb9e9634 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Request/Response/Http.php @@ -0,0 +1,140 @@ + + */ + +class Http extends Base +{ + + private $errno; + private $error; + private $info = array(); + protected $body; + protected $headers = array(); + + /** + * The constructor parses everything necessary + */ + public function __construct($request, $data) + { + // save the raw data (who knows? we might need it) + $this->setBody($data); + + // and split each line into name: value pairs + foreach($request->returnHeaders() as $line) { + if (preg_match('/^([^:]+):\s+(.+?)\s*$/', $line, $matches)) { + $this->headers[$matches[1]] = $matches[2]; + } else { + $this->headers[$line] = trim($line); + } + } + + // @codeCoverageIgnoreStart + if (isset($this->headers['Cache-Control'])) { + $this->getLogger()->info('Cache-Control: {header}', array( + 'headers' => $this->headers['Cache-Control'] + )); + } + if (isset($this->headers['Expires'])) { + $this->getLogger()->info('Expires: {header}', array( + 'headers' => $this->headers['Expires'] + )); + } + // @codeCoverageIgnoreEnd + + // set some other data + $this->info = $request->info(); + $this->errno = $request->errno(); + $this->error = $request->error(); + } + + /** + * Returns the full body of the request + * + * @return string + */ + public function httpBody() + { + return $this->body; + } + + /** + * Sets the body. + * + * @param string $body + */ + public function setBody($body) + { + $this->body = $body; + } + + /** + * Returns an array of headers + * + * @return associative array('header'=>value) + */ + public function headers() + { + return $this->headers; + } + + /** + * Returns a single header + * + * @return string with the value of the requested header, or NULL + */ + public function header($name) + { + return isset($this->headers[$name]) ? $this->headers[$name] : null; + } + + /** + * Returns an array of information + * + * @return array + */ + public function info() + { + return $this->info; + } + + /** + * Returns the most recent error number + * + * @return integer + */ + public function errno() + { + return $this->errno; + } + + /** + * Returns the most recent error message + * + * @return string + */ + public function error() + { + return $this->error; + } + + /** + * Returns the HTTP status code + * + * @return integer + */ + public function httpStatus() + { + return $this->info['http_code']; + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php new file mode 100644 index 0000000000..5b3aa729a9 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php @@ -0,0 +1,489 @@ + + */ + +namespace OpenCloud\Common; + +use OpenCloud\Common\Base; +use OpenCloud\Common\Lang; +use OpenCloud\OpenStack; +use OpenCloud\Common\Exceptions; + +/** + * This class defines a cloud service; a relationship between a specific OpenStack + * and a provided service, represented by a URL in the service catalog. + * + * Because Service is an abstract class, it cannot be called directly. Provider + * services such as Rackspace Cloud Servers or OpenStack Swift are each + * subclassed from Service. + * + * @author Glen Campbell + */ + +abstract class Service extends Base +{ + + protected $conn; + private $service_type; + private $service_name; + private $service_region; + private $service_url; + + protected $_namespaces = array(); + + /** + * Creates a service on the specified connection + * + * Usage: `$x = new Service($conn, $type, $name, $region, $urltype);` + * The service's URL is defined in the OpenStack's serviceCatalog; it + * uses the $type, $name, $region, and $urltype to find the proper URL + * and set it. If it cannot find a URL in the service catalog that matches + * the criteria, then an exception is thrown. + * + * @param OpenStack $conn - a Connection object + * @param string $type - the service type (e.g., "compute") + * @param string $name - the service name (e.g., "cloudServersOpenStack") + * @param string $region - the region (e.g., "ORD") + * @param string $urltype - the specified URL from the catalog + * (e.g., "publicURL") + */ + public function __construct( + OpenStack $conn, + $type, + $name, + $region, + $urltype = RAXSDK_URL_PUBLIC, + $customServiceUrl = null + ) { + $this->setConnection($conn); + $this->service_type = $type; + $this->service_name = $name; + $this->service_region = $region; + $this->service_url = $customServiceUrl ?: $this->getEndpoint($type, $name, $region, $urltype); + } + + /** + * Set this service's connection. + * + * @param type $connection + */ + public function setConnection($connection) + { + $this->conn = $connection; + } + + /** + * Get this service's connection. + * + * @return type + */ + public function getConnection() + { + return $this->conn; + } + + /** + * Returns the URL for the Service + * + * @param string $resource optional sub-resource + * @param array $query optional k/v pairs for query strings + * @return string + */ + public function url($resource = '', array $param = array()) + { + $baseurl = $this->service_url; + + // use strlen instead of boolean test because '0' is a valid name + if (strlen($resource) > 0) { + $baseurl = Lang::noslash($baseurl).'/'.$resource; + } + + if (!empty($param)) { + $baseurl .= '?'.$this->MakeQueryString($param); + } + + return $baseurl; + } + + /** + * Returns the /extensions for the service + * + * @api + * @return array of objects + */ + public function extensions() + { + $ext = $this->getMetaUrl('extensions'); + return (is_object($ext) && isset($ext->extensions)) ? $ext->extensions : array(); + } + + /** + * Returns the /limits for the service + * + * @api + * @return array of limits + */ + public function limits() + { + $limits = $this->getMetaUrl('limits'); + return (is_object($limits)) ? $limits->limits : array(); + } + + /** + * Performs an authenticated request + * + * This method handles the addition of authentication headers to each + * request. It always adds the X-Auth-Token: header and will add the + * X-Auth-Project-Id: header if there is a tenant defined on the + * connection. + * + * @param string $url The URL of the request + * @param string $method The HTTP method (defaults to "GET") + * @param array $headers An associative array of headers + * @param string $body An optional body for POST/PUT requests + * @return \OpenCloud\HttpResult + */ + public function request( + $url, + $method = 'GET', + array $headers = array(), + $body = null + ) { + + $headers['X-Auth-Token'] = $this->conn->Token(); + + if ($tenant = $this->conn->Tenant()) { + $headers['X-Auth-Project-Id'] = $tenant; + } + + return $this->conn->request($url, $method, $headers, $body); + } + + /** + * returns a collection of objects + * + * @param string $class the class of objects to fetch + * @param string $url (optional) the URL to retrieve + * @param mixed $parent (optional) the parent service/object + * @return OpenCloud\Common\Collection + */ + public function collection($class, $url = null, $parent = null) + { + // Set the element names + $collectionName = $class::JsonCollectionName(); + $elementName = $class::JsonCollectionElement(); + + // Set the parent if empty + if (!$parent) { + $parent = $this; + } + + // Set the URL if empty + if (!$url) { + $url = $parent->url($class::ResourceName()); + } + + // Save debug info + $this->getLogger()->info( + '{class}:Collection({url}, {collectionClass}, {collectionName})', + array( + 'class' => get_class($this), + 'url' => $url, + 'collectionClass' => $class, + 'collectionName' => $collectionName + ) + ); + + // Fetch the list + $response = $this->request($url); + + $this->getLogger()->info('Response {status} [{body}]', array( + 'status' => $response->httpStatus(), + 'body' => $response->httpBody() + )); + + // Check return code + if ($response->httpStatus() > 204) { + throw new Exceptions\CollectionError(sprintf( + Lang::translate('Unable to retrieve [%s] list from [%s], status [%d] response [%s]'), + $class, + $url, + $response->httpStatus(), + $response->httpBody() + )); + } + + // Handle empty response + if (strlen($response->httpBody()) == 0) { + return new Collection($parent, $class, array()); + } + + // Parse the return + $object = json_decode($response->httpBody()); + $this->checkJsonError(); + + // See if there's a "next" link + // Note: not sure if the current API offers links as top-level structures; + // might have to refactor to allow $nextPageUrl as method argument + // @codeCoverageIgnoreStart + if (isset($object->links) && is_array($object->links)) { + foreach($object->links as $link) { + if (isset($link->rel) && $link->rel == 'next') { + if (isset($link->href)) { + $nextPageUrl = $link->href; + } else { + $this->getLogger()->warning( + 'Unexpected [links] found with no [href]' + ); + } + } + } + } + // @codeCoverageIgnoreEnd + + // How should we populate the collection? + $data = array(); + + if (!$collectionName) { + // No element name, just a plain object + // @codeCoverageIgnoreStart + $data = $object; + // @codeCoverageIgnoreEnd + } elseif (isset($object->$collectionName)) { + if (!$elementName) { + // The object has a top-level collection name only + $data = $object->$collectionName; + } else { + // The object has element levels which need to be iterated over + $data = array(); + foreach($object->$collectionName as $item) { + $subValues = $item->$elementName; + unset($item->$elementName); + $data[] = array_merge((array)$item, (array)$subValues); + } + } + } + + $collectionObject = new Collection($parent, $class, $data); + + // if there's a $nextPageUrl, then we need to establish a callback + // @codeCoverageIgnoreStart + if (!empty($nextPageUrl)) { + $collectionObject->setNextPageCallback(array($this, 'Collection'), $nextPageUrl); + } + // @codeCoverageIgnoreEnd + + return $collectionObject; + } + + /** + * returns the Region associated with the service + * + * @api + * @return string + */ + public function region() + { + return $this->service_region; + } + + /** + * returns the serviceName associated with the service + * + * This is used by DNS for PTR record lookups + * + * @api + * @return string + */ + public function name() + { + return $this->service_name; + } + + /** + * Returns a list of supported namespaces + * + * @return array + */ + public function namespaces() + { + return (isset($this->_namespaces) && is_array($this->_namespaces)) ? $this->_namespaces : array(); + } + + /** + * Given a service type, name, and region, return the url + * + * This function ensures that services are represented by an entry in the + * service catalog, and NOT by an arbitrarily-constructed URL. + * + * Note that it will always return the first match found in the + * service catalog (there *should* be only one, but you never know...) + * + * @param string $type The OpenStack service type ("compute" or + * "object-store", for example + * @param string $name The name of the service in the service catlog + * @param string $region The region of the service + * @param string $urltype The URL type; defaults to "publicURL" + * @return string The URL of the service + */ + private function getEndpoint($type, $name, $region, $urltype = 'publicURL') + { + $catalog = $this->getConnection()->serviceCatalog(); + + // Search each service to find The One + foreach ($catalog as $service) { + // Find the service by comparing the type ("compute") and name ("openstack") + if (!strcasecmp($service->type, $type) && !strcasecmp($service->name, $name)) { + foreach($service->endpoints as $endpoint) { + // Only set the URL if: + // a. It is a regionless service (i.e. no region key set) + // b. The region matches the one we want + if (isset($endpoint->$urltype) && + (!isset($endpoint->region) || !strcasecmp($endpoint->region, $region)) + ) { + $url = $endpoint->$urltype; + } + } + } + } + + // error if not found + if (empty($url)) { + throw new Exceptions\EndpointError(sprintf( + 'No endpoints for service type [%s], name [%s], region [%s] and urlType [%s]', + $type, + $name, + $region, + $urltype + )); + } + + return $url; + } + + /** + * Constructs a specified URL from the subresource + * + * Given a subresource (e.g., "extensions"), this constructs the proper + * URL and retrieves the resource. + * + * @param string $resource The resource requested; should NOT have slashes + * at the beginning or end + * @return \stdClass object + */ + private function getMetaUrl($resource) + { + $urlBase = $this->getEndpoint( + $this->service_type, + $this->service_name, + $this->service_region, + RAXSDK_URL_PUBLIC + ); + + $url = Lang::noslash($urlBase) . '/' . $resource; + + $response = $this->request($url); + + // check for NOT FOUND response + if ($response->httpStatus() == 404) { + return array(); + } + + // @codeCoverageIgnoreStart + if ($response->httpStatus() >= 300) { + throw new Exceptions\HttpError(sprintf( + Lang::translate('Error accessing [%s] - status [%d], response [%s]'), + $urlBase, + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + // we're good; proceed + $object = json_decode($response->httpBody()); + + $this->checkJsonError(); + + return $object; + } + + /** + * Get all associated resources for this service. + * + * @access public + * @return void + */ + public function getResources() + { + return $this->resources; + } + + /** + * Internal method for accessing child namespace from parent scope. + * + * @return type + */ + protected function getCurrentNamespace() + { + $namespace = get_class($this); + return substr($namespace, 0, strrpos($namespace, '\\')); + } + + /** + * Resolves fully-qualified classname for associated local resource. + * + * @param string $resourceName + * @return string + */ + protected function resolveResourceClass($resourceName) + { + $className = substr_count($resourceName, '\\') + ? $resourceName + : $this->getCurrentNamespace() . '\\Resource\\' . ucfirst($resourceName); + + if (!class_exists($className)) { + throw new Exceptions\UnrecognizedServiceError(sprintf( + '%s resource does not exist, please try one of the following: %s', + $resourceName, + implode(', ', $this->getResources()) + )); + } + + return $className; + } + + /** + * Factory method for instantiating resource objects. + * + * @access public + * @param string $resourceName + * @param mixed $info (default: null) + * @return object + */ + public function resource($resourceName, $info = null) + { + $className = $this->resolveResourceClass($resourceName); + return new $className($this, $info); + } + + /** + * Factory method for instantiate a resource collection. + * + * @param string $resourceName + * @param string|null $url + * @return Collection + */ + public function resourceList($resourceName, $url = null, $service = null) + { + $className = $this->resolveResourceClass($resourceName); + return $this->collection($className, $url, $service); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/ServiceCatalogItem.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/ServiceCatalogItem.php new file mode 100644 index 0000000000..3e20bcbc7b --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/ServiceCatalogItem.php @@ -0,0 +1,18 @@ + $value) { + $this->$key = $value; + } + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Globals.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Globals.php new file mode 100644 index 0000000000..fbdc4355e0 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Globals.php @@ -0,0 +1,252 @@ + + */ + +namespace OpenCloud; + +/** + * This file contains only configuration data such as constants. + * You can override these constants by defining them BEFORE you including + * any of the top-level files from the SDK. + * + * Definitions: + * * RAXSDK_TIMEZONE - the default timezone for interpreting date/time requests + * * RAXSDK_STRICT_PROPERTY_CHECKS - if TRUE, the library will strictly enforce + * property names on objects; only properties that are pre-defined or + * appear in the extensions aliases for the service will be permitted. + * When FALSE (the default), then any property can be set on an object. + * * RAXSDK_COMPUTE_NAME - the default name for the compute service + * * RAXSDK_COMPUTE_REGION - the default region for the compute service + * * RAXSDK_COMPUTE_URLTYPE - the default URL type for the compute service + * * RAXSDK_OBJSTORE_NAME - the default name for the object storage service + * * RAXSDK_OBJSTORE_REGION - the default region for the object storage service + * * RAXSDK_OBJSTORE_URLTYPE - the default URL type for the object storage + * service + * * RAXSDK_DATABASE_NAME - the default name for the DbService service + * * RAXSDK_DATABASE_REGION - the default region for the DbService service + * * RAXSDK_DATABASE_URLTYPE - the default URL type for the DbService service + * * RAXSDK_CONNECTTIMEOUT - the time (in seconds) to wait for a connection + * to a service + * * RAXSDK_TIMEOUT - the max time (in seconds) to wait for an HTTP request + * to complete + * * RAXSDK_SERVER_MAXTIMEOUT - the max time (in seconds) that a server + * will wait for a change in status (Server::WaitFor() method) + * * RAXSDK_POLL_INTERVAL - how often (in seconds) the Server::WaitFor() method + * will poll for a status change + * * RAXSDK_DEFAULT_IP_VERSION - the default IP version (4 or 6) to return for + * the server's primary IP address + * * RAXSDK_OVERLIMIT_TIMEOUT - the max time (in seconds) to wait before + * retrying a request that has failed because of rate limits. If the + * next available time for the request is more than (X) seconds away, + * then the request will fail; otherwise, the request will sleep until + * available. + */ + +if (!defined('RAXSDK_TIMEZONE')) + define('RAXSDK_TIMEZONE', 'America/Chicago'); +if (!defined('RAXSDK_STRICT_PROPERTY_CHECKS')) + define('RAXSDK_STRICT_PROPERTY_CHECKS', FALSE); +if (!defined('RAXSDK_COMPUTE_NAME')) + define('RAXSDK_COMPUTE_NAME', 'cloudServersOpenStack'); +if (!defined('RAXSDK_COMPUTE_REGION')) + define('RAXSDK_COMPUTE_REGION', NULL); +if (!defined('RAXSDK_COMPUTE_URLTYPE')) + define('RAXSDK_COMPUTE_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_MONITORING_NAME')) + define('RAXSDK_MONITORING_NAME', 'cloudMonitoring'); +if (!defined('RAXSDK_MONITORING_REGION')) + define('RAXSDK_MONITORING_REGION', '{ignore}'); +if (!defined('RAXSDK_MONITORING_URLTYPE')) + define('RAXSDK_MONITORING_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_ORCHESTRATION_NAME')) + define('RAXSDK_ORCHESTRATION_NAME', 'cloudOrchestration'); +if (!defined('RAXSDK_ORCHESTRATION_REGION')) + define('RAXSDK_ORCHESTRATION_REGION', NULL); +if (!defined('RAXSDK_ORCHESTRATION_URLTYPE')) + define('RAXSDK_ORCHESTRATION_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_OBJSTORE_NAME')) + define('RAXSDK_OBJSTORE_NAME', 'cloudFiles'); +if (!defined('RAXSDK_OBJSTORE_REGION')) + define('RAXSDK_OBJSTORE_REGION', NULL); +if (!defined('RAXSDK_OBJSTORE_URLTYPE')) + define('RAXSDK_OBJSTORE_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_DATABASE_NAME')) + define('RAXSDK_DATABASE_NAME', 'cloudDatabases'); +if (!defined('RAXSDK_DATABASE_REGION')) + define('RAXSDK_DATABASE_REGION', NULL); +if (!defined('RAXSDK_DATABASE_URLTYPE')) + define('RAXSDK_DATABASE_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_VOLUME_NAME')) + define('RAXSDK_VOLUME_NAME', 'cloudBlockStorage'); +if (!defined('RAXSDK_VOLUME_REGION')) + define('RAXSDK_VOLUME_REGION', NULL); +if (!defined('RAXSDK_VOLUME_URLTYPE')) + define('RAXSDK_VOLUME_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_LBSERVICE_NAME')) + define('RAXSDK_LBSERVICE_NAME', 'cloudLoadBalancers'); +if (!defined('RAXSDK_LBSERVICE_REGION')) + define('RAXSDK_LBSERVICE_REGION', NULL); +if (!defined('RAXSDK_LBSERVICE_URLTYPE')) + define('RAXSDK_LBSERVICE_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_DNS_NAME')) + define('RAXSDK_DNS_NAME', 'cloudDNS'); +if (!defined('RAXSDK_DNS_REGION')) + define('RAXSDK_DNS_REGION', '{ignore}'); // DNS is regionless +if (!defined('RAXSDK_DNS_URLTYPE')) + define('RAXSDK_DNS_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_AUTOSCALE_NAME')) + define('RAXSDK_AUTOSCALE_NAME', 'autoscale'); +if (!defined('RAXSDK_AUTOSCALE_REGION')) + define('RAXSDK_AUTOSCALE_REGION', NULL); +if (!defined('RAXSDK_AUTOSCALE_URLTYPE')) + define('RAXSDK_AUTOSCALE_URLTYPE', 'publicURL'); +if (!defined('RAXSDK_DNS_ASYNC_TIMEOUT')) + define('RAXSDK_DNS_ASYNC_TIMEOUT', 60); +if (!defined('RAXSDK_DNS_ASYNC_INTERVAL')) + define('RAXSDK_DNS_ASYNC_INTERVAL', 1); +if (!defined('RAXSDK_CONNECTTIMEOUT')) + define('RAXSDK_CONNECTTIMEOUT', 5); +if (!defined('RAXSDK_TIMEOUT')) + define('RAXSDK_TIMEOUT', 60); +if (!defined('RAXSDK_SERVER_MAXTIMEOUT')) + define('RAXSDK_SERVER_MAXTIMEOUT', 3600); +if (!defined('RAXSDK_POLL_INTERVAL')) + define('RAXSDK_POLL_INTERVAL', 10); +if (!defined('RAXSDK_DEFAULT_IP_VERSION')) + define('RAXSDK_DEFAULT_IP_VERSION', 4); +if (!defined('RAXSDK_OVERLIMIT_TIMEOUT')) + define('RAXSDK_OVERLIMIT_TIMEOUT', 300); +/** + * sets default (highly secure) value for CURLOPT_SSL_VERIFYHOST. If you + * are using a self-signed SSL certificate, you can reduce this setting, but + * you do so at your own risk. + */ +if (!defined('RAXSDK_SSL_VERIFYHOST')) + define('RAXSDK_SSL_VERIFYHOST', 2); +/** + * sets default (highly secure) value for CURLOPT_SSL_VERIFYPEER. If you + * are using a self-signed SSL certificate, you can reduce this setting, but + * you do so at your own risk. + */ +if (!defined('RAXSDK_SSL_VERIFYPEER')) + define('RAXSDK_SSL_VERIFYPEER', TRUE); + +/** + * edit and uncomment this to set the default location of cacert.pem file + */ +//define('RAXSDK_CACERTPEM', __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem'); + +/* these should not be overridden */ +define('RAXSDK_VERSION', '1.5.10'); +define('RAXSDK_USER_AGENT', 'php-opencloud/'.RAXSDK_VERSION.' (Rackspace)'); +define('RAXSDK_ERROR', 'Error:'); +define('RAXSDK_FATAL', 'FATAL ERROR:'); +define('RAXSDK_TERMINATED', '*** PROCESSING HALTED ***'); +define('RAXSDK_CONTENT_TYPE_JSON', 'application/json'); +define('RAXSDK_URL_PUBLIC', 'publicURL'); +define('RAXSDK_URL_INTERNAL', 'internalURL'); +define('RAXSDK_URL_VERSION_INFO', 'versionInfo'); +define('RAXSDK_URL_VERSION_LIST', 'versionList'); + +/** + * definitions for Rackspace authentication endpoints + */ +define('RACKSPACE_US', 'https://identity.api.rackspacecloud.com/v2.0/'); +define('RACKSPACE_UK', 'https://lon.identity.api.rackspacecloud.com/v2.0/'); + +/** + * We can re-authenticate this many seconds before the token expires + * + * Set this to a higher value if your service does not cache tokens; if + * it *does* cache them, then this value is not required. + */ +define('RAXSDK_FUDGE', 0); + +/** + * Readable constants + */ +define('RAXSDK_SOFT_REBOOT', 'soft'); +define('RAXSDK_HARD_REBOOT', 'hard'); +define('RAXSDK_DETAILS', TRUE); +define('RAXSDK_MAX_CONTAINER_NAME_LEN', 256); + +/** + * UUID of the Rackspace 'public' network + */ +define('RAX_PUBLIC','00000000-0000-0000-0000-000000000000'); +/** + * UUID of the Rackspace 'private' network + */ +define('RAX_PRIVATE','11111111-1111-1111-1111-111111111111'); + +// Turn off debug mode by default +define('RAXSDK_DEBUG', false); + +/********** TIMEZONE MAGIC **********/ + +/** + * This is called if there is an error getting the default timezone; + * that means that the default timezone isn't set. + * + * @codeCoverageIgnore + */ +function __raxsdk_timezone_set($errno, $errstr) { + if ($errno==2) + date_default_timezone_set(RAXSDK_TIMEZONE); + else + die(sprintf("Unknown error %d: %s\n", $errno, $errstr)); +} +set_error_handler('\OpenCloud\__raxsdk_timezone_set'); +@date_default_timezone_get(); +restore_error_handler(); + +/********** SOME GLOBAL FUNCTIONS **********/ + + /** + * \OpenCloud\Common\Lang::translate() - this function should be used to wrap all static strings. In the future, + * this may provide us with a hook for providing different language + * translations. + * + * @codeCoverageIgnore + */ + function define_gettext() { + function translate($str) { + return $str; + } + } + + if (!function_exists('_')) + define_gettext(); + + /** + * removes trailing slash(es) from a URL string + * + * Mainly, this is just for appearance's sake. I really hate to see + * URLs like .../servers//address, for some reason. + * + * @codeCoverageIgnore + */ + function noslash($str) { + while ($str && (substr($str, -1) == '/')) + $str = substr($str, 0, strlen($str)-1); + return $str; + } + + /** + * Turns debugging on or off + * + * @codeCoverageIgnore + */ + function setDebug($state=TRUE) { + global $RAXSDK_DEBUG; + $RAXSDK_DEBUG=$state; + } + diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/AbstractService.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/AbstractService.php new file mode 100644 index 0000000000..4a2298d60e --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/AbstractService.php @@ -0,0 +1,57 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore; + +use OpenCloud\Common\Service as CommonService; + +define('SWIFT_MAX_OBJECT_SIZE', 5 * 1024 * 1024 * 1024 + 1); + +/** + * An abstract base class for common code shared between ObjectStore\Service + * (container) and ObjectStore\CDNService (CDN containers). + * + * @todo Maybe we use Traits instead of this small abstract class? + */ +abstract class AbstractService extends CommonService +{ + + const MAX_CONTAINER_NAME_LEN = 256; + const MAX_OBJECT_NAME_LEN = 1024; + const MAX_OBJECT_SIZE = SWIFT_MAX_OBJECT_SIZE; + + /** + * Creates a Container resource object. + * + * @param mixed $cdata The name of the container or an object from which to set values + * @return OpenCloud\ObjectStore\Resource\Container + */ + public function container($cdata = null) + { + return new Resource\Container($this, $cdata); + } + + /** + * Returns a Collection of Container objects. + * + * @param array $filter An array to filter the results + * @return OpenCloud\Common\Collection + */ + public function containerList(array $filter = array()) + { + $filter['format'] = 'json'; + + return $this->collection( + 'OpenCloud\ObjectStore\Resource\Container', $this->url(null, $filter) + ); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/CDNService.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/CDNService.php new file mode 100644 index 0000000000..132d5f47ad --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/CDNService.php @@ -0,0 +1,62 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore; + +use OpenCloud\OpenStack; +use OpenCloud\Common\Exceptions; + +/** + * This is the CDN version of the ObjectStore service. + */ +class CDNService extends AbstractService +{ + + /** + * Creates a new CDNService object. + * + * This is a simple wrapper function around the parent Service construct, + * but supplies defaults for the service type. + * + * @param OpenCloud\OpenStack $connection The connection object + * @param string $serviceName The name of the service + * @param string $serviceRegion The service's region + * @param string $urlType The type of URL (normally 'publicURL') + */ + public function __construct( + OpenStack $connection, + $serviceName = RAXSDK_OBJSTORE_NAME, + $serviceRegion = RAXSDK_OBJSTORE_REGION, + $urltype = RAXSDK_URL_PUBLIC + ) { + $this->getLogger()->info('Initializing CDN Service...'); + + parent::__construct( + $connection, + 'rax:object-cdn', + $serviceName, + $serviceRegion, + $urltype + ); + } + + /** + * Helps catch errors if someone calls the method on the + * wrong object + */ + public function CDN() + { + throw new Exceptions\CdnError( + 'Invalid method call; no CDN() on the CDN object' + ); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/AbstractStorageObject.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/AbstractStorageObject.php new file mode 100644 index 0000000000..c6799b22b7 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/AbstractStorageObject.php @@ -0,0 +1,170 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore\Resource; + +use OpenCloud\Common\Base; +use OpenCloud\Common\Metadata; +use OpenCloud\Common\Exceptions\NameError; +use OpenCloud\Common\Exceptions\MetadataPrefixError; +use OpenCloud\Common\Request\Response\Http; + +/** + * Abstract base class which implements shared functionality of ObjectStore + * resources. Provides support, for example, for metadata-handling and other + * features that are common to the ObjectStore components. + */ +abstract class AbstractStorageObject extends Base +{ + + const ACCOUNT_META_PREFIX = 'X-Account-'; + const CONTAINER_META_PREFIX = 'X-Container-Meta-'; + const OBJECT_META_PREFIX = 'X-Object-Meta-'; + const CDNCONTAINER_META_PREFIX = 'X-Cdn-'; + + /** + * Metadata belonging to a resource. + * + * @var OpenCloud\Common\Metadata + */ + public $metadata; + + /** + * Initializes the metadata component + */ + public function __construct() + { + $this->metadata = new Metadata; + } + + /** + * Given an Http response object, converts the appropriate headers + * to metadata + * + * @param OpenCloud\Common\Request\Response\Http + * @return void + */ + public function getMetadata(Http $response) + { + $this->metadata = new Metadata; + $this->metadata->setArray($response->headers(), $this->prefix()); + } + + /** + * If object has metadata, return an associative array of headers. + * + * For example, if a DataObject has a metadata item named 'FOO', + * then this would return array('X-Object-Meta-FOO'=>$value); + * + * @return array + */ + public function metadataHeaders() + { + $headers = array(); + + // only build if we have metadata + if (is_object($this->metadata)) { + foreach ($this->metadata as $key => $value) { + $headers[$this->prefix() . $key] = $value; + } + } + + return $headers; + } + + /** + * Returns the displayable name of the object + * + * Can be overridden by child objects; *must* be overridden by child + * objects if the object does not have a `name` attribute defined. + * + * @api + * @throws NameError if attribute 'name' is not defined + */ + public function name() + { + if (property_exists($this, 'name')) { + return $this->name; + } else { + throw new NameError(sprintf( + 'Name attribute does not exist for [%s]', + get_class($this) + )); + } + } + + /** + * Override parent method. + * + * @return null + */ + public static function jsonName() + { + return null; + } + + /** + * Override parent method. + * + * @return null + */ + public static function jsonCollectionName() + { + return null; + } + + /** + * Override parent method. + * + * @return null + */ + public static function jsonCollectionElement() + { + return null; + } + + /** + * Returns the proper prefix for the specified type of object + * + * @param string $type The type of object; derived from `get_class()` if not + * specified. + * @codeCoverageIgnore + */ + private function prefix($type = null) + { + if ($type === null) { + $parts = preg_split('/\\\/', get_class($this)); + $type = $parts[count($parts)-1]; + } + + switch($type) { + case 'Account': + $prefix = self::ACCOUNT_META_PREFIX; + break; + case 'CDNContainer': + $prefix = self::CDNCONTAINER_META_PREFIX; + break; + case 'Container': + $prefix = self::CONTAINER_META_PREFIX; + break; + case 'DataObject': + $prefix = self::OBJECT_META_PREFIX; + break; + default: + throw new MetadataPrefixError(sprintf( + 'Unrecognized metadata type [%s]', + $type + )); + } + + return $prefix; + } +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/CDNContainer.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/CDNContainer.php new file mode 100644 index 0000000000..9b6367c87e --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/CDNContainer.php @@ -0,0 +1,298 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore\Resource; + +use OpenCloud\Common\Service as AbstractService; +use OpenCloud\Common\Lang; +use OpenCloud\Common\Exceptions; +use OpenCloud\ObjectStore\AbstractService as AbstractObjectService; + +/** + * A container that has been CDN-enabled. Each CDN-enabled container has a unique + * Uniform Resource Locator (URL) that can be combined with its object names and + * openly distributed in web pages, emails, or other applications. + */ +class CDNContainer extends AbstractStorageObject +{ + /** + * The name of the container. + * + * The only restrictions on container names is that they cannot contain a + * forward slash (/) and must be less than 256 bytes in length. Please note + * that the length restriction applies to the name after it has been URL + * encoded. For example, a container named Course Docs would be URL encoded + * as Course%20Docs - which is 13 bytes in length rather than the expected 11. + * + * @var string + */ + public $name; + + /** + * Count of how many objects exist in the container. + * + * @var int + */ + public $count = 0; + + /** + * The total bytes used in the container. + * + * @var int + */ + public $bytes = 0; + + /** + * The service object. + * + * @var AbstractService + */ + private $service; + + /** + * URL of the container. + * + * @var string + */ + private $containerUrl; + + /** + * Creates the container object + * + * Creates a new container object or, if the $cdata object is a string, + * retrieves the named container from the object store. If $cdata is an + * array or an object, then its values are used to set this object. + * + * @param OpenCloud\ObjectStore $service - the ObjectStore service + * @param mixed $cdata - if supplied, the name of the object + */ + public function __construct(AbstractService $service, $cdata = null) + { + $this->getLogger()->info('Initializing CDN Container Service...'); + + parent::__construct(); + + $this->service = $service; + + // Populate data if set + $this->populate($cdata); + } + + /** + * Allow other objects to know what the primary key is. + * + * @return string + */ + public function primaryKeyField() + { + return 'name'; + } + + /** + * Returns the Service associated with the Container + */ + public function getService() + { + return $this->service; + } + + /** + * Returns the URL of the container + * + * @return string + * @param string $subresource not used; required for compatibility + * @throws NoNameError + */ + public function url($subresource = '') + { + if (strlen($this->name) == 0) { + throw new Exceptions\NoNameError( + Lang::translate('Container does not have an identifier') + ); + } + + return Lang::noslash($this->getService()->url(rawurlencode($this->name))); + } + + /** + * Creates a new container with the specified attributes + * + * @param array $params array of parameters + * @return boolean TRUE on success; FALSE on failure + * @throws ContainerCreateError + */ + public function create($params = array()) + { + // Populate object and check container name + $this->populate($params); + $this->isValidName($this->name); + + // Dispatch + $this->containerUrl = $this->url(); + $response = $this->getService()->request($this->url(), 'PUT', $this->metadataHeaders()); + + // Check return code + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 202) { + throw new Exceptions\ContainerCreateError(sprintf( + Lang::translate('Problem creating container [%s] status [%d] response [%s]'), + $this->url(), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return true; + } + + /** + * Updates the metadata for a container + * + * @return boolean TRUE on success; FALSE on failure + * @throws ContainerCreateError + */ + public function update() + { + $response = $this->getService()->request($this->url(), 'POST', $this->metadataHeaders()); + + // check return code + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 204) { + throw new Exceptions\ContainerCreateError(sprintf( + Lang::translate('Problem updating container [%s] status [%d] response [%s]'), + $this->Url(), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return true; + } + + /** + * Deletes the specified container + * + * @return boolean TRUE on success; FALSE on failure + * @throws ContainerDeleteError + */ + public function delete() + { + $response = $this->getService()->request($this->url(), 'DELETE'); + + // validate the response code + // @codeCoverageIgnoreStart + if ($response->httpStatus() == 404) { + throw new Exceptions\ContainerNotFoundError(sprintf( + Lang::translate('Container [%s] not found'), + $this->name + )); + } + + if ($response->httpStatus() == 409) { + throw new Exceptions\ContainerNotEmptyError(sprintf( + Lang::translate('Container [%s] must be empty before deleting'), + $this->name + )); + } + + if ($response->httpStatus() >= 300) { + throw new Exceptions\ContainerDeleteError(sprintf( + Lang::translate('Problem deleting container [%s] status [%d] response [%s]'), + $this->url(), + $response->httpStatus(), + $response->httpBody() + )); + return false; + } + // @codeCoverageIgnoreEnd + + return true; + } + + /** + * Loads the object from the service + * + * @return void + */ + public function refresh($name = null, $url = null) + { + $response = $this->getService()->request( + $this->url($name), 'HEAD', array('Accept' => '*/*') + ); + + // validate the response code + // @codeCoverageIgnoreStart + if ($response->HttpStatus() == 404) { + throw new Exceptions\ContainerNotFoundError(sprintf( + 'Container [%s] (%s) not found', + $this->name, + $this->url() + )); + } + + if ($response->HttpStatus() >= 300) { + throw new Exceptions\HttpError(sprintf( + 'Error retrieving Container, status [%d] response [%s]', + $response->httpStatus(), + $response->httpBody() + )); + } + + // check for headers (not metadata) + foreach($response->headers() as $header => $value) { + switch($header) { + case 'X-Container-Object-Count': + $this->count = $value; + break; + case 'X-Container-Bytes-Used': + $this->bytes = $value; + break; + } + } + // @codeCoverageIgnoreEnd + + // parse the returned object + $this->getMetadata($response); + } + + /** + * Validates that the container name is acceptable + * + * @param string $name the container name to validate + * @return boolean TRUE if ok; throws an exception if not + * @throws ContainerNameError + */ + public function isValidName($name) + { + if (strlen($name) == 0) { + throw new Exceptions\ContainerNameError( + 'Container name cannot be blank' + ); + } + + if (strpos($name, '/') !== false) { + throw new Exceptions\ContainerNameError( + 'Container name cannot contain "/"' + ); + } + + if (strlen($name) > AbstractObjectService::MAX_CONTAINER_NAME_LEN) { + throw new Exceptions\ContainerNameError( + 'Container name is too long' + ); + } + + return true; + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/Container.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/Container.php new file mode 100644 index 0000000000..3a56ebd9fc --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/Container.php @@ -0,0 +1,401 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore\Resource; + +use OpenCloud\Common\Exceptions; +use OpenCloud\Common\Lang; + +/** + * A container is a storage compartment for your data and provides a way for you + * to organize your data. You can think of a container as a folder in Windows® + * or a directory in UNIX®. The primary difference between a container and these + * other file system concepts is that containers cannot be nested. + * + * A container can also be CDN-enabled (for public access), in which case you + * will need to interact with a CDNContainer object instead of this one. + */ +class Container extends CDNContainer +{ + + /** + * CDN container (if set). + * + * @var CDNContainer|null + */ + private $cdn; + + /** + * Sets the CDN container. + * + * @param OpenCloud\ObjectStore\Resource\CDNContainer $cdn + */ + public function setCDN(CDNContainer $cdn) + { + $this->cdn = $cdn; + } + + /** + * Returns the CDN container. + * + * @returns CDNContainer + */ + public function getCDN() + { + if (!$this->cdn) { + throw new Exceptions\CdnNotAvailableError( + Lang::translate('CDN-enabled container is not available') + ); + } + + return $this->cdn; + } + + /** + * Backwards compatability. + */ + public function CDN() + { + return $this->getCDN(); + } + + /** + * Makes the container public via the CDN + * + * @api + * @param integer $TTL the Time-To-Live for the CDN container; if NULL, + * then the cloud's default value will be used for caching. + * @throws CDNNotAvailableError if CDN services are not available + * @return CDNContainer + */ + public function enableCDN($ttl = null) + { + $url = $this->getService()->CDN()->url() . '/' . rawurlencode($this->name); + + $headers = $this->metadataHeaders(); + + if ($ttl) { + + // Make sure we're dealing with a real figure + if (!is_integer($ttl)) { + throw new Exceptions\CdnTtlError(sprintf( + Lang::translate('TTL value [%s] must be an integer'), + $ttl + )); + } + + $headers['X-TTL'] = $ttl; + } + + $headers['X-Log-Retention'] = 'True'; + $headers['X-CDN-Enabled'] = 'True'; + + // PUT to the CDN container + $response = $this->getService()->request($url, 'PUT', $headers); + + // check the response status + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 202) { + throw new Exceptions\CdnHttpError(sprintf( + Lang::translate('HTTP error publishing to CDN, status [%d] response [%s]'), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + // refresh the data + $this->refresh(); + + // return the CDN container object + $cdn = new CDNContainer($this->getService()->getCDNService(), $this->name); + $this->setCDN($cdn); + + return $cdn; + } + + /** + * Backwards compatability. + */ + public function publishToCDN($ttl = null) + { + return $this->enableCDN($ttl); + } + + /** + * Disables the containers CDN function. + * + * Note that the container will still be available on the CDN until + * its TTL expires. + * + * @api + * @return void + */ + public function disableCDN() + { + // Set necessary headers + $headers['X-Log-Retention'] = 'False'; + $headers['X-CDN-Enabled'] = 'False'; + + // PUT it to the CDN service + $response = $this->getService()->request($this->CDNURL(), 'PUT', $headers); + + // check the response status + // @codeCoverageIgnoreStart + if ($response->httpStatus() != 201) { + throw new Exceptions\CdnHttpError(sprintf( + Lang::translate('HTTP error disabling CDN, status [%d] response [%s]'), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return true; + } + + /** + * Creates a static website from the container + * + * @api + * @link http://docs.rackspace.com/files/api/v1/cf-devguide/content/Create_Static_Website-dle4000.html + * @param string $index the index page (starting page) of the website + * @return \OpenCloud\HttpResponse + */ + public function createStaticSite($indexHtml) + { + $headers = array('X-Container-Meta-Web-Index' => $indexHtml); + $response = $this->getService()->request($this->url(), 'POST', $headers); + + // check return code + // @codeCoverageIgnoreStart + if ($response->HttpStatus() > 204) { + throw new Exceptions\ContainerError(sprintf( + Lang::translate('Error creating static website for [%s], status [%d] response [%s]'), + $this->name, + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Sets the error page(s) for the static website + * + * @api + * @link http://docs.rackspace.com/files/api/v1/cf-devguide/content/Set_Error_Pages_for_Static_Website-dle4005.html + * @param string $name the name of the error page + * @return \OpenCloud\HttpResponse + */ + public function staticSiteErrorPage($name) + { + $headers = array('X-Container-Meta-Web-Error' => $name); + $response = $this->getService()->request($this->url(), 'POST', $headers); + + // check return code + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 204) { + throw new Exceptions\ContainerError(sprintf( + Lang::translate('Error creating static site error page for [%s], status [%d] response [%s]'), + $this->name, + $response->httpStatus(), + $response->httpBody() + )); + } + + return $response; + // @codeCoverageIgnoreEnd + } + + /** + * Returns the CDN URL of the container (if enabled) + * + * The CDNURL() is used to manage the container. Note that it is different + * from the PublicURL() of the container, which is the publicly-accessible + * URL on the network. + * + * @api + * @return string + */ + public function CDNURL() + { + return $this->getCDN()->url(); + } + + /** + * Returns the Public URL of the container (on the CDN network) + * + */ + public function publicURL() + { + return $this->CDNURI(); + } + + /** + * Returns the CDN info about the container + * + * @api + * @return stdClass + */ + public function CDNinfo($property = null) + { + // Not quite sure why this is here... + // @codeCoverageIgnoreStart + if ($this->getService() instanceof CDNService) { + return $this->metadata; + } + // @codeCoverageIgnoreEnd + + // return NULL if the CDN container is not enabled + if (!isset($this->getCDN()->metadata->Enabled) + || $this->getCDN()->metadata->Enabled == 'False' + ) { + return null; + } + + // check to see if it's set + if (isset($this->getCDN()->metadata->$property)) { + return trim($this->getCDN()->metadata->$property); + } elseif ($property !== null) { + return null; + } + + // otherwise, return the whole metadata object + return $this->getCDN()->metadata; + } + + /** + * Returns the CDN container URI prefix + * + * @api + * @return string + */ + public function CDNURI() + { + return $this->CDNinfo('Uri'); + } + + /** + * Returns the SSL URI for the container + * + * @api + * @return string + */ + public function SSLURI() + { + return $this->CDNinfo('Ssl-Uri'); + } + + /** + * Returns the streaming URI for the container + * + * @api + * @return string + */ + public function streamingURI() + { + return $this->CDNinfo('Streaming-Uri'); + } + + /** + * Returns the IOS streaming URI for the container + * + * @api + * @link http://docs.rackspace.com/files/api/v1/cf-devguide/content/iOS-Streaming-d1f3725.html + * @return string + */ + public function iosStreamingURI() + { + return $this->CDNinfo('Ios-Uri'); + } + + /** + * Creates a Collection of objects in the container + * + * @param array $params associative array of parameter values. + * * account/tenant - The unique identifier of the account/tenant. + * * container- The unique identifier of the container. + * * limit (Optional) - The number limit of results. + * * marker (Optional) - Value of the marker, that the object names + * greater in value than are returned. + * * end_marker (Optional) - Value of the marker, that the object names + * less in value than are returned. + * * prefix (Optional) - Value of the prefix, which the returned object + * names begin with. + * * format (Optional) - Value of the serialized response format, either + * json or xml. + * * delimiter (Optional) - Value of the delimiter, that all the object + * names nested in the container are returned. + * @link http://api.openstack.org for a list of possible parameter + * names and values + * @return OpenCloud\Collection + * @throws ObjFetchError + */ + public function objectList($params = array()) + { + // construct a query string out of the parameters + $params['format'] = 'json'; + + $queryString = $this->makeQueryString($params); + + // append the query string to the URL + $url = $this->url(); + if (strlen($queryString) > 0) { + $url .= '?' . $queryString; + } + + return $this->getService()->collection( + 'OpenCloud\ObjectStore\Resource\DataObject', $url, $this + ); + } + + /** + * Returns a new DataObject associated with this container + * + * @param string $name if supplied, the name of the object to return + * @return DataObject + */ + public function dataObject($name = null) + { + return new DataObject($this, $name); + } + + /** + * Refreshes, then associates the CDN container + */ + public function refresh($id = null, $url = null) + { + parent::refresh($id, $url); + + // @codeCoverageIgnoreStart + if ($this->getService() instanceof CDNService) { + return; + } + + + if (null !== ($cdn = $this->getService()->CDN())) { + try { + $this->cdn = new CDNContainer( + $cdn, + $this->name + ); + } catch (Exceptions\ContainerNotFoundError $e) { + $this->cdn = new CDNContainer($cdn); + $this->cdn->name = $this->name; + } + } + // @codeCoverageIgnoreEnd + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/DataObject.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/DataObject.php new file mode 100644 index 0000000000..443df1f651 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Resource/DataObject.php @@ -0,0 +1,941 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore\Resource; + +use finfo as FileInfo; +use OpenCloud\Common\Lang; +use OpenCloud\Common\Exceptions; +use OpenCloud\ObjectStore\AbstractService; +use OpenCloud\Common\Request\Response\Http; + +/** + * Objects are the basic storage entities in Cloud Files. They represent the + * files and their optional metadata you upload to the system. When you upload + * objects to Cloud Files, the data is stored as-is (without compression or + * encryption) and consists of a location (container), the object's name, and + * any metadata you assign consisting of key/value pairs. + */ +class DataObject extends AbstractStorageObject +{ + /** + * Object name. The only restriction on object names is that they must be + * less than 1024 bytes in length after URL encoding. + * + * @var string + */ + public $name; + + /** + * Hash value of the object. + * + * @var string + */ + public $hash; + + /** + * Size of object in bytes. + * + * @var string + */ + public $bytes; + + /** + * Date of last modification. + * + * @var string + */ + public $last_modified; + + /** + * Object's content type. + * + * @var string + */ + public $content_type; + + /** + * Object's content length. + * + * @var string + */ + public $content_length; + + /** + * Other headers set for this object (e.g. Access-Control-Allow-Origin) + * + * @var array + */ + public $extra_headers = array(); + + /** + * Whether or not to calculate and send an ETag on create. + * + * @var bool + */ + public $send_etag = true; + + /** + * The data contained by the object. + * + * @var string + */ + private $data; + + /** + * The ETag value. + * + * @var string + */ + private $etag; + + /** + * The parent container of this object. + * + * @var CDNContainer + */ + private $container; + + /** + * Is this data object a pseudo directory? + * + * @var bool + */ + private $directory = false; + + /** + * Used to translate header values (returned by requests) into properties. + * + * @var array + */ + private $headerTranslate = array( + 'Etag' => 'hash', + 'ETag' => 'hash', + 'Last-Modified' => 'last_modified', + 'Content-Length' => array('bytes', 'content_length'), + ); + + /** + * These properties can be freely set by the user for CRUD operations. + * + * @var array + */ + private $allowedProperties = array( + 'name', + 'content_type', + 'extra_headers', + 'send_etag' + ); + + /** + * Option for clearing the status cache when objects are uploaded to API. + * By default, it is set to FALSE for performance; but if you have files + * that are rapidly and very often updated, you might want to clear the status + * cache so PHP reads the files directly, instead of relying on the cache. + * + * @link http://php.net/manual/en/function.clearstatcache.php + * @var bool + */ + public $clearStatusCache = false; + + /** + * A DataObject is related to a container and has a name + * + * If `$name` is specified, then it attempts to retrieve the object from the + * object store. + * + * @param Container $container the container holding this object + * @param mixed $cdata if an object or array, it is treated as values + * with which to populate the object. If it is a string, it is + * treated as a name and the object's info is retrieved from + * the service. + * @return void + */ + public function __construct($container, $cdata = null) + { + parent::__construct(); + + $this->container = $container; + + // For pseudo-directories, we need to ensure the name is set + if (!empty($cdata->subdir)) { + $this->name = $cdata->subdir; + $this->directory = true; + } else { + $this->populate($cdata); + } + } + + /** + * Is this data object a pseudo-directory? + * + * @return bool + */ + public function isDirectory() + { + return $this->directory; + } + + /** + * Allow other objects to know what the primary key is. + * + * @return string + */ + public function primaryKeyField() + { + return 'name'; + } + + /** + * Is this a real file? + * + * @param string $filename + * @return bool + */ + private function isRealFile($filename) + { + return $filename != '/dev/null' && $filename != 'NUL'; + } + + /** + * Set this file's content type. + * + * @param string $contentType + */ + public function setContentType($contentType) + { + $this->content_type = $contentType; + } + + /** + * Return the content type. + * + * @return string + */ + public function getContentType() + { + return $this->content_type; + } + + /** + * Returns the URL of the data object + * + * If the object is new and doesn't have a name, then an exception is + * thrown. + * + * @param string $subresource Not used + * @return string + * @throws NoNameError + */ + public function url($subresource = '') + { + if (!$this->name) { + throw new Exceptions\NoNameError(Lang::translate('Object has no name')); + } + + return Lang::noslash( + $this->container->url()) . '/' . str_replace('%2F', '/', rawurlencode($this->name) + ); + } + + /** + * Creates (or updates; both the same) an instance of the object + * + * @api + * @param array $params an optional associative array that can contain the + * 'name' and 'content_type' of the object + * @param string $filename if provided, then the object is loaded from the + * specified file + * @return boolean + * @throws CreateUpdateError + */ + public function create($params = array(), $filename = null, $extractArchive = null) + { + // Set and validate params + $this->setParams($params); + + // assume no file upload + $fp = false; + + // if the filename is provided, process it + if ($filename) { + + if (!$fp = @fopen($filename, 'r')) { + throw new Exceptions\IOError(sprintf( + Lang::translate('Could not open file [%s] for reading'), + $filename + )); + } + + // @todo Maybe, for performance, we could set the "clear status cache" + // feature to false by default - but allow users to set to true if required + clearstatcache($this->clearStatusCache === true, $filename); + + // Cast filesize as a floating point + $filesize = (float) filesize($filename); + + // Check it's below a reasonable size, and set + // @codeCoverageIgnoreStart + if ($filesize > AbstractService::MAX_OBJECT_SIZE) { + throw new Exceptions\ObjectError("File size exceeds maximum object size."); + } + // @codeCoverageIgnoreEnd + $this->content_length = $filesize; + + // Guess the content type if necessary + if (!$this->getContentType() && $this->isRealFile($filename)) { + $this->setContentType($this->inferContentType($filename)); + } + + // Send ETag checksum if necessary + if ($this->send_etag) { + $this->etag = md5_file($filename); + } + + // Announce to the world + $this->getLogger()->info('Uploading {size} bytes from {name}', array( + 'size' => $filesize, + 'name' => $filename + )); + + } else { + // compute the length + $this->content_length = strlen($this->data); + + if ($this->send_etag) { + $this->etag = md5($this->data); + } + } + + // Only allow supported archive types + // http://docs.rackspace.com/files/api/v1/cf-devguide/content/Extract_Archive-d1e2338.html + $extractArchiveUrlArg = ''; + + if ($extractArchive) { + if ($extractArchive !== "tar.gz" && $extractArchive !== "tar.bz2") { + throw new Exceptions\ObjectError( + "Extract Archive only supports tar.gz and tar.bz2" + ); + } else { + $extractArchiveUrlArg = "?extract-archive=" . $extractArchive; + $this->etag = null; + $this->setContentType(''); + } + } + + // Set headers + $headers = $this->metadataHeaders(); + + if (!empty($this->etag)) { + $headers['ETag'] = $this->etag; + } + + // Content-Type is no longer required; if not specified, it will + // attempt to guess based on the file extension. + if (!$this->getContentType()) { + $headers['Content-Type'] = $this->getContentType(); + } + + $headers['Content-Length'] = $this->content_length; + + // Merge in extra headers + if (!empty($this->extra_headers)) { + $headers = $this->extra_headers + $headers; + } + + // perform the request + $response = $this->getService()->request( + $this->url() . $extractArchiveUrlArg, + 'PUT', + $headers, + $fp ? $fp : $this->data + ); + + // check the status + // @codeCoverageIgnoreStart + if (($status = $response->httpStatus()) >= 300) { + throw new Exceptions\CreateUpdateError(sprintf( + Lang::translate('Problem saving/updating object [%s] HTTP status [%s] response [%s]'), + $this->url() . $extractArchiveUrlArg, + $status, + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + // set values from response + $this->saveResponseHeaders($response); + + // close the file handle + if ($fp) { + fclose($fp); + } + + return $response; + } + + /** + * Update() is provided as an alias for the Create() method + * + * Since update and create both use a PUT request, the different functions + * may allow the developer to distinguish between the semantics in his or + * her application. + * + * @api + * @param array $params an optional associative array that can contain the + * 'name' and 'type' of the object + * @param string $filename if provided, the object is loaded from the file + * @return boolean + */ + public function update($params = array(), $filename = '') + { + return $this->create($params, $filename); + } + + /** + * UpdateMetadata() - updates headers + * + * Updates metadata headers + * + * @api + * @param array $params an optional associative array that can contain the + * 'name' and 'type' of the object + * @return boolean + */ + public function updateMetadata($params = array()) + { + $this->setParams($params); + + // set the headers + $headers = $this->metadataHeaders(); + $headers['Content-Type'] = $this->getContentType(); + + $response = $this->getService()->request( + $this->url(), + 'POST', + $headers + ); + + // check the status + // @codeCoverageIgnoreStart + if (($stat = $response->httpStatus()) >= 204) { + throw new Exceptions\UpdateError(sprintf( + Lang::translate('Problem updating object [%s] HTTP status [%s] response [%s]'), + $this->url(), + $stat, + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Deletes an object from the Object Store + * + * Note that we can delete without retrieving by specifying the name in the + * parameter array. + * + * @api + * @param array $params an array of parameters + * @return HttpResponse if successful; FALSE if not + * @throws DeleteError + */ + public function delete($params = array()) + { + $this->setParams($params); + + $response = $this->getService()->request($this->url(), 'DELETE'); + + // check the status + // @codeCoverageIgnoreStart + if (($stat = $response->httpStatus()) >= 300) { + throw new Exceptions\DeleteError(sprintf( + Lang::translate('Problem deleting object [%s] HTTP status [%s] response [%s]'), + $this->url(), + $stat, + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Copies the object to another container/object + * + * Note that this function, because it operates within the Object Store + * itself, is much faster than downloading the object and re-uploading it + * to a new object. + * + * @param DataObject $target the target of the COPY command + */ + public function copy(DataObject $target) + { + $uri = sprintf('/%s/%s', $target->container()->name(), $target->name()); + + $this->getLogger()->info('Copying object to [{uri}]', array('uri' => $uri)); + + $response = $this->getService()->request( + $this->url(), + 'COPY', + array('Destination' => $uri) + ); + + // check response code + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 202) { + throw new Exceptions\ObjectCopyError(sprintf( + Lang::translate('Error copying object [%s], status [%d] response [%s]'), + $this->url(), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Returns the container of the object + * + * @return Container + */ + public function container() + { + return $this->container; + } + + /** + * returns the TEMP_URL for the object + * + * Some notes: + * * The `$secret` value is arbitrary; it must match the value set for + * the `X-Account-Meta-Temp-URL-Key` on the account level. This can be + * set by calling `$service->SetTempUrlSecret($secret)`. + * * The `$expires` value is the number of seconds you want the temporary + * URL to be valid for. For example, use `60` to make it valid for a + * minute + * * The `$method` must be either GET or PUT. No other methods are + * supported. + * + * @param string $secret the shared secret + * @param integer $expires the expiration time (in seconds) + * @param string $method either GET or PUT + * @return string the temporary URL + */ + public function tempUrl($secret, $expires, $method) + { + $method = strtoupper($method); + $expiry_time = time() + $expires; + + // check for proper method + if ($method != 'GET' && $method != 'PUT') { + throw new Exceptions\TempUrlMethodError(sprintf( + Lang::translate( + 'Bad method [%s] for TempUrl; only GET or PUT supported'), + $method + )); + } + + // construct the URL + $url = $this->url(); + $path = urldecode(parse_url($url, PHP_URL_PATH)); + + $hmac_body = "$method\n$expiry_time\n$path"; + $hash = hash_hmac('sha1', $hmac_body, $secret); + + $this->getLogger()->info('URL [{url}]; SIG [{sig}]; HASH [{hash}]', array( + 'url' => $url, + 'sig' => $hmac_body, + 'hash' => $hash + )); + + $temp_url = sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry_time); + + // debug that stuff + $this->getLogger()->info('TempUrl generated [{url}]', array( + 'url' => $temp_url + )); + + return $temp_url; + } + + /** + * Sets object data from string + * + * This is a convenience function to permit the use of other technologies + * for setting an object's content. + * + * @param string $data + * @return void + */ + public function setData($data) + { + $this->data = (string) $data; + } + + /** + * Return object's data as a string + * + * @return string the entire object + */ + public function saveToString() + { + return $this->getService()->request($this->url())->httpBody(); + } + + /** + * Saves the object's data to local filename + * + * Given a local filename, the Object's data will be written to the newly + * created file. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Whoops! I deleted my local README, let me download/save it + * # + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * $doc->SaveToFilename("/home/ej/cloudfiles/readme.restored"); + * + * + * @param string $filename name of local file to write data to + * @return boolean TRUE if successful + * @throws IOException error opening file + * @throws InvalidResponseException unexpected response + */ + public function saveToFilename($filename) + { + if (!$fp = @fopen($filename, "wb")) { + throw new Exceptions\IOError(sprintf( + Lang::translate('Could not open file [%s] for writing'), + $filename + )); + } + + $result = $this->getService()->request($this->url(), 'GET', array(), $fp); + + fclose($fp); + + return $result; + } + + /** + * Saves the object's to a stream filename + * + * Given a local filename, the Object's data will be written to the stream + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # If I want to write the README to a temporary memory string I + * # do : + * # + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->DataObject(array("name"=>"README")); + * + * $fp = fopen('php://temp', 'r+'); + * $doc->SaveToStream($fp); + * fclose($fp); + * + * + * @param string $filename name of local file to write data to + * @return boolean TRUE if successful + * @throws IOException error opening file + * @throws InvalidResponseException unexpected response + */ + public function saveToStream($resource) + { + if (!is_resource($resource)) { + throw new Exceptions\ObjectError( + Lang::translate("Resource argument not a valid PHP resource." + )); + } + + return $this->getService()->request($this->url(), 'GET', array(), $resource); + } + + + /** + * Returns the object's MD5 checksum + * + * Accessor method for reading Object's private ETag attribute. + * + * @api + * @return string MD5 checksum hexidecimal string + */ + public function getETag() + { + return $this->etag; + } + + /** + * Purges the object from the CDN + * + * Note that the object will still be served up to the time of its + * TTL value. + * + * @api + * @param string $email An email address that will be notified when + * the object is purged. + * @return void + * @throws CdnError if the container is not CDN-enabled + * @throws CdnHttpError if there is an HTTP error in the transaction + */ + public function purgeCDN($email) + { + // @codeCoverageIgnoreStart + if (!$cdn = $this->Container()->CDNURL()) { + throw new Exceptions\CdnError(Lang::translate('Container is not CDN-enabled')); + } + // @codeCoverageIgnoreEnd + + $url = $cdn . '/' . $this->name; + $headers['X-Purge-Email'] = $email; + $response = $this->getService()->request($url, 'DELETE', $headers); + + // check the status + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 204) { + throw new Exceptions\CdnHttpError(sprintf( + Lang::translate('Error purging object, status [%d] response [%s]'), + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return true; + } + + /** + * Returns the CDN URL (for managing the object) + * + * Note that the DataObject::PublicURL() method is used to return the + * publicly-available URL of the object, while the CDNURL() is used + * to manage the object. + * + * @return string + */ + public function CDNURL() + { + return $this->container()->CDNURL() . '/' . $this->name; + } + + /** + * Returns the object's Public CDN URL, if available + * + * @api + * @param string $type can be 'streaming', 'ssl', 'ios-streaming', + * or anything else for the + * default URL. For example, `$object->PublicURL('ios-streaming')` + * @return string + */ + public function publicURL($type = null) + { + if (!$prefix = $this->container()->CDNURI()) { + return null; + } + + switch(strtoupper($type)) { + case 'SSL': + $url = $this->container()->SSLURI().'/'.$this->name; + break; + case 'STREAMING': + $url = $this->container()->streamingURI().'/'.$this->name; + break; + case 'IOS': + case 'IOS-STREAMING': + $url = $this->container()->iosStreamingURI().'/'.$this->name; + break; + default: + $url = $prefix.'/'.$this->name; + break; + } + + return $url; + } + + /** + * Sets parameters from an array and validates them. + * + * @param array $params Associative array of parameters + * @return void + */ + private function setParams(array $params = array()) + { + // Inspect the user's array for any unapproved keys, and unset if necessary + foreach (array_diff(array_keys($params), $this->allowedProperties) as $key) { + $this->getLogger()->warning('You cannot use the {keyName} key when creating an object', array( + 'keyName' => $key + )); + unset($params[$key]); + } + + $this->populate($params); + } + + /** + * Retrieves a single object, parses headers + * + * @return void + * @throws NoNameError, ObjFetchError + */ + private function fetch() + { + if (!$this->name) { + throw new Exceptions\NoNameError(Lang::translate('Cannot retrieve an unnamed object')); + } + + $response = $this->getService()->request($this->url(), 'HEAD', array('Accept' => '*/*')); + + // check for errors + // @codeCoverageIgnoreStart + if ($response->httpStatus() >= 300) { + throw new Exceptions\ObjFetchError(sprintf( + Lang::translate('Problem retrieving object [%s]'), + $this->url() + )); + } + // @codeCoverageIgnoreEnd + + // set headers as metadata? + $this->saveResponseHeaders($response); + + // parse the metadata + $this->getMetadata($response); + } + + /** + * Extracts the headers from the response, and saves them as object + * attributes. Additional name conversions are done where necessary. + * + * @param Http $response + */ + private function saveResponseHeaders(Http $response, $fillExtraIfNotFound = true) + { + foreach ($response->headers() as $header => $value) { + if (isset($this->headerTranslate[$header])) { + // This header needs to be translated + $property = $this->headerTranslate[$header]; + // Are there multiple properties that need to be set? + if (is_array($property)) { + foreach ($property as $subProperty) { + $this->$subProperty = $value; + } + } else { + $this->$property = $value; + } + } elseif ($fillExtraIfNotFound === true) { + // Otherwise, stock extra headers + $this->extra_headers[$header] = $value; + } + } + } + + /** + * Compatability. + */ + public function refresh() + { + return $this->fetch(); + } + + /** + * Returns the service associated with this object + * + * It's actually the object's container's service, so this method will + * simplify things a bit. + */ + private function getService() + { + return $this->container->getService(); + } + + /** + * Performs an internal check to get the proper MIME type for an object + * + * This function would go over the available PHP methods to get + * the MIME type. + * + * By default it will try to use the PHP fileinfo library which is + * available from PHP 5.3 or as an PECL extension + * (http://pecl.php.net/package/Fileinfo). + * + * It will get the magic file by default from the system wide file + * which is usually available in /usr/share/magic on Unix or try + * to use the file specified in the source directory of the API + * (share directory). + * + * if fileinfo is not available it will try to use the internal + * mime_content_type function. + * + * @param string $handle name of file or buffer to guess the type from + * @return boolean TRUE if successful + * @throws BadContentTypeException + * @codeCoverageIgnore + */ + private function inferContentType($handle) + { + if ($contentType = $this->getContentType()) { + return $contentType; + } + + $contentType = false; + + $filePath = (is_string($handle)) ? $handle : (string) $handle; + + if (function_exists("finfo_open")) { + + $magicPath = dirname(__FILE__) . "/share/magic"; + $finfo = new FileInfo(FILEINFO_MIME, file_exists($magicPath) ? $magicPath : null); + + if ($finfo) { + + $contentType = is_file($filePath) + ? $finfo->file($handle) + : $finfo->buffer($handle); + + /** + * PHP 5.3 fileinfo display extra information like charset so we + * remove everything after the ; since we are not into that stuff + */ + if (null !== ($extraInfo = strpos($contentType, "; "))) { + $contentType = substr($contentType, 0, $extraInfo); + } + } + + //unset($finfo); + } + + if (!$contentType) { + // Try different native function instead + if (is_file((string) $handle) && function_exists("mime_content_type")) { + $contentType = mime_content_type($handle); + } else { + $this->getLogger()->error('Content-Type cannot be found'); + } + } + + return $contentType; + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Service.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Service.php new file mode 100644 index 0000000000..571b33378a --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/ObjectStore/Service.php @@ -0,0 +1,115 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud\ObjectStore; + +use OpenCloud\OpenStack; +use OpenCloud\Common\Exceptions; +use OpenCloud\Common\Lang; + +/** + * The ObjectStore (Cloud Files) service. + */ +class Service extends AbstractService +{ + + /** + * This holds the associated CDN service (for Rackspace public cloud) + * or is NULL otherwise. The existence of an object here is + * indicative that the CDN service is available. + */ + private $cdn; + + /** + * Creates a new ObjectStore service object. + * + * @param OpenCloud\OpenStack $connection The connection object + * @param string $serviceName The name of the service + * @param string $serviceRegion The service's region + * @param string $urlType The type of URL (normally 'publicURL') + */ + public function __construct( + OpenStack $connection, + $serviceName = RAXSDK_OBJSTORE_NAME, + $serviceRegion = RAXSDK_OBJSTORE_REGION, + $urltype = RAXSDK_OBJSTORE_URLTYPE + ) { + $this->getLogger()->info('Initializing Container Service...'); + + parent::__construct( + $connection, + 'object-store', + $serviceName, + $serviceRegion, + $urltype + ); + + // establish the CDN container, if available + try { + $this->cdn = new CDNService( + $connection, + $serviceName . 'CDN', + $serviceRegion, + $urltype + ); + } catch (Exceptions\EndpointError $e) { + // If we have an endpoint error, then the CDN functionality is not + // available. In this case, we silently ignore it. + } + } + + /** + * Sets the shared secret value for the TEMP_URL + * + * @param string $secret the shared secret + * @return HttpResponse + */ + public function setTempUrlSecret($secret) + { + $response = $this->request( + $this->url(), + 'POST', + array('X-Account-Meta-Temp-Url-Key' => $secret) + ); + + // @codeCoverageIgnoreStart + if ($response->httpStatus() > 204) { + throw new Exceptions\HttpError(sprintf( + Lang::translate('Error in request, status [%d] for URL [%s] [%s]'), + $response->httpStatus(), + $this->url(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + return $response; + } + + /** + * Get the CDN service. + * + * @return null|CDNService + */ + public function getCDNService() + { + return $this->cdn; + } + + /** + * Backwards compability. + */ + public function CDN() + { + return $this->getCDNService(); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/OpenStack.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/OpenStack.php new file mode 100644 index 0000000000..c3e645a540 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/OpenStack.php @@ -0,0 +1,1198 @@ + + * @author Jamie Hannaford + */ + +namespace OpenCloud; + +require_once __DIR__ . '/Globals.php'; + +use OpenCloud\Common\Base; +use OpenCloud\Common\Lang; +use OpenCloud\Common\Exceptions; +use OpenCloud\Common\ServiceCatalogItem; + +/** + * The OpenStack class represents a relationship (or "connection") + * between a user and a service. + * + * This is the primary entry point into an OpenStack system, and the only one + * where the developer is required to know and provide the endpoint URL (in + * all other cases, the endpoint is derived from the Service Catalog provided + * by the authentication system). + * + * Since various providers have different mechanisms for authentication, users + * will often use a subclass of OpenStack. For example, the Rackspace + * class is provided for users of Rackspace's cloud services, and other cloud + * providers are welcome to add their own subclasses as well. + * + * General usage example: + * + * $username = 'My Username'; + * $secret = 'My Secret'; + * $connection = new OpenCloud\OpenStack($username, $secret); + * // having established the connection, we can set some defaults + * // this sets the default name and region of the Compute service + * $connection->SetDefaults('Compute', 'cloudServersOpenStack', 'ORD'); + * // access a Compute service + * $chicago = $connection->Compute(); + * // if we want to access a different service, we can: + * $dallas = $connection->Compute('cloudServersOpenStack', 'DFW'); + * + */ +class OpenStack extends Base +{ + + /** + * This holds the HTTP User-Agent: used for all requests to the services. It + * is public so that, if necessary, it can be entirely overridden by the + * developer. However, it's strongly recomended that you use the + * appendUserAgent() method to APPEND your own User Agent identifier to the + * end of this string; the user agent information can be very valuable to + * service providers to track who is using their service. + * + * @var string + */ + public $useragent = RAXSDK_USER_AGENT; + + protected $url; + protected $secret = array(); + protected $token; + protected $expiration = 0; + protected $tenant; + protected $catalog; + protected $connectTimeout = RAXSDK_CONNECTTIMEOUT; + protected $httpTimeout = RAXSDK_TIMEOUT; + protected $overlimitTimeout = RAXSDK_OVERLIMIT_TIMEOUT; + + /** + * This associative array holds default values used to identify each + * service (and to select it from the Service Catalog). Use the + * Compute::SetDefaults() method to change the default values, or + * define the global constants (for example, RAXSDK_COMPUTE_NAME) + * BEFORE loading the OpenCloud library: + * + * + * define('RAXSDK_COMPUTE_NAME', 'cloudServersOpenStack'); + * include('openstack.php'); + * + */ + protected $defaults = array( + 'Compute' => array( + 'name' => RAXSDK_COMPUTE_NAME, + 'region' => RAXSDK_COMPUTE_REGION, + 'urltype' => RAXSDK_COMPUTE_URLTYPE + ), + 'ObjectStore' => array( + 'name' => RAXSDK_OBJSTORE_NAME, + 'region' => RAXSDK_OBJSTORE_REGION, + 'urltype' => RAXSDK_OBJSTORE_URLTYPE + ), + 'Database' => array( + 'name' => RAXSDK_DATABASE_NAME, + 'region' => RAXSDK_DATABASE_REGION, + 'urltype' => RAXSDK_DATABASE_URLTYPE + ), + 'Volume' => array( + 'name' => RAXSDK_VOLUME_NAME, + 'region' => RAXSDK_VOLUME_REGION, + 'urltype' => RAXSDK_VOLUME_URLTYPE + ), + 'LoadBalancer' => array( + 'name' => RAXSDK_LBSERVICE_NAME, + 'region' => RAXSDK_LBSERVICE_REGION, + 'urltype' => RAXSDK_LBSERVICE_URLTYPE + ), + 'DNS' => array( + 'name' => RAXSDK_DNS_NAME, + 'region' => RAXSDK_DNS_REGION, + 'urltype' => RAXSDK_DNS_URLTYPE + ), + 'Orchestration' => array( + 'name' => RAXSDK_ORCHESTRATION_NAME, + 'region' => RAXSDK_ORCHESTRATION_REGION, + 'urltype' => RAXSDK_ORCHESTRATION_URLTYPE + ), + 'CloudMonitoring' => array( + 'name' => RAXSDK_MONITORING_NAME, + 'region' => RAXSDK_MONITORING_REGION, + 'urltype' => RAXSDK_MONITORING_URLTYPE + ), + 'Autoscale' => array( + 'name' => RAXSDK_AUTOSCALE_NAME, + 'region' => RAXSDK_AUTOSCALE_REGION, + 'urltype' => RAXSDK_AUTOSCALE_URLTYPE + ) + ); + + private $_user_write_progress_callback_func; + private $_user_read_progress_callback_func; + + /** + * Tracks file descriptors used by streaming downloads + * + * This will permit multiple simultaneous streaming downloads; the + * key is the URL of the object, and the value is its file descriptor. + * + * To prevent memory overflows, each array element is deleted when + * the end of the file is reached. + */ + private $fileDescriptors = array(); + + /** + * array of options to pass to the CURL request object + */ + private $curlOptions = array(); + + /** + * list of attributes to export/import + */ + private $exportItems = array( + 'token', + 'expiration', + 'tenant', + 'catalog' + ); + + /** + * Creates a new OpenStack object + * + * The OpenStack object needs two bits of information: the URL to + * authenticate against, and a "secret", which is an associative array + * of name/value pairs. Usually, the secret will be a username and a + * password, but other values may be required by different authentication + * systems. For example, OpenStack Keystone requires a username and + * password, but Rackspace uses a username, tenant ID, and API key. + * (See OpenCloud\Rackspace for that.) + * + * @param string $url - the authentication endpoint URL + * @param array $secret - an associative array of auth information: + * * username + * * password + * @param array $options - CURL options to pass to the HttpRequest object + */ + public function __construct($url, array $secret, array $options = array()) + { + // check for supported version + // @codeCoverageIgnoreStart + $version = phpversion(); + if ($version < '5.3.1') { + throw new Exceptions\UnsupportedVersionError(sprintf( + Lang::translate('PHP version [%s] is not supported'), + $version + )); + } + // @codeCoverageIgnoreEnd + + // Start processing + $this->getLogger()->info(Lang::translate('Initializing OpenStack client')); + + // Set properties + $this->setUrl($url); + $this->setSecret($secret); + $this->setCurlOptions($options); + } + + /** + * Set user agent. + * + * @param string $useragent + * @return OpenCloud\OpenStack + */ + public function setUserAgent($useragent) + { + $this->useragent = $useragent; + + return $this; + } + + /** + * Allows the user to append a user agent string + * + * Programs that are using these bindings are encouraged to add their + * user agent to the one supplied by this SDK. This will permit cloud + * providers to track users so that they can provide better service. + * + * @param string $agent an arbitrary user-agent string; e.g. "My Cloud App" + * @return OpenCloud\OpenStack + */ + public function appendUserAgent($useragent) + { + $this->useragent .= ';' . $useragent; + + return $this; + } + + /** + * Get user agent. + * + * @return string + */ + public function getUserAgent() + { + return $this->useragent; + } + + /** + * Sets the URL which the client will access. + * + * @param string $url + * @return OpenCloud\OpenStack + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * Get the URL. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set the secret for the client. + * + * @param array $secret + * @return OpenCloud\OpenStack + */ + public function setSecret(array $secret = array()) + { + $this->secret = $secret; + + return $this; + } + + /** + * Get the secret. + * + * @return array + */ + public function getSecret() + { + return $this->secret; + } + + /** + * Set the token for this client. + * + * @param string $token + * @return OpenCloud\OpenStack + */ + public function setToken($token) + { + $this->token = $token; + + return $this; + } + + /** + * Get the token for this client. + * + * @return string + */ + public function getToken() + { + return $this->token; + } + + /** + * Set the expiration for this token. + * + * @param int $expiration + * @return OpenCloud\OpenStack + */ + public function setExpiration($expiration) + { + $this->expiration = $expiration; + + return $this; + } + + /** + * Get the expiration time. + * + * @return int + */ + public function getExpiration() + { + return $this->expiration; + } + + /** + * Set the tenant for this client. + * + * @param string $tenant + * @return OpenCloud\OpenStack + */ + public function setTenant($tenant) + { + $this->tenant = $tenant; + + return $this; + } + + /** + * Get the tenant for this client. + * + * @return string + */ + public function getTenant() + { + return $this->tenant; + } + + /** + * Set the service catalog. + * + * @param mixed $catalog + * @return OpenCloud\OpenStack + */ + public function setCatalog($catalog) + { + $this->catalog = $catalog; + + return $this; + } + + /** + * Get the service catalog. + * + * @return array + */ + public function getCatalog() + { + return $this->catalog; + } + + /** + * Set (all) the cURL options. + * + * @param array $options + * @return OpenCloud\OpenStack + */ + public function setCurlOptions(array $options) + { + $this->curlOptions = $options; + + return $this; + } + + /** + * Get the cURL options. + * + * @return array + */ + public function getCurlOptions() + { + return $this->curlOptions; + } + + /** + * Set a specific file descriptor (associated with a URL) + * + * @param string $key + * @param resource $value + * @return OpenCloud\OpenStack + */ + public function setFileDescriptor($key, $value) + { + $this->descriptors[$key] = $value; + + return $this; + } + + /** + * Get a specific file descriptor (associated with a URL) + * + * @param string $key + * @return resource|false + */ + public function getFileDescriptor($key) + { + return (!isset($this->descriptors[$key])) ? false : $this->descriptors[$key]; + } + + /** + * Get the items to be exported. + * + * @return array + */ + public function getExportItems() + { + return $this->exportItems; + } + + /** + * Sets the connect timeout. + * + * @param int $timeout + * @return OpenCloud\OpenStack + */ + public function setConnectTimeout($timeout) + { + $this->connectTimeout = $timeout; + + return $this; + } + + /** + * Get the connect timeout. + * + * @return int + */ + public function getConnectTimeout() + { + return $this->connectTimeout; + } + + /** + * Set the HTTP timeout. + * + * @param int $timeout + * @return OpenCloud\OpenStack + */ + public function setHttpTimeout($timeout) + { + $this->httpTimeout = $timeout; + + return $this; + } + + /** + * Get the HTTP timeout. + * + * @return int + */ + public function getHttpTimeout() + { + return $this->httpTimeout; + } + + /** + * Set the overlimit timeout. + * + * @param int $timeout + * @return OpenCloud\OpenStack + */ + public function setOverlimitTimeout($timeout) + { + $this->overlimitTimeout = $timeout; + + return $this; + } + + /** + * Get the overlimit timeout. + * + * @return int + */ + public function getOverlimitTimeout() + { + return $this->overlimitTimeout; + } + + /** + * Sets default values (an array) for a service. Each array must contain a + * "name", "region" and "urltype" key. + * + * @param string $service + * @param array $value + * @return OpenCloud\OpenStack + */ + public function setDefault($service, array $value = array()) + { + if (isset($value['name']) && isset($value['region']) && isset($value['urltype'])) { + $this->defaults[$service] = $value; + } + + return $this; + } + + /** + * Get a specific default value for a service. If none exist, return FALSE. + * + * @param string $service + * @return array|false + */ + public function getDefault($service) + { + return (!isset($this->defaults[$service])) ? false : $this->defaults[$service]; + } + +/** + * Sets the timeouts for the current connection + * + * @api + * @param integer $t_http the HTTP timeout value (the max period that + * the OpenStack object will wait for any HTTP request to complete). + * Value is in seconds. + * @param integer $t_conn the Connect timeout value (the max period + * that the OpenStack object will wait to establish an HTTP + * connection). Value is in seconds. + * @param integer $t_overlimit the overlimit timeout value (the max period + * that the OpenStack object will wait to retry on an overlimit + * condition). Value is in seconds. + * @return void + */ + public function setTimeouts($httpTimeout, $connectTimeout = null, $overlimitTimeout = null) + { + $this->setHttpTimeout($httpTimeout); + + if (isset($connectTimeout)) { + $this->setConnectTimeout($connectTimeout); + } + + if (isset($overlimitTimeout)) { + $this->setOverlimitTimeout($overlimitTimeout); + } + } + + /** + * Returns the URL of this object + * + * @api + * @param string $subresource specified subresource + * @return string + */ + public function url($subresource='tokens') + { + return Lang::noslash($this->url) . '/' . $subresource; + } + + /** + * Returns the stored secret + * + * @return array + */ + public function secret() + { + return $this->getSecret(); + } + + /** + * Re-authenticates session if expired. + */ + public function checkExpiration() + { + if ($this->hasExpired()) { + $this->authenticate(); + } + } + + /** + * Checks whether token has expired. + * + * @return bool + */ + public function hasExpired() + { + return time() > ($this->getExpiration() - RAXSDK_FUDGE); + } + + /** + * Returns the cached token; if it has expired, then it re-authenticates + * + * @api + * @return string + */ + public function token() + { + $this->checkExpiration(); + + return $this->getToken(); + } + + /** + * Returns the cached expiration time; + * if it has expired, then it re-authenticates + * + * @api + * @return string + */ + public function expiration() + { + $this->checkExpiration(); + + return $this->getExpiration(); + } + + /** + * Returns the tenant ID, re-authenticating if necessary + * + * @api + * @return string + */ + public function tenant() + { + $this->checkExpiration(); + + return $this->getTenant(); + } + + /** + * Returns the service catalog object from the auth service + * + * @return \stdClass + */ + public function serviceCatalog() + { + $this->checkExpiration(); + + return $this->getCatalog(); + } + + /** + * Returns a Collection of objects with information on services + * + * Note that these are informational (read-only) and are not actually + * 'Service'-class objects. + */ + public function serviceList() + { + return new Common\Collection($this, 'ServiceCatalogItem', $this->serviceCatalog()); + } + + /** + * Creates and returns the formatted credentials to POST to the auth + * service. + * + * @return string + */ + public function credentials() + { + if (isset($this->secret['username']) && isset($this->secret['password'])) { + + $credentials = array( + 'auth' => array( + 'passwordCredentials' => array( + 'username' => $this->secret['username'], + 'password' => $this->secret['password'] + ) + ) + ); + + if (isset($this->secret['tenantName'])) { + $credentials['auth']['tenantName'] = $this->secret['tenantName']; + } + + return json_encode($credentials); + + } else { + throw new Exceptions\CredentialError( + Lang::translate('Unrecognized credential secret') + ); + } + } + + /** + * Authenticates using the supplied credentials + * + * @api + * @return void + * @throws AuthenticationError + */ + public function authenticate() + { + // try to auth + $response = $this->request( + $this->url(), + 'POST', + array('Content-Type'=>'application/json'), + $this->credentials() + ); + + $json = $response->httpBody(); + + // check for errors + if ($response->HttpStatus() >= 400) { + throw new Exceptions\AuthenticationError(sprintf( + Lang::translate('Authentication failure, status [%d], response [%s]'), + $response->httpStatus(), + $json + )); + } + + // Decode and check + $object = json_decode($json); + $this->checkJsonError(); + + // Save the token information as well as the ServiceCatalog + $this->setToken($object->access->token->id); + $this->setExpiration(strtotime($object->access->token->expires)); + $this->setCatalog($object->access->serviceCatalog); + + /** + * In some cases, the tenant name/id is not returned + * as part of the auth token, so we check for it before + * we set it. This occurs with pure Keystone, but not + * with the Rackspace auth. + */ + if (isset($object->access->token->tenant)) { + $this->setTenant($object->access->token->tenant->id); + } + } + + /** + * Performs a single HTTP request + * + * The request() method is one of the most frequently-used in the entire + * library. It performs an HTTP request using the specified URL, method, + * and with the supplied headers and body. It handles error and + * exceptions for the request. + * + * @api + * @param string url - the URL of the request + * @param string method - the HTTP method (defaults to GET) + * @param array headers - an associative array of headers + * @param string data - either a string or a resource (file pointer) to + * use as the request body (for PUT or POST) + * @return HttpResponse object + * @throws HttpOverLimitError, HttpUnauthorizedError, HttpForbiddenError + */ + public function request($url, $method = 'GET', $headers = array(), $data = null) + { + $this->getLogger()->info('Resource [{url}] method [{method}] body [{body}]', array( + 'url' => $url, + 'method' => $method, + 'data' => $data + )); + + // get the request object + $http = $this->getHttpRequestObject($url, $method, $this->getCurlOptions()); + + // set various options + $this->getLogger()->info('Headers: [{headers}]', array( + 'headers' => print_r($headers, true) + )); + + $http->setheaders($headers); + $http->setHttpTimeout($this->getHttpTimeout()); + $http->setConnectTimeout($this->getConnectTimeout()); + $http->setOption(CURLOPT_USERAGENT, $this->getUserAgent()); + + // data can be either a resource or a string + if (is_resource($data)) { + // loading from or writing to a file + // set the appropriate callback functions + switch($method) { + // @codeCoverageIgnoreStart + case 'GET': + // need to save the file descriptor + $this->setFileDescriptor($url, $data); + // set the CURL options + $http->setOption(CURLOPT_FILE, $data); + $http->setOption(CURLOPT_WRITEFUNCTION, array($this, '_write_cb')); + break; + // @codeCoverageIgnoreEnd + case 'PUT': + case 'POST': + // need to save the file descriptor + $this->setFileDescriptor($url, $data); + if (!isset($headers['Content-Length'])) { + throw new Exceptions\HttpError( + Lang::translate('The Content-Length: header must be specified for file uploads') + ); + } + $http->setOption(CURLOPT_UPLOAD, TRUE); + $http->setOption(CURLOPT_INFILE, $data); + $http->setOption(CURLOPT_INFILESIZE, $headers['Content-Length']); + $http->setOption(CURLOPT_READFUNCTION, array($this, '_read_cb')); + break; + default: + // do nothing + break; + } + } elseif (is_string($data)) { + $http->setOption(CURLOPT_POSTFIELDS, $data); + } elseif (isset($data)) { + throw new Exceptions\HttpError( + Lang::translate('Unrecognized data type for PUT/POST body, must be string or resource') + ); + } + + // perform the HTTP request; returns an HttpResult object + $response = $http->execute(); + + // handle and retry on overlimit errors + if ($response->httpStatus() == 413) { + + $object = json_decode($response->httpBody()); + $this->checkJsonError(); + + // @codeCoverageIgnoreStart + if (isset($object->overLimit)) { + /** + * @TODO(glen) - The documentation says "retryAt", but + * the field returned is "retryAfter". If the doc changes, + * then there's no problem, but we'll need to fix this if + * they change the code to match the docs. + */ + $retryAfter = $object->overLimit->retryAfter; + $sleepInterval = strtotime($retryAfter) - time(); + + if ($sleepInterval && $sleepInterval <= $this->getOverlimitTimeout()) { + sleep($sleepInterval); + $response = $http->Execute(); + } else { + throw new Exceptions\HttpOverLimitError(sprintf( + Lang::translate('Over limit; next available request [%s][%s] is not for [%d] seconds at [%s]'), + $method, + $url, + $sleepInterval, + $retryAfter + )); + } + } + // @codeCoverageIgnoreEnd + } + + // do some common error checking + switch ($response->httpStatus()) { + case 401: + throw new Exceptions\HttpUnauthorizedError(sprintf( + Lang::translate('401 Unauthorized for [%s] [%s]'), + $url, + $response->HttpBody() + )); + break; + case 403: + throw new Exceptions\HttpForbiddenError(sprintf( + Lang::translate('403 Forbidden for [%s] [%s]'), + $url, + $response->HttpBody() + )); + break; + case 413: // limit + throw new Exceptions\HttpOverLimitError(sprintf( + Lang::translate('413 Over limit for [%s] [%s]'), + $url, + $response->HttpBody() + )); + break; + default: + // everything is fine here, we're fine, how are you? + break; + } + + // free the handle + $http->close(); + + // return the HttpResponse object + $this->getLogger()->info('HTTP STATUS [{code}]', array( + 'code' => $response->httpStatus() + )); + + return $response; + } + + /** + * Sets default values for name, region, URL type for a service + * + * Once these are set (and they can also be set by defining global + * constants), then you do not need to specify these values when + * creating new service objects. + * + * @api + * @param string $service the name of a supported service; e.g. 'Compute' + * @param string $name the service name; e.g., 'cloudServersOpenStack' + * @param string $region the region name; e.g., 'LON' + * @param string $urltype the type of URL to use; e.g., 'internalURL' + * @return void + * @throws UnrecognizedServiceError + */ + public function setDefaults( + $service, + $name = null, + $region = null, + $urltype = null + ) { + + if (!isset($this->defaults[$service])) { + throw new Exceptions\UnrecognizedServiceError(sprintf( + Lang::translate('Service [%s] is not recognized'), $service + )); + } + + if (isset($name)) { + $this->defaults[$service]['name'] = $name; + } + + if (isset($region)) { + $this->defaults[$service]['region'] = $region; + } + + if (isset($urltype)) { + $this->defaults[$service]['urltype'] = $urltype; + } + } + + /** + * Allows the user to define a function for tracking uploads + * + * This can be used to implement a progress bar or similar function. The + * callback function is called with a single parameter, the length of the + * data that is being uploaded on this call. + * + * @param callable $callback the name of a global callback function, or an + * array($object, $functionname) + * @return void + */ + public function setUploadProgressCallback($callback) + { + $this->_user_write_progress_callback_func = $callback; + } + + /** + * Allows the user to define a function for tracking downloads + * + * This can be used to implement a progress bar or similar function. The + * callback function is called with a single parameter, the length of the + * data that is being downloaded on this call. + * + * @param callable $callback the name of a global callback function, or an + * array($object, $functionname) + * @return void + */ + public function setDownloadProgressCallback($callback) + { + $this->_user_read_progress_callback_func = $callback; + } + + /** + * Callback function to handle reads for file uploads + * + * Internal function for handling file uploads. Note that, although this + * function's visibility is public, this is only because it must be called + * from the HttpRequest interface. This should NOT be called by users + * directly. + * + * @param resource $ch a CURL handle + * @param resource $fd a file descriptor + * @param integer $length the amount of data to read + * @return string the data read + * @codeCoverageIgnore + */ + public function _read_cb($ch, $fd, $length) + { + $data = fread($fd, $length); + $len = strlen($data); + if (isset($this->_user_write_progress_callback_func)) { + call_user_func($this->_user_write_progress_callback_func, $len); + } + return $data; + } + + /** + * Callback function to handle writes for file downloads + * + * Internal function for handling file downloads. Note that, although this + * function's visibility is public, this is only because it must be called + * via the HttpRequest interface. This should NOT be called by users + * directly. + * + * @param resource $ch a CURL handle + * @param string $data the data to be written to a file + * @return integer the number of bytes written + * @codeCoverageIgnore + */ + public function _write_cb($ch, $data) + { + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + + if (false === ($fp = $this->getFileDescriptor($url))) { + throw new Exceptions\HttpUrlError(sprintf( + Lang::translate('Cannot find file descriptor for URL [%s]'), $url) + ); + } + + $dlen = strlen($data); + fwrite($fp, $data, $dlen); + + // call used callback function + if (isset($this->_user_read_progress_callback_func)) { + call_user_func($this->_user_read_progress_callback_func, $dlen); + } + + // MUST return the length to CURL + return $dlen; + } + + /** + * exports saved token, expiration, tenant, and service catalog as an array + * + * This could be stored in a cache (APC or disk file) and reloaded using + * ImportCredentials() + * + * @return array + */ + public function exportCredentials() + { + $this->authenticate(); + + $array = array(); + + foreach ($this->getExportItems() as $key) { + $array[$key] = $this->$key; + } + + return $array; + } + + /** + * imports credentials from an array + * + * Takes the same values as ExportCredentials() and reuses them. + * + * @return void + */ + public function importCredentials(array $values) + { + foreach ($this->getExportItems() as $item) { + $this->$item = $values[$item]; + } + } + + /********** FACTORY METHODS ********** + * + * These methods are provided to permit easy creation of services + * (for example, Nova or Swift) from a connection object. As new + * services are supported, factory methods should be provided here. + */ + + /** + * Creates a new ObjectStore object (Swift/Cloud Files) + * + * @api + * @param string $name the name of the Object Storage service to attach to + * @param string $region the name of the region to use + * @param string $urltype the URL type (normally "publicURL") + * @return ObjectStore + */ + public function objectStore($name = null, $region = null, $urltype = null) + { + return $this->service('ObjectStore', $name, $region, $urltype); + } + + /** + * Creates a new Compute object (Nova/Cloud Servers) + * + * @api + * @param string $name the name of the Compute service to attach to + * @param string $region the name of the region to use + * @param string $urltype the URL type (normally "publicURL") + * @return Compute + */ + public function compute($name = null, $region = null, $urltype = null) + { + return $this->service('Compute', $name, $region, $urltype); + } + + /** + * Creates a new Orchestration (heat) service object + * + * @api + * @param string $name the name of the Compute service to attach to + * @param string $region the name of the region to use + * @param string $urltype the URL type (normally "publicURL") + * @return Orchestration\Service + * @codeCoverageIgnore + */ + public function orchestration($name = null, $region = null, $urltype = null) + { + return $this->service('Orchestration', $name, $region, $urltype); + } + + /** + * Creates a new VolumeService (cinder) service object + * + * This is a factory method that is Rackspace-only (NOT part of OpenStack). + * + * @param string $name the name of the service (e.g., 'cloudBlockStorage') + * @param string $region the region (e.g., 'DFW') + * @param string $urltype the type of URL (e.g., 'publicURL'); + */ + public function volumeService($name = null, $region = null, $urltype = null) + { + return $this->service('Volume', $name, $region, $urltype); + } + + /** + * Generic Service factory method + * + * Contains code reused by the other service factory methods. + * + * @param string $class the name of the Service class to produce + * @param string $name the name of the Compute service to attach to + * @param string $region the name of the region to use + * @param string $urltype the URL type (normally "publicURL") + * @return Service (or subclass such as Compute, ObjectStore) + * @throws ServiceValueError + */ + public function service($class, $name = null, $region = null, $urltype = null) + { + // debug message + $this->getLogger()->info('Factory for class [{class}] [{name}/{region}/{urlType}]', array( + 'class' => $class, + 'name' => $name, + 'region' => $region, + 'urlType' => $urltype + )); + + // Strips off base namespace + $class = preg_replace('#\\\?OpenCloud\\\#', '', $class); + + // check for defaults + $default = $this->getDefault($class); + + // report errors + if (!$name = $name ?: $default['name']) { + throw new Exceptions\ServiceValueError(sprintf( + Lang::translate('No value for %s name'), + $class + )); + } + + if (!$region = $region ?: $default['region']) { + throw new Exceptions\ServiceValueError(sprintf( + Lang::translate('No value for %s region'), + $class + )); + } + + if (!$urltype = $urltype ?: $default['urltype']) { + throw new Exceptions\ServiceValueError(sprintf( + Lang::translate('No value for %s URL type'), + $class + )); + } + + // return the object + $fullclass = 'OpenCloud\\' . $class . '\\Service'; + + return new $fullclass($this, $name, $region, $urltype); + } + + /** + * returns a service catalog item + * + * This is a helper function used to list service catalog items easily + */ + public function serviceCatalogItem($info = array()) + { + return new ServiceCatalogItem($info); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Rackspace.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Rackspace.php new file mode 100644 index 0000000000..41be608b34 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Rackspace.php @@ -0,0 +1,132 @@ + + */ + +namespace OpenCloud; + +/** + * Rackspace extends the OpenStack class with support for Rackspace's + * API key and tenant requirements. + * + * The only difference between Rackspace and OpenStack is that the + * Rackspace class generates credentials using the username + * and API key, as required by the Rackspace authentication + * service. + * + * Example: + * + * $username = 'FRED'; + * $apiKey = '0900af093093788912388fc09dde090ffee09'; + * $conn = new Rackspace( + * 'https://identity.api.rackspacecloud.com/v2.0/', + * array( + * 'username' => $username, + * 'apiKey' => $apiKey + * )); + * + */ +class Rackspace extends OpenStack +{ + + //this is the JSON string for our new credentials +const APIKEYTEMPLATE = <<Secret(); + if (isset($sec['username']) + && isset($sec['apiKey']) + ) { + return sprintf( + self::APIKEYTEMPLATE, + $sec['username'], + $sec['apiKey'] + ); + } else { + return parent::Credentials(); + } + } + + /** + * Creates a new DbService (Database as a Service) object + * + * This is a factory method that is Rackspace-only (NOT part of OpenStack). + * + * @param string $name the name of the service (e.g., 'Cloud Databases') + * @param string $region the region (e.g., 'DFW') + * @param string $urltype the type of URL (e.g., 'publicURL'); + */ + public function DbService($name = null, $region = null, $urltype = null) + { + return $this->Service('Database', $name, $region, $urltype); + } + + /** + * Creates a new LoadBalancerService object + * + * This is a factory method that is Rackspace-only (NOT part of OpenStack). + * + * @param string $name the name of the service + * (e.g., 'Cloud Load Balancers') + * @param string $region the region (e.g., 'DFW') + * @param string $urltype the type of URL (e.g., 'publicURL'); + */ + public function LoadBalancerService($name = null, $region = null, $urltype = null) + { + return $this->Service('LoadBalancer', $name, $region, $urltype); + } + + /** + * creates a new DNS service object + * + * This is a factory method that is currently Rackspace-only + * (not available via the OpenStack class) + */ + public function DNS($name = null, $region = null, $urltype = null) + { + return $this->Service('DNS', $name, $region, $urltype); + } + + /** + * creates a new CloudMonitoring service object + * + * This is a factory method that is currently Rackspace-only + * (not available via the OpenStack class) + */ + public function CloudMonitoring($name=null, $region=null, $urltype=null) + { + return $this->Service('CloudMonitoring', $name, $region, $urltype); + } + + /** + * creates a new Autoscale service object + * + * This is a factory method that is currently Rackspace-only + * (not available via the OpenStack class) + */ + public function Autoscale($name=null, $region=null, $urltype=null) + { + return $this->Service('Autoscale', $name, $region, $urltype); + } + +} diff --git a/apps/files_external/3rdparty/php-opencloud/lib/openstack.php b/apps/files_external/3rdparty/php-opencloud/lib/openstack.php new file mode 100644 index 0000000000..738902d244 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/openstack.php @@ -0,0 +1,8 @@ +registerNamespaces(array( + 'OpenCloud' => array(__DIR__, __DIR__ . '/../tests') +)); +$classLoader->register(); \ No newline at end of file diff --git a/apps/files_external/3rdparty/php-opencloud/lib/rackspace.php b/apps/files_external/3rdparty/php-opencloud/lib/rackspace.php new file mode 100644 index 0000000000..738902d244 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/rackspace.php @@ -0,0 +1,8 @@ + Date: Tue, 22 Oct 2013 13:34:24 +0200 Subject: [PATCH 03/36] adjustment of the configuration parameters --- apps/files_external/lib/config.php | 23 +++++++++++++++-------- apps/files_external/tests/config.php | 14 +++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 659959e662..65d541c4b1 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -84,14 +84,21 @@ class OC_Mount_Config { 'token' => '#token'), 'custom' => 'google'); - $backends['\OC\Files\Storage\SWIFT']=array( - 'backend' => 'OpenStack Swift', - 'configuration' => array( - 'host' => 'URL', - 'user' => 'Username', - 'token' => '*Token', - 'root' => '&Root', - 'secure' => '!Secure ftps://')); + if(OC_Mount_Config::checkcurl()) { + $backends['\OC\Files\Storage\Swift'] = array( + 'backend' => 'OpenStack Object Storage', + 'configuration' => array( + 'user' => 'Username', + 'bucket' => 'Bucket', + 'region' => 'Region', + 'key' => '*API Key (Rackspace Cloud Files)', + 'tenant' => 'Tenantname (OpenStack Object Storage)', + 'password' => '*Password (OpenStack Object Storage)', + 'service_name' => 'Service Name (OpenStack Object Storage)', + 'url' => 'URL of identity endpoint (OpenStack Object Storage)' + ) + ); + } if (!OC_Util::runningOnWindows()) { if (OC_Mount_Config::checksmbclient()) { diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index d4a69d29c0..57653d05d0 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -31,11 +31,15 @@ return array( 'token' => '', ), 'swift'=>array( - 'run'=>false, - 'user'=>'test:tester', - 'token'=>'testing', - 'host'=>'localhost.local:8080/auth', - 'root'=>'/', + 'run' => false, + 'user' => 'test', + 'bucket' => 'test', + 'region' => 'DFW', + 'key' => 'test', //to be used only with Rackspace Cloud Files + //'tenant' => 'test', //to be used only with OpenStack Object Storage + //'password' => 'test', //to be use only with OpenStack Object Storage + //'service_name' => 'swift', //should be 'swift' for OpenStack Object Storage and 'cloudFiles' for Rackspace Cloud Files (default value) + //'url' => 'https://identity.api.rackspacecloud.com/v2.0/' //to be used with Rackspace Cloud Files and OpenStack Object Storage ), 'smb'=>array( 'run'=>false, From e0bac3ec71ad9f3a2624866b1603c8bf57a22fa7 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Tue, 22 Oct 2013 13:36:23 +0200 Subject: [PATCH 04/36] adapted existing test cases --- apps/files_external/tests/swift.php | 45 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php index 5c78284024..bdfdbdeebe 100644 --- a/apps/files_external/tests/swift.php +++ b/apps/files_external/tests/swift.php @@ -1,30 +1,51 @@ - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. + * ownCloud + * + * @author Christian Berendt + * @copyright 2013 Christian Berendt berendt@b1-systems.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . */ namespace Test\Files\Storage; -class SWIFT extends Storage { +class Swift extends Storage { + private $config; public function setUp() { - $id = uniqid(); $this->config = include('files_external/tests/config.php'); - if ( ! is_array($this->config) or ! isset($this->config['swift']) or ! $this->config['swift']['run']) { - $this->markTestSkipped('OpenStack SWIFT backend not configured'); + if (!is_array($this->config) or !isset($this->config['swift']) + or !$this->config['swift']['run']) { + $this->markTestSkipped('OpenStack Object Storage backend not configured'); } - $this->config['swift']['root'] .= '/' . $id; //make sure we have an new empty folder to work in - $this->instance = new \OC\Files\Storage\SWIFT($this->config['swift']); + $this->instance = new \OC\Files\Storage\Swift($this->config['swift']); } - public function tearDown() { if ($this->instance) { - $this->instance->rmdir(''); + $connection = $this->instance->getConnection(); + $container = $connection->Container($this->config['swift']['bucket']); + + $objects = $container->ObjectList(); + while($object = $objects->Next()) { + $object->Delete(); + } + + $container->Delete(); } } } From 452d0a20a6c764a5a35069114602b2c0431011be Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Tue, 22 Oct 2013 14:22:03 +0200 Subject: [PATCH 05/36] renaming OC\Files\Storage\SWIFT to OC\Files\Storage\Swift --- apps/files_external/appinfo/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index dd0b76ed9d..f78f3abf0f 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -10,7 +10,7 @@ OC::$CLASSPATH['OC\Files\Storage\StreamWrapper'] = 'files_external/lib/streamwra OC::$CLASSPATH['OC\Files\Storage\FTP'] = 'files_external/lib/ftp.php'; OC::$CLASSPATH['OC\Files\Storage\DAV'] = 'files_external/lib/webdav.php'; OC::$CLASSPATH['OC\Files\Storage\Google'] = 'files_external/lib/google.php'; -OC::$CLASSPATH['OC\Files\Storage\SWIFT'] = 'files_external/lib/swift.php'; +OC::$CLASSPATH['OC\Files\Storage\Swift'] = 'files_external/lib/swift.php'; OC::$CLASSPATH['OC\Files\Storage\SMB'] = 'files_external/lib/smb.php'; OC::$CLASSPATH['OC\Files\Storage\AmazonS3'] = 'files_external/lib/amazons3.php'; OC::$CLASSPATH['OC\Files\Storage\Dropbox'] = 'files_external/lib/dropbox.php'; From 506db6c63a4ddc9ffb68af659cd09b26c40e4394 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Tue, 22 Oct 2013 14:59:09 +0200 Subject: [PATCH 06/36] rewrite of OC\Files\Storage\Swift --- apps/files_external/lib/swift.php | 860 +++++++++++++----------------- 1 file changed, 379 insertions(+), 481 deletions(-) diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index a9cfe5bd20..981a118f18 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -1,392 +1,265 @@ - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. + * ownCloud + * + * @author Christian Berendt + * @copyright 2013 Christian Berendt berendt@b1-systems.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . */ namespace OC\Files\Storage; -require_once 'php-cloudfiles/cloudfiles.php'; +set_include_path(get_include_path() . PATH_SEPARATOR . + \OC_App::getAppPath('files_external') . '/3rdparty/php-opencloud/lib'); +require_once 'openstack.php'; -class SWIFT extends \OC\Files\Storage\Common{ - private $id; - private $host; - private $root; - private $user; - private $token; - private $secure; - private $ready = false; - /** - * @var \CF_Authentication auth - */ - private $auth; - /** - * @var \CF_Connection conn - */ - private $conn; - /** - * @var \CF_Container rootContainer - */ - private $rootContainer; +use \OpenCloud; +use \OpenCloud\Common\Exceptions; - private static $tempFiles=array(); - private $objects=array(); - private $containers=array(); +class Swift extends \OC\Files\Storage\Common { - const SUBCONTAINER_FILE='.subcontainers'; + /** + * @var \OpenCloud\ObjectStore + */ + private $connection; + /** + * @var \OpenCloud\ObjectStore\Container + */ + private $container; + /** + * @var \OpenCloud\OpenStack + */ + private $anchor; + /** + * @var string + */ + private $bucket; + /** + * @var array + */ + private static $tmpFiles = array(); - /** - * translate directory path to container name - * @param string $path - * @return string - */ - private function getContainerName($path) { - $path=trim(trim($this->root, '/') . "/".$path, '/.'); - return str_replace('/', '\\', $path); + private function normalizePath($path) { + $path = trim($path, '/'); + + if (!$path) { + $path = '.'; + } + + return $path; } - /** - * get container by path - * @param string $path - * @return \CF_Container - */ - private function getContainer($path) { - if ($path=='' or $path=='/') { - return $this->rootContainer; - } - if (isset($this->containers[$path])) { - return $this->containers[$path]; - } + private function doesObjectExist($path) { try { - $container=$this->conn->get_container($this->getContainerName($path)); - $this->containers[$path]=$container; - return $container; - } catch(\NoSuchContainerException $e) { - return null; - } - } - - /** - * create container - * @param string $path - * @return \CF_Container - */ - private function createContainer($path) { - if ($path=='' or $path=='/' or $path=='.') { - return $this->conn->create_container($this->getContainerName($path)); - } - $parent=dirname($path); - if ($parent=='' or $parent=='/' or $parent=='.') { - $parentContainer=$this->rootContainer; - } else { - if ( ! $this->containerExists($parent)) { - $parentContainer=$this->createContainer($parent); - } else { - $parentContainer=$this->getContainer($parent); - } - } - $this->addSubContainer($parentContainer, basename($path)); - return $this->conn->create_container($this->getContainerName($path)); - } - - /** - * get object by path - * @param string $path - * @return \CF_Object - */ - private function getObject($path) { - if (isset($this->objects[$path])) { - return $this->objects[$path]; - } - $container=$this->getContainer(dirname($path)); - if (is_null($container)) { - return null; - } else { - if ($path=="/" or $path=='') { - return null; - } - try { - $obj=$container->get_object(basename($path)); - $this->objects[$path]=$obj; - return $obj; - } catch(\NoSuchObjectException $e) { - return null; - } - } - } - - /** - * get the names of all objects in a container - * @param CF_Container - * @return array - */ - private function getObjects($container) { - if (is_null($container)) { - return array(); - } else { - $files=$container->get_objects(); - foreach ($files as &$file) { - $file=$file->name; - } - return $files; - } - } - - /** - * create object - * @param string $path - * @return \CF_Object - */ - private function createObject($path) { - $container=$this->getContainer(dirname($path)); - if ( ! is_null($container)) { - $container=$this->createContainer(dirname($path)); - } - return $container->create_object(basename($path)); - } - - /** - * check if an object exists - * @param string - * @return bool - */ - private function objectExists($path) { - return !is_null($this->getObject($path)); - } - - /** - * check if container for path exists - * @param string $path - * @return bool - */ - private function containerExists($path) { - return !is_null($this->getContainer($path)); - } - - /** - * get the list of emulated sub containers - * @param \CF_Container $container - * @return array - */ - private function getSubContainers($container) { - $tmpFile=\OCP\Files::tmpFile(); - $obj=$this->getSubContainerFile($container); - try { - $obj->save_to_filename($tmpFile); - } catch(\Exception $e) { - return array(); - } - $obj->save_to_filename($tmpFile); - $containers=file($tmpFile); - unlink($tmpFile); - foreach ($containers as &$sub) { - $sub=trim($sub); - } - return $containers; - } - - /** - * add an emulated sub container - * @param \CF_Container $container - * @param string $name - * @return bool - */ - private function addSubContainer($container, $name) { - if ( ! $name) { + $object = $this->container->DataObject($path); + return true; + } catch (Exceptions\ObjFetchError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); return false; - } - $tmpFile=\OCP\Files::tmpFile(); - $obj=$this->getSubContainerFile($container); - try { - $obj->save_to_filename($tmpFile); - $containers=file($tmpFile); - foreach ($containers as &$sub) { - $sub=trim($sub); - } - if(array_search($name, $containers) !== false) { - unlink($tmpFile); - return false; - } else { - $fh=fopen($tmpFile, 'a'); - fwrite($fh, $name . "\n"); - } - } catch(\Exception $e) { - file_put_contents($tmpFile, $name . "\n"); - } - - $obj->load_from_filename($tmpFile); - unlink($tmpFile); - return true; - } - - /** - * remove an emulated sub container - * @param \CF_Container $container - * @param string $name - * @return bool - */ - private function removeSubContainer($container, $name) { - if ( ! $name) { + } catch (Exceptions\HttpError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); return false; } - $tmpFile=\OCP\Files::tmpFile(); - $obj=$this->getSubContainerFile($container); - try { - $obj->save_to_filename($tmpFile); - $containers=file($tmpFile); - } catch (\Exception $e) { - return false; - } - foreach ($containers as &$sub) { - $sub=trim($sub); - } - $i=array_search($name, $containers); - if ($i===false) { - unlink($tmpFile); - return false; - } else { - unset($containers[$i]); - file_put_contents($tmpFile, implode("\n", $containers)."\n"); - } - - $obj->load_from_filename($tmpFile); - unlink($tmpFile); - return true; - } - - /** - * ensure a subcontainer file exists and return it's object - * @param \CF_Container $container - * @return \CF_Object - */ - private function getSubContainerFile($container) { - try { - return $container->get_object(self::SUBCONTAINER_FILE); - } catch(\NoSuchObjectException $e) { - return $container->create_object(self::SUBCONTAINER_FILE); - } } public function __construct($params) { - if (isset($params['token']) && isset($params['host']) && isset($params['user'])) { - $this->token=$params['token']; - $this->host=$params['host']; - $this->user=$params['user']; - $this->root=isset($params['root'])?$params['root']:'/'; - if (isset($params['secure'])) { - if (is_string($params['secure'])) { - $this->secure = ($params['secure'] === 'true'); - } else { - $this->secure = (bool)$params['secure']; - } - } else { - $this->secure = false; - } - if ( ! $this->root || $this->root[0]!='/') { - $this->root='/'.$this->root; - } - $this->id = 'swift:' . $this->host . ':'.$this->root . ':' . $this->user; - } else { - throw new \Exception(); + if ((!isset($params['key']) and !isset($params['password'])) + or !isset($params['user']) or !isset($params['bucket']) + or !isset($params['region'])) { + throw new \Exception("API Key or password, Username, Bucket and Region have to be configured."); } - } + $this->id = 'swift::' . $params['user'] . md5($params['bucket']); + $this->bucket = $params['bucket']; - private function init(){ - if($this->ready) { - return; + if (!isset($params['url'])) { + $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; } - $this->ready = true; - $this->auth = new \CF_Authentication($this->user, $this->token, null, $this->host); - $this->auth->authenticate(); + if (!isset($params['service_name'])) { + $params['service_name'] = 'cloudFiles'; + } - $this->conn = new \CF_Connection($this->auth); + $settings = array( + 'username' => $params['user'], + + ); - if ( ! $this->containerExists('/')) { - $this->rootContainer=$this->createContainer('/'); - } else { - $this->rootContainer=$this->getContainer('/'); + if (isset($params['password'])) { + $settings['password'] = $params['password']; + } else if (isset($params['key'])) { + $settings['apiKey'] = $params['key']; + } + + if (isset($params['tenant'])) { + $settings['tenantName'] = $params['tenant']; + } + + $this->anchor = new \OpenCloud\OpenStack($params['url'], $settings); + $this->connection = $this->anchor->ObjectStore($params['service_name'], $params['region'], 'publicURL'); + + try { + $this->container = $this->connection->Container($this->bucket); + } catch (Exceptions\ContainerNotFoundError $e) { + $this->container = $this->connection->Container(); + $this->container->Create(array('name' => $this->bucket)); + } + + if (!$this->file_exists('.')) { + $this->mkdir('.'); } } - public function getId(){ - return $this->id; - } - - public function mkdir($path) { - $this->init(); - if ($this->containerExists($path)) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { return false; - } else { - $this->createContainer($path); - return true; } + + if($path !== '.') { + $path .= '/'; + } + + try { + $object = $this->container->DataObject(); + $object->Create(array( + 'name' => $path, + 'content_type' => 'httpd/unix-directory' + )); + } catch (Exceptions\CreateUpdateError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + public function file_exists($path) { + $path = $this->normalizePath($path); + + if ($path !== '.' && $this->is_dir($path)) { + $path .= '/'; + } + + return $this->doesObjectExist($path); } public function rmdir($path) { - $this->init(); - if (!$this->containerExists($path)) { + $path = $this->normalizePath($path); + + if (!$this->is_dir($path)) { return false; - } else { - $this->emptyContainer($path); - if ($path!='' and $path!='/') { - $parentContainer=$this->getContainer(dirname($path)); - $this->removeSubContainer($parentContainer, basename($path)); + } + + $dh = $this->opendir($path); + while ($file = readdir($dh)) { + if ($file === '.' || $file === '..') { + continue; } - $this->conn->delete_container($this->getContainerName($path)); - unset($this->containers[$path]); - return true; - } - } - - private function emptyContainer($path) { - $container=$this->getContainer($path); - if (is_null($container)) { - return; - } - $subContainers=$this->getSubContainers($container); - foreach ($subContainers as $sub) { - if ($sub) { - $this->emptyContainer($path.'/'.$sub); - $this->conn->delete_container($this->getContainerName($path.'/'.$sub)); - unset($this->containers[$path.'/'.$sub]); + if ($this->is_dir($path . '/' . $file)) { + $this->rmdir($path . '/' . $file); + } else { + $this->unlink($path . '/' . $file); } } - $objects=$this->getObjects($container); - foreach ($objects as $object) { - $container->delete_object($object); - unset($this->objects[$path.'/'.$object]); + try { + $object = $this->container->DataObject($path . '/'); + $object->Delete(); + } catch (Exceptions\DeleteError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; } + + return true; } public function opendir($path) { - $this->init(); - $container=$this->getContainer($path); - $files=$this->getObjects($container); - $i=array_search(self::SUBCONTAINER_FILE, $files); - if ($i!==false) { - unset($files[$i]); + $path = $this->normalizePath($path); + + if ($path === '.') { + $path = ''; + } else { + $path .= '/'; } - $subContainers=$this->getSubContainers($container); - $files=array_merge($files, $subContainers); - $id=$this->getContainerName($path); - \OC\Files\Stream\Dir::register($id, $files); - return opendir('fakedir://'.$id); + + try { + $files = array(); + $objects = $this->container->ObjectList(array( + 'prefix' => $path + )); + + while ($object = $objects->Next()) { + $file = basename($object->Name()); + if ($file !== basename($path)) { + $files[] = $file; + } + } + + \OC\Files\Stream\Dir::register('swift' . $path, $files); + return opendir('fakedir://swift' . $path); + } catch (Exception $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + } + + public function stat($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path) && $path != '.') { + $path .= '/'; + } + + try { + $object = $this->container->DataObject($path); + } catch (Exceptions\ObjFetchError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + $mtime = $object->extra_headers['X-Timestamp']; + if (isset($object->extra_headers['X-Object-Meta-Timestamp'])) { + $mtime = $object->extra_headers['X-Object-Meta-Timestamp']; + } + + $stat = array(); + $stat['size'] = $object->content_length; + $stat['mtime'] = $mtime; + $stat['atime'] = time(); + return $stat; } public function filetype($path) { - $this->init(); - if ($this->containerExists($path)) { - return 'dir'; - } else { + $path = $this->normalizePath($path); + + if ($path !== '.' && $this->doesObjectExist($path)) { return 'file'; } + + if ($path !== '.') { + $path .= '/'; + } + + if ($this->doesObjectExist($path)) { + return 'dir'; + } } public function isReadable($path) { @@ -397,66 +270,44 @@ class SWIFT extends \OC\Files\Storage\Common{ return true; } - public function file_exists($path) { - $this->init(); - if ($this->is_dir($path)) { - return true; - } else { - return $this->objectExists($path); - } - } - - public function file_get_contents($path) { - $this->init(); - $obj=$this->getObject($path); - if (is_null($obj)) { - return false; - } - return $obj->read(); - } - - public function file_put_contents($path, $content) { - $this->init(); - $obj=$this->getObject($path); - if (is_null($obj)) { - $container=$this->getContainer(dirname($path)); - if (is_null($container)) { - return false; - } - $obj=$container->create_object(basename($path)); - } - $this->resetMTime($obj); - return $obj->write($content); - } - public function unlink($path) { - $this->init(); - if ($this->containerExists($path)) { - return $this->rmdir($path); - } - if ($this->objectExists($path)) { - $container=$this->getContainer(dirname($path)); - $container->delete_object(basename($path)); - unset($this->objects[$path]); - } else { + $path = $this->normalizePath($path); + + try { + $object = $this->container->DataObject($path); + $object->Delete(); + } catch (Exceptions\DeleteError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } catch (Exceptions\ObjFetchError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); return false; } + + return true; } public function fopen($path, $mode) { - $this->init(); - switch($mode) { + $path = $this->normalizePath($path); + + switch ($mode) { case 'r': case 'rb': - $obj=$this->getObject($path); - if (is_null($obj)) { + $tmpFile = \OC_Helper::tmpFile(); + self::$tmpFiles[$tmpFile] = $path; + try { + $object = $this->container->DataObject($path); + } catch (Exceptions\ObjFetchError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); return false; } - $fp = fopen('php://temp', 'r+'); - $obj->stream($fp); - - rewind($fp); - return $fp; + try { + $object->SaveToFilename($tmpFile); + } catch (Exceptions\IOError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + return fopen($tmpFile, 'r'); case 'w': case 'wb': case 'a': @@ -469,119 +320,166 @@ class SWIFT extends \OC\Files\Storage\Common{ case 'x+': case 'c': case 'c+': - $tmpFile=$this->getTmpFile($path); + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + $tmpFile = \OC_Helper::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - self::$tempFiles[$tmpFile]=$path; - return fopen('close://'.$tmpFile, $mode); + if ($this->file_exists($path)) { + $source = $this->fopen($path, 'r'); + file_put_contents($tmpFile, $source); + } + self::$tmpFiles[$tmpFile] = $path; + + return fopen('close://' . $tmpFile, $mode); } } - public function writeBack($tmpFile) { - if (isset(self::$tempFiles[$tmpFile])) { - $this->fromTmpFile($tmpFile, self::$tempFiles[$tmpFile]); - unlink($tmpFile); + public function getMimeType($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { + return 'httpd/unix-directory'; + } else if ($this->file_exists($path)) { + $object = $this->container->DataObject($path); + return $object->extra_headers["Content-Type"]; } + return false; } - public function touch($path, $mtime=null) { - $this->init(); - $obj=$this->getObject($path); - if (is_null($obj)) { - return false; - } - if (is_null($mtime)) { - $mtime=time(); - } + public function touch($path, $mtime = null) { + $path = $this->normalizePath($path); + if ($this->file_exists($path)) { + if ($this->is_dir($path) && $path != '.') { + $path .= '/'; + } - //emulate setting mtime with metadata - $obj->metadata['Mtime']=$mtime; - $obj->sync_metadata(); - } - - public function rename($path1, $path2) { - $this->init(); - $sourceContainer=$this->getContainer(dirname($path1)); - $targetContainer=$this->getContainer(dirname($path2)); - $result=$sourceContainer->move_object_to(basename($path1), $targetContainer, basename($path2)); - unset($this->objects[$path1]); - if ($result) { - $targetObj=$this->getObject($path2); - $this->resetMTime($targetObj); + $object = $this->container->DataObject($path); + if( is_null($mtime)) { + $mtime = time(); + } + $settings = array( + 'name' => $path, + 'extra_headers' => array( + 'X-Object-Meta-Timestamp' => $mtime + ) + ); + $object->Update($settings); + } else { + $object = $this->container->DataObject(); + if (is_null($mtime)) { + $mtime = time(); + } + $settings = array( + 'name' => $path, + 'content_type' => 'text/plain', + 'extra_headers' => array( + 'X-Object-Meta-Timestamp' => $mtime + ) + ); + $object->Create($settings); } - return $result; } public function copy($path1, $path2) { - $this->init(); - $sourceContainer=$this->getContainer(dirname($path1)); - $targetContainer=$this->getContainer(dirname($path2)); - $result=$sourceContainer->copy_object_to(basename($path1), $targetContainer, basename($path2)); - if ($result) { - $targetObj=$this->getObject($path2); - $this->resetMTime($targetObj); + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + if ($this->is_file($path1)) { + try { + $source = $this->container->DataObject($path1); + $target = $this->container->DataObject(); + $target->Create(array( + 'name' => $path2, + )); + $source->Copy($target); + } catch (Exceptions\ObjectCopyError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + } else { + if ($this->file_exists($path2)) { + return false; + } + + try { + $source = $this->container->DataObject($path1 . '/'); + $target = $this->container->DataObject(); + $target->Create(array( + 'name' => $path2 . '/', + )); + $source->Copy($target); + } catch (Exceptions\ObjectCopyError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + $dh = $this->opendir($path1); + while ($file = readdir($dh)) { + if ($file === '.' || $file === '..') { + continue; + } + + $source = $path1 . '/' . $file; + $target = $path2 . '/' . $file; + $this->copy($source, $target); + } } - return $result; + + return true; } - public function stat($path) { - $this->init(); - $container=$this->getContainer($path); - if ( ! is_null($container)) { - return array( - 'mtime'=>-1, - 'size'=>$container->bytes_used, - 'ctime'=>-1 - ); + public function rename($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + if ($this->is_file($path1)) { + if ($this->copy($path1, $path2) === false) { + return false; + } + + if ($this->unlink($path1) === false) { + $this->unlink($path2); + return false; + } + } else { + if ($this->file_exists($path2)) { + return false; + } + + if ($this->copy($path1, $path2) === false) { + return false; + } + + if ($this->rmdir($path1) === false) { + $this->rmdir($path2); + return false; + } } - $obj=$this->getObject($path); + return true; + } - if (is_null($obj)) { + public function getId() { + return $this->id; + } + + public function getConnection() { + return $this->connection; + } + + public function writeBack($tmpFile) { + if (!isset(self::$tmpFiles[$tmpFile])) { return false; } - if (isset($obj->metadata['Mtime']) and $obj->metadata['Mtime']>-1) { - $mtime=$obj->metadata['Mtime']; - } else { - $mtime=strtotime($obj->last_modified); - } - return array( - 'mtime'=>$mtime, - 'size'=>$obj->content_length, - 'ctime'=>-1, - ); - } - - private function getTmpFile($path) { - $this->init(); - $obj=$this->getObject($path); - if ( ! is_null($obj)) { - $tmpFile=\OCP\Files::tmpFile(); - $obj->save_to_filename($tmpFile); - return $tmpFile; - } else { - return \OCP\Files::tmpFile(); - } - } - - private function fromTmpFile($tmpFile, $path) { - $this->init(); - $obj=$this->getObject($path); - if (is_null($obj)) { - $obj=$this->createObject($path); - } - $obj->load_from_filename($tmpFile); - $this->resetMTime($obj); - } - - /** - * remove custom mtime metadata - * @param \CF_Object $obj - */ - private function resetMTime($obj) { - if (isset($obj->metadata['Mtime'])) { - $obj->metadata['Mtime']=-1; - $obj->sync_metadata(); - } + $object = $this->container->DataObject(); + $object->Create(array( + 'name' => self::$tmpFiles[$tmpFile], + 'content_type' => \OC_Helper::getMimeType($tmpFile) + ), $tmpFile); + unlink($tmpFile); } } From b92061da5ab2d11f803731628c4304aa8cd302e3 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Wed, 23 Oct 2013 07:57:41 +0200 Subject: [PATCH 07/36] make Swift::testLocal workable by fixing the prefix used with the object listing in opendir --- apps/files_external/lib/swift.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 981a118f18..2d236eded7 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -198,15 +198,18 @@ class Swift extends \OC\Files\Storage\Common { $path .= '/'; } + error_log($path."\n", 3, "/tmp/opendir"); + try { $files = array(); $objects = $this->container->ObjectList(array( - 'prefix' => $path + 'prefix' => $path . '/' )); while ($object = $objects->Next()) { $file = basename($object->Name()); if ($file !== basename($path)) { + error_log($file."\n", 3, "/tmp/opendir"); $files[] = $file; } } From a1e956263c3a02ec692894990f535e0304576c20 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Wed, 23 Oct 2013 08:01:32 +0200 Subject: [PATCH 08/36] Revert "make Swift::testLocal workable by fixing the prefix used with the object listing in opendir" This reverts commit b92061da5ab2d11f803731628c4304aa8cd302e3. --- apps/files_external/lib/swift.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 2d236eded7..981a118f18 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -198,18 +198,15 @@ class Swift extends \OC\Files\Storage\Common { $path .= '/'; } - error_log($path."\n", 3, "/tmp/opendir"); - try { $files = array(); $objects = $this->container->ObjectList(array( - 'prefix' => $path . '/' + 'prefix' => $path )); while ($object = $objects->Next()) { $file = basename($object->Name()); if ($file !== basename($path)) { - error_log($file."\n", 3, "/tmp/opendir"); $files[] = $file; } } From 128d3221cc6a0cb4f111b2c4c5024345a6b1dd10 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Wed, 23 Oct 2013 08:07:59 +0200 Subject: [PATCH 09/36] make Swift::testLocal workable by using a delimiter --- apps/files_external/lib/swift.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 981a118f18..fe28e124fe 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -201,7 +201,8 @@ class Swift extends \OC\Files\Storage\Common { try { $files = array(); $objects = $this->container->ObjectList(array( - 'prefix' => $path + 'prefix' => $path, + 'delimiter' => '/' )); while ($object = $objects->Next()) { From 1317b7c03dbbd98165ef29b50aa26bb1dd283cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 23 Oct 2013 18:39:37 +0200 Subject: [PATCH 10/36] pass the name of the item source from the browser to the server - no need to get the data via complicated db queries --- apps/files_sharing/js/share.js | 4 ++-- core/ajax/share.php | 3 ++- core/js/share.js | 24 +++++++++++++++---- lib/public/share.php | 44 ++++++++++++++++------------------ 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 68f6f3ba76..340e093944 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -35,14 +35,14 @@ $(document).ready(function() { if ($(tr).data('id') != $('#dropdown').attr('data-item-source')) { OC.Share.hideDropDown(function () { $(tr).addClass('mouseOver'); - OC.Share.showDropDown(itemType, $(tr).data('id'), appendTo, true, possiblePermissions); + OC.Share.showDropDown(itemType, $(tr).data('id'), appendTo, true, possiblePermissions, filename); }); } else { OC.Share.hideDropDown(); } } else { $(tr).addClass('mouseOver'); - OC.Share.showDropDown(itemType, $(tr).data('id'), appendTo, true, possiblePermissions); + OC.Share.showDropDown(itemType, $(tr).data('id'), appendTo, true, possiblePermissions, filename); } }); } diff --git a/core/ajax/share.php b/core/ajax/share.php index 0dacc17d3a..be02c05635 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -41,7 +41,8 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo $_POST['itemSource'], $shareType, $shareWith, - $_POST['permissions'] + $_POST['permissions'], + $_POST['itemSourceName'] ); if (is_string($token)) { diff --git a/core/js/share.js b/core/js/share.js index 281cccaaef..352ad4d4ca 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -136,8 +136,17 @@ OC.Share={ return data; }, - share:function(itemType, itemSource, shareType, shareWith, permissions, callback) { - $.post(OC.filePath('core', 'ajax', 'share.php'), { action: 'share', itemType: itemType, itemSource: itemSource, shareType: shareType, shareWith: shareWith, permissions: permissions }, function(result) { + share:function(itemType, itemSource, shareType, shareWith, permissions, itemSourceName, callback) { + $.post(OC.filePath('core', 'ajax', 'share.php'), + { + action: 'share', + itemType: itemType, + itemSource: itemSource, + shareType: shareType, + shareWith: shareWith, + permissions: permissions, + itemSourceName: itemSourceName + }, function (result) { if (result && result.status === 'success') { if (callback) { callback(result.data); @@ -170,9 +179,9 @@ OC.Share={ } }); }, - showDropDown:function(itemType, itemSource, appendTo, link, possiblePermissions) { + showDropDown:function(itemType, itemSource, appendTo, link, possiblePermissions, filename) { var data = OC.Share.loadItem(itemType, itemSource); - var html = '