/** * Produces the proxied version of a URL if it falls within the content-rewrite params and * will append a refresh param to the proxied url based on the expires param, and the gadget * url so that the proxy server knows to rewrite it's content or not * * @param string $url * @return string */ private function getProxyUrl($url) { if (strpos(strtolower($url), 'http://') === false && strpos(strtolower($url), 'https://') === false) { $url = $this->baseUrl . $url; } $url = Config::get('web_prefix') . '/gadgets/proxy?url=' . urlencode($url); $url .= '&refresh=' . (isset($this->rewrite['expires']) && is_numeric($this->rewrite['expires']) ? $this->rewrite['expires'] : '3600'); $url .= '&gadget=' . urlencode($this->context->getUrl()); $url .= '&st=' . urlencode(BasicSecurityToken::getTokenStringFromRequest()); return $url; }
/** * Fetches the content and returns it as-is using the headers as returned * by the remote host. * * @param string $url the url to retrieve */ public function fetch($url) { // TODO: Check to see if we can just use MakeRequestOptions::fromCurrentRequest $st = BasicSecurityToken::getTokenStringFromRequest(); $body = isset($_GET['postData']) ? $_GET['postData'] : (isset($_POST['postData']) ? $_POST['postData'] : false); $authz = isset($_GET['authz']) ? $_GET['authz'] : (isset($_POST['authz']) ? $_POST['authz'] : null); $headers = isset($_GET['headers']) ? $_GET['headers'] : (isset($_POST['headers']) ? $_POST['headers'] : null); $params = new MakeRequestOptions($url); $params->setSecurityTokenString($st)->setAuthz($authz)->setRequestBody($body)->setHttpMethod('GET')->setFormEncodedRequestHeaders($headers)->setNoCache($this->context->getIgnoreCache()); $result = $this->makeRequest->fetch($this->context, $params); $httpCode = (int) $result->getHttpCode(); $cleanedResponseHeaders = $this->makeRequest->cleanResponseHeaders($result->getResponseHeaders()); $isShockwaveFlash = false; foreach ($cleanedResponseHeaders as $key => $val) { header("{$key}: {$val}", true); if (strtoupper($key) == 'CONTENT-TYPE' && strtolower($val) == 'application/x-shockwave-flash') { // We're skipping the content disposition header for flash due to an issue with Flash player 10 // This does make some sites a higher value phishing target, but this can be mitigated by // additional referer checks. $isShockwaveFlash = true; } } if (!$isShockwaveFlash && !Config::get('debug')) { header('Content-Disposition: attachment;filename=p.txt'); } $lastModified = $result->getResponseHeader('Last-Modified') != null ? $result->getResponseHeader('Last-Modified') : gmdate('D, d M Y H:i:s', $result->getCreated()) . ' GMT'; $notModified = false; if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $lastModified && !isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $if_modified_since = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); // Use the request's Last-Modified, otherwise fall back on our internal time keeping (the time the request was created) $lastModified = strtotime($lastModified); if ($lastModified <= $if_modified_since) { $notModified = true; } } if ($httpCode == 200) { // only set caching headers if the result was 'OK' $this->setCachingHeaders($lastModified); // was the &gadget=<gadget url> specified in the request? if so parse it and check the rewrite settings if (isset($_GET['gadget'])) { $this->rewriteContent($_GET['gadget'], $result); } } // If the cached file time is within the refreshInterval params value, return not-modified if ($notModified) { header('HTTP/1.0 304 Not Modified', true); header('Content-Length: 0', true); } else { header("HTTP/1.1 {$httpCode} " . $result->getHttpCodeMsg()); // then echo the content echo $result->getResponseContent(); } }
/** * * @return SecurityToken */ private function getSecurityToken() { $token = BasicSecurityToken::getTokenStringFromRequest(); if (empty($token)) { if (Config::get('allow_anonymous_token')) { // no security token, continue anonymously, remeber to check // for private profiles etc in your code so their not publicly // accessable to anoymous users! Anonymous == owner = viewer = appId = modId = 0 // create token with 0 values, no gadget url, no domain and 0 duration $gadgetSigner = Config::get('security_token'); return new $gadgetSigner(null, 0, SecurityToken::$ANONYMOUS, SecurityToken::$ANONYMOUS, 0, '', '', 0, Config::get('container_id')); } else { return null; } } $gadgetSigner = Config::get('security_token_signer'); $gadgetSigner = new $gadgetSigner(); return $gadgetSigner->createToken($token); }
/** * Builds a MakeRequestOptions object from the current $_GET and $_POST * superglobals. * * @return MakeRequestOptions An object initialized from the current request. * @throws MakeRequestParameterException If any of the parameters were * invalid. */ public static function fromCurrentRequest() { $href = MakeRequestOptions::getRequestParam('href'); if (!isset($href)) { $href = MakeRequestOptions::getRequestParam('url'); } $options = new MakeRequestOptions($href); $options->setHttpMethod(MakeRequestOptions::getRequestParam('httpMethod'))->setRequestBody(MakeRequestOptions::getRequestParam('postData'))->setFormEncodedRequestHeaders(MakeRequestOptions::getRequestParam('headers'))->setResponseFormat(MakeRequestOptions::getRequestParam('contentType'))->setAuthz(MakeRequestOptions::getRequestParam('authz'))->setSignViewer(MakeRequestOptions::getRequestParam('signViewer', 'boolean'))->setSignOwner(MakeRequestOptions::getRequestParam('signOwner', 'boolean'))->setNumEntries(MakeRequestOptions::getRequestParam('numEntries', 'integer'))->setGetSummaries(MakeRequestOptions::getRequestParam('getSummaries', 'boolean'))->setRefreshInterval(MakeRequestOptions::getRequestParam('refreshInterval', 'integer'))->setNoCache(MakeRequestOptions::getRequestParam('bypassSpecCache', 'boolean'))->setOAuthServiceName(MakeRequestOptions::getRequestParam('OAUTH_SERVICE_NAME'))->setOAuthTokenName(MakeRequestOptions::getRequestParam('OAUTH_TOKEN_NAME'))->setOAuthRequestToken(MakeRequestOptions::getRequestParam('OAUTH_REQUEST_TOKEN'))->setOAuthRequestTokenSecret(MakeRequestOptions::getRequestParam('OAUTH_REQUEST_TOKEN_SECRET'))->setOAuthUseToken(MakeRequestOptions::getRequestParam('OAUTH_USE_TOKEN'))->setOAuthReceivedCallback(MakeRequestOptions::getRequestParam('OAUTH_RECEIVED_CALLBACK'))->setOAuthClientState(MakeRequestOptions::getRequestParam('oauthState'))->setSecurityTokenString(BasicSecurityToken::getTokenStringFromRequest()); return $options; }
/** * * @return SecurityToken */ public function getSecurityToken() { // Support a configurable host name ('http_host' key) so that OAuth signatures don't fail in reverse-proxy type situations $scheme = !isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on" ? 'http' : 'https'; $http_url = $scheme . '://' . (Config::get('http_host') ? Config::get('http_host') : $_SERVER['HTTP_HOST']) . $_SERVER['REQUEST_URI']; // see if we have an OAuth request $request = OAuthRequest::from_request(null, $http_url, null); $appUrl = $request->get_parameter('oauth_consumer_key'); $userId = $request->get_parameter('xoauth_requestor_id'); // from Consumer Request extension (2-legged OAuth) $signature = $request->get_parameter('oauth_signature'); if ($appUrl && $signature) { //if ($appUrl && $signature && $userId) { // look up the user and perms for this oauth request $oauthLookupService = Config::get('oauth_lookup_service'); $oauthLookupService = new $oauthLookupService(); $token = $oauthLookupService->getSecurityToken($request, $appUrl, $userId, $this->getContentType()); if ($token) { $token->setAuthenticationMode(AuthenticationMode::$OAUTH_CONSUMER_REQUEST); return $token; } else { return null; // invalid oauth request, or 3rd party doesn't have access to this user } } // else, not a valid oauth request, so don't bother // look for encrypted security token $token = BasicSecurityToken::getTokenStringFromRequest(); if (empty($token)) { if (Config::get('allow_anonymous_token')) { // no security token, continue anonymously, remeber to check // for private profiles etc in your code so their not publicly // accessable to anoymous users! Anonymous == owner = viewer = appId = modId = 0 // create token with 0 values, no gadget url, no domain and 0 duration $gadgetSigner = Config::get('security_token'); return new $gadgetSigner(null, 0, SecurityToken::$ANONYMOUS, SecurityToken::$ANONYMOUS, 0, '', '', 0, Config::get('container_id')); } else { return null; } } $gadgetSigner = Config::get('security_token_signer'); $gadgetSigner = new $gadgetSigner(); return $gadgetSigner->createToken($token); }
/** * Extracts the 'st' token from the GET or POST params and calls the * signer to validate the token * * @param SecurityTokenDecoder $signer the signer to use (configured in config.php) * @return SecurityToken An object representation of the token data. */ public function extractAndValidateToken($signer) { if ($signer == null) { return null; } $token = BasicSecurityToken::getTokenStringFromRequest(); return $this->validateToken($token, $signer); }
/** * Peforms the actual http fetching of the data-pipelining requests, all social requests * are made to $_SERVER['HTTP_HOST'] (the virtual host name of this server) / (optional) web_prefix / social / rpc, and * the httpRequest's are made to $_SERVER['HTTP_HOST'] (the virtual host name of this server) / (optional) web_prefix / gadgets / makeRequest * both request types use the current security token ($_GET['st']) when performing the requests so they happen in the correct context * * @param array $requests * @param GadgetContext $context * @return array response */ private static function performRequests($requests, GadgetContext $context) { $jsonRequests = array(); $httpRequests = array(); $decodedResponse = array(); // Using the same gadget security token for all social & http requests so everything happens in the right context if (!BasicSecurityToken::getTokenStringFromRequest()) { throw new ExpressionException("No security token set, required for data-pipeling"); } $securityToken = $_GET['st']; foreach ($requests as $request) { switch ($request['type']) { case 'os:DataRequest': // Add to the social request batch $id = $request['key']; $method = $request['method']; // remove our internal fields so we can use the remainder as params unset($request['key']); unset($request['method']); unset($request['type']); if (isset($request['fields'])) { $request['fields'] = explode(',', $request['fields']); } $jsonRequests[] = array('method' => $method, 'id' => $id, 'params' => $request); break; case 'os:HttpRequest': $id = $request['key']; $url = $request['href']; $format = isset($request['format']) ? $request['format'] : 'json'; unset($request['key']); unset($request['type']); unset($request['href']); $httpRequests[$url] = array('id' => $id, 'url' => $url, 'format' => $format, 'queryStr' => implode('&', $request)); break; } } if (count($jsonRequests)) { // perform social api requests $request = new RemoteContentRequest('http://' . $_SERVER['HTTP_HOST'] . Config::get('web_prefix') . '/rpc?st=' . urlencode($securityToken) . '&format=json', "Content-Type: application/json\n", json_encode($jsonRequests)); $request->setMethod('POST'); $remoteFetcherClass = Config::get('remote_content_fetcher'); $remoteFetcher = new $remoteFetcherClass(); $basicRemoteContent = new BasicRemoteContent($remoteFetcher); $response = $basicRemoteContent->fetch($request); $decodedResponse = json_decode($response->getResponseContent(), true); } if (count($httpRequests)) { $requestQueue = array(); foreach ($httpRequests as $request) { $req = new RemoteContentRequest($_SERVER['HTTP_HOST'] . Config::get('web_prefix') . '/gadgets/makeRequest?url=' . urlencode($request['url']) . '&st=' . urlencode($securityToken) . (!empty($request['queryStr']) ? '&' . $request['queryStr'] : '')); $req->getOptions()->ignoreCache = $context->getIgnoreCache(); $req->setNotSignedUri($request['url']); $requestQueue[] = $req; } $basicRemoteContent = new BasicRemoteContent(); $resps = $basicRemoteContent->multiFetch($requestQueue); foreach ($resps as $response) { //FIXME: this isn't completely correct yet since this picks up the status code and headers // as they are returned by the makeRequest handler and not the ones from the original request $url = $response->getNotSignedUrl(); $id = $httpRequests[$url]['id']; // strip out the UNPARSEABLE_CRUFT (see makeRequestHandler.php) on assigning the body $resp = json_decode(str_replace("throw 1; < don't be evil' >", '', $response->getResponseContent()), true); if (is_array($resp)) { $statusCode = $response->getHttpCode(); $statusCodeMessage = $response->getHttpCodeMsg(); $headers = $response->getHeaders(); if (intval($statusCode) == 200) { $content = $httpRequests[$url]['format'] == 'json' ? json_decode($resp[$url]['body'], true) : $resp[$url]['body']; $toAdd = array('result' => array('content' => $content, 'status' => $statusCode, 'headers' => $headers)); } else { $content = $resp[$url]['body']; $toAdd = array('error' => array('code' => $statusCode, 'message' => $statusCodeMessage, 'result' => array('content' => $content, 'headers' => $headers))); } //$toAdd[$id] = array('id' => $id, 'result' => $httpRequests[$url]['format'] == 'json' ? json_decode($resp[$url]['body'], true) : $resp[$url]['body']); $decodedResponse[] = array('id' => $id, 'result' => $toAdd); } } } return $decodedResponse; }
/** * Function that handles the os: and osx: tags * * @param DOMNode $node * @return DOMNode or false */ private function parseOsmlNode(DOMNode &$node) { $tagName = strtolower($node->tagName); if (!$this->checkIf($node)) { // If the OSML tag contains an if attribute and the expression evaluates to false // flag it for removal and don't process it return $node; } switch ($tagName) { /****** Control statements ******/ case 'os:repeat': if (!$node->getAttribute('expression')) { throw new ExpressionException("Invalid os:Repeat tag, missing expression attribute"); } $expressions = array(); preg_match_all('/(\\$\\{)(.*)(\\})/imsxU', $node->getAttribute('expression'), $expressions); $expression = $expressions[2][0]; $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext); if (!is_array($expressionResult)) { throw new ExpressionException("Can't repeat on a singular var"); } // Store the current 'Cur', index and count state, we might be in a nested repeat loop $previousCount = isset($this->dataContext['Context']['Count']) ? $this->dataContext['Context']['Count'] : null; $previousIndex = isset($this->dataContext['Context']['Index']) ? $this->dataContext['Context']['Index'] : null; $previousCur = $this->dataContext['Cur']; // Is a named var requested? $variableName = $node->getAttribute('var') ? trim($node->getAttribute('var')) : false; // For information on the loop context, see http://opensocial-resources.googlecode.com/svn/spec/0.9/OpenSocial-Templating.xml#rfc.section.10.1 $this->dataContext['Context']['Count'] = count($expressionResult); foreach ($expressionResult as $index => $entry) { if ($variableName) { // this is cheating a little since we're not putting it on the top level scope, the variable resolver will check 'Cur' first though so myVar.Something will still resolve correctly $this->dataContext['Cur'][$variableName] = $entry; } $this->dataContext['Cur'] = $entry; $this->dataContext['Context']['Index'] = $index; foreach ($node->childNodes as $childNode) { $newNode = $childNode->cloneNode(true); $newNode = $node->parentNode->insertBefore($newNode, $node); $this->parseNode($newNode); } } // Restore our previous data context state $this->dataContext['Cur'] = $previousCur; if ($previousCount) { $this->dataContext['Context']['Count'] = $previousCount; } else { unset($this->dataContext['Context']['Count']); } if ($previousIndex) { $this->dataContext['Context']['Index'] = $previousIndex; } else { unset($this->dataContext['Context']['Index']); } return $node; break; case 'os:if': $expressions = array(); if (!$node->getAttribute('condition')) { throw new ExpressionException("Invalid os:If tag, missing condition attribute"); } preg_match_all('/(\\$\\{)(.*)(\\})/imsxU', $node->getAttribute('condition'), $expressions); if (!count($expressions[2])) { throw new ExpressionException("Invalid os:If tag, missing condition expression"); } $expression = $expressions[2][0]; $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext); if ($expressionResult) { foreach ($node->childNodes as $childNode) { $newNode = $childNode->cloneNode(true); $this->parseNode($newNode); $newNode = $node->parentNode->insertBefore($newNode, $node); } } return $node; break; /****** OSML tags (os: name space) ******/ /****** OSML tags (os: name space) ******/ case 'os:name': $this->parseLibrary('os:Name', $node); return $node; break; case 'os:badge': $this->parseLibrary('os:Badge', $node); return $node; break; case 'os:peopleselector': $this->parseLibrary('os:PeopleSelector', $node); return $node; break; case 'os:html': if (!$node->getAttribute('code')) { throw new ExpressionException("Invalid os:Html tag, missing code attribute"); } preg_match_all('/(\\$\\{)(.*)(\\})/imsxU', $node->getAttribute('code'), $expressions); if (count($expressions[2])) { $expression = $expressions[2][0]; $code = ExpressionParser::evaluate($expression, $this->dataContext); } else { $code = $node->getAttribute('code'); } $node->parentNode->insertBefore($node->ownerDocument->createTextNode($code), $node); return $node; break; case 'os:render': if (!($content = $node->getAttribute('content'))) { throw new ExpressionException("os:Render missing attribute: content"); } $content = $node->getAttribute('content'); if (!isset($this->dataContext['_os_render_nodes'][$content])) { throw new ExpressionException("os:Render, Unknown entry: " . htmlentities($content)); } $nodes = $this->dataContext['_os_render_nodes'][$content]; $ownerDocument = $node->ownerDocument; // Only parse the child nodes of the dom tree and not the (myapp:foo) top level element foreach ($nodes->childNodes as $childNode) { $importedNode = $ownerDocument->importNode($childNode, true); $importedNode = $node->parentNode->insertBefore($importedNode, $node); $this->parseNode($importedNode); } return $node; break; /****** Extension - Tags ******/ /****** Extension - Tags ******/ case 'os:flash': // handle expressions $this->parseNodeAttributes($node); // read swf config from attributes $swfConfig = array('width' => '100px', 'height' => '100px', 'play' => 'immediate'); foreach ($node->attributes as $attr) { $swfConfig[$attr->name] = $attr->value; } // attach security token in the flash var $st = 'st=' . BasicSecurityToken::getTokenStringFromRequest(); if (array_key_exists('flashvars', $swfConfig)) { $swfConfig['flashvars'] = $swfConfig['flashvars'] . '&' . $st; } else { $swfConfig['flashvars'] = $st; } // Restrict the content if sanitization is enabled $sanitizationEnabled = Config::get('sanitize_views'); if ($sanitizationEnabled) { $swfConfig['allowscriptaccess'] = 'never'; $swfConfig['swliveconnect'] = 'false'; $swfConfig['allownetworking'] = 'internal'; } // Generate unique id for this swf $ALT_CONTENT_PREFIX = 'os_Flash_alt_'; $altContentId = uniqid($ALT_CONTENT_PREFIX); // Create a div wrapper around the provided alternate content, and add the alternate content to the holder $altHolder = $node->ownerDocument->createElement('div'); $altHolder->setAttribute('id', $altContentId); foreach ($node->childNodes as $childNode) { $altHolder->appendChild($childNode); } $node->parentNode->insertBefore($altHolder, $node); // Create the call to swfobject in header $scriptCode = SwfConfig::buildSwfObjectCall($swfConfig, $altContentId); $scriptBlock = $node->ownerDocument->createElement('script'); $scriptBlock->setAttribute('type', 'text/javascript'); $node->parentNode->insertBefore($scriptBlock, $node); if ($swfConfig['play'] != 'immediate') { // Add onclick handler to trigger call to swfobject $scriptCode = "function {$altContentId}()\\{{$scriptCode};\\}"; $altHolder->setAttribute('onclick', "{$altContentId}()"); } $scriptCodeNode = $node->ownerDocument->createTextNode($scriptCode); $scriptBlock->appendChild($scriptCodeNode); return $node; break; case 'os:var': // handle expressions $this->parseNodeAttributes($node); if (!($key = $node->getAttribute('key'))) { throw new ExpressionException("os:Var missing attribute: key"); } // either get value from attribute if (!($value = $node->getAttribute('value'))) { $value = ''; } // or from inner text of node if (!$value && $node->textContent) { $value = $node->textContent; } // try to decode if the value is a valid json object $parsedValue = json_decode($value, true); if ($parsedValue) { $value = $parsedValue; } $this->dataContext['Top'][$key] = $value; return $node; break; case 'osx:navigatetoapp': break; case 'osx:navigatetoperson': break; } return false; }