1############################################################################### 2# RSS.pm # 3# $Date: 12.02.14 $ # 4############################################################################### 5# YaBB: Yet another Bulletin Board # 6# Open-Source Community Software for Webmasters # 7# Version: YaBB 2.6.11 # 8# Packaged: December 2, 2014 # 9# Distributed by: http://www.yabbforum.com # 10# =========================================================================== # 11# Copyright (c) 2000-2014 YaBB (www.yabbforum.com) - All Rights Reserved. # 12# Software by: The YaBB Development Team # 13# with assistance from the YaBB community. # 14############################################################################### 15use CGI::Carp qw(fatalsToBrowser); 16our $VERSION = '2.6.11'; 17 18$rsspmver = 'YaBB 2.6.11 $Revision: 1611 $'; 19if ( $action eq 'detailedversion' ) { return 1; } 20 21# Change the error routine for here. 22local $SIG{__WARN__} = sub { RSS_error(@_) }; 23 24# Allow us to be called by a system()-like call 25# This lets us send data to any language that supports capturing STDOUT. 26# Usage is detailed in POD at the bottom. 27if ( scalar @ARGV ) { shellaccess(); } 28 29# Is RSS disabled? 30if ($rss_disabled) { RSS_error('not_allowed'); } 31 32LoadCensorList(); 33 34# Load YaBBC if it is enabled 35if ($enable_ubbc) { require Sources::YaBBC; } 36 37# Read from a single board 38sub RSS_board { 39 ### Arguments: 40 # board: the board to load from. Defaults to all boards. 41 # showauthor: show the author or not? Defaults to false. 42 # topics: Number of topics to show. Defaults to 5. 43 ### 44 45 # Local variables 46 my ( $board, $topics ); # Variables for settings 47 48 # Settings 49 $board = $INFO{'board'}; 50 $topics = $INFO{'topics'} || $rss_limit || 10; 51 if ( $rss_limit && $topics > $rss_limit ) { $topics = $rss_limit; } 52 53 ### Security check ### 54 if ( AccessCheck( $currentboard, q{}, $boardperms ) ne 'granted' ) { 55 RSS_error('no_access'); 56 } 57 if ( $annboard eq $board && !$iamadmin && !$iamgmod ) { 58 RSS_error('no_access'); 59 } 60 if ( ${ $uid . $currentboard }{'brdpasswr'} ) { 61 my $cookiename = "$cookiepassword$currentboard$username"; 62 my $crypass = ${ $uid . $currentboard }{'brdpassw'}; 63 if ( !$staff && $yyCookies{$cookiename} ne $crypass ) { 64 RSS_error('no_access'); 65 } 66 } 67 68 # Now, go into the board and look for the last X topics 69 fopen( BRDTXT, "$boardsdir/$board.txt" ) 70 || RSS_error( 'cannot_open', "$boardsdir/$board.txt", 1 ); 71 my @threadlist = <BRDTXT>; 72 fclose(BRDTXT); 73 my $threadcount = @threadlist; 74 if ( $threadcount < $topics ) { $topics = $threadcount; } 75 76 @threadlist = splice @threadlist, 0, $topics; 77 78 # Sorting mode 79 if ( $rss_message == 2 ) { 80 81 # Sort by original post 82 @threadlist = sort @threadlist; 83 } 84 85 # Otherwise, it's good enough as-is 86 chomp @threadlist; 87 88 my $i = 0; 89 foreach (@threadlist) { 90 ( 91 $mnum, $msub, $mname, $memail, $mdate, 92 $mreplies, $musername, $micon, $mstate, $ns 93 ) = split /\|/xsm, $_; 94 $curnum = $mnum; 95 96 # See if this is a topic that we don't want displayed. 97 if ( $mstate =~ /h/sm && !$iamadmin && !$iamgmod ) { next; } 98 99 # Does it need to be returned as a 304? 100 if ( $i == 0 ) { # Do this for the first request only 101 $cachedate = RFC822Date($mdate); 102 if ( $ENV{'HTTP_IF_NONE_MATCH'} eq qq~"$cachedate"~ 103 || $ENV{'HTTP_IF_MODIFIED_SINCE'} eq $cachedate ) 104 { 105 Send304NotModified(); 106 # Comment this out to test with caching disabled 107 } 108 } 109 110 ( $msub, undef ) = Split_Splice_Move( $msub, 0 ); 111 FromHTML($msub); 112 ToChars($msub); 113 114 # Censor the subject of the thread. 115 $msub = Censor($msub); 116 117 my $postid = "$mreplies#$mreplies"; 118 if ( $rss_message == 2 ) { $postid = '0#0'; } 119 120 my $category = "$mbname/$boardname"; 121 FromHTML($category); 122 123 # Show the minimum stuff (topic title, link to it) 124 if ($accept_permalink) { 125 $permdate = permtimer($curnum); 126 $yymain .= q~ <item> 127 <title>~ . RSSDescriptionTrim($msub) . q~</title> 128 <link>~ 129 . RSSDescriptionTrim( 130 "http://$perm_domain/$symlink$permdate/$currentboard/$curnum") 131 . q~</link> 132 <category>~ . RSSDescriptionTrim($category) . q~</category> 133 <guid isPermaLink="true">~ 134 . RSSDescriptionTrim( 135 "http://$perm_domain/$symlink$permdate/$currentboard/$curnum") 136 . q~</guid> 137~; 138 } 139 else { 140 $yymain .= q~ <item> 141 <title>~ . RSSDescriptionTrim($msub) . q~</title> 142 <link>~ 143 . RSSDescriptionTrim("$scripturl?num=$curnum") . q~</link> 144 <category>~ . RSSDescriptionTrim($category) . q~</category> 145 <guid>~ 146 . RSSDescriptionTrim("$scripturl?num=$curnum") . q~</guid> 147~; 148 } 149 150 my $post; 151 fopen( TOPIC, "$datadir/$curnum.txt" ) 152 || RSS_error( 'cannot_open', "$datadir/$curnum.txt", 1 ); 153 if ( $rss_message == 1 ) { 154 155 # Open up the thread and read the last post. 156 while (<TOPIC>) { 157 chomp $_; 158 if ($_) { $post = $_; } 159 } 160 } 161 elsif ( $rss_message == 2 ) { 162 163 # Open up the thread and read the first post. 164 $post = <TOPIC>; 165 } 166 fclose(TOPIC); 167 if ( $post ne q{} ) { 168 ( 169 undef, undef, undef, undef, $musername, 170 undef, undef, undef, $message, $ns 171 ) = split /\|/xsm, $post; 172 } 173 if ($showauthor) { 174 if ( -e "$memberdir/$musername.vars" ) { 175 LoadUser($musername); 176 if ( !${ $uid . $musername }{'hidemail'} ) { 177 $yymain .= 178 q~<author>~ 179 . RSSDescriptionTrim( 180"${$uid.$musername}{'email'} (${$uid.$musername}{'realname'})" 181 ) . q~</author>~; 182 } 183 else { 184 $yymain .= 185 q~ <author>~ 186 . RSSDescriptionTrim("$rssemail (${$uid.$musername}{'realname'})") 187 . qq~</author>\n~; 188 } 189 } 190 } 191 if ($showdate) { 192 if ( $rss_message == 2 ) { 193 $mdate = $curnum; 194 } # Sort by topic creation if requested. 195 # Get the date how the user wants it. 196 my $realdate = RFC822Date($mdate); 197 $yymain .= qq~ <pubDate>$realdate</pubDate> 198~; 199 } 200 if ( $message ne q{} ) { 201 ( $message, undef ) = Split_Splice_Move( $message, $curnum ); 202 if ($enable_ubbc) { 203 LoadUser($musername); 204 $displayname = ${ $uid . $musername }{'realname'}; 205 DoUBBC(); 206 } 207 FromHTML($message); 208 ToChars($message); 209 $message = Censor($message); 210 $yymain .= 211 q~ <description>~ 212 . RSSDescriptionTrim($message) 213 . q~</description> 214~; 215 } 216 217 # Finish up the item 218 $yymain .= q~ </item> 219~; 220 $yymain =~ s/data-rel/rel/gsm; 221 $i++; # Increment 222 } 223 224 ToChars($boardname); 225 $yytitle = $boardname; 226 $yydesc = ${ $uid . $curboard }{'description'}; 227 228 RSS_template(); 229 return; 230} 231 232# Similar to Recent.pl&RecentList but uses original code 233# RSS feed from multiple boards (a category or the whole forum) 234sub RSS_recent { 235 ### Arguments: 236 # catselect: use a specific category instead of the whole forum (optional) 237 # topics: Number of topics to show. Defaults to 5. 238 ### 239 240 # Local variables 241 my ($topics); # Variables for settings 242 my ( @threadlist, $i ); # Variables for the messages 243 244 # Settings 245 $topics = $INFO{'topics'} || $rss_limit || 10; 246 if ( $rss_limit && $topics > $rss_limit ) { $topics = $rss_limit; } 247 248 $yytitle = "$topics $maintxt{'214b'}"; 249 250 # If this is just a single category, handle it. 251 if ( $INFO{'catselect'} ) { 252 @categoryorder = ( $INFO{'catselect'} ); 253 } 254 255 # Find the latest $topics post times in all boards that we have access to 256 # and add them to a giant array 257 foreach my $catid (@categoryorder) { 258 my $boardlist = $cat{$catid}; 259 260 my @bdlist = split /\,/xsm, $boardlist; 261 my ( $catname, $catperms ) = split /\|/xsm, $catinfo{$catid}; 262 my $cataccess = CatAccess($catperms); 263 if ( !$cataccess ) { next; } 264 265 if ( $INFO{'catselect'} ) { 266 $yytitle = $catname; 267 $mydesc = $catname; 268 } 269 270 foreach my $curboard (@bdlist) { 271 ( $boardname{$curboard}, $boardperms, $boardview ) = split /\|/xsm, 272 $board{$curboard}; 273 274 my $access = AccessCheck( $curboard, q{}, $boardperms ); 275 if ( !$iamadmin && $access ne 'granted' ) { next; } 276 if ( ${ $uid . $curboard }{'brdpasswr'} ) { 277 my $cookiename = "$cookiepassword$curboard$username"; 278 my $crypass = ${ $uid . $curboard }{'brdpassw'}; 279 if ( !$staff && $yyCookies{$cookiename} ne $crypass ) { next; } 280 } 281 282 fopen( BOARD, "$boardsdir/$curboard.txt" ) 283 || RSS_error( 'cannot_open', "$boardsdir/$curboard.txt", 1 ); 284 for my $i ( 0 .. ( $topics - 1 ) ) { 285 my ( $buffer, $mnum, $mdate, $mstate ); 286 287 $buffer = <BOARD>; 288 if ( !$buffer ) { last; } 289 chomp $buffer; 290 291 ( 292 $mnum, undef, undef, undef, $mdate, 293 undef, undef, undef, $mstate 294 ) = split /\|/xsm, $buffer; 295 $mdate = sprintf '%010d', $mdate; 296 if ( $rss_message == 2 ) { 297 $mdate = $mnum; 298 } # Sort by topic creation if requested. 299 300 # Check if it's hidden. If so, don't show it 301 if ( $mstate =~ /h/sm && !$iamadmin && !$iamgmod ) { next; } 302 303 # Add it to an array, using $mdate as the first value so we can easily sort 304 push @threadlist, "$mdate|$curboard|$buffer"; 305 } 306 fclose(BOARD); 307 308 # Clean out the extra entries in the threadlist 309 @threadlist = reverse sort @threadlist; 310 $threadcount = @threadlist; 311 if ( $threadcount < $topics ) { $topics = $threadcount; } 312 @threadlist = @threadlist[ 0 .. $topics - 1 ]; 313 } 314 } 315 316 for my $i ( 0 .. ( @threadlist - 1 ) ) { 317 318 # Opening item stuff 319 ( 320 $mdate, $board, $mnum, $msub, 321 $mname, $memail, $modate, $mreplies, 322 $musername, $micon, $mstate 323 ) = split /\|/xsm, $threadlist[$i]; 324 $curnum = $mnum; 325 326 ( $msub, undef ) = Split_Splice_Move( $msub, 0 ); 327 FromHTML($msub); 328 ToChars($msub); 329 330 # Censor the subject of the thread. 331 $msub = Censor($msub); 332 333 # Does it need to be returned as a 304? 334 if ( $i == 0 ) { # Do this for the first request only 335 $cachedate = RFC822Date($mdate); 336 if ( $ENV{'HTTP_IF_NONE_MATCH'} eq qq~"$cachedate"~ 337 || $ENV{'HTTP_IF_MODIFIED_SINCE'} eq $cachedate ) 338 { 339 Send304NotModified(); 340 # Comment this out to test with caching disabled 341 } 342 } 343 344 my $postid = "$mreplies#$mreplies"; 345 if ( $rss_message == 2 ) { $postid = '0#0'; } 346 347 my $category = "$mbname/$boardname{$board}"; 348 FromHTML($category); 349 my $bn = $boardname{$board}; 350 FromHTML($bn); 351 if ($accept_permalink) { 352 my $permsub = $msub; 353 $permdate = permtimer($curnum); 354 $permsub =~ s/ /$perm_spacer/gsm; 355 $yymain .= q~ <item> 356 <title>~ . RSSDescriptionTrim("$bn - $msub") . q~</title> 357 <link>~ 358 . RSSDescriptionTrim( 359 "http://$perm_domain/$symlink$permdate/$board/$curnum") 360 . q~</link> 361 <category>~ . RSSDescriptionTrim($category) . q~</category> 362 <guid isPermaLink="true">~ 363 . RSSDescriptionTrim( 364 "http://$perm_domain/$symlink$permdate/$board/$curnum") 365 . qq~</guid>\n~; 366 } 367 else { 368 $yymain .= q~ <item> 369 <title>~ . RSSDescriptionTrim("$bn - $msub") . q~</title> 370 <link>~ 371 . RSSDescriptionTrim("$scripturl?num=$curnum/$postid") . q~</link> 372 <category>~ . RSSDescriptionTrim($category) . q~</category> 373 <guid>~ 374 . RSSDescriptionTrim("$scripturl?num=$curnum/$postid") 375 . qq~</guid>\n~; 376 } 377 378 my $post; 379 fopen( TOPIC, "$datadir/$curnum.txt" ) 380 || RSS_error( 'cannot_open', "$datadir/$curnum.txt", 1 ); 381 if ( $rss_message == 1 ) { 382 383 # Open up the thread and read the last post. 384 while (<TOPIC>) { 385 chomp $_; 386 if ($_) { $post = $_; } 387 } 388 } 389 elsif ( $rss_message == 2 ) { 390 391 # Open up the thread and read the first post. 392 $post = <TOPIC>; 393 } 394 fclose(TOPIC); 395 396 if ( $post ne q{} ) { 397 ( 398 undef, undef, undef, undef, $musername, 399 undef, undef, undef, $message, $ns 400 ) = split /\|/xsm, $post; 401 } 402 403 if ($showauthor) { 404 405# The spec really wants us to include their email. 406# That's not advisable for us (spambots anyone?). So we skip author if the email hidden flag is on for that user. 407 if ( -e "$memberdir/$musername.vars" ) { 408 LoadUser($musername); 409 if ( !${ $uid . $musername }{'hidemail'} ) { 410 $yymain .= 411 q~ <author>~ 412 . RSSDescriptionTrim( 413"${$uid.$musername}{'email'} (${$uid.$musername}{'realname'})" 414 ) . qq~</author>\n~; 415 } 416 else { 417 $yymain .= 418 q~ <author>~ 419 . RSSDescriptionTrim("$rssemail (${$uid.$musername}{'realname'})") 420 . qq~</author>\n~; 421 } 422 } 423 } 424 425 if ($showdate) { 426 if ( $rss_message == 2 ) { 427 $mdate = $curnum; 428 } # Sort by topic creation if requested. 429 # Get the date how the user wants it. 430 my $realdate = RFC822Date($mdate); 431 $yymain .= qq~ <pubDate>$realdate</pubDate>\n~; 432 } 433 434 if ( $message ne q{} ) { 435 ( $message, undef ) = Split_Splice_Move( $message, $curnum ); 436 if ($enable_ubbc) { 437 LoadUser($musername); 438 $displayname = ${ $uid . $musername }{'realname'}; 439 DoUBBC(); 440 } 441 FromHTML($message); 442 ToChars($message); 443 $message = Censor($message); 444 $yymain .= 445 q~ <description>~ 446 . RSSDescriptionTrim($message) 447 . qq~</description>\n~; 448 } 449 450 $yymain .= qq~ </item>\n 451~; 452 $yymain =~ s/data-rel/rel/gsm; 453 } 454 455 ToChars($boardname); 456 $yydesc = ${ $uid . $curboard }{'description'}; 457 458 RSS_template(); 459 return; 460} 461 462sub RSS_template { # print RSS output 463 # Generate the lastBuildDate 464 my $rssdate = RFC822Date($date); 465 466# Send out the "Last-Modified" and "ETag" headers so nice readers will ask before downloading. 467 $LastModified = $ETag = $cachedate || $rssdate; 468 $contenttype = 'text/xml'; 469 print_output_header(); 470 471 # Make the generator look better 472 my $RSSplver = $rssplver; 473 $RSSplver =~ s/\$//gxsm; 474 475# Removed per Corey's suggestion: http://www.yabbforum.com/community/YaBB.pl?num=1142571424/20#20 476#my $docs = " <docs>http://$perm_domain</docs>\n" if $perm_domain; 477 478 my $mainlink = $scripturl; 479 my $tit = "$yytitle - $mbname"; 480 if ( $INFO{'board'} ) { $mainlink .= "?board=$INFO{'board'}"; 481 $descr = ( $boardname ? "$boardname - " : q{} ) . $mbname; 482 } 483 elsif ( $INFO{'catselect'} ) { $mainlink .= "?catselect=$INFO{'catselect'}"; 484 $descr = qq{$mydesc - $mbname}; 485 } 486 487 488 FromHTML($tit); 489 FromHTML($descr); 490 my $mn = $mbname; 491 FromHTML($mn); 492 $output = qq~<?xml version="1.0" encoding="$yymycharset" ?> 493<!-- IF YOU'RE SEEING THIS AND ARE USING CHROME GO TO https://chrome.google.com/webstore/detail/rss-subscription-extensio/nlbjncdgjeocebhnmkbbbdekmmmcbfjd AND GET THE ADD-IN --> 494<!-- IF YOU'RE SEEING THIS AND ARE USING OPERA GO TO https://addons.opera.com/en/extensions/ and search for 'RSS' to get an add-in --> 495<!-- Generated by YaBB on $rssdate --> 496<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 497 <channel> 498 <atom:link href="$scripturl?action=$INFO{'action'}~ 499 . ( $INFO{'board'} ? ";board=$INFO{'board'}" : q{} ) . ( $INFO{'catselect'} ? ";catselect=$INFO{'catselect'}" : q{} ) 500 . q~" rel="self" type="application/rss+xml" /> 501 <title>~ . RSSDescriptionTrim($tit) . q~</title> 502 <link>~ . RSSDescriptionTrim($mainlink) . q~</link> 503 <description>~ . RSSDescriptionTrim($descr) . q~</description> 504 <language>~ 505 . RSSDescriptionTrim("$maintxt{'w3c_lngcode'}") . q~</language> 506 507 <copyright>~ . RSSDescriptionTrim($mn) . qq~</copyright> 508 <lastBuildDate>$rssdate</lastBuildDate> 509 <docs>http://blogs.law.harvard.edu/tech/rss</docs> 510 <generator>$RSSplver</generator> 511 <ttl>30</ttl> 512$yymain 513 </channel> 514</rss>~; 515 516 print_HTML_output_and_finish(); 517 return; 518} 519 520sub RSS_error { 521 522 # This routine is mostly a copy of fatal_error except it uses RSS templating 523 my ( $e, $t, $v ) = @_; 524 LoadLanguage('Error'); 525 my ( $e_filename, $e_line, $e_subroutine, $l, $ot ); 526 527 # Gets filename and line where fatal_error was called. 528 # Need to go further back to get correct subroutine name, 529 # otherwise will print fatal_error as current subroutine! 530 ( undef, $e_filename, $e_line ) = caller 0; 531 ( undef, undef, undef, $e_subroutine ) = caller 1; 532 ( undef, $e_subroutine ) = split /::/xsm, $e_subroutine; 533 if ( $t || $e ) { 534 $ot = "<b>$maintxt{'error_description'}</b>: $error_txt{$e} $t"; 535 } 536 if ( ( $debug == 1 or ( $debug == 2 && $iamadmin ) ) 537 && ( $e_filename || $e_line || $e_subroutine ) ) 538 { 539 $l = 540"<br />$maintxt{'error_location'}: $e_filename<br />$maintxt{'error_line'}: $e_line<br />$maintxt{'error_subroutine'}: $e_subroutine"; 541 } 542 if ($v) { $v = "<br />$maintxt{'error_verbose'}: $!"; } 543 544 if ($elenable) { 545 fatal_error_logging("$ot$l$v"); 546 } 547 548 my $tit = $error_txt{'error_occurred'}; 549 FromHTML($tit); 550 my $ed = "$ot$l$v"; 551 FromHTML($ed); 552 my $mn = $mbname; 553 FromHTML($mn); 554 $yymain = q~ 555 <item> 556 <title>~ . RSSDescriptionTrim($tit) . q~</title> 557 <description>~ . RSSDescriptionTrim($ed) . q~</description> 558 <category>~ . RSSDescriptionTrim($mn) . q~</category> 559 </item>~; 560 561 RSS_template(); 562 return; 563} 564 565sub Send304NotModified { 566 print "Status: 304 Not Modified\n\n" or croak "$croak{'print'} 304"; 567 exit; 568} 569 570sub RFC822Date { 571 572 # Takes a Unix timestamp and returns the RFC-822 date format 573 # of it: Sat, 07 Sep 2002 9:42:31 GMT 574 my @GMTime = split / +/sm, gmtime shift; 575 return "$GMTime[0], $GMTime[2] $GMTime[1] $GMTime[4] $GMTime[3] GMT"; 576} 577 578sub RSSDescriptionTrim { # This formats the RSS 579 my @x = @_; 580 581 $x[0] =~ s/ (class|style)\s*=\s*["'].+?['"]//gsm; 582 583 $x[0] =~ s/&/&/gsm; 584 $x[0] =~ s/"/"/gsm; #"; 585 $x[0] =~ s/'/'/gsm; #'; 586 $x[0] =~ s/ /  /gsm; 587 $x[0] =~ s/</</gsm; 588 $x[0] =~ s/>/>/gsm; 589 $x[0] =~ s/\|/|/gsm; 590 $x[0] =~ s/\{/{/gsm; 591 $x[0] =~ s/\}/}/gsm; 592 593 return $x[0]; 594} 595 596sub shellaccess { 597 598 # Parse the arguments 599 my ( $i, %arguments ); 600 601 for my $i ( 0 .. ( @ARGV - 1 ) ) { 602 if ( $ARGV[$i] =~ /\A\-/sm ) { 603 my ( $option, $value ); 604 $option = $ARGV[$i]; 605 $option =~ s/\A\-\-?//xsm; 606 ( $option, $value ) = split /\=/xsm, $option; 607 $arguments{$option} = $value || q{}; 608 if ( !defined $arguments{$option} ) { $arguments{$option} = 1; } 609 } 610 } 611 612 ### Requirements and Errors ### 613 $script_root = $arguments{'script-root'}; 614 615 if ( -e 'Paths.pm' ) { require Paths; } 616 elsif ( -e "$script_root/Paths.pm" ) { require "$script_root/Paths.pm"; } 617 618 require Variables::Settings; 619 require Sources::Subs; 620 require Sources::DateTime; 621 require Sources::Load; 622 623 LoadCookie(); # Load the user's cookie (or set to guest) 624 LoadUserSettings(); # Load user settings 625 WhatLanguage(); # Figure out which language file we should be using! :D 626 627 get_forum_master(); 628 require Sources::Security; 629 630 # Is RSS disabled? 631 if ($rss_disabled) { RSS_error('rss_disabled'); } 632 633 $gzcomp = 0; # Disable gzip so we can talk clearly 634 635 # Map %arguments to %INFO 636 foreach my $var (qw(action board catselect topics)) { 637 $INFO{$var} = $arguments{$var}; 638 } 639 640 # Run the subroutine 641 require Sources::SubList; 642 my $action = $INFO{'action'}; 643 my ( $file, $sub ) = split /&/xsm, $director{$action}; 644 if ( $file eq 'RSS.pm' ) { &{$sub}(); } 645 exit; 646} 647 6481; 649