public function __toString() { $prefix = null; if ($this->protocol || $this->domain || $this->port) { $protocol = ValueAs::nonempty($this->protocol, 'http'); $auth = ''; if (strlen($this->user) && strlen($this->pass)) { $auth = SafeHtml::escapeUri($this->user) . ':' . SafeHtml::escapeUri($this->pass) . '@'; } else { if (strlen($this->user)) { $auth = SafeHtml::escapeUri($this->user) . '@'; } } if ($protocol != 'javascript') { $prefix = $protocol . '://' . $auth . $this->domain; } else { $prefix = $protocol . ':'; } if ($this->port) { $prefix .= ':' . $this->port; } } if ($this->query) { $query = '?' . http_build_query($this->query); } else { $query = null; } if (strlen($this->getFragment())) { $fragment = '#' . $this->getFragment(); } else { $fragment = null; } return $prefix . $this->getPath() . $query . $fragment; }
public function testURIPathComponentEscape() { $this->assertEquals('a%252Fb', SafeHtml::escapeUriPathComponent('a/b')); $str = ''; for ($ii = 0; $ii <= 255; $ii++) { $str .= chr($ii); } $this->assertEquals($str, SafeHtml::unescapeUriPathComponent(rawurldecode(SafeHtml::escapeUriPathComponent($str)))); }
public function testCollection() { $tags = Paragraph::collection(['a', 'b', 'c']); $this->assertEquals('<p>a</p><p>b</p><p>c</p>', SafeHtml::escape($tags, '')); }
/** * @return SafeHtml|SafeHtml[] * @throws \Exception */ public function produceSafeHTML() { // If the `href` attribute is present: // - make sure it is not a "javascript:" URI. We never permit these. // - if the tag is an `<a>` and the link is to some foreign resource, // add `rel="nofollow"` by default. if (!empty($this->_attributes['href'])) { // This might be a URI object, so cast it to a string. $href = (string) $this->_attributes['href']; if (isset($href[0])) { $isAnchorHref = $href[0] == '#'; // Is this a link to a resource on the same domain? The second part of // this excludes "///evil.com/" protocol-relative hrefs. $isDomainHref = $href[0] == '/' && (!isset($href[1]) || $href[1] != '/'); // Block 'javascript:' hrefs at the tag level: no well-designed // application should ever use them, and they are a potent attack vector. // This function is deep in the core and performance sensitive, so we're // doing a cheap version of this test first to avoid calling preg_match() // on URIs which begin with '/' or `#`. These cover essentially all URIs // in Phabricator. if (!$isAnchorHref && !$isDomainHref) { // Chrome 33 and IE 11 both interpret "javascript\n:" as a Javascript // URI, and all browsers interpret " javascript:" as a Javascript URI, // so be aggressive about looking for "javascript:" in the initial // section of the string. $normalizedHref = preg_replace('([^a-z0-9/:]+)i', '', $href); if (preg_match('/^javascript:/i', $normalizedHref)) { throw new \Exception("Attempting to render a tag with an 'href' attribute that " . "begins with 'javascript:'. This is either a serious security " . "concern or a serious architecture concern. Seek urgent " . "remedy."); } } } } // For tags which can't self-close, treat null as the empty string -- for // example, always render `<div></div>`, never `<div />`. $selfClosingTags = ['area' => true, 'base' => true, 'br' => true, 'col' => true, 'command' => true, 'embed' => true, 'frame' => true, 'hr' => true, 'img' => true, 'input' => true, 'keygen' => true, 'link' => true, 'meta' => true, 'param' => true, 'source' => true, 'track' => true, 'wbr' => true]; $attrString = ''; foreach ($this->_attributes as $k => $v) { if ($v !== null) { $attrString .= ' ' . $k . '="' . SafeHtml::escape($v) . '"'; } else { $attrString .= ' ' . $k; } } $content = $this->_getContentForRender(); if ($content === null) { if (isset($selfClosingTags[$this->_tag])) { return new SafeHtml('<' . $this->_tag . $attrString . ' />'); } $content = ''; } else { $content = SafeHtml::escape($content, ''); } return new SafeHtml('<' . $this->_tag . $attrString . '>' . $content . '</' . $this->_tag . '>'); }