public function testAddCacheHeaders() { $body = "<html><head></head><body><h1>Mysite</h1></body></html>"; $response = new HTTPResponse($body, 200); $this->assertEmpty($response->getHeader('Cache-Control')); HTTP::set_cache_age(30); HTTP::add_cache_headers($response); $this->assertNotEmpty($response->getHeader('Cache-Control')); // Ensure max-age is zero for development. Director::config()->update('environment_type', 'dev'); $response = new HTTPResponse($body, 200); HTTP::add_cache_headers($response); $this->assertContains('max-age=0', $response->getHeader('Cache-Control')); // Ensure max-age setting is respected in production. Director::config()->update('environment_type', 'live'); $response = new HTTPResponse($body, 200); HTTP::add_cache_headers($response); $this->assertContains('max-age=30', explode(', ', $response->getHeader('Cache-Control'))); $this->assertNotContains('max-age=0', $response->getHeader('Cache-Control')); // Still "live": Ensure header's aren't overridden if already set (using purposefully different values). $headers = array('Vary' => '*', 'Pragma' => 'no-cache', 'Cache-Control' => 'max-age=0, no-cache, no-store'); $response = new HTTPResponse($body, 200); foreach ($headers as $name => $value) { $response->addHeader($name, $value); } HTTP::add_cache_headers($response); foreach ($headers as $name => $value) { $this->assertEquals($value, $response->getHeader($name)); } }
/** * Get the most recent response's content * * @return string */ public function lastContent() { if (is_string($this->lastResponse)) { return $this->lastResponse; } else { return $this->lastResponse->getBody(); } }
/** * @param HTTPResponse|string $body Either the plaintext content of the error * message, or an HTTPResponse object representing it. In either case, the * $statusCode and $statusDescription will be the HTTP status of the resulting * response. * @param int $statusCode * @param string $statusDescription * @see HTTPResponse::__construct(); */ public function __construct($body = null, $statusCode = null, $statusDescription = null) { if ($body instanceof HTTPResponse) { // statusCode and statusDescription should override whatever is passed in the body if ($statusCode) { $body->setStatusCode($statusCode); } if ($statusDescription) { $body->setStatusDescription($statusDescription); } $this->setResponse($body); } else { $response = new HTTPResponse($body, $statusCode, $statusDescription); // Error responses should always be considered plaintext, for security reasons $response->addHeader('Content-Type', 'text/plain'); $this->setResponse($response); } parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode()); }
/** * @param array $record * @return bool */ protected function write(array $record) { ini_set('display_errors', 0); // TODO: This coupling isn't ideal // See https://github.com/silverstripe/silverstripe-framework/issues/4484 if (Controller::has_curr()) { $response = Controller::curr()->getResponse(); } else { $response = new HTTPResponse(); } // If headers have been sent then these won't be used, and may throw errors that we wont' want to see. if (!headers_sent()) { $response->setStatusCode($this->statusCode); $response->addHeader("Content-Type", $this->contentType); } else { // To supress errors aboot errors $response->setStatusCode(200); } $response->setBody($record['formatted']); $response->output(); return false === $this->bubble; }
/** * Determines if a specified file exists * * @param HTTPRequest $request * @return HTTPResponse */ public function fileexists(HTTPRequest $request) { // Assert that requested filename doesn't attempt to escape the directory $originalFile = $request->requestVar('filename'); if ($originalFile !== basename($originalFile)) { $return = array('error' => _t('File.NOVALIDUPLOAD', 'File is not a valid upload')); } else { $return = array('exists' => $this->checkFileExists($originalFile)); } // Encode and present response $response = new HTTPResponse(Convert::raw2json($return)); $response->addHeader('Content-Type', 'application/json'); if (!empty($return['error'])) { $response->setStatusCode(400); } return $response; }
/** * Skip any further processing and immediately respond with a redirect to the passed URL. * * @param string $destURL */ protected static function force_redirect($destURL) { $response = new HTTPResponse(); $response->redirect($destURL, 301); HTTP::add_cache_headers($response); // TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block $response->output(); die; }
/** * @param HTTPRequest $request * * @return HTTPResponse */ public function apiSearch(HTTPRequest $request) { $params = $request->getVars(); $list = $this->getList($params); $response = new HTTPResponse(); $response->addHeader('Content-Type', 'application/json'); $response->setBody(json_encode(["files" => array_map(function ($file) { return $this->getObjectFromData($file); }, $list->toArray()), "count" => $list->count()])); return $response; }
/** * Returns the appropriate response up the controller chain * if {@link validate()} fails (which is checked prior to executing any form actions). * By default, returns different views for ajax/non-ajax request, and * handles 'application/json' requests with a JSON object containing the error messages. * Behaviour can be influenced by setting {@link $redirectToFormOnValidationError}, * and can be overruled by setting {@link $validationResponseCallback}. * * @return HTTPResponse|string */ protected function getValidationErrorResponse() { $callback = $this->getValidationResponseCallback(); if ($callback && ($callbackResponse = $callback())) { return $callbackResponse; } $request = $this->getRequest(); if ($request->isAjax()) { // Special case for legacy Validator.js implementation // (assumes eval'ed javascript collected through FormResponse) $acceptType = $request->getHeader('Accept'); if (strpos($acceptType, 'application/json') !== FALSE) { // Send validation errors back as JSON with a flag at the start $response = new HTTPResponse(Convert::array2json($this->validator->getErrors())); $response->addHeader('Content-Type', 'application/json'); } else { $this->setupFormErrors(); // Send the newly rendered form tag as HTML $response = new HTTPResponse($this->forTemplate()); $response->addHeader('Content-Type', 'text/html'); } return $response; } else { if ($this->getRedirectToFormOnValidationError()) { if ($pageURL = $request->getHeader('Referer')) { if (Director::is_site_url($pageURL)) { // Remove existing pragmas $pageURL = preg_replace('/(#.*)/', '', $pageURL); $pageURL = Director::absoluteURL($pageURL, true); return $this->controller->redirect($pageURL . '#' . $this->FormName()); } } } return $this->controller->redirectBack(); } }
/** * Generate a secure token which can be used as a crypto key. * Returns the token and suggests PHP configuration to set it. */ public function generatesecuretoken() { $generator = Injector::inst()->create('SilverStripe\\Security\\RandomGenerator'); $token = $generator->randomToken('sha1'); $body = <<<TXT Generated new token. Please add the following code to your YAML configuration: Security: token: {$token} TXT; $response = new HTTPResponse($body); return $response->addHeader('Content-Type', 'text/plain'); }
/** * @return string */ public function getContent() { return $this->response->getBody(); }
/** * Performs the actual action of adding the object to the ChangeSet, once the ChangeSet ID is known * * @param DataObject $object The object to add to the ChangeSet * @param int $campaignID The ID of the ChangeSet to add $object to * @return HTTPResponse * @throws HTTPResponse_Exception */ public function addToCampaign($object, $campaignID) { /** @var ChangeSet $changeSet */ $changeSet = ChangeSet::get()->byID($campaignID); if (!$changeSet) { $this->controller->httpError(404, _t('AddToCampaign.ErrorNotFound', 'That {Type} couldn\'t be found', '', ['Type' => 'Campaign'])); return null; } if (!$changeSet->canEdit()) { $this->controller->httpError(403, _t('AddToCampaign.ErrorCampaignPermissionDenied', 'It seems you don\'t have the necessary permissions to add {ObjectTitle} to {CampaignTitle}', '', ['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title])); return null; } $changeSet->addObject($object); $request = $this->controller->getRequest(); $message = _t('AddToCampaign.Success', 'Successfully added {ObjectTitle} to {CampaignTitle}', '', ['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]); if ($request->getHeader('X-Formschema-Request')) { return $message; } elseif (Director::is_ajax()) { $response = new HTTPResponse($message, 200); $response->addHeader('Content-Type', 'text/plain; charset=utf-8'); return $response; } else { return $this->controller->getController()->redirectBack(); } }
public function testStatusDescriptionStripsNewlines() { $r = new HTTPResponse('my body', 200, "my description \nwith newlines \rand carriage returns"); $this->assertEquals("my description with newlines and carriage returns", $r->getStatusDescription()); }
/** * Performs the following replacements: * - Check user defined content type and use it, if it's empty use the text/html. * - If find a XML header replaces it and existing doctypes with HTML4.01 Strict. * - Replaces self-closing tags like <img /> with unclosed solitary tags like <img>. * - Replaces all occurrences of "application/xhtml+xml" with "text/html" in the template. * - Removes "xmlns" attributes and any <?xml> Pragmas. * * @param HTTPResponse $response */ public function html(HTTPResponse $response) { $encoding = $this->config()->get('encoding'); $contentType = $this->config()->get('content_type'); if (empty($contentType)) { $response->addHeader("Content-Type", "text/html; charset=" . $encoding); } else { $response->addHeader("Content-Type", $contentType . "; charset=" . $encoding); } $response->addHeader("Vary", "Accept"); $content = $response->getBody(); $hasXMLHeader = substr($content, 0, 5) == '<' . '?xml'; // Fix base tag $content = preg_replace('/<base href="([^"]*)" \\/>/', '<base href="$1"><!--[if lte IE 6]></base><![endif]-->', $content); $content = preg_replace("#<\\?xml[^>]+\\?>\n?#", '', $content); $content = str_replace(array('/>', 'xml:lang', 'application/xhtml+xml'), array('>', 'lang', 'text/html'), $content); // Only replace the doctype in templates with the xml header if ($hasXMLHeader) { $content = preg_replace('/<!DOCTYPE[^>]+>/', '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">', $content); } $content = preg_replace('/<html xmlns="[^"]+"/', '<html ', $content); $response->setBody($content); }
/** * Action to handle upload of a single file * * @param HTTPRequest $request * @return HTTPResponse */ public function upload(HTTPRequest $request) { if ($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) { return $this->httpError(403); } // Protect against CSRF on destructive action $token = $this->getForm()->getSecurityToken(); if (!$token->checkRequest($request)) { return $this->httpError(400); } // Get form details $name = $this->getName(); $postVars = $request->postVar($name); // Extract uploaded files from Form data $uploadedFile = $this->extractUploadedFileData($postVars); if (!$uploadedFile) { return $this->httpError(400); } // Save the temporary files into a File objects // and save data/error on a per file basis $result = $this->saveTemporaryFile($uploadedFile, $error); if (empty($result)) { $return = array('error' => $error); } else { $return = $this->encodeAssetAttributes($result['Filename'], $result['Hash'], $result['Variant']); } $this->getUpload()->clearErrors(); // Format response with json $response = new HTTPResponse(Convert::raw2json(array($return))); $response->addHeader('Content-Type', 'text/plain'); return $response; }
/** * Require basic authentication. Will request a username and password if none is given. * * Used by {@link Controller::init()}. * * @throws HTTPResponse_Exception * * @param string $realm * @param string|array $permissionCode Optional * @param boolean $tryUsingSessionLogin If true, then the method with authenticate against the * session log-in if those credentials are disabled. * @return Member|bool $member */ public static function requireLogin($realm, $permissionCode = null, $tryUsingSessionLogin = true) { $isRunningTests = class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test(); if (!Security::database_is_ready() || Director::is_cli() && !$isRunningTests) { return true; } /* * Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache * Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or * REDIRECT_HTTP_AUTHORIZATION * * The follow rewrite rule must be in the sites .htaccess file to enable this workaround * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] */ $authHeader = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null); $matches = array(); if ($authHeader && preg_match('/Basic\\s+(.*)$/i', $authHeader, $matches)) { list($name, $password) = explode(':', base64_decode($matches[1])); $_SERVER['PHP_AUTH_USER'] = strip_tags($name); $_SERVER['PHP_AUTH_PW'] = strip_tags($password); } $member = null; if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { $member = MemberAuthenticator::authenticate(array('Email' => $_SERVER['PHP_AUTH_USER'], 'Password' => $_SERVER['PHP_AUTH_PW']), null); } if (!$member && $tryUsingSessionLogin) { $member = Member::currentUser(); } // If we've failed the authentication mechanism, then show the login form if (!$member) { $response = new HTTPResponse(null, 401); $response->addHeader('WWW-Authenticate', "Basic realm=\"{$realm}\""); if (isset($_SERVER['PHP_AUTH_USER'])) { $response->setBody(_t('BasicAuth.ERRORNOTREC', "That username / password isn't recognised")); } else { $response->setBody(_t('BasicAuth.ENTERINFO', "Please enter a username and password.")); } // Exception is caught by RequestHandler->handleRequest() and will halt further execution $e = new HTTPResponse_Exception(null, 401); $e->setResponse($response); throw $e; } if ($permissionCode && !Permission::checkMember($member->ID, $permissionCode)) { $response = new HTTPResponse(null, 401); $response->addHeader('WWW-Authenticate', "Basic realm=\"{$realm}\""); if (isset($_SERVER['PHP_AUTH_USER'])) { $response->setBody(_t('BasicAuth.ERRORNOTADMIN', "That user is not an administrator.")); } // Exception is caught by RequestHandler->handleRequest() and will halt further execution $e = new HTTPResponse_Exception(null, 401); $e->setResponse($response); throw $e; } return $member; }
/** * Assert that a response matches the given parameters * * @param int $code HTTP code * @param string $body Body expected for 200 responses * @param HTTPResponse $response */ protected function assertResponseEquals($code, $body, HTTPResponse $response) { $this->assertEquals($code, $response->getStatusCode()); if ($code === 200) { $this->assertFalse($response->isError()); $this->assertEquals($body, $response->getBody()); $this->assertEquals('text/plain', $response->getHeader('Content-Type')); } else { $this->assertTrue($response->isError()); } }
/** * Save handler * * @param array $data * @param Form $form * @return HTTPResponse */ public function save($data, $form) { $request = $this->getRequest(); $className = $this->stat('tree_class'); // Existing or new record? $id = $data['ID']; if (is_numeric($id) && $id > 0) { $record = DataObject::get_by_id($className, $id); if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { $this->httpError(404, "Bad record ID #" . (int) $id); } } else { if (!singleton($this->stat('tree_class'))->canCreate()) { return Security::permissionFailure($this); } $record = $this->getNewItem($id, false); } // save form data into record $form->saveInto($record, true); $record->write(); $this->extend('onAfterSave', $record); $this->setCurrentPageID($record->ID); $message = _t('LeftAndMain.SAVEDUP', 'Saved.'); if ($request->getHeader('X-Formschema-Request')) { $schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $id); // Ensure that newly created records have all their data loaded back into the form. $form->loadDataFrom($record); $form->setMessage($message, 'good'); $data = $this->getSchemaForForm($form, $schemaId); $response = new HTTPResponse(Convert::raw2json($data)); $response->addHeader('Content-Type', 'application/json'); } else { $response = $this->getResponseNegotiator()->respond($request); } $response->addHeader('X-Status', rawurlencode($message)); return $response; }
/** * Check if this action has a confirmation step * * @param HTTPRequest $request * @return HTTPResponse */ public function handleConfirmation($request) { // Find the action handler $action = $request->param('BatchAction'); $actionHandler = $this->actionByName($action); // Sanitise ID list and query the database for apges $csvIDs = $request->requestVar('csvIDs'); $ids = $this->cleanIDs($csvIDs); // Check dialog if ($actionHandler->hasMethod('confirmationDialog')) { $response = new HTTPResponse(json_encode($actionHandler->confirmationDialog($ids))); } else { $response = new HTTPResponse(json_encode(array('alert' => false))); } $response->addHeader("Content-type", "application/json"); return $response; }
/** * Construct an HTTPResponse that will deliver a file to the client. * Caution: Since it requires $fileData to be passed as binary data (no stream support), * it's only advisable to send small files through this method. * * @static * @param $fileData * @param $fileName * @param null $mimeType * @return HTTPResponse */ public static function send_file($fileData, $fileName, $mimeType = null) { if (!$mimeType) { $mimeType = HTTP::get_mime_type($fileName); } $response = new HTTPResponse($fileData); $response->addHeader("Content-Type", "{$mimeType}; name=\"" . addslashes($fileName) . "\""); // Note a IE-only fix that inspects this header in HTTP::add_cache_headers(). $response->addHeader("Content-Disposition", "attachment; filename=\"" . addslashes($fileName) . "\""); $response->addHeader("Content-Length", strlen($fileData)); return $response; }
/** * REST endpoint to get a campaign. * * @param HTTPRequest $request * * @return HTTPResponse */ public function readCampaign(HTTPRequest $request) { $response = new HTTPResponse(); if ($request->getHeader('Accept') == 'text/json') { $response->addHeader('Content-Type', 'application/json'); if (!$request->param('Name')) { return new HTTPResponse(null, 400); } /** @var ChangeSet $changeSet */ $changeSet = ChangeSet::get()->byID($request->param('ID')); if (!$changeSet) { return new HTTPResponse(null, 404); } if (!$changeSet->canView()) { return new HTTPResponse(null, 403); } $body = Convert::raw2json($this->getChangeSetResource($changeSet)); return (new HTTPResponse($body, 200))->addHeader('Content-Type', 'application/json'); } else { return $this->index($request); } }
/** * Create a response with the given error code * * @param int $code * @return HTTPResponse */ protected function createErrorResponse($code) { $response = new HTTPResponse('', $code); // Show message in dev if (!Director::isLive()) { $response->setBody($response->getStatusDescription()); } return $response; }
public function isFinished() { return parent::isFinished() || $this->isFinished; }