diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php index 1940ee8..4640df2 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@\\*\\*(.+?)\\*\\*@s', array($this, 'applyCallback'), $text); } - protected function applyCallback($matches) { + protected function applyCallback(array $matches) { return hsprintf('%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php index 623441c..82f23d2 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php index 818ff9e..ed85d72 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php @@ -1,133 +1,132 @@ getEngine()->isTextMode()) { $text = $link; if (strncmp($link, '/', 1) == 0 || strncmp($link, '#', 1) == 0) { $base = $this->getEngine()->getConfig('uri.prefix'); if (strncmp($link, '/', 1) == 0) { $base = rtrim($base, '/'); } $text = $base.$text; } // If present, strip off "mailto:" or "tel:". $text = preg_replace('/^(?:mailto|tel):/', '', $text); if ($link == $name) { return $text; } return $name.' <'.$text.'>'; } else if ($this->getEngine()->isHTMLMailMode()) { if (strncmp($link, '/', 1) == 0 || strncmp($link, '#', 1) == 0) { $base = $this->getEngine()->getConfig('uri.base'); $text = $link; if (strncmp($link, '/', 1) == 0) { $base = rtrim($base, '/'); } $link = $base.$text; } } // By default, we open links in a new window or tab. For anchors on the same // page, just jump normally. $target = '_blank'; if (strncmp($link, '#', 1) == 0) { $target = null; } $name = preg_replace('/^(?:mailto|tel):/', '', $name); if ($this->getEngine()->getState('toc')) { return $name; } else { return phutil_tag( 'a', array( 'href' => $link, 'class' => 'remarkup-link', 'target' => $target, ), $name); } } - public function markupAlternateLink($matches) { + public function markupAlternateLink(array $matches) { $uri = trim($matches[2]); // NOTE: We apply some special rules to avoid false positives here. The // major concern is that we do not want to convert `x[0][1](y)` in a // discussion about C source code into a link. To this end, we: // // - Don't match at word boundaries; // - require the URI to contain a "/" character or "@" character; and // - reject URIs which being with a quote character. if ($uri[0] == '"' || $uri[0] == "'" || $uri[0] == '`') { return $matches[0]; } if (strpos($uri, '/') === false && strpos($uri, '@') === false && strncmp($uri, 'tel:', 4)) { return $matches[0]; } return $this->markupDocumentLink( array( $matches[0], $matches[2], $matches[1], )); } - public function markupDocumentLink($matches) { + public function markupDocumentLink(array $matches) { $uri = trim($matches[1]); $name = trim(idx($matches, 2, $uri)); // If whatever is being linked to begins with "/" or "#", or has "://", // or is "mailto:" or "tel:", treat it as a URI instead of a wiki page. $is_uri = preg_match('@(^/)|(://)|(^#)|(^(?:mailto|tel):)@', $uri); if ($is_uri && strncmp('/', $uri, 1) && strncmp('#', $uri, 1)) { $protocols = $this->getEngine()->getConfig( 'uri.allowed-protocols', array()); $protocol = id(new PhutilURI($uri))->getProtocol(); if (!idx($protocols, $protocol)) { // Don't treat this as a URI if it's not an allowed protocol. $is_uri = false; } } if (!$is_uri) { return $matches[0]; } return $this->getEngine()->storeText($this->renderHyperlink($uri, $name)); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php index 2e3d87b..b9c51e5 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php @@ -1,99 +1,99 @@ " around them get linked exactly, without // the "<>". Angle brackets are basically special and mean "this is a URL // with weird characters". This is assumed to be reasonable because they // don't appear in normal text or normal URLs. $text = preg_replace_callback( '@<(\w{3,}://[^\s'.PhutilRemarkupBlockStorage::MAGIC_BYTE.']+?)>@', array($this, 'markupHyperlink'), $text); // Anything else we match "ungreedily", which means we'll look for // stuff that's probably puncutation or otherwise not part of the URL and // not link it. This lets someone write "QuicK! Go to // http://www.example.com/!". We also apply some paren balancing rules. // NOTE: We're explicitly avoiding capturing stored blocks, so text like // `http://www.example.com/[[x | y]]` doesn't get aggressively captured. $text = preg_replace_callback( '@(\w{3,}://[^\s'.PhutilRemarkupBlockStorage::MAGIC_BYTE.']+)@', array($this, 'markupHyperlinkUngreedy'), $text); return $text; } - protected function markupHyperlink($matches) { + protected function markupHyperlink(array $matches) { $protocols = $this->getEngine()->getConfig( 'uri.allowed-protocols', array()); $protocol = id(new PhutilURI($matches[1]))->getProtocol(); if (!idx($protocols, $protocol)) { // If this URI doesn't use a whitelisted protocol, don't link it. This // is primarily intended to prevent javascript:// silliness. return $this->getEngine()->storeText($matches[1]); } return $this->storeRenderedHyperlink($matches[1]); } protected function storeRenderedHyperlink($link) { return $this->getEngine()->storeText($this->renderHyperlink($link)); } protected function renderHyperlink($link) { if ($this->getEngine()->isTextMode()) { return $link; } if ($this->getEngine()->getState('toc')) { return $link; } else { return phutil_tag( 'a', array( 'href' => $link, 'class' => 'remarkup-link', 'target' => '_blank', ), $link); } } protected function markupHyperlinkUngreedy($matches) { $match = $matches[1]; $tail = null; $trailing = null; if (preg_match('/[;,.:!?]+$/', $match, $trailing)) { $tail = $trailing[0]; $match = substr($match, 0, -strlen($tail)); } // If there's a closing paren at the end but no balancing open paren in // the URL, don't link the close paren. This is an attempt to gracefully // handle the two common paren cases, Wikipedia links and English language // parentheticals, e.g.: // // http://en.wikipedia.org/wiki/Noun_(disambiguation) // (see also http://www.example.com) // // We could apply a craftier heuristic here which tries to actually balance // the parens, but this is probably sufficient. if (preg_match('/\\)$/', $match) && !preg_match('/\\(/', $match)) { $tail = ')'.$tail; $match = substr($match, 0, -1); } return hsprintf('%s%s', $this->markupHyperlink(array(null, $match)), $tail); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php index f05410c..9eefe2a 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php index e63415a..cd5ab8a 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php @@ -1,49 +1,49 @@ getEngine()->isTextMode()) { $result = $matches[0]; } else if ($this->getEngine()->isHTMLMailMode()) { $match = isset($matches[2]) ? $matches[2] : $matches[1]; $result = phutil_tag( 'tt', array( 'style' => 'background: #ebebeb; font-size: 13px;', ), $match); } else { $match = isset($matches[2]) ? $matches[2] : $matches[1]; $result = phutil_tag( 'tt', array( 'class' => 'remarkup-monospaced', ), $match); } return $this->getEngine()->storeText($result); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupRule.php index 3ef5c87..292bfe6 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupRule.php @@ -1,112 +1,112 @@ engine = $engine; return $this; } public function getEngine() { return $this->engine; } public function getPriority() { return 500.0; } abstract public function apply($text); public function getPostprocessKey() { return spl_object_hash($this); } public function didMarkupText() { return; } protected function replaceHTML($pattern, $callback, $text) { $this->replaceCallback = $callback; return phutil_safe_html(preg_replace_callback( $pattern, array($this, 'replaceHTMLCallback'), phutil_escape_html($text))); } - private function replaceHTMLCallback($match) { + private function replaceHTMLCallback(array $match) { return phutil_escape_html(call_user_func( $this->replaceCallback, array_map('phutil_safe_html', $match))); } /** * Safely generate a tag. * * In Remarkup contexts, it's not safe to use arbitrary text in tag * attributes: even though it will be escaped, it may contain replacement * tokens which are then replaced with markup. * * This method acts as @{function:phutil_tag}, but checks attributes before * using them. * * @param string Tag name. * @param dict Tag attributes. * @param wild Tag content. * @return PhutilSafeHTML Tag object. */ protected function newTag($name, array $attrs, $content = null) { foreach ($attrs as $key => $attr) { if ($attr !== null) { $attrs[$key] = $this->assertFlatText($attr); } } return phutil_tag($name, $attrs, $content); } /** * Assert that a text token is flat (it contains no replacement tokens). * * Because tokens can be replaced with markup, it is dangerous to use * arbitrary input text in tag attributes. Normally, rule precedence should * prevent this. Asserting that text is flat before using it as an attribute * provides an extra layer of security. * * Normally, you can call @{method:newTag} rather than calling this method * directly. @{method:newTag} will check attributes for you. * * @param wild Ostensibly flat text. * @return string Flat text. */ protected function assertFlatText($text) { $text = (string)hsprintf('%s', phutil_safe_html($text)); $rich = (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) !== false); if ($rich) { throw new Exception( pht( 'Remarkup rule precedence is dangerous: rendering text with tokens '. 'as flat text!')); } return $text; } /** * Check whether text is flat (contains no replacement tokens) or not. * * @param wild Ostensibly flat text. * @return bool True if the text is flat. */ protected function isFlatText($text) { $text = (string)hsprintf('%s', phutil_safe_html($text)); return (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) === false); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php index 0224fb7..1f15728 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } }