MW-Peachy /
Peachy
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * RfA Analysis Library, modified for use with Peachy |
||
| 4 | * Copyright (C) 2006 Tangotango (tangotango.wp _at_ gmail _dot_ com) |
||
| 5 | * |
||
| 6 | * This program is free software; you can redistribute it and/or |
||
| 7 | * modify it under the terms of the GNU General Public License |
||
| 8 | * as published by the Free Software Foundation; either version 2 |
||
| 9 | * of the License, or (at your option) any later version. |
||
| 10 | * |
||
| 11 | * This program is distributed in the hope that it will be useful, |
||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 14 | * GNU General Public License for more details. |
||
| 15 | * |
||
| 16 | * You should have received a copy of the GNU General Public License |
||
| 17 | * along with this program; if not, write to the Free Software |
||
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 19 | */ |
||
| 20 | |||
| 21 | /** |
||
| 22 | * An RFA object contains the parsed information for an RFA |
||
| 23 | */ |
||
| 24 | class RFA { |
||
| 25 | |||
| 26 | protected $pgUsername = false; |
||
| 27 | protected $enddate = false; |
||
| 28 | protected $support = array(); |
||
| 29 | protected $oppose = array(); |
||
| 30 | protected $neutral = array(); |
||
| 31 | protected $duplicates = array(); |
||
| 32 | protected $lasterror = ''; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Analyzes an RFA. Returns TRUE on success, FALSE on failure |
||
| 36 | * @param Wiki $wiki |
||
| 37 | * @param $page |
||
| 38 | * @param null $rawwikitext |
||
| 39 | */ |
||
| 40 | public function __construct( Wiki $wiki, $page, $rawwikitext = null ) { |
||
| 41 | if( is_null( $rawwikitext ) ) $rawwikitext = $wiki->initPage( $page )->get_text(); |
||
| 42 | |||
| 43 | $split = preg_split( |
||
| 44 | "/^(?:(?:'''|(?:<includeonly><noin<\/includeonly><includeonly>clude><\/includeonly>)?={4,5}(?:<includeonly><\/noin<\/includeonly><includeonly>clude><\/includeonly>''')?)" |
||
| 45 | . "\s*?(Support|Oppose|Neutral|Comments)\s*?(?:'''|(?:'''<includeonly><noin<\/includeonly><includeonly>clude><\/includeonly>)?={4,5}(?:<includeonly><\/noin<\/includeonly><includeonly>clude><\/includeonly>)?)|;\s*(Support|Oppose|Neutral|Comments))\s*(?:<br>|<br \/>)?\s*$/im" |
||
| 46 | , $rawwikitext, -1, PREG_SPLIT_DELIM_CAPTURE |
||
| 47 | ); |
||
| 48 | |||
| 49 | $header = array_shift( $split ); |
||
| 50 | |||
| 51 | //=== Deal with the header ===// |
||
| 52 | $header = str_ireplace( array( '<nowiki>', '</nowiki>' ), '', $header ); |
||
| 53 | |||
| 54 | if( preg_match( "/===\s*\[\[User:(.*?)\|.*?\]\]\s*===/", $header, $matches ) ) { |
||
| 55 | $this->username = $matches[1]; |
||
| 56 | } elseif( preg_match( "/===\s*\[\[.*?\|(.*?)\]\]\s*===/", $header, $matches ) ) { |
||
| 57 | $this->username = $matches[1]; |
||
| 58 | } |
||
| 59 | |||
| 60 | $header = str_replace( array( '[[', ']]' ), '', $header ); |
||
| 61 | |||
| 62 | if( preg_match( "/end(?:ing|ed)?(?: no earlier than)? (.*?) \(UTC\)/i", $header, $matches ) ) { |
||
| 63 | $this->enddate = $matches[1]; |
||
| 64 | } |
||
| 65 | //=== End header stuff ===// |
||
| 66 | |||
| 67 | //Now parse through each non-header section, figuring out what they are |
||
| 68 | //Nothing expected = 0, Support = 1, Oppose = 2, Neutral = 3 |
||
| 69 | $nextsection = 0; |
||
| 70 | |||
| 71 | foreach( $split as $splut ){ |
||
| 72 | $splut = trim( $splut ); |
||
| 73 | if( empty( $splut ) ) { |
||
| 74 | continue; |
||
| 75 | } |
||
| 76 | |||
| 77 | if( strcasecmp( $splut, 'Support' ) == 0 ) { |
||
| 78 | $nextsection = 1; |
||
| 79 | } elseif( strcasecmp( $splut, 'Oppose' ) == 0 ) { |
||
| 80 | $nextsection = 2; |
||
| 81 | } elseif( strcasecmp( $splut, 'Neutral' ) == 0 ) { |
||
| 82 | $nextsection = 3; |
||
| 83 | } else { |
||
| 84 | switch( $nextsection ){ |
||
| 85 | case 1: |
||
| 86 | $support = $splut; |
||
| 87 | break; |
||
| 88 | case 2: |
||
| 89 | $oppose = $splut; |
||
| 90 | break; |
||
| 91 | case 3: |
||
| 92 | $neutral = $splut; |
||
| 93 | break; |
||
| 94 | } |
||
| 95 | $nextsection = 0; |
||
| 96 | } |
||
| 97 | } |
||
| 98 | |||
| 99 | if( !isset( $support ) ) { |
||
| 100 | $this->lasterror = "Support section not found"; |
||
| 101 | return false; |
||
| 102 | } |
||
| 103 | if( !isset( $oppose ) ) { |
||
| 104 | $this->lasterror = "Oppose section not found"; |
||
| 105 | return false; |
||
| 106 | } |
||
| 107 | if( !isset( $neutral ) ) { |
||
| 108 | $this->lasterror = "Neutral section not found"; |
||
| 109 | return false; |
||
| 110 | } |
||
| 111 | |||
| 112 | $this->support = $this->analyzeSection( $support ); |
||
| 113 | $this->oppose = $this->analyzeSection( $oppose ); |
||
| 114 | $this->neutral = $this->analyzeSection( $neutral ); |
||
| 115 | |||
| 116 | //Merge all votes in one array and sort: |
||
| 117 | $m = array(); |
||
| 118 | foreach( $this->support as $s ){ |
||
| 119 | if( isset( $s['name'] ) ) $m[] = $s['name']; |
||
| 120 | } |
||
| 121 | foreach( $this->oppose as $o ){ |
||
| 122 | if( isset( $o['name'] ) ) $m[] = $o['name']; |
||
| 123 | } |
||
| 124 | foreach( $this->neutral as $n ){ |
||
| 125 | if( isset( $n['name'] ) ) $m[] = $n['name']; |
||
| 126 | } |
||
| 127 | sort( $m ); |
||
| 128 | //Find duplicates: |
||
| 129 | for( $i = 0; $i < count( $m ); $i++ ){ |
||
| 130 | if( $i != count( $m ) - 1 ) { |
||
| 131 | if( $m[$i] == $m[$i + 1] ) { |
||
| 132 | $this->duplicates[] = $m[$i]; |
||
| 133 | } |
||
| 134 | } |
||
| 135 | } |
||
| 136 | |||
| 137 | return true; |
||
| 138 | } |
||
| 139 | |||
| 140 | public function get_username() { |
||
| 141 | return $this->username; |
||
| 142 | } |
||
| 143 | |||
| 144 | public function get_enddate() { |
||
| 145 | return $this->enddate; |
||
| 146 | } |
||
| 147 | |||
| 148 | public function get_support() { |
||
| 149 | return $this->support; |
||
| 150 | } |
||
| 151 | |||
| 152 | public function get_oppose() { |
||
| 153 | return $this->oppose; |
||
| 154 | } |
||
| 155 | |||
| 156 | public function get_neutral() { |
||
| 157 | return $this->neutral; |
||
| 158 | } |
||
| 159 | |||
| 160 | public function get_duplicates() { |
||
| 161 | return $this->duplicates; |
||
| 162 | } |
||
| 163 | |||
| 164 | public function get_lasterror() { |
||
| 165 | return $this->lasterror; |
||
| 166 | } |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Attempts to find a signature in $input using the default regex. Returns matches. |
||
| 170 | * @param $input |
||
| 171 | * @param $matches |
||
| 172 | * |
||
| 173 | * @return int |
||
| 174 | */ |
||
| 175 | protected function findSig( $input, &$matches ) { |
||
| 176 | //Supports User: and User talk: wikilinks, {{fullurl}}, unsubsted {{unsigned}}, unsubsted {{unsigned2}}, anything that looks like a custom sig template |
||
| 177 | return preg_match_all( |
||
| 178 | "/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\|\/]*)(?:\|[^\]]*)?\]\]" //1: Normal [[User:XX]] and [[User talk:XX]] |
||
| 179 | . "|\{\{(?:[Ff]ullurl\:[Uu]ser(?:[\s_][Tt]alk)?\:|[Uu]nsigned\|)([^\}\|]*)(?:|[\|\}]*)?\}\}" //2: {{fullurl}} and {{unsigned}} templates |
||
| 180 | . "|(?:\{\{)[Uu]ser(?:[\s_][Tt]alk)?\:([^\}\/\|]*)" //3: {{User:XX/sig}} templates |
||
| 181 | . "|\{\{[Uu]nsigned2\|[^\|]*\|([^\}]*)\}\}" //4: {{unsigned2|Date|XX}} templates |
||
| 182 | . "|(?:\[\[)[Uu]ser\:([^\]\/\|]*)\/[Ss]ig[\|\]]/" //5: [[User:XX/sig]] links (compromise measure) |
||
| 183 | , $input, $matches, PREG_OFFSET_CAPTURE |
||
| 184 | ); |
||
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Attempts to find a signature in $input using a different regex. Returns matches. |
||
| 189 | * @param $input |
||
| 190 | * @param $matches |
||
| 191 | * |
||
| 192 | * @return int |
||
| 193 | */ |
||
| 194 | protected function findSigAlt( $input, &$matches ) { |
||
| 195 | return preg_match_all( |
||
| 196 | "/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\/\|]*)" //5: "[[User:XX/PageAboutMe" links (notice no end tag) |
||
| 197 | . "|\[\[[Ss]pecial\:[Cc]ontributions\/([^\|\]]*)/" |
||
| 198 | , $input, $matches, PREG_OFFSET_CAPTURE |
||
| 199 | ); |
||
| 200 | } |
||
| 201 | |||
| 202 | /** |
||
| 203 | * Attempts to find a signature in $input. Returns the name of the user, false on failure. |
||
| 204 | * @param $input |
||
| 205 | * @param $iffy |
||
| 206 | * |
||
| 207 | * @return bool|string false if not found Signature, or the Signature if it is found |
||
| 208 | */ |
||
| 209 | protected function findSigInLine( $input, &$iffy ) { |
||
| 210 | $iffy = 0; |
||
| 211 | |||
| 212 | $parsee_array = explode( "\n", $input ); |
||
| 213 | for( $n = 0; $n < count( $parsee_array ); $n++ ){ //This for will terminate when a sig is found. |
||
|
0 ignored issues
–
show
Consider avoiding function calls on each iteration of the
for loop.
If you have a function call in the test part of a // count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }
// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
|
|||
| 214 | $parsee = $parsee_array[$n]; |
||
| 215 | //Okay, let's try and remove "copied from above" messages. If the line has more than one timestamp, we'll disregard anything after the first. |
||
| 216 | //Note: we're ignoring people who use custom timestamps - if these peoples' votes are moved, the mover's name will show up as having voted. |
||
| 217 | |||
| 218 | //If more than one timestamp is found in the first portion of the vote: |
||
| 219 | $tsmatches = array(); |
||
| 220 | $dummymatches = array(); |
||
| 221 | if( preg_match_all( '/' . "[0-2][0-9]\:[0-5][0-9], [1-3]?[0-9] (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{4} \(UTC\)" . '/', $parsee, $tsmatches, PREG_OFFSET_CAPTURE ) > 1 ) { |
||
| 222 | //Go through each timestamp-section, looking for a signature |
||
| 223 | foreach( $tsmatches[0] as $minisection ){ |
||
| 224 | $temp = substr( $parsee, 0, $minisection[1] ); |
||
| 225 | //If a signature is found, stop and use it as voter |
||
| 226 | if( $this->findSig( $temp, $dummymatches ) != 0 ) { //KNOWN ISSUE: Write description later |
||
| 227 | $parsee = $temp; |
||
| 228 | break; |
||
| 229 | } |
||
| 230 | } |
||
| 231 | } |
||
| 232 | |||
| 233 | //Start the main signature-finding: |
||
| 234 | $matches = array(); |
||
| 235 | if( $this->findSig( $parsee, $matches ) == 0 ) { |
||
| 236 | //Okay, signature not found. Let's try the backup regex |
||
| 237 | if( $this->findSigAlt( $parsee, $matches ) == 0 ) { |
||
| 238 | //Signature was not found in this iteration of the main loop :( |
||
| 239 | continue; //Go on to next newline (may be iffy) |
||
| 240 | } else { |
||
| 241 | $merged = array_merge( $matches[1], $matches[2] ); |
||
| 242 | } |
||
| 243 | } else { |
||
| 244 | //Merge the match arrays: |
||
| 245 | $merged = array_merge( $matches[5], $matches[1], $matches[3], $matches[2], $matches[4] ); |
||
| 246 | } |
||
| 247 | //Remove blank values and arrays of the form ('',-1): |
||
| 248 | foreach( $merged as $key => $value ){ |
||
| 249 | if( is_array( $value ) && ( $value[0] == '' ) && ( $value[1] == -1 ) ) { |
||
| 250 | unset( $merged[$key] ); |
||
| 251 | } elseif( $value == "" ) { |
||
| 252 | unset( $merged[$key] ); |
||
| 253 | } |
||
| 254 | } |
||
| 255 | |||
| 256 | //Let's find out the real signature |
||
| 257 | $keys = array(); |
||
| 258 | $values = array(); |
||
| 259 | foreach( $merged as $mergee ){ |
||
| 260 | $keys[] = $mergee[0]; |
||
| 261 | $values[] = $mergee[1]; |
||
| 262 | } |
||
| 263 | //Now sort: |
||
| 264 | array_multisort( $values, SORT_DESC, SORT_NUMERIC, $keys ); |
||
| 265 | //Now we should have the most relevant match (i.e., the sig) at the top of $keys |
||
| 266 | $i = 0; |
||
| 267 | $foundsig = ''; |
||
| 268 | while( $foundsig == '' ){ |
||
| 269 | $foundsig = trim( $keys[$i++] ); |
||
| 270 | if( $i == count( $keys ) ) break; //If we can only find blank usernames in the sig, catch overflow |
||
| 271 | //Also fires when the first sig is also the last sig, so not an error |
||
| 272 | } |
||
| 273 | |||
| 274 | //Set iffy flag (level 1) if went beyond first line |
||
| 275 | if( $n > 0 ) { |
||
| 276 | $iffy = 1; |
||
| 277 | } |
||
| 278 | return $foundsig; |
||
| 279 | } |
||
| 280 | |||
| 281 | return false; |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * Analyzes an RFA section. Returns an array of parsed signatures on success. Undefined behaviour on failure. |
||
| 286 | * @param string $input |
||
| 287 | * |
||
| 288 | * @return array |
||
| 289 | */ |
||
| 290 | private function analyzeSection( $input ) { |
||
| 291 | //Remove trailing sharp, if any |
||
| 292 | $input = preg_replace( '/#\s*$/', '', $input ); |
||
| 293 | |||
| 294 | //Old preg_split regex: "/(^|\n)\s*\#[^\#\:\*]/" |
||
| 295 | $parsed = preg_split( "/(^|\n)\#/", $input ); |
||
| 296 | //Shift off first empty element: |
||
| 297 | array_shift( $parsed ); |
||
| 298 | |||
| 299 | foreach( $parsed as &$parsee ){ //Foreach line |
||
| 300 | //If the line is empty for some reason, ignore |
||
| 301 | $parsee = trim( $parsee ); |
||
| 302 | if( empty( $parsee ) ) continue; |
||
| 303 | |||
| 304 | //If the line has been indented (disabled), or is a comment, ignore |
||
| 305 | if( ( $parsee[0] == ':' ) || ( $parsee[0] == '*' ) || ( $parsee[0] == '#' ) ) { |
||
| 306 | $parsee = '///###///'; |
||
| 307 | continue; |
||
| 308 | }; //struck-out vote or comment |
||
| 309 | |||
| 310 | $parsedsig = $this->findSigInLine( $parsee, $iffy ); //Find signature |
||
| 311 | $orgsig = $parsee; |
||
| 312 | $parsee = array(); |
||
| 313 | $parsee['context'] = $orgsig; |
||
| 314 | if( $parsedsig === false ) { |
||
| 315 | $parsee['error'] = 'Signature not found'; |
||
| 316 | } else { |
||
| 317 | $parsee['name'] = $parsedsig; |
||
| 318 | } |
||
| 319 | if( @$iffy == 1 ) { |
||
| 320 | $parsee['iffy'] = '1'; |
||
| 321 | } |
||
| 322 | } //Foreach line |
||
| 323 | |||
| 324 | if( ( count( $parsed ) == 1 ) && ( @trim( $parsed[0]['name'] ) == '' ) ) { //filters out placeholder sharp sign used in empty sections |
||
| 325 | $parsed = array(); |
||
| 326 | } |
||
| 327 | |||
| 328 | //Delete struck-out keys "continued" in foreach |
||
| 329 | foreach( $parsed as $key => $value ){ |
||
| 330 | if( $value == '///###///' ) { |
||
| 331 | unset( $parsed[$key] ); |
||
| 332 | } |
||
| 333 | } |
||
| 334 | |||
| 335 | return $parsed; |
||
| 336 | } |
||
| 337 | |||
| 338 | } |
||
| 339 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: