cookieFile = drupal_tempnam(variable_get('file_temporary_path'), 'services_cookiejar'); // Load the cookie file when initializing Curl. $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile; } /** * Perform GET request. */ protected function servicesGet($url, $data = NULL, $headers = array()) { $options = array('query' => $data); $url = url($this->getAbsoluteUrl($url) . '.php', $options); $content = $this->curlExec(array( CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_HEADER => TRUE, CURLOPT_HTTPHEADER => $headers )); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content); $this->verbose('GET request to: ' . $url . '
headers: ' . highlight_string('Arguments: ' . highlight_string('Response: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /** * Post file as multipart/form-data. */ protected function servicesPostFile($url, $filepath, $headers = array(), $additional_arguments = array()) { $this->addCSRFHeader($headers); if (!is_array($filepath)) { $filepath = array($filepath); } // Add .php to get serialized response. $url = $this->getAbsoluteUrl($url) . '.php'; // Otherwise Services will reject arguments. $headers[] = "Content-type: multipart/form-data"; // Prepare arguments. $post = $additional_arguments; $i = 0; foreach ($filepath as $path) { $post['files[file_contents' . $i . ']'] = '@' . variable_get('file_public_path', '') . '/' . file_uri_target($path); $i++; } $content = $this->curlExec(array( CURLOPT_URL => $url, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers, CURLOPT_HEADER => TRUE, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_FOLLOWLOCATION => TRUE, CURLOPT_VERBOSE => TRUE, )); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content); $this->verbose('POST request to: ' . $url . '
File Name(s): ' . highlight_string('Response: ' . highlight_string('Curl info: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /** * Perform POST request. */ protected function servicesPost($url, $data = array(), $headers = array(), $call_type = 'php') { $this->addCSRFHeader($headers); switch ($call_type) { case 'php': // Add .php to get serialized response. $url = $this->getAbsoluteUrl($url) . '.php'; // Otherwise Services will reject arguments. $headers[] = "Content-type: application/x-www-form-urlencoded"; // Prepare arguments. $post = drupal_http_build_query($data, '', '&'); break; case 'json': // Add .json to get json encoded response. $url = $this->getAbsoluteUrl($url) . '.json'; // Set proper headers. $headers[] = "Content-type: application/json"; // Prepare arguments. $post = json_encode($data); break; } $content = $this->curlExec(array( CURLOPT_URL => $url, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers, CURLOPT_HEADER => TRUE, CURLOPT_RETURNTRANSFER => TRUE )); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content, $call_type); $this->verbose('POST request to: ' . $url . '
Arguments: ' . highlight_string('Raw POST body: ' . $post . '
Response: ' . highlight_string('Curl info: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /** * Perform PUT request. */ protected function servicesPut($url, $data = NULL, $headers = array(), $call_type = 'php') { $this->addCSRFHeader($headers); switch ($call_type) { case 'php': // Add .php to get serialized response. $url = $this->getAbsoluteUrl($url) . '.php'; // Otherwise Services will reject arguments. $headers[] = "Content-type: application/x-www-form-urlencoded"; // Prepare arguments. $post = drupal_http_build_query($data, '', '&'); break; case 'json': // Add .json to get json encoded response. $url = $this->getAbsoluteUrl($url) . '.json'; // Set proper headers. $headers[] = "Content-type: application/json"; // Prepare arguments. $post = json_encode($data); break; } // Emulate file. $putData = fopen('php://temp', 'rw+'); fwrite($putData, $post); fseek($putData, 0); $content = $this->curlExec(array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_PUT => TRUE, CURLOPT_HEADER => TRUE, CURLOPT_HTTPHEADER => $headers, CURLOPT_INFILE => $putData, CURLOPT_INFILESIZE => drupal_strlen($post) )); fclose($putData); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content, $call_type); $this->verbose('PUT request to: ' . $url . '
Arguments: ' . highlight_string('Raw POST body: ' . $post . '
Response: ' . highlight_string('Curl info: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /** * Perform DELETE request. */ protected function servicesDelete($url, $data = NULL, $headers = array()) { $this->addCSRFHeader($headers); $options = array('query' => $data); $url = url($this->getAbsoluteUrl($url) . '.php', $options); $content = $this->curlExec(array( CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => "DELETE", CURLOPT_HEADER => TRUE, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => TRUE )); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content); $this->verbose('DELETE request to: ' . $url . '
Arguments: ' . highlight_string('Response: ' . highlight_string('Curl info: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /** * Perform HEAD request. */ protected function servicesHead($url) { $url = url($this->getAbsoluteUrl($url) . '.php'); $content = $this->curlExec(array( CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_HEADER => TRUE, CURLOPT_RETURNTRANSFER => TRUE )); // Parse response. list($info, $header, $status, $code, $body) = $this->parseHeader($content); $this->verbose('HEAD request to: ' . $url . '
Curl info: ' . highlight_string('Raw response: ' . $content); return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body); } /* ------------------------------------ HELPER METHODS ------------------------------------ */ /** * Parse header. * * @param type $content * @return type */ function parseHeader($content, $call_type = 'php') { $info = curl_getinfo($this->curlHandle); $header = drupal_substr($content, 0, $info['header_size']); $header = str_replace("HTTP/1.1 100 Continue\r\n\r\n", '', $header); $status = strtok($header, "\r\n"); $code = $info['http_code']; $raw_body = drupal_substr($content, $info['header_size'], drupal_strlen($content) - $info['header_size']); switch ($call_type) { case 'php': $body = unserialize($raw_body); break; case 'json': $body = json_decode($raw_body); break; } return array($info, $header, $status, $code, $body); } /** * Retrieve and set CSFR token header. * * @param array $headers */ function addCSRFHeader(&$headers) { $csrf_token = $this->drupalGet('services/session/token'); $headers[] = 'X-CSRF-Token: ' . $csrf_token; } /** * Creates a data array for populating an endpoint creation form. * * @return * An array of fields for fully populating an endpoint creation form. */ public function populateEndpointFAPI() { return array( 'name' => strtolower($this->randomName(10)), 'path' => $this->randomName(10), 'server' => 'rest_server', ); } public function saveNewEndpoint() { $edit = $this->populateEndpointFAPI() ; $endpoint = new stdClass; $endpoint->disabled = FALSE; /* Edit this to true to make a default endpoint disabled initially */ $endpoint->api_version = 3; $endpoint->name = $edit['name']; $endpoint->server = $edit['server']; $endpoint->path = $edit['path']; $endpoint->authentication = array( 'services' => 'services', ); $endpoint->server_settings = array( 'formatters' => array( 'json' => TRUE, 'bencode' => TRUE, 'rss' => TRUE, 'plist' => TRUE, 'xmlplist' => TRUE, 'php' => TRUE, 'yaml' => TRUE, 'jsonp' => FALSE, 'xml' => FALSE, ), 'parsers' => array( 'application/x-yaml' => TRUE, 'application/json' => TRUE, 'application/vnd.php.serialized' => TRUE, 'application/plist' => TRUE, 'application/plist+xml' => TRUE, 'application/x-www-form-urlencoded' => TRUE, 'multipart/form-data' => TRUE, ), ); $endpoint->resources = array( 'comment' => array( 'operations' => array( 'create' => array( 'enabled' => 1, ), 'retrieve' => array( 'enabled' => 1, ), 'update' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'actions' => array( 'countAll' => array( 'enabled' => 1, ), 'countNew' => array( 'enabled' => 1, ), ), ), 'file' => array( 'operations' => array( 'create' => array( 'enabled' => 1, ), 'retrieve' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'actions' => array( 'create_raw' => array( 'enabled' => 1, ), ), ), 'node' => array( 'operations' => array( 'retrieve' => array( 'enabled' => 1, ), 'create' => array( 'enabled' => 1, ), 'update' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'relationships' => array( 'files' => array( 'enabled' => 1, ), 'comments' => array( 'enabled' => 1, ), ), 'targeted_actions' => array( 'attach_file' => array( 'enabled' => 1, ), ), ), 'system' => array( 'actions' => array( 'connect' => array( 'enabled' => 1, ), 'get_variable' => array( 'enabled' => 1, ), 'set_variable' => array( 'enabled' => 1, ), 'del_variable' => array( 'enabled' => 1, ), ), ), 'taxonomy_term' => array( 'operations' => array( 'retrieve' => array( 'enabled' => 1, ), 'create' => array( 'enabled' => 1, ), 'update' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'actions' => array( 'selectNodes' => array( 'enabled' => 1, ), ), ), 'taxonomy_vocabulary' => array( 'operations' => array( 'retrieve' => array( 'enabled' => 1, ), 'create' => array( 'enabled' => 1, ), 'update' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'actions' => array( 'getTree' => array( 'enabled' => 1, ), ), ), 'user' => array( 'operations' => array( 'retrieve' => array( 'enabled' => 1, ), 'create' => array( 'enabled' => 1, ), 'update' => array( 'enabled' => 1, ), 'delete' => array( 'enabled' => 1, ), 'index' => array( 'enabled' => 1, ), ), 'actions' => array( 'login' => array( 'enabled' => 1, ), 'logout' => array( 'enabled' => '1', 'settings' => array( 'services' => array( 'resource_api_version' => '1.1', ), ), ), 'register' => array( 'enabled' => 1, ), 'request_new_password' => array( 'enabled' => 1, ), ), 'targeted_actions' => array( 'cancel' => array( 'enabled' => 1, ), 'password_reset' => array( 'enabled' => 1, ), 'resend_welcome_email' => array( 'enabled' => 1, ), ), ), ); $endpoint->debug = 1; $endpoint->export_type = FALSE; services_endpoint_save($endpoint); $endpoint = services_endpoint_load($endpoint->name); $this->assertTrue($endpoint->name == $edit['name'], 'Endpoint successfully created'); return $endpoint; } public function saveNewVersionEndpoint($version = '1.0') { $edit = $this->populateEndpointFAPI() ; $endpoint = new stdClass(); $endpoint->disabled = FALSE; /* Edit this to true to make a default endpoint disabled initially */ $endpoint->api_version = 3; $endpoint->name = $edit['name']; $endpoint->server = $edit['server']; $endpoint->path = $edit['path']; $endpoint->authentication = array( 'services' => array(), ); $endpoint->server_settings = array( 'formatters' => array( 'bencode' => TRUE, 'json' => TRUE, 'php' => TRUE, 'plist' => TRUE, 'rss' => TRUE, 'xml' => TRUE, 'xmlplist' => TRUE, 'jsonp' => FALSE, ), 'parsers' => array( 'application/json' => TRUE, 'application/plist' => TRUE, 'application/plist+xml' => TRUE, 'application/vnd.php.serialized' => TRUE, 'multipart/form-data' => TRUE, 'application/x-www-form-urlencoded' => TRUE, ), ); $endpoint->resources = array( 'comment' => array( 'operations' => array( 'create' => array( 'enabled' => '1', ), 'retrieve' => array( 'enabled' => '1', ), 'update' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'actions' => array( 'countAll' => array( 'enabled' => '1', ), 'countNew' => array( 'enabled' => '1', ), ), ), 'file' => array( 'operations' => array( 'create' => array( 'enabled' => '1', ), 'retrieve' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'actions' => array( 'create_raw' => array( 'enabled' => '1', ), ), ), 'node' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', ), 'create' => array( 'enabled' => '1', ), 'update' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'relationships' => array( 'files' => array( 'enabled' => '1', ), 'comments' => array( 'enabled' => '1', ), ), ), 'system' => array( 'actions' => array( 'connect' => array( 'enabled' => '1', ), 'get_variable' => array( 'enabled' => '1', ), 'set_variable' => array( 'enabled' => '1', ), 'del_variable' => array( 'enabled' => '1', ), ), ), 'taxonomy_term' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', ), 'create' => array( 'enabled' => '1', ), 'update' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'actions' => array( 'selectNodes' => array( 'enabled' => '1', ), ), ), 'taxonomy_vocabulary' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', ), 'create' => array( 'enabled' => '1', ), 'update' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'actions' => array( 'getTree' => array( 'enabled' => '1', ), ), ), 'user' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', ), 'create' => array( 'enabled' => '1', ), 'update' => array( 'enabled' => '1', ), 'delete' => array( 'enabled' => '1', ), 'index' => array( 'enabled' => '1', ), ), 'actions' => array( 'login' => array( 'enabled' => '1', 'settings' => array( 'services' => array( 'resource_api_version' => $version, ), ), ), 'logout' => array( 'enabled' => '1', ), 'register' => array( 'enabled' => '1', ), ), 'targeted_actions' => array( 'cancel' => array( 'enabled' => 1, ), 'password_reset' => array( 'enabled' => 1, ), 'resend_welcome_email' => array( 'enabled' => 1, ), ), ), 'services_test' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', 'settings' => array( 'services' => array( 'resource_api_version' => $version, ), ), ), ), ), 'views' => array( 'operations' => array( 'retrieve' => array( 'enabled' => '1', ), ), ), ); $endpoint->debug = 0; $endpoint->export_type = FALSE; services_endpoint_save($endpoint); $endpoint = services_endpoint_load($endpoint->name); $this->assertTrue($endpoint->name == $edit['name'], 'Endpoint successfully created'); return $endpoint; } /** * Performs a cURL exec with the specified options after calling curlConnect(). * * @param $curl_options * Custom cURL options. * @return * Content returned from the exec. */ protected function curlExec($curl_options, $redirect = FALSE) { // Some Curl options might leave the handle in a state where subsequent // request can cause warnings or even weird failures, so to be on the safe // side we reinitialize Curl for each request. $this->curlClose(); $this->curlInitialize(); // cURL incorrectly handles URLs with a fragment by including the // fragment in the request to the server, causing some web servers // to reject the request citing "400 - Bad Request". To prevent // this, we strip the fragment from the request. // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) { $original_url = $curl_options[CURLOPT_URL]; $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#'); } $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; if (!empty($curl_options[CURLOPT_POST])) { // This is a fix for the Curl library to prevent Expect: 100-continue // headers in POST requests, that may cause unexpected HTTP response // codes from some webservers (like lighttpd that returns a 417 error // code). It is done by setting an empty "Expect" header field that is // not overwritten by Curl. $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:'; } curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); if (!$redirect) { // Reset headers, the session ID and the redirect counter. $this->session_id = NULL; $this->headers = array(); $this->redirect_count = 0; } $content = curl_exec($this->curlHandle); $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); // cURL incorrectly handles URLs with fragments, so instead of // letting cURL handle redirects we take of them ourselves to // to prevent fragments being sent to the web server as part // of the request. // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. if (in_array($status, array(300, 301, 302, 303, 305, 307)) && $this->redirect_count < variable_get('simpletest_maximum_redirects', 5)) { if ($this->drupalGetHeader('location')) { $this->redirect_count++; $curl_options = array(); $curl_options[CURLOPT_URL] = $this->drupalGetHeader('location'); $curl_options[CURLOPT_HTTPGET] = TRUE; return $this->curlExec($curl_options, TRUE); } } $this->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); // Analyze the method for log message. $method = ''; if (!empty($curl_options[CURLOPT_NOBODY])) { $method = 'HEAD'; } if (empty($method) && !empty($curl_options[CURLOPT_PUT])) { $method = 'PUT'; } if (empty($method) && !empty($curl_options[CURLOPT_CUSTOMREQUEST])) { $method = $curl_options[CURLOPT_CUSTOMREQUEST]; } if (empty($method)) { $method = empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'; } $message_vars = array( '!method' => $method, '@url' => isset($original_url) ? $original_url : $url, '@status' => $status, '!length' => format_size(drupal_strlen($this->drupalGetContent())) ); $message = format_string('!method @url returned @status (!length).', $message_vars); $this->assertTrue($this->drupalGetContent() !== FALSE, $message, 'Browser'); return $this->drupalGetContent(); } /** * Default values of comment for creating. */ public function getCommentValues($nid) { return array( 'subject' => $this->randomString(), 'comment_body' => array( LANGUAGE_NONE => array( array( 'value' => $this->randomString(), 'format' => filter_default_format(), ) ) ), 'name' => $this->privileged_user->name, 'language' => LANGUAGE_NONE, 'nid' => $nid, 'uid' => $this->privileged_user->uid, 'cid' => NULL, 'pid' => 0, ); } }