From 439f59fb7b1d1d0640a9969ffb59353dc6d87702 Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Fri, 8 Dec 2006 06:09:15 +0000 Subject: [PATCH] * Added redirect to section feature. Use it wisely. * Added a configuration variable allowing the "break out of framesets" feature to be switched on and off. Off by default -- there's all sorts of flashy frameset gadgets around, outright abuse is more rare, especially for small default installs. * Refactored URL fragment handling. * Made Title::secureAndSplit() slightly more legible. * Refactored makeGlobalVariablesScript() -- it's not particularly convenient or maintainable to pass all global variables through the template array. The array is there for BC, not flexibility. --- RELEASE-NOTES | 4 ++ includes/Article.php | 10 ++- includes/DefaultSettings.php | 14 ++++ includes/Linker.php | 23 +------ includes/OutputPage.php | 10 +++ includes/Skin.php | 81 ++++++++++++----------- includes/Title.php | 125 +++++++++++++++++++++++------------ includes/Xml.php | 27 ++++++++ skins/common/wikibits.js | 21 +++++- 9 files changed, 206 insertions(+), 109 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 0efdf9aec4..0157af69c8 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -250,6 +250,10 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * Enable QueryPage classes to override list formatting * (bug 5485) Show number of intervening revisions in diff view * (bug 8100) Fix XHTML validity in Taiwanese localization +* Added redirect to section feature. Use it wisely. +* Added a configuration variable allowing the "break out of framesets" feature + to be switched on and off ($wgBreakFrames). Off by default. + == Languages updated == diff --git a/includes/Article.php b/includes/Article.php index f998147644..edf7aea80d 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -691,6 +691,12 @@ class Article { $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); + + // Set the fragment if one was specified in the redirect + if ( strval( $this->mTitle->getFragment() ) != '' ) { + $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() ); + $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" ); + } $wasRedirected = true; } } elseif ( !empty( $rdfrom ) ) { @@ -778,7 +784,7 @@ class Article { if( !$wasRedirected && $this->isCurrent() ) { $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) ); } - $link = $sk->makeLinkObj( $rt ); + $link = $sk->makeLinkObj( $rt, $rt->getFullText() ); $wgOut->addHTML( '#REDIRECT' . ''.$link.'' ); @@ -2666,7 +2672,7 @@ class Article { public static function getRedirectAutosummary( $text ) { $rt = Title::newFromRedirect( $text ); if( is_object( $rt ) ) - return wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() ); + return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); else return ''; } diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 36255bdf0e..a972ad2863 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2334,4 +2334,18 @@ $wgParserTestFiles = array( "$IP/maintenance/parserTests.txt", ); +/** + * Break out of framesets. This can be used to prevent external sites from + * framing your site with ads. + */ +$wgBreakFrames = false; + +/** + * Break frameset exception list + */ +$wgBreakFramesExceptions = array( + 'babelfish.altavista.com', + 'translate.google.com', +); + ?> diff --git a/includes/Linker.php b/includes/Linker.php index f7b5c0aed3..d74770cc84 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -213,22 +213,6 @@ class Linker { $trail = $m[2]; } } - - # Check for anchors, normalize the anchor - - $parts = explode( '#', $u, 2 ); - if ( count( $parts ) == 2 ) { - $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace(' ', '_', $parts[1] ) ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - $u = $parts[0] . '#' . - str_replace( array_keys( $replacearray ), - array_values( $replacearray ), - $anchor ); - } - $t = "{$text}{$inside}"; wfProfileOut( $fname ); @@ -307,12 +291,7 @@ class Linker { $text = htmlspecialchars( $nt->getFragment() ); } } - $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace( ' ', '_', $nt->getFragment() ) ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - $u .= '#' . str_replace(array_keys($replacearray),array_values($replacearray),$anchor); + $u .= $nt->getFragmentForURL(); } if ( $text == '' ) { $text = htmlspecialchars( $nt->getPrefixedText() ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 7cfd108af7..bbb068b61d 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -72,6 +72,16 @@ class OutputPage { function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); } function addKeyword( $text ) { array_push( $this->mKeywords, $text ); } function addScript( $script ) { $this->mScripts .= $script; } + + /** + * Add a self-contained script tag with the given contents + * @param string $script JavaScript text, no "; + } + function getScript() { return $this->mScripts; } function setETag($tag) { $this->mETag = $tag; } diff --git a/includes/Skin.php b/includes/Skin.php index 28945a785d..982d842924 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -270,60 +270,61 @@ class Skin extends Linker { $out->out( "\n" ); } - static function makeGlobalVariablesScript( $data ) { - $r = ' - '; + static function makeVariablesScript( $data ) { + global $wgJsMimeType; + + $r = "\n"; return $r; } - function getHeadScripts() { - global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType, $wgStyleVersion; + /** + * Make a \n"; global $wgUseSiteJs; diff --git a/includes/Title.php b/includes/Title.php index d836d14325..e564eb6f17 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -567,6 +567,19 @@ class Title { } } + /** + * Escape a text fragment, say from a link, for a URL + */ + static function escapeFragmentForURL( $fragment ) { + $fragment = trim( str_replace( ' ', '_', $fragment ), '_' ); + $fragment = urlencode( Sanitizer::decodeCharReferences( $fragment ) ); + $replaceArray = array( + '%3A' => ':', + '%' => '.' + ); + return strtr( $fragment, $replaceArray ); + } + #---------------------------------------------------------------------------- # Other stuff #---------------------------------------------------------------------------- @@ -639,11 +652,24 @@ class Title { */ function getInterwiki() { return $this->mInterwiki; } /** - * Get the Title fragment (i.e. the bit after the #) + * Get the Title fragment (i.e. the bit after the #) in text form * @return string * @access public */ function getFragment() { return $this->mFragment; } + /** + * Get the fragment in URL form, including the "#" character if there is one + * + * @return string + * @access public + */ + function getFragmentForURL() { + if ( $this->mFragment == '' ) { + return ''; + } else { + return '#' . Title::escapeFragmentForURL( $this->mFragment ); + } + } /** * Get the default namespace index, for when there is no namespace * @return int @@ -803,9 +829,7 @@ class Title { } # Finally, add the fragment. - if ( '' != $this->mFragment ) { - $url .= '#' . $this->mFragment; - } + $url .= $this->getFragmentForURL(); wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) ); return $url; @@ -1442,41 +1466,41 @@ class Title { # Clean up whitespace # - $t = preg_replace( '/[ _]+/', '_', $this->mDbkeyform ); - $t = trim( $t, '_' ); + $dbkey = preg_replace( '/[ _]+/', '_', $this->mDbkeyform ); + $dbkey = trim( $dbkey, '_' ); - if ( '' == $t ) { + if ( '' == $dbkey ) { return false; } - if( false !== strpos( $t, UTF8_REPLACEMENT ) ) { + if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) { # Contained illegal UTF-8 sequences or forbidden Unicode chars. return false; } - $this->mDbkeyform = $t; + $this->mDbkeyform = $dbkey; # Initial colon indicates main namespace rather than specified default # but should not create invalid {ns,title} pairs such as {0,Project:Foo} - if ( ':' == $t{0} ) { + if ( ':' == $dbkey{0} ) { $this->mNamespace = NS_MAIN; - $t = substr( $t, 1 ); # remove the colon but continue processing + $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing } # Namespace or interwiki prefix $firstPass = true; do { $m = array(); - if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $t, $m ) ) { + if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) { $p = $m[1]; $lowerNs = $wgContLang->lc( $p ); if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) { # Canonical namespace - $t = $m[2]; + $dbkey = $m[2]; $this->mNamespace = $ns; } elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) { # Ordinary namespace - $t = $m[2]; + $dbkey = $m[2]; $this->mNamespace = $ns; } elseif( $this->getInterwikiLink( $p ) ) { if( !$firstPass ) { @@ -1486,12 +1510,12 @@ class Title { } # Interwiki link - $t = $m[2]; + $dbkey = $m[2]; $this->mInterwiki = $wgContLang->lc( $p ); # Redundant interwiki prefix to the local wiki if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) { - if( $t == '' ) { + if( $dbkey == '' ) { # Can't have an empty self-link return false; } @@ -1503,9 +1527,9 @@ class Title { # If there's an initial colon after the interwiki, that also # resets the default namespace - if ( $t !== '' && $t[0] == ':' ) { + if ( $dbkey !== '' && $dbkey[0] == ':' ) { $this->mNamespace = NS_MAIN; - $t = substr( $t, 1 ); + $dbkey = substr( $dbkey, 1 ); } } # If there's no recognized interwiki or namespace, @@ -1513,25 +1537,24 @@ class Title { } break; } while( true ); - $r = $t; # We already know that some pages won't be in the database! # if ( '' != $this->mInterwiki || NS_SPECIAL == $this->mNamespace ) { $this->mArticleID = 0; } - $f = strstr( $r, '#' ); - if ( false !== $f ) { - $this->mFragment = substr( $f, 1 ); - $r = substr( $r, 0, strlen( $r ) - strlen( $f ) ); + $fragment = strstr( $dbkey, '#' ); + if ( false !== $fragment ) { + $this->setFragment( $fragment ); + $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) ); # remove whitespace again: prevents "Foo_bar_#" # becoming "Foo_bar_" - $r = preg_replace( '/_*$/', '', $r ); + $dbkey = preg_replace( '/_*$/', '', $dbkey ); } # Reject illegal characters. # - if( preg_match( $rxTc, $r ) ) { + if( preg_match( $rxTc, $dbkey ) ) { return false; } @@ -1540,19 +1563,26 @@ class Title { * often be unreachable due to the way web browsers deal * with 'relative' URLs. Forbid them explicitly. */ - if ( strpos( $r, '.' ) !== false && - ( $r === '.' || $r === '..' || - strpos( $r, './' ) === 0 || - strpos( $r, '../' ) === 0 || - strpos( $r, '/./' ) !== false || - strpos( $r, '/../' ) !== false ) ) + if ( strpos( $dbkey, '.' ) !== false && + ( $dbkey === '.' || $dbkey === '..' || + strpos( $dbkey, './' ) === 0 || + strpos( $dbkey, '../' ) === 0 || + strpos( $dbkey, '/./' ) !== false || + strpos( $dbkey, '/../' ) !== false ) ) { return false; } - # We shouldn't need to query the DB for the size. - #$maxSize = $dbr->textFieldSize( 'page', 'page_title' ); - if ( strlen( $r ) > 255 ) { + /** + * Limit the size of titles to 255 bytes. + * This is typically the size of the underlying database field. + * We make an exception for special pages, which don't need to be stored + * in the database, and may edge over 255 bytes due to subpage syntax + * for long titles, e.g. [[Special:Block/Long name]] + */ + if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) || + strlen( $dbkey ) > 512 ) + { return false; } @@ -1565,9 +1595,7 @@ class Title { * site might be case-sensitive. */ if( $wgCapitalLinks && $this->mInterwiki == '') { - $t = $wgContLang->ucfirst( $r ); - } else { - $t = $r; + $dbkey = $wgContLang->ucfirst( $dbkey ); } /** @@ -1575,26 +1603,39 @@ class Title { * "empty" local links can only be self-links * with a fragment identifier. */ - if( $t == '' && + if( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) { return false; } // Any remaining initial :s are illegal. - if ( $t !== '' && ':' == $t{0} ) { + if ( $dbkey !== '' && ':' == $dbkey{0} ) { return false; } # Fill fields - $this->mDbkeyform = $t; - $this->mUrlform = wfUrlencode( $t ); + $this->mDbkeyform = $dbkey; + $this->mUrlform = wfUrlencode( $dbkey ); - $this->mTextform = str_replace( '_', ' ', $t ); + $this->mTextform = str_replace( '_', ' ', $dbkey ); return true; } + /** + * Set the fragment for this title + * This is kind of bad, since except for this rarely-used function, Title objects + * are immutable. The reason this is here is because it's better than setting the + * members directly, which is what Linker::formatComment was doing previously. + * + * @param string $fragment text + * @access kind of public + */ + function setFragment( $fragment ) { + $this->mFragment = trim( str_replace( '_', ' ', substr( $fragment, 1 ) ), ' ' ); + } + /** * Get a Title object associated with the talk page of this article * @return Title the object for the talk page diff --git a/includes/Xml.php b/includes/Xml.php index 202f99023b..509ed9a68c 100644 --- a/includes/Xml.php +++ b/includes/Xml.php @@ -254,6 +254,33 @@ class Xml { return strtr( $string, $pairs ); } + /** + * Encode a variable of unknown type to JavaScript. + * Doesn't support hashtables just yet. + */ + public static function encodeJsVar( $value ) { + if ( is_bool( $value ) ) { + $s = $value ? 'true' : 'false'; + } elseif ( is_null( $value ) ) { + $s = 'null'; + } elseif ( is_int( $value ) ) { + $s = $value; + } elseif ( is_array( $value ) ) { + $s = '['; + foreach ( $value as $name => $elt ) { + if ( $s != '[' ) { + $s .= ', '; + } + $s .= self::encodeJsVar( $elt ); + } + $s .= ']'; + } else { + $s = '"' . self::escapeJsString( $value ) . '"'; + } + return $s; + } + + /** * Check if a string is well-formed XML. * Must include the surrounding tag. diff --git a/skins/common/wikibits.js b/skins/common/wikibits.js index d20ca1c968..fe0278a9bc 100644 --- a/skins/common/wikibits.js +++ b/skins/common/wikibits.js @@ -47,9 +47,12 @@ if (typeof stylepath != 'undefined' && typeof skin != 'undefined') { document.write(''); } } -// Un-trap us from framesets -if (window.top != window) { - window.top.location = window.location; + +if (wgBreakFrames) { + // Un-trap us from framesets + if (window.top != window && wgBreakFramesExceptions.indexOf(window.top.location.hostname) == -1) { + window.top.location = window.location; + } } // for enhanced RecentChanges @@ -843,6 +846,18 @@ function sortableTables() { } } +function redirectToFragment(fragment) { + if (is_gecko) { + // Mozilla needs to wait until after load, otherwise the window doesn't scroll + addOnloadHook(function () { + if (window.location.hash == "") + window.location.hash = fragment; + }); + } else { + if (window.location.hash == "") + window.location.hash = fragment; + } +} function runOnloadHook() { // don't run anything below this for non-dom browsers -- 2.20.1