* @copyright Copyright (c) 2008, Rackspace US, Inc. * @package php-cloudfiles-http */ /** */ require_once("cloudfiles_exceptions.php"); define("PHP_CF_VERSION", "1.7.10"); define("USER_AGENT", sprintf("PHP-CloudFiles/%s", PHP_CF_VERSION)); define("MAX_HEADER_NAME_LEN", 128); define("MAX_HEADER_VALUE_LEN", 256); define("ACCOUNT_CONTAINER_COUNT", "X-Account-Container-Count"); define("ACCOUNT_BYTES_USED", "X-Account-Bytes-Used"); define("CONTAINER_OBJ_COUNT", "X-Container-Object-Count"); define("CONTAINER_BYTES_USED", "X-Container-Bytes-Used"); define("MANIFEST_HEADER", "X-Object-Manifest"); define("METADATA_HEADER_PREFIX", "X-Object-Meta-"); define("CONTENT_HEADER_PREFIX", "Content-"); define("ACCESS_CONTROL_HEADER_PREFIX", "Access-Control-"); define("ORIGIN_HEADER", "Origin"); define("CDN_URI", "X-CDN-URI"); define("CDN_SSL_URI", "X-CDN-SSL-URI"); define("CDN_STREAMING_URI", "X-CDN-Streaming-URI"); define("CDN_ENABLED", "X-CDN-Enabled"); define("CDN_LOG_RETENTION", "X-Log-Retention"); define("CDN_ACL_USER_AGENT", "X-User-Agent-ACL"); define("CDN_ACL_REFERRER", "X-Referrer-ACL"); define("CDN_TTL", "X-TTL"); define("CDNM_URL", "X-CDN-Management-Url"); define("STORAGE_URL", "X-Storage-Url"); define("AUTH_TOKEN", "X-Auth-Token"); define("AUTH_USER_HEADER", "X-Auth-User"); define("AUTH_KEY_HEADER", "X-Auth-Key"); define("AUTH_USER_HEADER_LEGACY", "X-Storage-User"); define("AUTH_KEY_HEADER_LEGACY", "X-Storage-Pass"); define("AUTH_TOKEN_LEGACY", "X-Storage-Token"); define("CDN_EMAIL", "X-Purge-Email"); define("DESTINATION", "Destination"); define("ETAG_HEADER", "ETag"); define("LAST_MODIFIED_HEADER", "Last-Modified"); define("CONTENT_TYPE_HEADER", "Content-Type"); define("CONTENT_LENGTH_HEADER", "Content-Length"); define("USER_AGENT_HEADER", "User-Agent"); /** * HTTP/cURL wrapper for Cloud Files * * This class should not be used directly. It's only purpose is to abstract * out the HTTP communication from the main API. * * @package php-cloudfiles-http */ class CF_Http { private $error_str; private $dbug; private $cabundle_path; private $api_version; # Authentication instance variables # private $storage_url; private $cdnm_url; private $auth_token; # Request/response variables # private $response_status; private $response_reason; private $connections; # Variables used for content/header callbacks # private $_user_read_progress_callback_func; private $_user_write_progress_callback_func; private $_write_callback_type; private $_text_list; private $_account_container_count; private $_account_bytes_used; private $_container_object_count; private $_container_bytes_used; private $_obj_etag; private $_obj_last_modified; private $_obj_content_type; private $_obj_content_length; private $_obj_metadata; private $_obj_headers; private $_obj_manifest; private $_obj_write_resource; private $_obj_write_string; private $_cdn_enabled; private $_cdn_ssl_uri; private $_cdn_streaming_uri; private $_cdn_uri; private $_cdn_ttl; private $_cdn_log_retention; private $_cdn_acl_user_agent; private $_cdn_acl_referrer; function __construct($api_version) { $this->dbug = False; $this->cabundle_path = NULL; $this->api_version = $api_version; $this->error_str = NULL; $this->storage_url = NULL; $this->cdnm_url = NULL; $this->auth_token = NULL; $this->response_status = NULL; $this->response_reason = NULL; # Curl connections array - since there is no way to "re-set" the # connection paramaters for a cURL handle, we keep an array of # the unique use-cases and funnel all of those same type # requests through the appropriate curl connection. # $this->connections = array( "GET_CALL" => NULL, # GET objects/containers/lists "PUT_OBJ" => NULL, # PUT object "HEAD" => NULL, # HEAD requests "PUT_CONT" => NULL, # PUT container "DEL_POST" => NULL, # DELETE containers/objects, POST objects "COPY" => null, # COPY objects ); $this->_user_read_progress_callback_func = NULL; $this->_user_write_progress_callback_func = NULL; $this->_write_callback_type = NULL; $this->_text_list = array(); $this->_return_list = NULL; $this->_account_container_count = 0; $this->_account_bytes_used = 0; $this->_container_object_count = 0; $this->_container_bytes_used = 0; $this->_obj_write_resource = NULL; $this->_obj_write_string = ""; $this->_obj_etag = NULL; $this->_obj_last_modified = NULL; $this->_obj_content_type = NULL; $this->_obj_content_length = NULL; $this->_obj_metadata = array(); $this->_obj_manifest = NULL; $this->_obj_headers = NULL; $this->_cdn_enabled = NULL; $this->_cdn_ssl_uri = NULL; $this->_cdn_streaming_uri = NULL; $this->_cdn_uri = NULL; $this->_cdn_ttl = NULL; $this->_cdn_log_retention = NULL; $this->_cdn_acl_user_agent = NULL; $this->_cdn_acl_referrer = NULL; # The OS list with a PHP without an updated CA File for CURL to # connect to SSL Websites. It is the first 3 letters of the PHP_OS # variable. $OS_CAFILE_NONUPDATED=array( "win","dar" ); if (in_array((strtolower (substr(PHP_OS, 0,3))), $OS_CAFILE_NONUPDATED)) $this->ssl_use_cabundle(); } function ssl_use_cabundle($path=NULL) { if ($path) { $this->cabundle_path = $path; } else { $this->cabundle_path = dirname(__FILE__) . "/share/cacert.pem"; } if (!file_exists($this->cabundle_path)) { throw new IOException("Could not use CA bundle: " . $this->cabundle_path); } return; } # Uses separate cURL connection to authenticate # function authenticate($user, $pass, $acct=NULL, $host=NULL) { $path = array(); if (isset($acct)){ $headers = array( sprintf("%s: %s", AUTH_USER_HEADER_LEGACY, $user), sprintf("%s: %s", AUTH_KEY_HEADER_LEGACY, $pass), ); $path[] = $host; $path[] = rawurlencode(sprintf("v%d",$this->api_version)); $path[] = rawurlencode($acct); } else { $headers = array( sprintf("%s: %s", AUTH_USER_HEADER, $user), sprintf("%s: %s", AUTH_KEY_HEADER, $pass), ); $path[] = $host; } $path[] = "v1.0"; $url = implode("/", $path); $curl_ch = curl_init(); if (!is_null($this->cabundle_path)) { curl_setopt($curl_ch, CURLOPT_SSL_VERIFYPEER, True); curl_setopt($curl_ch, CURLOPT_CAINFO, $this->cabundle_path); } curl_setopt($curl_ch, CURLOPT_VERBOSE, $this->dbug); curl_setopt($curl_ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($curl_ch, CURLOPT_MAXREDIRS, 4); curl_setopt($curl_ch, CURLOPT_HEADER, 0); curl_setopt($curl_ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl_ch, CURLOPT_USERAGENT, USER_AGENT); curl_setopt($curl_ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($curl_ch, CURLOPT_HEADERFUNCTION,array(&$this,'_auth_hdr_cb')); curl_setopt($curl_ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($curl_ch, CURLOPT_URL, $url); curl_exec($curl_ch); curl_close($curl_ch); return array($this->response_status, $this->response_reason, $this->storage_url, $this->cdnm_url, $this->auth_token); } # (CDN) GET /v1/Account # function list_cdn_containers($enabled_only) { $conn_type = "GET_CALL"; $url_path = $this->_make_path("CDN"); $this->_write_callback_type = "TEXT_LIST"; if ($enabled_only) { $return_code = $this->_send_request($conn_type, $url_path . '/?enabled_only=true'); } else { $return_code = $this->_send_request($conn_type, $url_path); } if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,array()); } if ($return_code == 401) { return array($return_code,"Unauthorized",array()); } if ($return_code == 404) { return array($return_code,"Account not found.",array()); } if ($return_code == 204) { return array($return_code,"Account has no CDN enabled Containers.", array()); } if ($return_code == 200) { $this->create_array(); return array($return_code,$this->response_reason,$this->_text_list); } $this->error_str = "Unexpected HTTP response: ".$this->response_reason; return array($return_code,$this->error_str,array()); } # (CDN) DELETE /v1/Account/Container or /v1/Account/Container/Object # function purge_from_cdn($path, $email=null) { if(!$path) throw new SyntaxException("Path not set"); $url_path = $this->_make_path("CDN", NULL, $path); if($email) { $hdrs = array(CDN_EMAIL => $email); $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"DELETE"); } else $return_code = $this->_send_request("DEL_POST",$url_path,null,"DELETE"); return $return_code; } # (CDN) POST /v1/Account/Container function update_cdn_container($container_name, $ttl=86400, $cdn_log_retention=False, $cdn_acl_user_agent="", $cdn_acl_referrer) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $url_path = $this->_make_path("CDN", $container_name); $hdrs = array( CDN_ENABLED => "True", CDN_TTL => $ttl, CDN_LOG_RETENTION => $cdn_log_retention ? "True" : "False", CDN_ACL_USER_AGENT => $cdn_acl_user_agent, CDN_ACL_REFERRER => $cdn_acl_referrer, ); $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); if ($return_code == 401) { $this->error_str = "Unauthorized"; return array($return_code, $this->error_str, NULL); } if ($return_code == 404) { $this->error_str = "Container not found."; return array($return_code, $this->error_str, NULL); } if ($return_code != 202) { $this->error_str="Unexpected HTTP response: ".$this->response_reason; return array($return_code, $this->error_str, NULL); } return array($return_code, "Accepted", $this->_cdn_uri, $this->_cdn_ssl_uri); } # (CDN) PUT /v1/Account/Container # function add_cdn_container($container_name, $ttl=86400) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $url_path = $this->_make_path("CDN", $container_name); $hdrs = array( CDN_ENABLED => "True", CDN_TTL => $ttl, ); $return_code = $this->_send_request("PUT_CONT", $url_path, $hdrs); if ($return_code == 401) { $this->error_str = "Unauthorized"; return array($return_code,$this->response_reason,False); } if (!in_array($return_code, array(201,202))) { $this->error_str="Unexpected HTTP response: ".$this->response_reason; return array($return_code,$this->response_reason,False); } return array($return_code,$this->response_reason,$this->_cdn_uri, $this->_cdn_ssl_uri); } # (CDN) POST /v1/Account/Container # function remove_cdn_container($container_name) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $url_path = $this->_make_path("CDN", $container_name); $hdrs = array(CDN_ENABLED => "False"); $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); if ($return_code == 401) { $this->error_str = "Unauthorized"; return array($return_code, $this->error_str); } if ($return_code == 404) { $this->error_str = "Container not found."; return array($return_code, $this->error_str); } if ($return_code != 202) { $this->error_str="Unexpected HTTP response: ".$this->response_reason; return array($return_code, $this->error_str); } return array($return_code, "Accepted"); } # (CDN) HEAD /v1/Account # function head_cdn_container($container_name) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $conn_type = "HEAD"; $url_path = $this->_make_path("CDN", $container_name); $return_code = $this->_send_request($conn_type, $url_path, NULL, "GET", True); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); } if ($return_code == 401) { return array($return_code,"Unauthorized",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); } if ($return_code == 404) { return array($return_code,"Account not found.",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); } if ($return_code == 204) { return array($return_code,$this->response_reason, $this->_cdn_enabled, $this->_cdn_ssl_uri, $this->_cdn_streaming_uri, $this->_cdn_uri, $this->_cdn_ttl, $this->_cdn_log_retention, $this->_cdn_acl_user_agent, $this->_cdn_acl_referrer ); } return array($return_code,$this->response_reason, NULL,NULL,NULL,NULL, $this->_cdn_log_retention, $this->_cdn_acl_user_agent, $this->_cdn_acl_referrer, NULL ); } # GET /v1/Account # function list_containers($limit=0, $marker=NULL) { $conn_type = "GET_CALL"; $url_path = $this->_make_path(); $limit = intval($limit); $params = array(); if ($limit > 0) { $params[] = "limit=$limit"; } if ($marker) { $params[] = "marker=".rawurlencode($marker); } if (!empty($params)) { $url_path .= "?" . implode("&", $params); } $this->_write_callback_type = "TEXT_LIST"; $return_code = $this->_send_request($conn_type, $url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,array()); } if ($return_code == 204) { return array($return_code, "Account has no containers.", array()); } if ($return_code == 404) { $this->error_str = "Invalid account name for authentication token."; return array($return_code,$this->error_str,array()); } if ($return_code == 200) { $this->create_array(); return array($return_code, $this->response_reason, $this->_text_list); } $this->error_str = "Unexpected HTTP response: ".$this->response_reason; return array($return_code,$this->error_str,array()); } # GET /v1/Account?format=json # function list_containers_info($limit=0, $marker=NULL) { $conn_type = "GET_CALL"; $url_path = $this->_make_path() . "?format=json"; $limit = intval($limit); $params = array(); if ($limit > 0) { $params[] = "limit=$limit"; } if ($marker) { $params[] = "marker=".rawurlencode($marker); } if (!empty($params)) { $url_path .= "&" . implode("&", $params); } $this->_write_callback_type = "OBJECT_STRING"; $return_code = $this->_send_request($conn_type, $url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,array()); } if ($return_code == 204) { return array($return_code, "Account has no containers.", array()); } if ($return_code == 404) { $this->error_str = "Invalid account name for authentication token."; return array($return_code,$this->error_str,array()); } if ($return_code == 200) { $json_body = json_decode($this->_obj_write_string, True); return array($return_code, $this->response_reason, $json_body); } $this->error_str = "Unexpected HTTP response: ".$this->response_reason; return array($return_code,$this->error_str,array()); } # HEAD /v1/Account # function head_account() { $conn_type = "HEAD"; $url_path = $this->_make_path(); $return_code = $this->_send_request($conn_type,$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,0,0); } if ($return_code == 404) { return array($return_code,"Account not found.",0,0); } if ($return_code == 204) { return array($return_code,$this->response_reason, $this->_account_container_count, $this->_account_bytes_used); } return array($return_code,$this->response_reason,0,0); } # PUT /v1/Account/Container # function create_container($container_name) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $url_path = $this->_make_path("STORAGE", $container_name); $return_code = $this->_send_request("PUT_CONT",$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return False; } return $return_code; } # DELETE /v1/Account/Container # function delete_container($container_name) { if ($container_name == "") throw new SyntaxException("Container name not set."); if ($container_name != "0" and !isset($container_name)) throw new SyntaxException("Container name not set."); $url_path = $this->_make_path("STORAGE", $container_name); $return_code = $this->_send_request("DEL_POST",$url_path,array(),"DELETE"); switch ($return_code) { case 204: break; case 0: $this->error_str .= ": Failed to obtain valid HTTP response.";; break; case 409: $this->error_str = "Container must be empty prior to removing it."; break; case 404: $this->error_str = "Specified container did not exist to delete."; break; default: $this->error_str = "Unexpected HTTP return code: $return_code."; } return $return_code; } # GET /v1/Account/Container # function list_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) { if (!$cname) { $this->error_str = "Container name not set."; return array(0, $this->error_str, array()); } $url_path = $this->_make_path("STORAGE", $cname); $limit = intval($limit); $params = array(); if ($limit > 0) { $params[] = "limit=$limit"; } if ($marker) { $params[] = "marker=".rawurlencode($marker); } if ($prefix) { $params[] = "prefix=".rawurlencode($prefix); } if ($path) { $params[] = "path=".rawurlencode($path); } if (!empty($params)) { $url_path .= "?" . implode("&", $params); } $conn_type = "GET_CALL"; $this->_write_callback_type = "TEXT_LIST"; $return_code = $this->_send_request($conn_type,$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,array()); } if ($return_code == 204) { $this->error_str = "Container has no Objects."; return array($return_code,$this->error_str,array()); } if ($return_code == 404) { $this->error_str = "Container has no Objects."; return array($return_code,$this->error_str,array()); } if ($return_code == 200) { $this->create_array(); return array($return_code,$this->response_reason, $this->_text_list); } $this->error_str = "Unexpected HTTP response code: $return_code"; return array(0,$this->error_str,array()); } # GET /v1/Account/Container?format=json # function get_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) { if (!$cname) { $this->error_str = "Container name not set."; return array(0, $this->error_str, array()); } $url_path = $this->_make_path("STORAGE", $cname); $limit = intval($limit); $params = array(); $params[] = "format=json"; if ($limit > 0) { $params[] = "limit=$limit"; } if ($marker) { $params[] = "marker=".rawurlencode($marker); } if ($prefix) { $params[] = "prefix=".rawurlencode($prefix); } if ($path) { $params[] = "path=".rawurlencode($path); } if (!empty($params)) { $url_path .= "?" . implode("&", $params); } $conn_type = "GET_CALL"; $this->_write_callback_type = "OBJECT_STRING"; $return_code = $this->_send_request($conn_type,$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,array()); } if ($return_code == 204) { $this->error_str = "Container has no Objects."; return array($return_code,$this->error_str,array()); } if ($return_code == 404) { $this->error_str = "Container has no Objects."; return array($return_code,$this->error_str,array()); } if ($return_code == 200) { $json_body = json_decode($this->_obj_write_string, True); return array($return_code,$this->response_reason, $json_body); } $this->error_str = "Unexpected HTTP response code: $return_code"; return array(0,$this->error_str,array()); } # HEAD /v1/Account/Container # function head_container($container_name) { if ($container_name == "") { $this->error_str = "Container name not set."; return False; } if ($container_name != "0" and !isset($container_name)) { $this->error_str = "Container name not set."; return False; } $conn_type = "HEAD"; $url_path = $this->_make_path("STORAGE", $container_name); $return_code = $this->_send_request($conn_type,$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,0,0); } if ($return_code == 404) { return array($return_code,"Container not found.",0,0); } if ($return_code == 204 || $return_code == 200) { return array($return_code,$this->response_reason, $this->_container_object_count, $this->_container_bytes_used); } return array($return_code,$this->response_reason,0,0); } # GET /v1/Account/Container/Object # function get_object_to_string(&$obj, $hdrs=array()) { if (!is_object($obj) || get_class($obj) != "CF_Object") { throw new SyntaxException( "Method argument is not a valid CF_Object."); } $conn_type = "GET_CALL"; $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); $this->_write_callback_type = "OBJECT_STRING"; $return_code = $this->_send_request($conn_type,$url_path,$hdrs); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array($return_code0,$this->error_str,NULL); } if ($return_code == 404) { $this->error_str = "Object not found."; return array($return_code0,$this->error_str,NULL); } if (($return_code < 200) || ($return_code > 299 && $return_code != 412 && $return_code != 304)) { $this->error_str = "Unexpected HTTP return code: $return_code"; return array($return_code,$this->error_str,NULL); } return array($return_code,$this->response_reason, $this->_obj_write_string); } # GET /v1/Account/Container/Object # function get_object_to_stream(&$obj, &$resource=NULL, $hdrs=array()) { if (!is_object($obj) || get_class($obj) != "CF_Object") { throw new SyntaxException( "Method argument is not a valid CF_Object."); } if (!is_resource($resource)) { throw new SyntaxException( "Resource argument not a valid PHP resource."); } $conn_type = "GET_CALL"; $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); $this->_obj_write_resource = $resource; $this->_write_callback_type = "OBJECT_STREAM"; $return_code = $this->_send_request($conn_type,$url_path,$hdrs); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array($return_code,$this->error_str); } if ($return_code == 404) { $this->error_str = "Object not found."; return array($return_code,$this->error_str); } if (($return_code < 200) || ($return_code > 299 && $return_code != 412 && $return_code != 304)) { $this->error_str = "Unexpected HTTP return code: $return_code"; return array($return_code,$this->error_str); } return array($return_code,$this->response_reason); } # PUT /v1/Account/Container/Object # function put_object(&$obj, &$fp) { if (!is_object($obj) || get_class($obj) != "CF_Object") { throw new SyntaxException( "Method argument is not a valid CF_Object."); } if (!is_resource($fp)) { throw new SyntaxException( "File pointer argument is not a valid resource."); } $conn_type = "PUT_OBJ"; $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); $hdrs = $this->_headers($obj); $etag = $obj->getETag(); if (isset($etag)) { $hdrs[] = "ETag: " . $etag; } if (!$obj->content_type) { $hdrs[] = "Content-Type: application/octet-stream"; } else { $hdrs[] = "Content-Type: " . $obj->content_type; } $this->_init($conn_type); curl_setopt($this->connections[$conn_type], CURLOPT_INFILE, $fp); if (!$obj->content_length) { # We don''t know the Content-Length, so assumed "chunked" PUT # curl_setopt($this->connections[$conn_type], CURLOPT_UPLOAD, True); $hdrs[] = 'Transfer-Encoding: chunked'; } else { # We know the Content-Length, so use regular transfer # curl_setopt($this->connections[$conn_type], CURLOPT_INFILESIZE, $obj->content_length); } $return_code = $this->_send_request($conn_type,$url_path,$hdrs); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0,$this->error_str,NULL); } if ($return_code == 412) { $this->error_str = "Missing Content-Type header"; return array($return_code,$this->error_str,NULL); } if ($return_code == 422) { $this->error_str = "Derived and computed checksums do not match."; return array($return_code,$this->error_str,NULL); } if ($return_code != 201) { $this->error_str = "Unexpected HTTP return code: $return_code"; return array($return_code,$this->error_str,NULL); } return array($return_code,$this->response_reason,$this->_obj_etag); } # POST /v1/Account/Container/Object # function update_object(&$obj) { if (!is_object($obj) || get_class($obj) != "CF_Object") { throw new SyntaxException( "Method argument is not a valid CF_Object."); } # TODO: The is_array check isn't in sync with the error message if (!$obj->manifest && !(is_array($obj->metadata) || is_array($obj->headers))) { $this->error_str = "Metadata and headers arrays are empty."; return 0; } $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); $hdrs = $this->_headers($obj); $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); switch ($return_code) { case 202: break; case 0: $this->error_str .= ": Failed to obtain valid HTTP response."; $return_code = 0; break; case 404: $this->error_str = "Account, Container, or Object not found."; break; default: $this->error_str = "Unexpected HTTP return code: $return_code"; break; } return $return_code; } # HEAD /v1/Account/Container/Object # function head_object(&$obj) { if (!is_object($obj) || get_class($obj) != "CF_Object") { throw new SyntaxException( "Method argument is not a valid CF_Object."); } $conn_type = "HEAD"; $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); $return_code = $this->_send_request($conn_type,$url_path); if (!$return_code) { $this->error_str .= ": Failed to obtain valid HTTP response."; return array(0, $this->error_str." ".$this->response_reason, NULL, NULL, NULL, NULL, array(), NULL, array()); } if ($return_code == 404) { return array($return_code, $this->response_reason, NULL, NULL, NULL, NULL, array(), NULL, array()); } if ($return_code == 204 || $return_code == 200) { return array($return_code,$this->response_reason, $this->_obj_etag, $this->_obj_last_modified, $this->_obj_content_type, $this->_obj_content_length, $this->_obj_metadata, $this->_obj_manifest, $this->_obj_headers); } $this->error_str = "Unexpected HTTP return code: $return_code"; return array($return_code, $this->error_str." ".$this->response_reason, NULL, NULL, NULL, NULL, array(), NULL, array()); } # COPY /v1/Account/Container/Object # function copy_object($src_obj_name, $dest_obj_name, $container_name_source, $container_name_target, $metadata=NULL, $headers=NULL) { if (!$src_obj_name) { $this->error_str = "Object name not set."; return 0; } if ($container_name_source == "") { $this->error_str = "Container name source not set."; return 0; } if ($container_name_source != "0" and !isset($container_name_source)) { $this->error_str = "Container name source not set."; return 0; } if ($container_name_target == "") { $this->error_str = "Container name target not set."; return 0; } if ($container_name_target != "0" and !isset($container_name_target)) { $this->error_str = "Container name target not set."; return 0; } $conn_type = "COPY"; $url_path = $this->_make_path("STORAGE", $container_name_source, rawurlencode($src_obj_name)); $destination = rawurlencode($container_name_target."/".$dest_obj_name); $hdrs = self::_process_headers($metadata, $headers); $hdrs[DESTINATION] = $destination; $return_code = $this->_send_request($conn_type,$url_path,$hdrs,"COPY"); switch ($return_code) { case 201: break; case 0: $this->error_str .= ": Failed to obtain valid HTTP response."; $return_code = 0; break; case 404: $this->error_str = "Specified container/object did not exist."; break; default: $this->error_str = "Unexpected HTTP return code: $return_code."; } return $return_code; } # DELETE /v1/Account/Container/Object # function delete_object($container_name, $object_name) { if ($container_name == "") { $this->error_str = "Container name not set."; return 0; } if ($container_name != "0" and !isset($container_name)) { $this->error_str = "Container name not set."; return 0; } if (!$object_name) { $this->error_str = "Object name not set."; return 0; } $url_path = $this->_make_path("STORAGE", $container_name,$object_name); $return_code = $this->_send_request("DEL_POST",$url_path,NULL,"DELETE"); switch ($return_code) { case 204: break; case 0: $this->error_str .= ": Failed to obtain valid HTTP response."; $return_code = 0; break; case 404: $this->error_str = "Specified container did not exist to delete."; break; default: $this->error_str = "Unexpected HTTP return code: $return_code."; } return $return_code; } function get_error() { return $this->error_str; } function setDebug($bool) { $this->dbug = $bool; foreach ($this->connections as $k => $v) { if (!is_null($v)) { curl_setopt($this->connections[$k], CURLOPT_VERBOSE, $this->dbug); } } } function getCDNMUrl() { return $this->cdnm_url; } function getStorageUrl() { return $this->storage_url; } function getAuthToken() { return $this->auth_token; } function setCFAuth($cfs_auth, $servicenet=False) { if ($servicenet) { $this->storage_url = "https://snet-" . substr($cfs_auth->storage_url, 8); } else { $this->storage_url = $cfs_auth->storage_url; } $this->auth_token = $cfs_auth->auth_token; $this->cdnm_url = $cfs_auth->cdnm_url; } function setReadProgressFunc($func_name) { $this->_user_read_progress_callback_func = $func_name; } function setWriteProgressFunc($func_name) { $this->_user_write_progress_callback_func = $func_name; } private function _header_cb($ch, $header) { $header_len = strlen($header); if (preg_match("/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches)) { $this->response_status = $matches[2]; $this->response_reason = $matches[3]; return $header_len; } if (strpos($header, ":") === False) return $header_len; list($name, $value) = explode(":", $header, 2); $value = trim($value); switch (strtolower($name)) { case strtolower(CDN_ENABLED): $this->_cdn_enabled = strtolower($value) == "true"; break; case strtolower(CDN_URI): $this->_cdn_uri = $value; break; case strtolower(CDN_SSL_URI): $this->_cdn_ssl_uri = $value; break; case strtolower(CDN_STREAMING_URI): $this->_cdn_streaming_uri = $value; break; case strtolower(CDN_TTL): $this->_cdn_ttl = $value; break; case strtolower(MANIFEST_HEADER): $this->_obj_manifest = $value; break; case strtolower(CDN_LOG_RETENTION): $this->_cdn_log_retention = strtolower($value) == "true"; break; case strtolower(CDN_ACL_USER_AGENT): $this->_cdn_acl_user_agent = $value; break; case strtolower(CDN_ACL_REFERRER): $this->_cdn_acl_referrer = $value; break; case strtolower(ACCOUNT_CONTAINER_COUNT): $this->_account_container_count = (float)$value+0; break; case strtolower(ACCOUNT_BYTES_USED): $this->_account_bytes_used = (float)$value+0; break; case strtolower(CONTAINER_OBJ_COUNT): $this->_container_object_count = (float)$value+0; break; case strtolower(CONTAINER_BYTES_USED): $this->_container_bytes_used = (float)$value+0; break; case strtolower(ETAG_HEADER): $this->_obj_etag = $value; break; case strtolower(LAST_MODIFIED_HEADER): $this->_obj_last_modified = $value; break; case strtolower(CONTENT_TYPE_HEADER): $this->_obj_content_type = $value; break; case strtolower(CONTENT_LENGTH_HEADER): $this->_obj_content_length = (float)$value+0; break; case strtolower(ORIGIN_HEADER): $this->_obj_headers[ORIGIN_HEADER] = $value; break; default: if (strncasecmp($name, METADATA_HEADER_PREFIX, strlen(METADATA_HEADER_PREFIX)) == 0) { $name = substr($name, strlen(METADATA_HEADER_PREFIX)); $this->_obj_metadata[$name] = $value; } elseif ((strncasecmp($name, CONTENT_HEADER_PREFIX, strlen(CONTENT_HEADER_PREFIX)) == 0) || (strncasecmp($name, ACCESS_CONTROL_HEADER_PREFIX, strlen(ACCESS_CONTROL_HEADER_PREFIX)) == 0)) { $this->_obj_headers[$name] = $value; } } return $header_len; } private 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; } private function _write_cb($ch, $data) { $dlen = strlen($data); switch ($this->_write_callback_type) { case "TEXT_LIST": $this->_return_list = $this->_return_list . $data; //= explode("\n",$data); # keep tab,space //his->_text_list[] = rtrim($data,"\n\r\x0B"); # keep tab,space break; case "OBJECT_STREAM": fwrite($this->_obj_write_resource, $data, $dlen); break; case "OBJECT_STRING": $this->_obj_write_string .= $data; break; } if (isset($this->_user_read_progress_callback_func)) { call_user_func($this->_user_read_progress_callback_func, $dlen); } return $dlen; } private function _auth_hdr_cb($ch, $header) { preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches); if (isset($matches[1])) { $this->response_status = $matches[1]; } if (isset($matches[2])) { $this->response_reason = $matches[2]; } if (stripos($header, STORAGE_URL) === 0) { $this->storage_url = trim(substr($header, strlen(STORAGE_URL)+1)); } if (stripos($header, CDNM_URL) === 0) { $this->cdnm_url = trim(substr($header, strlen(CDNM_URL)+1)); } if (stripos($header, AUTH_TOKEN) === 0) { $this->auth_token = trim(substr($header, strlen(AUTH_TOKEN)+1)); } if (stripos($header, AUTH_TOKEN_LEGACY) === 0) { $this->auth_token = trim(substr($header,strlen(AUTH_TOKEN_LEGACY)+1)); } return strlen($header); } private function _make_headers($hdrs=NULL) { $new_headers = array(); $has_stoken = False; $has_uagent = False; if (is_array($hdrs)) { foreach ($hdrs as $h => $v) { if (is_int($h)) { list($h, $v) = explode(":", $v, 2); } if (strncasecmp($h, AUTH_TOKEN, strlen(AUTH_TOKEN)) === 0) { $has_stoken = True; } if (strncasecmp($h, USER_AGENT_HEADER, strlen(USER_AGENT_HEADER)) === 0) { $has_uagent = True; } $new_headers[] = $h . ": " . trim($v); } } if (!$has_stoken) { $new_headers[] = AUTH_TOKEN . ": " . $this->auth_token; } if (!$has_uagent) { $new_headers[] = USER_AGENT_HEADER . ": " . USER_AGENT; } return $new_headers; } private function _init($conn_type, $force_new=False) { if (!array_key_exists($conn_type, $this->connections)) { $this->error_str = "Invalid CURL_XXX connection type"; return False; } if (is_null($this->connections[$conn_type]) || $force_new) { $ch = curl_init(); } else { return; } if ($this->dbug) { curl_setopt($ch, CURLOPT_VERBOSE, 1); } if (!is_null($this->cabundle_path)) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); curl_setopt($ch, CURLOPT_CAINFO, $this->cabundle_path); } curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_MAXREDIRS, 4); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this, '_header_cb')); if ($conn_type == "GET_CALL") { curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$this, '_write_cb')); } if ($conn_type == "PUT_OBJ") { curl_setopt($ch, CURLOPT_PUT, 1); curl_setopt($ch, CURLOPT_READFUNCTION, array(&$this, '_read_cb')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); } if ($conn_type == "HEAD") { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD"); curl_setopt($ch, CURLOPT_NOBODY, 1); } if ($conn_type == "PUT_CONT") { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); curl_setopt($ch, CURLOPT_INFILESIZE, 0); curl_setopt($ch, CURLOPT_NOBODY, 1); } if ($conn_type == "DEL_POST") { curl_setopt($ch, CURLOPT_NOBODY, 1); } if ($conn_type == "COPY") { curl_setopt($ch, CURLOPT_NOBODY, 1); } $this->connections[$conn_type] = $ch; return; } private function _reset_callback_vars() { $this->_text_list = array(); $this->_return_list = NULL; $this->_account_container_count = 0; $this->_account_bytes_used = 0; $this->_container_object_count = 0; $this->_container_bytes_used = 0; $this->_obj_etag = NULL; $this->_obj_last_modified = NULL; $this->_obj_content_type = NULL; $this->_obj_content_length = NULL; $this->_obj_metadata = array(); $this->_obj_manifest = NULL; $this->_obj_headers = NULL; $this->_obj_write_string = ""; $this->_cdn_streaming_uri = NULL; $this->_cdn_enabled = NULL; $this->_cdn_ssl_uri = NULL; $this->_cdn_uri = NULL; $this->_cdn_ttl = NULL; $this->response_status = 0; $this->response_reason = ""; } private function _make_path($t="STORAGE",$c=NULL,$o=NULL) { $path = array(); switch ($t) { case "STORAGE": $path[] = $this->storage_url; break; case "CDN": $path[] = $this->cdnm_url; break; } if ($c == "0") $path[] = rawurlencode($c); if ($c) { $path[] = rawurlencode($c); } if ($o) { # mimic Python''s urllib.quote() feature of a "safe" '/' character # $path[] = str_replace("%2F","/",rawurlencode($o)); } return implode("/",$path); } private function _headers(&$obj) { $hdrs = self::_process_headers($obj->metadata, $obj->headers); if ($obj->manifest) $hdrs[MANIFEST_HEADER] = $obj->manifest; return $hdrs; } private function _process_headers($metadata=null, $headers=null) { $rules = array( array( 'prefix' => METADATA_HEADER_PREFIX, ), array( 'prefix' => '', 'filter' => array( # key order is important, first match decides CONTENT_TYPE_HEADER => false, CONTENT_LENGTH_HEADER => false, CONTENT_HEADER_PREFIX => true, ACCESS_CONTROL_HEADER_PREFIX => true, ORIGIN_HEADER => true, ), ), ); $hdrs = array(); $argc = func_num_args(); $argv = func_get_args(); for ($argi = 0; $argi < $argc; $argi++) { if(!is_array($argv[$argi])) continue; $rule = $rules[$argi]; foreach ($argv[$argi] as $k => $v) { $k = trim($k); $v = trim($v); if (strpos($k, ":") !== False) throw new SyntaxException( "Header names cannot contain a ':' character."); if (array_key_exists('filter', $rule)) { $result = null; foreach ($rule['filter'] as $p => $f) { if (strncasecmp($k, $p, strlen($p)) == 0) { $result = $f; break; } } if (!$result) throw new SyntaxException(sprintf( "Header name %s is not allowed", $k)); } $k = $rule['prefix'] . $k; if (strlen($k) > MAX_HEADER_NAME_LEN || strlen($v) > MAX_HEADER_VALUE_LEN) throw new SyntaxException(sprintf( "Header %s exceeds maximum length: %d/%d", $k, strlen($k), strlen($v))); $hdrs[$k] = $v; } } return $hdrs; } private function _send_request($conn_type, $url_path, $hdrs=NULL, $method="GET", $force_new=False) { $this->_init($conn_type, $force_new); $this->_reset_callback_vars(); $headers = $this->_make_headers($hdrs); if (gettype($this->connections[$conn_type]) == "unknown type") throw new ConnectionNotOpenException ( "Connection is not open." ); switch ($method) { case "COPY": curl_setopt($this->connections[$conn_type], CURLOPT_CUSTOMREQUEST, "COPY"); break; case "DELETE": curl_setopt($this->connections[$conn_type], CURLOPT_CUSTOMREQUEST, "DELETE"); break; case "POST": curl_setopt($this->connections[$conn_type], CURLOPT_CUSTOMREQUEST, "POST"); default: break; } curl_setopt($this->connections[$conn_type], CURLOPT_HTTPHEADER, $headers); curl_setopt($this->connections[$conn_type], CURLOPT_URL, $url_path); if (!curl_exec($this->connections[$conn_type]) && curl_errno($this->connections[$conn_type]) !== 0) { $this->error_str = "(curl error: " . curl_errno($this->connections[$conn_type]) . ") "; $this->error_str .= curl_error($this->connections[$conn_type]); return False; } return curl_getinfo($this->connections[$conn_type], CURLINFO_HTTP_CODE); } function close() { foreach ($this->connections as $cnx) { if (isset($cnx)) { curl_close($cnx); $this->connections[$cnx] = NULL; } } } private function create_array() { $this->_text_list = explode("\n",rtrim($this->_return_list,"\n\x0B")); return True; } } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * c-hanging-comment-ender-p: nil * End: */ ?>