/** * Helper function to get a formatter and apply a method. * * @param string $method * A valid method to call on the FormatterInterface object. * @param array $data * The array of data to process. * @param string $formatter_name * The name of the formatter for the current resource. Leave it NULL to use * the Accept headers. * * @return string * The processed output. */ protected function processData($method, array $data, $formatter_name = NULL) { if ($resource = $this->resource) { $request = $resource->getRequest(); } else { $request = restful()->getRequest(); } $accept = $request->getHeaders()->get('accept')->getValueString(); $formatter = $this->negotiateFormatter($accept, $formatter_name); $output = ResourceManager::executeCallback(array($formatter, $method), array($data, $formatter_name)); // The content type header is modified after the massaging if there is // an error code. Therefore we need to set the content type header after // formatting the output. $content_type = $formatter->getContentTypeHeader(); $response_headers = restful()->getResponse()->getHeaders(); $response_headers->add(HttpHeader::create('Content-Type', $content_type)); return $output; }
/** * Sets an HTTP header. * * @param string $name * The header name. * @param string $value * The header value. */ protected function setHttpHeader($name, $value) { $this->getRequest()->getHeaders()->add(HttpHeader::create($name, $value)); }
/** * {@inheritdoc} */ public function setDate(\DateTime $date) { $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->add(HttpHeader::create('Date', $date->format('D, d M Y H:i:s') . ' GMT')); }
/** * Adds the Allowed-Origin headers. * * @param string $path * The requested path. */ protected function preflight($path) { $plugin_definition = $this->getPluginDefinition(); $header_bag = restful()->getResponse()->getHeaders(); // Populate the Accept header. $accepted_formats = array(); $formatter_manager = restful()->getFormatterManager(); if (empty($plugin_definition['formatter'])) { foreach ($formatter_manager->getPlugins() as $formatter) { /** @var $formatter \Drupal\restful\Plugin\formatter\FormatterInterface */ $header_bag->append(HttpHeader::create('Accept', $formatter->getContentTypeHeader())); } } else { try { $accepted_format = $formatter_manager->getPlugin($plugin_definition['formatter'])->getContentTypeHeader(); $header_bag->add(HttpHeader::create('Accept', $accepted_format)); } catch (PluginNotFoundException $e) { throw new NotImplementedException($e->getMessage()); } } $allowed_origin = empty($plugin_definition['allowOrigin']) ? variable_get('restful_allowed_origin', NULL) : $plugin_definition['allowOrigin']; // Always add the allow origin if configured. if ($allowed_origin) { $header_bag->add(HttpHeader::create('Access-Control-Allow-Origin', check_plain($allowed_origin))); // @see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials $accepts_credentials = $allowed_origin == '*' ? 'false' : 'true'; $header_bag->add(HttpHeader::create('Access-Control-Allow-Credentials', $accepts_credentials)); } // Make sure the Access-Control-Allow-Methods is populated. $allowed_methods = array(); foreach ($this->getControllers() as $pattern => $controllers) { // Find the controllers for the provided path. if ($pattern == $path || $pattern && preg_match('/' . $pattern . '/', $path)) { foreach ($controllers as $method => $controller) { if (is_array($controller)) { // If there is a custom access method for this endpoint check it. if (!empty($selected_controller['access callback']) && !ResourceManager::executeCallback(array($this, $selected_controller['access callback']), array($path))) { // There is no access for this method. continue; } } $allowed_methods[] = $method; } $header_bag->add(HttpHeader::create('Access-Control-Allow-Methods', implode(',', $allowed_methods))); break; } } }
/** * Parses the header names and values from globals. * * @return HttpHeaderBag * The headers. */ protected static function parseHeadersFromGlobals() { $bag = new HttpHeaderBag(); $headers = array(); if (function_exists('apache_request_headers')) { $headers = apache_request_headers(); } else { foreach ($_SERVER as $key => $value) { if (strpos($key, 'HTTP_') === 0) { // Generate the plausible header name based on the $name. // Converts 'HTTP_X_FORWARDED_FOR' to 'X-Forwarded-For' $name = substr($key, 5); $parts = explode('_', $name); $parts = array_map('strtolower', $parts); $parts = array_map('ucfirst', $parts); $name = implode('-', $parts); $headers[$name] = $value; } } } // Iterate over the headers and bag them. foreach ($headers as $name => $value) { $bag->add(HttpHeader::create($name, $value)); } return $bag; }
/** * {@inheritdoc} */ public function view($path) { // TODO: This is duplicating the code from Resource::view $ids = explode(static::IDS_SEPARATOR, $path); // REST requires a canonical URL for every resource. $canonical_path = $this->getDataProvider()->canonicalPath($path); $this->getRequest()->getHeaders()->add(HttpHeader::create('Link', $this->versionedUrl($canonical_path, array(), FALSE) . '; rel="canonical"')); // If there is only one ID then use 'view'. Else, use 'viewMultiple'. The // difference between the two is that 'view' allows access denied // exceptions. if (count($ids) == 1) { return array($this->getDataProvider()->view($ids[0])); } else { return $this->getDataProvider()->viewMultiple($ids); } }
/** * Checks if the current request has reached the rate limit. * * If the user has reached the limit this method will throw an exception. If * not, the hits counter will be updated for subsequent calls. Since the * request can match multiple events, the access is only granted if all events * are cleared. * * @param RequestInterface $request * The request array. * * @throws FloodException if the rate limit has been reached for the * current request. */ public function checkRateLimit(RequestInterface $request) { $now = new \DateTime(); $now->setTimestamp(REQUEST_TIME); // Check all rate limits configured for this handler. foreach ($this->plugins as $instance_id => $plugin) { // If the limit is unlimited then skip everything. /* @var RateLimit $plugin */ $limit = $plugin->getLimit($this->account); $period = $plugin->getPeriod(); if ($limit == static::UNLIMITED_RATE_LIMIT) { // User has unlimited access to the resources. continue; } // If the current request matches the configured event then check if the // limit has been reached. if (!$plugin->isRequestedEvent($request)) { continue; } if (!($rate_limit_entity = $plugin->loadRateLimitEntity($this->account))) { // If there is no entity, then create one. // We don't need to save it since it will be saved upon hit. $rate_limit_entity = entity_create('rate_limit', array('timestamp' => REQUEST_TIME, 'expiration' => $now->add($period)->format('U'), 'hits' => 0, 'event' => $plugin->getPluginId(), 'identifier' => $plugin->generateIdentifier($this->account))); } // When the new rate limit period starts. $new_period = new \DateTime(); $new_period->setTimestamp($rate_limit_entity->expiration); if ($rate_limit_entity->isExpired()) { // If the rate limit has expired renew the timestamps and assume 0 // hits. $rate_limit_entity->timestamp = REQUEST_TIME; $rate_limit_entity->expiration = $now->add($period)->format('U'); $rate_limit_entity->hits = 0; if ($limit == 0) { $exception = new FloodException('Rate limit reached'); $exception->setHeader('Retry-After', $new_period->format(\DateTime::RFC822)); throw $exception; } } else { if ($rate_limit_entity->hits >= $limit) { $exception = new FloodException('Rate limit reached'); $exception->setHeader('Retry-After', $new_period->format(\DateTime::RFC822)); throw $exception; } } // Save a new hit after generating the exception to mitigate DoS attacks. $rate_limit_entity->hit(); // Add the limit headers to the response. $remaining = $limit == static::UNLIMITED_RATE_LIMIT ? 'unlimited' : $limit - ($rate_limit_entity->hits + 1); $response = restful()->getResponse(); $headers = $response->getHeaders(); $headers->append(HttpHeader::create('X-Rate-Limit-Limit', $limit)); $headers->append(HttpHeader::create('X-Rate-Limit-Remaining', $remaining)); $time_remaining = $rate_limit_entity->expiration - REQUEST_TIME; $headers->append(HttpHeader::create('X-Rate-Limit-Reset', $time_remaining)); } }