| <?php |
| /** |
| * Copyright 2007 Google 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. |
| */ |
| /** |
| */ |
| namespace google\appengine\api\cloud_storage; |
| |
| use google\appengine\BlobstoreServiceError\ErrorCode; |
| use google\appengine\CreateEncodedGoogleStorageKeyRequest; |
| use google\appengine\CreateEncodedGoogleStorageKeyResponse; |
| use google\appengine\CreateUploadURLRequest; |
| use google\appengine\CreateUploadURLResponse; |
| use google\appengine\ImagesDeleteUrlBaseRequest; |
| use google\appengine\ImagesDeleteUrlBaseResponse; |
| use google\appengine\ImagesGetUrlBaseRequest; |
| use google\appengine\ImagesGetUrlBaseResponse; |
| use google\appengine\ImagesServiceError; |
| use google\appengine\ext\cloud_storage_streams\CloudStorageClient; |
| use google\appengine\ext\cloud_storage_streams\CloudStorageStreamWrapper; |
| use google\appengine\files\GetDefaultGsBucketNameRequest; |
| use google\appengine\files\GetDefaultGsBucketNameResponse; |
| use google\appengine\runtime\ApiProxy; |
| use google\appengine\runtime\ApplicationError; |
| use google\appengine\util\ArrayUtil; |
| use google\appengine\util\StringUtil; |
| |
| /** |
| * CloudStorageTools allows the user to create and serve data with |
| * <a href="http://cloud.google.com/products/cloud-storage">Google Cloud Storage |
| * </a>. |
| */ |
| final class CloudStorageTools { |
| const GS_PREFIX = 'gs://'; |
| const BLOB_KEY_HEADER = "X-AppEngine-BlobKey"; |
| const BLOB_RANGE_HEADER = "X-AppEngine-BlobRange"; |
| const MAX_IMAGE_SERVING_SIZE = 1600; |
| |
| // The GCS endpoint path when running in the dev appserver. |
| const LOCAL_ENDPOINT = "/_ah/gcs"; |
| |
| // The storage host when running in production |
| // - The subdomain format is more secure but does not work for HTTPS if the |
| // bucket name contains ".". This is becuase the wildcard SSL certificate |
| // used by GCS can only validate one level of subdomain. |
| // - The path format is less secure and should only be used for the specific |
| // case when the subdomain format fails. |
| const PRODUCTION_HOST_SUBDOMAIN_FORMAT = "%s.storage.googleapis.com"; |
| const PRODUCTION_HOST_PATH_FORMAT = "storage.googleapis.com"; |
| |
| // The GCS filename format (bucket, object). |
| const GS_FILENAME_FORMAT = "gs://%s/%s"; |
| |
| /** |
| * The list of options that can be supplied to createUploadUrl. |
| * @see CloudStorageTools::createUploadUrl() |
| * @var array |
| */ |
| private static $create_upload_url_options = ['gs_bucket_name', |
| 'max_bytes_per_blob', 'max_bytes_total']; |
| |
| /** |
| * The list of options that can be suppied to serve. |
| * @var array |
| */ |
| private static $serve_options = ['content_type', 'save_as', 'start', 'end', |
| 'use_range']; |
| |
| private static $get_image_serving_url_default_options = [ |
| 'crop' => false, |
| 'secure_url' => false, |
| 'size' => null, |
| ]; |
| |
| /** |
| * Workaround for the 'Cannot modify header information' problem when |
| * trying to send headers from unit tests. If set, then $send_header is |
| * expected to be a closure that accepts a key, value pair where key is the |
| * header name, and value is the header value. |
| */ |
| private static $send_header = null; |
| |
| /** |
| * Object names may contain characters that need to be percent-encoded when |
| * building the URL. All characters allowed for bucket name are URL-safe. See |
| * https://developers.google.com/storage/docs/bucketnaming#requirements for |
| * more details. |
| */ |
| private static $url_path_translation_map = [ |
| ' ' => '%20', |
| '#' => '%23', |
| '%' => '%25', |
| '?' => '%3F', |
| ]; |
| |
| /** |
| * Create an absolute URL that can be used by a user to asynchronously upload |
| * a large blob. Upon completion of the upload, a callback is made to the |
| * specified URL. |
| * |
| * @param string $success_path A relative URL which will be invoked after the |
| * user successfully uploads a blob. |
| * @param mixed[] $options A key value pair array of upload options. Valid |
| * options are:<ul> |
| * <li>'max_bytes_per_blob': integer The value of the largest size that any |
| * one uploaded blob may be. Default value: unlimited. |
| * <li>'max_bytes_total': integer The value that is the total size that sum of |
| * all uploaded blobs may be. Default value: unlimited. |
| * <li>'gs_bucket_name': string The name of a Google Cloud Storage |
| * bucket that the blobs should be uploaded to. Not specifying a value |
| * will result in the blob being uploaded to the application's default |
| * bucket. |
| * </ul> |
| * @return string The upload URL. |
| * |
| * @throws \InvalidArgumentException If $success_path is not valid, or one of |
| * the options is not valid. |
| * @throws CloudStorageException Thrown when there is a failure using the |
| * blobstore service. |
| */ |
| public static function createUploadUrl($success_path, $options = array()) { |
| $req = new CreateUploadURLRequest(); |
| $resp = new CreateUploadURLResponse(); |
| |
| if (!is_string($success_path)) { |
| throw new \InvalidArgumentException('$success_path must be a string'); |
| } |
| |
| $req->setSuccessPath($success_path); |
| $max_upload_size_ini = self::getUploadMaxFileSizeInBytes(); |
| |
| if (array_key_exists('max_bytes_per_blob', $options)) { |
| $val = $options['max_bytes_per_blob']; |
| if (!is_int($val)) { |
| throw new \InvalidArgumentException( |
| 'max_bytes_per_blob must be an integer'); |
| } |
| if ($val < 1) { |
| throw new \InvalidArgumentException( |
| 'max_bytes_per_blob must be positive.'); |
| } |
| $req->setMaxUploadSizePerBlobBytes($val); |
| } else if ($max_upload_size_ini > 0) { |
| $req->setMaxUploadSizePerBlobBytes($max_upload_size_ini); |
| } |
| |
| if (array_key_exists('max_bytes_total', $options)) { |
| $val = $options['max_bytes_total']; |
| if (!is_int($val)) { |
| throw new \InvalidArgumentException( |
| 'max_bytes_total must be an integer'); |
| } |
| if ($val < 1) { |
| throw new \InvalidArgumentException( |
| 'max_bytes_total must be positive.'); |
| } |
| $req->setMaxUploadSizeBytes($val); |
| } |
| |
| if (array_key_exists('gs_bucket_name', $options)) { |
| $val = $options['gs_bucket_name']; |
| if (!is_string($val)) { |
| throw new \InvalidArgumentException('gs_bucket_name must be a string'); |
| } |
| $req->setGsBucketName($val); |
| } else { |
| $bucket = self::getDefaultGoogleStorageBucketName(); |
| |
| if (!$bucket) { |
| throw new \InvalidArgumentException( |
| 'Application does not have a default Cloud Storage Bucket, ' . |
| 'gs_bucket_name must be specified'); |
| } |
| $req->setGsBucketName($bucket); |
| } |
| |
| $extra_options = array_diff(array_keys($options), |
| self::$create_upload_url_options); |
| |
| if (!empty($extra_options)) { |
| throw new \InvalidArgumentException('Invalid options supplied: ' . |
| htmlspecialchars(implode(',', $extra_options))); |
| } |
| |
| try { |
| ApiProxy::makeSyncCall('blobstore', 'CreateUploadURL', $req, $resp); |
| } catch (ApplicationError $e) { |
| throw self::applicationErrorToException($e); |
| } |
| return $resp->getUrl(); |
| } |
| |
| /** |
| * Returns a URL that serves an image. |
| * |
| * @param string $gs_filename The name of the Google Cloud Storage object to |
| * serve. In the format gs://bucket_name/object_name |
| * |
| * @param mixed[] $options Array of additional options for serving the object. |
| * Valid options are: |
| * <ul> |
| * <li>'crop': boolean Whether the image should be cropped. If set to true, a |
| * size must also be supplied. Default value: false. |
| * <li>'secure_url': boolean Whether to request an https URL. Default value: |
| * false. |
| * <li>'size': integer The size of the longest dimension of the resulting |
| * image. Size must be in the range 0 to 1600, with 0 specifying the size of |
| * the original image. The aspect ratio is preserved unless 'crop' is |
| * specified. |
| * </ul> |
| * @return string The image serving URL. |
| * |
| * @throws \InvalidArgumentException if any of the arguments are not valid. |
| * @throws CloudStorageException If there was a problem contacting the |
| * service. |
| */ |
| public static function getImageServingUrl($gs_filename, $options = []) { |
| $blob_key = self::createGsKey($gs_filename); |
| if (!is_array($options)) { |
| throw new \InvalidArgumentException('$options must be an array. ' . |
| 'Actual type: ' . gettype($options)); |
| } |
| |
| $extra_options = array_diff(array_keys($options), array_keys( |
| self::$get_image_serving_url_default_options)); |
| if (!empty($extra_options)) { |
| throw new \InvalidArgumentException('Invalid options supplied: ' . |
| htmlspecialchars(implode(',', $extra_options))); |
| } |
| $options = array_merge(self::$get_image_serving_url_default_options, |
| $options); |
| |
| # Validate options. |
| if (!is_bool($options['crop'])) { |
| throw new \InvalidArgumentException( |
| '$options[\'crop\'] must be a boolean. ' . |
| 'Actual type: ' . gettype($options['crop'])); |
| } |
| if ($options['crop'] && is_null($options['size'])) { |
| throw new \InvalidArgumentException( |
| '$options[\'size\'] must be set because $options[\'crop\'] is true.'); |
| } |
| if (!is_null($options['size'])) { |
| $size = $options['size']; |
| if (!is_int($size)) { |
| throw new \InvalidArgumentException( |
| '$options[\'size\'] must be an integer. ' . |
| 'Actual type: ' . gettype($size)); |
| } |
| if ($size < 0 || $size > self::MAX_IMAGE_SERVING_SIZE) { |
| throw new \InvalidArgumentException( |
| '$options[\'size\'] must be >= 0 and <= ' . |
| self::MAX_IMAGE_SERVING_SIZE . '. Actual value: ' . $size); |
| } |
| } |
| if (!is_bool($options['secure_url'])) { |
| throw new \InvalidArgumentException( |
| '$options[\'secure_url\'] must be a boolean. ' . |
| 'Actual type: ' . gettype($options['secure_url'])); |
| } |
| |
| $req = new ImagesGetUrlBaseRequest(); |
| $resp = new ImagesGetUrlBaseResponse(); |
| $req->setBlobKey($blob_key); |
| $req->setCreateSecureUrl($options['secure_url']); |
| |
| try { |
| ApiProxy::makeSyncCall('images', |
| 'GetUrlBase', |
| $req, |
| $resp); |
| } catch (ApplicationError $e) { |
| throw self::imagesApplicationErrorToException($e); |
| } |
| $url = $resp->getUrl(); |
| if (!is_null($options['size'])) { |
| $url .= ('=s' . $options['size']); |
| if ($options['crop']) { |
| $url .= '-c'; |
| } |
| } |
| return $url; |
| } |
| |
| /** |
| * Deletes an image serving URL that was created using getImageServingUrl. |
| * |
| * @param string $gs_filename The name of the Google Cloud Storage object |
| * that has an existing URL to delete. In the format |
| * gs://bucket_name/object_name |
| * |
| * @throws \InvalidArgumentException if any of the arguments are not valid. |
| * @throws CloudStorageException If there was a problem contacting the |
| * service. |
| */ |
| public static function deleteImageServingUrl($gs_filename) { |
| $blob_key = self::createGsKey($gs_filename); |
| $req = new ImagesDeleteUrlBaseRequest(); |
| $resp = new ImagesDeleteUrlBaseResponse(); |
| $req->setBlobKey($blob_key); |
| |
| try { |
| ApiProxy::makeSyncCall('images', |
| 'DeleteUrlBase', |
| $req, |
| $resp); |
| } catch (ApplicationError $e) { |
| throw self::imagesApplicationErrorToException($e); |
| } |
| } |
| |
| /** |
| * Get the public URL for a Google Cloud Storage filename. |
| * |
| * @param string $gs_filename The Google Cloud Storage filename, in the |
| * format gs://bucket_name/object_name. |
| * @param boolean $use_https If True then return a HTTPS URL. Note that the |
| * development server ignores this argument and returns only HTTP URLs. |
| * |
| * @return string The public URL. |
| * |
| * @throws \InvalidArgumentException if the filename is not in the correct |
| * format or $use_https is not a boolean. |
| */ |
| public static function getPublicUrl($gs_filename, $use_https) { |
| if (!is_bool($use_https)) { |
| throw new \InvalidArgumentException( |
| 'Parameter $use_https must be boolean but was ' . |
| typeOrClass($use_https)); |
| } |
| |
| if (!self::parseFilename($gs_filename, $bucket, $object)) { |
| throw new \InvalidArgumentException( |
| sprintf('Invalid Google Cloud Storage filename: %s', |
| htmlspecialchars($gs_filename))); |
| } |
| |
| if (self::isDevelServer()) { |
| $scheme = 'http'; |
| $host = getenv('HTTP_HOST'); |
| $path = sprintf('%s/%s%s', self::LOCAL_ENDPOINT, $bucket, $object); |
| } else { |
| // Use path format for HTTPS URL when the bucket name contains "." to |
| // avoid SSL certificate validation issue. |
| if ($use_https && strpos($bucket, '.') !== false) { |
| $host = self::PRODUCTION_HOST_PATH_FORMAT; |
| $path = sprintf('/%s%s', $bucket, $object); |
| } else { |
| $host = sprintf(self::PRODUCTION_HOST_SUBDOMAIN_FORMAT, $bucket); |
| $path = strlen($object) > 0 ? $object : '/'; |
| } |
| |
| $scheme = $use_https ? 'https' : 'http'; |
| } |
| |
| return sprintf('%s://%s%s', |
| $scheme, |
| $host, |
| strtr($path, self::$url_path_translation_map)); |
| } |
| |
| /** |
| * Get the filename of a Google Cloud Storage object. |
| * |
| * @param string $bucket The Google Cloud Storage bucket name. |
| * @param string $object The Google Cloud Stroage object name. |
| * |
| * @return string The filename in the format gs://bucket_name/object_name. |
| * |
| * @throws \InvalidArgumentException if bucket or object name is invalid. |
| */ |
| public static function getFilename($bucket, $object) { |
| if (self::validateBucketName($bucket) === false) { |
| throw new \InvalidArgumentException( |
| sprintf('Invalid cloud storage bucket name \'%s\'', |
| htmlspecialchars($bucket))); |
| } |
| |
| if (self::validateObjectName($object) === false) { |
| throw new \InvalidArgumentException( |
| sprintf('Invalid cloud storage object name \'%s\'', |
| htmlspecialchars($object))); |
| } |
| |
| return sprintf(self::GS_FILENAME_FORMAT, $bucket, $object); |
| } |
| |
| /** |
| * Parse and extract the bucket and object names from the supplied filename. |
| * |
| * @param string $filename The filename in the format gs://bucket_name or |
| * gs://bucket_name/object_name. |
| * @param string &$bucket The extracted bucket. |
| * @param string &$object The extracted bucket. Can be null if the filename |
| * contains only bucket name. |
| * |
| * @return bool true if the filename is successfully parsed, false otherwise. |
| */ |
| public static function parseFilename($filename, &$bucket, &$object) { |
| $bucket = null; |
| $object = null; |
| |
| // $filename may contain nasty characters like # and ? that can throw off |
| // parse_url(). It is best to do a manual parse here. |
| $gs_prefix_len = strlen(self::GS_PREFIX); |
| if (!StringUtil::startsWith($filename, self::GS_PREFIX)) { |
| return false; |
| } |
| |
| $first_slash_pos = strpos($filename, '/', $gs_prefix_len); |
| if ($first_slash_pos === false) { |
| $bucket = substr($filename, $gs_prefix_len); |
| } else { |
| $bucket = substr($filename, $gs_prefix_len, |
| $first_slash_pos - $gs_prefix_len); |
| // gs://bucket_name/ is treated the same as gs://bucket_name where |
| // $object should be set to null. |
| if ($first_slash_pos != strlen($filename) - 1) { |
| $object = substr($filename, $first_slash_pos); |
| } |
| } |
| |
| if (strlen($bucket) == 0) { |
| return false; |
| } |
| |
| // Validate bucket & object names. |
| if (self::validateBucketName($bucket) === false) { |
| trigger_error(sprintf('Invalid cloud storage bucket name \'%s\'', |
| $bucket), E_USER_ERROR); |
| return false; |
| } |
| |
| if (isset($object) && self::validateObjectName($object) === false) { |
| trigger_error(sprintf('Invalid cloud storage object name \'%s\'', |
| $object), E_USER_ERROR); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Validate the bucket name according to the rules stated at |
| * https://developers.google.com/storage/docs/bucketnaming. |
| */ |
| private static function validateBucketName($bucket_name) { |
| $valid_bucket_regex = '/^[a-z0-9]+[a-z0-9\.\-_]+[a-z0-9]+$/'; |
| if (preg_match($valid_bucket_regex, $bucket_name) === 0) { |
| return false; |
| } |
| |
| if (strpos($bucket_name, 'goog') === 0) { |
| return false; |
| } |
| |
| if (strlen($bucket_name) > 222) { |
| return false; |
| } |
| |
| $parts = explode('.', $bucket_name); |
| foreach ($parts as $part) { |
| if (strlen($part) < 3 || strlen($part) > 63) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Validate the object name according to the rules stated at |
| * https://developers.google.com/storage/docs/bucketnaming. |
| */ |
| private static function validateObjectName($object_name) { |
| $invalid_object_regex = "/[\n\r]/"; |
| if (preg_match($invalid_object_regex, $object_name) === 1) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Create a blob key for a Google Cloud Storage file. |
| * |
| * @param string $filename The google cloud storage filename, in the format |
| * gs://bucket_name/object_name |
| * |
| * @return string A blob key for this filename that can be used in other API |
| * calls. |
| * |
| * @throws \InvalidArgumentException if the filename is not in the correct |
| * format. |
| * @throws CloudStorageException If there was a problem contacting the |
| * service. |
| * @deprecated This method will be made private in the next version. |
| */ |
| private static function createGsKey($filename) { |
| $gs_filename = sprintf('/gs/%s', self::stripGsPrefix($filename)); |
| |
| $request = new CreateEncodedGoogleStorageKeyRequest(); |
| $response = new CreateEncodedGoogleStorageKeyResponse(); |
| |
| $request->setFilename($gs_filename); |
| |
| try { |
| ApiProxy::makeSyncCall('blobstore', |
| 'CreateEncodedGoogleStorageKey', |
| $request, |
| $response); |
| } catch (ApplicationError $e) { |
| throw self::applicationErrorToException($e); |
| } |
| |
| return $response->getBlobKey(); |
| } |
| |
| /** |
| * Serve a Google Cloud Storage file as the response. |
| * |
| * @param string $gs_filename The name of the Google Cloud Storage object to |
| * serve. |
| * @param mixed[] $options Array of additional options for serving the object. |
| * <ul> |
| * <li>'content_type': string Content-Type to override when known. |
| * <li>'save_as': boolean If True then send the file as an attachment. |
| * <li>'start': int Start index of content-range to send. |
| * <li>'end': int End index of content-range to send. End index is |
| * inclusive. |
| * <li>'use_range': boolean Use provided content range from the request's |
| * Range header. Mutually exclusive with start and end. |
| * </ul> |
| * |
| * @throws \InvalidArgumentException If invalid options are supplied. |
| */ |
| public static function serve($gs_filename, $options = []) { |
| $extra_options = array_diff(array_keys($options), self::$serve_options); |
| |
| if (!empty($extra_options)) { |
| throw new \InvalidArgumentException('Invalid options supplied: ' . |
| htmlspecialchars(implode(',', $extra_options))); |
| } |
| |
| // Determine the range to send |
| $start = ArrayUtil::findByKeyOrNull($options, "start"); |
| $end = ArrayUtil::findByKeyOrNull($options, "end"); |
| $use_range = ArrayUtil::findByKeyOrNull($options, "use_range"); |
| $request_range_header = ArrayUtil::findByKeyOrNull($_SERVER, "HTTP_RANGE"); |
| |
| $range_header = self::checkRanges($start, |
| $end, |
| $use_range, |
| $request_range_header); |
| |
| $save_as = ArrayUtil::findByKeyOrNull($options, "save_as"); |
| if (isset($save_as) && !is_string($save_as)) { |
| throw new \InvalidArgumentException("Unexpected value for save_as."); |
| } |
| |
| $blob_key = self::createGsKey($gs_filename); |
| self::sendHeader(self::BLOB_KEY_HEADER, $blob_key); |
| |
| if (isset($range_header)) { |
| self::sendHeader(self::BLOB_RANGE_HEADER, $range_header); |
| } |
| |
| $content_type = ArrayUtil::findByKeyOrNull($options, "content_type"); |
| if (isset($content_type)) { |
| self::sendHeader("Content-Type", $content_type); |
| } |
| |
| if (isset($save_as)) { |
| self::sendHeader("Content-Disposition", sprintf( |
| "attachment; filename=%s", $save_as)); |
| } |
| } |
| |
| /** |
| * Return the name of the default Google Cloud Storage bucket for the |
| * application, if one has been configured. |
| * |
| * @return string The bucket name, or an empty string if no bucket has been |
| * configured. |
| */ |
| public static function getDefaultGoogleStorageBucketName() { |
| $request = new GetDefaultGsBucketNameRequest(); |
| $response = new GetDefaultGsBucketNameResponse(); |
| |
| ApiProxy::makeSyncCall('file', |
| 'GetDefaultGsBucketName', |
| $request, |
| $response); |
| |
| return $response->getDefaultGsBucketName(); |
| } |
| |
| /** |
| * This function is used for unit testing only, it allows replacement of the |
| * send_header function that is used to set headers on the response. |
| * |
| * @param mixed $new_header_func The function to use to set response headers. |
| * Set to null to use the inbuilt PHP method header(). |
| */ |
| public static function setSendHeaderFunction($new_header_func) { |
| self::$send_header = $new_header_func; |
| } |
| |
| /** |
| * Get metadata from a Google Cloud Storage file pointer resource. |
| * |
| * @param resource $handle A Google Cloud Storage file pointer resource that |
| * is typically created using fopen(). |
| * |
| * @return array An array that maps metadata keys to values. |
| * |
| * @throws \InvalidArgumentException If $handler is not a Google Cloud Storage |
| * file pointer resource. |
| */ |
| public static function getMetaData($handle) { |
| $wrapper = self::getStreamWrapperFromFileHandle($handle); |
| return $wrapper->getMetaData(); |
| } |
| |
| /** |
| * Get content type from a Google Cloud Storage file pointer resource. |
| * |
| * @param resource $handle A Google Cloud Storage file pointer resource that |
| * is typically created using fopen(). |
| * |
| * @return string The content type of the Google Cloud Storage object. |
| * |
| * @throws \InvalidArgumentException If $handler is not a Google Cloud Storage |
| * file pointer resource. |
| */ |
| public static function getContentType($handle) { |
| $wrapper = self::getStreamWrapperFromFileHandle($handle); |
| return $wrapper->getContentType(); |
| } |
| |
| private static function getStreamWrapperFromFileHandle($handle) { |
| $wrapper = stream_get_meta_data($handle)['wrapper_data']; |
| if (!$wrapper instanceof CloudStorageStreamWrapper) { |
| throw new \InvalidArgumentException( |
| '$handle must be a Google Cloud Storage file pointer resource'); |
| } |
| |
| return $wrapper; |
| } |
| |
| /** |
| * Validates the format of a GCS filename and strips the gs:// prefix. |
| * |
| * @param string $filename The google cloud storage filename, in the format |
| * gs://bucket_name/object_name |
| * |
| * @return string The string that follows gs:// |
| * |
| * @throws \InvalidArgumentException if the filename is not in the correct |
| * format. |
| */ |
| private static function stripGsPrefix($filename) { |
| if (!is_string($filename)) { |
| throw new \InvalidArgumentException('filename must be a string. ' . |
| 'Actual type: ' . gettype($filename)); |
| } |
| |
| $gs_prefix_len = strlen(self::GS_PREFIX); |
| |
| if (strncmp($filename, self::GS_PREFIX, $gs_prefix_len) != 0) { |
| throw new \InvalidArgumentException( |
| sprintf('filename must start with the prefix %s.', self::GS_PREFIX)); |
| } |
| |
| $stripped = substr($filename, $gs_prefix_len); |
| |
| if (!strpos($stripped, "/")) { |
| throw new \InvalidArgumentException( |
| 'filename not in the format gs://bucket_name/object_name.'); |
| } |
| |
| return $stripped; |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function applicationErrorToException($error) { |
| switch($error->getApplicationError()) { |
| case ErrorCode::URL_TOO_LONG: |
| return new \InvalidArgumentException( |
| 'The upload URL supplied was too long.'); |
| case ErrorCode::PERMISSION_DENIED: |
| return new CloudStorageException('Permission Denied'); |
| case ErrorCode::ARGUMENT_OUT_OF_RANGE: |
| return new \InvalidArgumentException($error->getMessage()); |
| default: |
| return new CloudStorageException( |
| 'Error Code: ' . $error->getApplicationError()); |
| } |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function imagesApplicationErrorToException($error) { |
| switch($error->getApplicationError()) { |
| case ImagesServiceError\ErrorCode::UNSPECIFIED_ERROR: |
| return new CloudStorageException('Unspecified error with image.'); |
| case ImagesServiceError\ErrorCode::BAD_TRANSFORM_DATA: |
| return new CloudStorageException('Bad image transform data.'); |
| case ImagesServiceError\ErrorCode::NOT_IMAGE: |
| return new CloudStorageException('Not an image.'); |
| case ImagesServiceError\ErrorCode::BAD_IMAGE_DATA: |
| return new CloudStorageException('Bad image data.'); |
| case ImagesServiceError\ErrorCode::IMAGE_TOO_LARGE: |
| return new CloudStorageException('Image too large.'); |
| case ImagesServiceError\ErrorCode::INVALID_BLOB_KEY: |
| return new CloudStorageException('Invalid blob key for image.'); |
| case ImagesServiceError\ErrorCode::ACCESS_DENIED: |
| return new CloudStorageException('Access denied to image.'); |
| case ImagesServiceError\ErrorCode::OBJECT_NOT_FOUND: |
| return new CloudStorageException('Image object not found.'); |
| default: |
| return new CloudStorageException( |
| 'Images Error Code: ' . $error->getApplicationError()); |
| } |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function checkRanges($start, $end, $use_range, $range_header) { |
| if ($end && !$start) { |
| throw new \InvalidArgumentException( |
| "May not specify an end range value without a start value."); |
| } |
| |
| $use_indexes = isset($start); |
| if ($use_indexes) { |
| if (isset($end)) { |
| if ($start > $end) { |
| throw new \InvalidArgumentException( |
| sprintf( |
| "Start range (%d) cannot be greater than the end range (%d).", |
| $start, |
| $end)); |
| } |
| if ($start < 0) { |
| throw new \InvalidArgumentException( |
| sprintf("The start range (%d) cannot be less than 0.", $start)); |
| } |
| } |
| $range_indexes = self::serializeRange($start, $end); |
| } |
| |
| // If both headers and index parameters are in use they must be the same. |
| if ($use_range && $use_indexes) { |
| if (strcmp($range_header, $range_indexes) != 0) { |
| throw new \InvalidArgumentException( |
| sprintf("May not provide non-equivalent range indexes and " . |
| "range headers: (header) %s != (indexes) %s.", |
| $range_header, |
| $range_indexes)); |
| } |
| } |
| |
| if ($use_range && isset($range_header)) { |
| return $range_header; |
| } else if ($use_indexes) { |
| return $range_indexes; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function serializeRange($start, $end) { |
| if ($start < 0) { |
| $range_str = sprintf('%d', $start); |
| } else if (!isset($end)) { |
| $range_str = sprintf("%d-", $start); |
| } else { |
| $range_str = sprintf("%d-%d", $start, $end); |
| } |
| return sprintf("bytes=%s", $range_str); |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function sendHeader($key, $value) { |
| if (isset(self::$send_header)) { |
| call_user_func(self::$send_header, $key, $value); |
| } else { |
| header(sprintf("%s: %s", $key, $value)); |
| } |
| } |
| |
| /** |
| * @access private |
| */ |
| private static function getUploadMaxFileSizeInBytes() { |
| $val = trim(ini_get('upload_max_filesize')); |
| $unit = strtolower(substr($val, -1)); |
| switch ($unit) { |
| case 'g': |
| $val *= 1024; |
| // Fall through |
| case 'm': |
| $val *= 1024; |
| // Fall through |
| case 'k': |
| $val *= 1024; |
| break; |
| } |
| return intval($val); |
| } |
| |
| /** |
| * Determine if the code is executing on the development server. |
| * |
| * @return bool True if running in the developement server, false otherwise. |
| */ |
| private static function isDevelServer() { |
| $server_software = getenv("SERVER_SOFTWARE"); |
| $key = "Development"; |
| return strncmp($server_software, $key, strlen($key)) === 0; |
| } |
| } |