############################################################################### # RSS.pm # # $Date: 06.01.17 $ # ############################################################################### # YaBB: Yet another Bulletin Board # # Open-Source Community Software for Webmasters # # Version: YaBB 2.7.00 # # Packaged: June 1, 2017 # # Distributed by: http://www.yabbforum.com # # =========================================================================== # # Copyright (c) 2000-2017 YaBB (www.yabbforum.com) - All Rights Reserved. # # Software by: The YaBB Development Team # # with assistance from the YaBB community. # ############################################################################### use strict; use warnings; use CGI::Carp qw(fatalsToBrowser); use English qw(-no_match_vars); our $VERSION = '2.7.00'; our $rsspmver = 'YaBB 2.7.00 $Revision$'; our @rsspmmods = (); our $rsspmmods = 0; if (@rsspmmods) { $rsspmmods = 1; } our ($action); $action ||= q{}; if ( $action eq 'detailedversion' ) { return 1; } ## language/ paths ## our ( $boardsdir, $datadir, $memberdir, $scripturl, %croak, %error_txt, %maintxt, ); ## settings ## our ( $accept_permafull, $accept_permalink, $cookiepassword, $debug, $elenable, $enable_ubbc, $gzcomp, $mbname, $perm_domain, $perm_spacer, $rss_disabled, $rss_limit, $rss_message, $rssemail, $showauthor, $showdate, $symlink, $yymycharset, ); ## system ## our ( $annboard, $boardname, $boardperms, $curboard, $currentboard, $date, $iamadmin, $iamgmod, $mydesc, $staff, $uid, $username, $yydesc, $yymain, $yytitle, %board, %cat, %catinfo, %director, %INFO, %subboard, %yy_cookies, @categoryorder, $iamguest, ); # Change the error routine for here. local $SIG{__WARN__} = sub { rss_error(@_) }; # Allow us to be called by a system()-like call # This lets us send data to any language that supports capturing STDOUT. # Usage is detailed in POD at the bottom. if ( scalar @ARGV ) { shellaccess(); } # Is RSS disabled? if ($rss_disabled) { rss_error('not_allowed'); } load_censor_list(); # Load YaBBC if it is enabled if ($enable_ubbc) { require Sources::YaBBC; } my ($cachedate); # Read from a single board sub rss_board { my $board = $INFO{'board'}; if ( $board && ${ $board{$board} }[0] !~ m/[ht|f]tps?\:\/\//xsm ) { my $topics = $INFO{'topics'} || $rss_limit || 10; if ( $rss_limit && $topics > $rss_limit ) { $topics = $rss_limit; } ### Security check ### get_rss_noaccess($board); # Now, go into the board and look for the last X topics open my $BRDTXT, '<', "$boardsdir/$board.txt" || rss_error( 'cannot_open', "$boardsdir/$board.txt", 1 ); my @threadlist = <$BRDTXT>; close $BRDTXT or croak "$croak{'close'} $board.txt"; my $threadcount = @threadlist; if ( $threadcount < $topics ) { $topics = $threadcount; } @threadlist = splice @threadlist, 0, $topics; # Sorting mode if ( $rss_message == 2 ) { @threadlist = sort @threadlist; } chomp @threadlist; my $i = 0; foreach my $t (@threadlist) { no strict qw(refs); my ( $mnum, $msub, $mname, $memail, $mdate, $mreplies, $musername, $micon, $mstate, $ns ) = split /[|]/xsm, $t; my $curnum = $mnum; # See if this is a topic that we don't want displayed. if ( $mstate =~ /h/xsm && !$iamadmin && !$iamgmod ) { next; } # Does it need to be returned as a 304? if ( $i == 0 ) { # Do this for the first request only $cachedate = rfc822date($mdate); if ( ( $ENV{'HTTP_IF_NONE_MATCH'} && $ENV{'HTTP_IF_NONE_MATCH'} eq $cachedate ) || ( $ENV{'HTTP_IF_MODIFIED_SINCE'} && $ENV{'HTTP_IF_MODIFIED_SINCE'} eq $cachedate ) ) { send304notmodified(); # Comment this out to test with caching disabled } } ( $msub, undef ) = split_splice_move( $msub, 0 ); $msub = from_html($msub); $msub = to_chars($msub); # Censor the subject of the thread. $msub = do_censor($msub); my $postid = "$mreplies#$mreplies"; if ( $rss_message == 2 ) { $postid = '0#0'; } $boardname = $INFO{'board'}; my $category = "$mbname/$boardname"; $category = from_html($category); # Show the minimum stuff (topic title, link to it) $yymain .= get_rss_mins( $curnum, $msub, $category ); my $post; our ($TOPIC); fopen( 'TOPIC', '<', "$datadir/$curnum.txt" ) || rss_error( 'cannot_open', "$datadir/$curnum.txt", 1 ); if ( $rss_message == 1 ) { # Open up the thread and read the last post. while (<$TOPIC>) { chomp; if ($_) { $post = $_; } } } elsif ( $rss_message == 2 ) { $post = <$TOPIC>; } fclose('TOPIC') or croak "$croak{'close'} $curnum.txt"; our ($message); if ($post) { ( undef, undef, undef, undef, $musername, undef, undef, undef, $message, $ns ) = split /[|]/xsm, $post; } $yymain .= get_rss_aut($musername); if ($showdate) { if ( $rss_message == 2 ) { $mdate = $curnum; } # Sort by topic creation if requested. # Get the date how the user wants it. my $realdate = rfc822date($mdate); $yymain .= qq~ $realdate ~; } $yymain .= get_rss_msg( $message, $curnum, $ns, $musername ); # Finish up the item $yymain .= q~ ~; $yymain =~ s/data-rel/rel/gxsm; $i++; # Increment } my $brdname = ${ $board{$board} }[0]; $brdname = to_chars($brdname); $yytitle = $brdname; $mydesc = q{}; rss_template(); } else { our $yysetlocation = "$scripturl?action=RSSrecent"; redirectexit(); } return; } sub rss_recent { my $topics = $INFO{'topics'} || $rss_limit || 10; if ( $rss_limit && $topics > $rss_limit ) { $topics = $rss_limit; } $yytitle = "$topics $maintxt{'214b'}"; if ( $INFO{'catselect'} ) { @categoryorder = ( $INFO{'catselect'} ); } my ( $threadlist, $boardnme ) = get_rss_threadlist($topics); my @threadlist = @{$threadlist}; my %boardname = %{$boardnme}; if ( !@threadlist ) { my $category = ${ $catinfo{ $categoryorder[0] } }[0]; $yymain .= q~ ~ . rss_description_trim($category) . q~ ~ . rss_description_trim("$scripturl?cat=$categoryorder[0]") . q~ ~ . rss_description_trim( $maintxt{'rss_nopost'} ) . qq~ \n~; } else { foreach my $i ( 0 .. $#threadlist ) { if ( $i == ( $topics - 1 ) ) { last; } # Opening item stuff my ( $mdate, $board, $mnum, $msub, $mname, $memail, $modate, $mreplies, $musername, $micon, $mstate ) = split /[|]/xsm, $threadlist[$i]; my $curnum = $mnum; ( $msub, undef ) = split_splice_move( $msub, 0 ); $msub = from_html($msub); $msub = to_chars($msub); # Censor the subject of the thread. $msub = do_censor($msub); # Does it need to be returned as a 304? my ($permdate); if ( $i == 0 ) { # Do this for the first request only $cachedate = rfc822date($mdate); if ( ( $ENV{'HTTP_IF_NONE_MATCH'} && $ENV{'HTTP_IF_NONE_MATCH'} eq $cachedate ) || ( $ENV{'HTTP_IF_MODIFIED_SINCE'} && $ENV{'HTTP_IF_MODIFIED_SINCE'} eq $cachedate ) ) { send304notmodified(); # Comment this out to test with caching disabled } } my $postid = "$mreplies#$mreplies"; if ( $rss_message == 2 ) { $postid = '0#0'; } my $category = "$mbname/$boardname{$board}"; $category = from_html($category); my $bn = $boardname{$board}; $bn = from_html($bn); if ( $accept_permalink || $accept_permafull ) { my $permsub = $msub; $permdate = permtimer($curnum); $permsub =~ s/ /$perm_spacer/gsm; $yymain .= q~ ~ . rss_description_trim("$bn - $msub") . q~ ~ . rss_description_trim( "$perm_domain/$symlink/$permdate/$board/$curnum") . q~ ~ . rss_description_trim($category) . q~ ~ . rss_description_trim( "$perm_domain/$symlink/$permdate/$board/$curnum") . qq~\n~; } else { $yymain .= q~ ~ . rss_description_trim("$bn - $msub") . q~ ~ . rss_description_trim("$scripturl?num=$curnum/$postid") . q~ ~ . rss_description_trim($category) . q~ ~ . rss_description_trim("$scripturl?num=$curnum/$postid") . qq~\n~; } my $post; our ($TOPIC); fopen( 'TOPIC', '<', "$datadir/$curnum.txt" ) || rss_error( 'cannot_open', "$datadir/$curnum.txt", 1 ); if ( $rss_message == 1 ) { # Open up the thread and read the last post. while (<$TOPIC>) { chomp; if ($_) { $post = $_; } } } elsif ( $rss_message == 2 ) { # Open up the thread and read the first post. $post = <$TOPIC>; } fclose('TOPIC') or croak "$croak{'close'} $curnum.txt"; my ( $message, $ns ); if ($post) { ( undef, undef, undef, undef, $musername, undef, undef, undef, $message, $ns ) = split /[|]/xsm, $post; } $yymain .= get_rss_aut($musername); $yymain .= get_rss_showdate( $curnum, $mdate ); $yymain .= get_rss_mess( $message, $curnum, $ns, $username ); $yymain .= qq~ \n~; $yymain =~ s/data-rel/rel/gxsm; } } $boardname = to_chars($boardname); $curboard ||= q{}; { no strict qw(refs); $yydesc = ${ $uid . $curboard }{'description'}; } rss_template(); return; } sub rss_template { # print RSS output # Generate the lastBuildDate my $rssdate = rfc822date($date); # Send out the "Last-Modified" and "ETag" headers so nice readers will ask before downloading. our $last_modified = our $e_tag = $cachedate || $rssdate; my $contenttype = 'text/xml'; print_output_header(); # Make the generator look better my $rss_pmver = $rsspmver; $rss_pmver =~ s/\$//gxsm; # Removed per Corey's suggestion: http://www.yabbforum.com/community/YaBB.pl?num=1142571424/20#20 #my $docs = " http://$perm_domain\n" if $perm_domain; my $mainlink = $scripturl; $yytitle ||= 'error'; my $tit = "$yytitle - $mbname"; my $descr = q{}; if ( $INFO{'board'} ) { $mainlink .= "?board=$INFO{'board'}"; $descr = $mydesc; } elsif ( $INFO{'catselect'} ) { $mainlink .= "?catselect=$INFO{'catselect'}"; $descr = qq~$mydesc - $mbname~; } $tit = from_html($tit); $descr = from_html($descr); my $mn = $mbname; $mn = from_html($mn); our $output = qq~ ~ . rss_description_trim($tit) . q~ ~ . rss_description_trim($mainlink) . q~ ~ . rss_description_trim($descr) . q~ ~ . rss_description_trim("$maintxt{'w3c_lngcode'}") . q~ ~ . rss_description_trim($mn) . qq~ $rssdate http://blogs.law.harvard.edu/tech/rss $rss_pmver 30 $yymain ~; print_html_output_and_finish(); return; } sub rss_error { # This routine is mostly a copy of fatal_error except it uses RSS templating my ( $e, $t, $v ) = @_; load_language('Error'); my ( $e_filename, $e_line, $e_subroutine, $l, $ot ); # Gets filename and line where fatal_error was called. # Need to go further back to get correct subroutine name, # otherwise will print fatal_error as current subroutine! ( undef, $e_filename, $e_line ) = caller 0; ( undef, undef, undef, $e_subroutine ) = caller 1; ( undef, $e_subroutine ) = split /::/xsm, $e_subroutine; if ( $t || $e ) { $ot = "$maintxt{'error_description'}: $error_txt{$e} $t"; } if ( ( $debug == 1 or ( $debug == 2 && $iamadmin ) ) && ( $e_filename || $e_line || $e_subroutine ) ) { $l = "
$maintxt{'error_location'}: $e_filename
$maintxt{'error_line'}: $e_line
$maintxt{'error_subroutine'}: $e_subroutine"; } if ($v) { $v = "
$maintxt{'error_verbose'}: $OS_ERROR"; } if ($elenable) { $l ||= q{}; $v ||= q{}; fatal_error_logging("$ot$l$v"); } my $tit = $error_txt{'error_occurred'}; $tit = from_html($tit); my $ed = "$ot$l$v"; $ed = from_html($ed); my $mn = from_html($mbname); $yymain = q~ ~ . rss_description_trim($tit) . q~ ~ . rss_description_trim($ed) . q~ ~ . rss_description_trim($mn) . q~ ~; rss_template(); return; } sub send304notmodified { print "Status: 304 Not Modified\n\n" or croak "$croak{'print'} 304"; exit; } sub rfc822date { # Takes a Unix timestamp and returns the RFC-822 date format # of it: Sat, 07 Sep 2002 9:42:31 GMT my @gmt_time = split /\s+/xsm, gmtime shift; return "$gmt_time[0], $gmt_time[2] $gmt_time[1] $gmt_time[4] $gmt_time[3] GMT"; } sub rss_description_trim { # This formats the RSS my @x = @_; $x[0] =~ s/\s (class|style)\s*=\s*[\x22\x27].+?[\x27\x22]//gxsm; $x[0] =~ s/&/&\x2338;/gxsm; $x[0] =~ s/\x22/&\x2334;/gxsm; $x[0] =~ s/\x27/&\x2339;/gxsm; $x[0] =~ s/[ ]{2}/ &\x23160;/gxsm; $x[0] =~ s//&\x2362;/gxsm; $x[0] =~ s/[|]/&\x23124;/gxsm; $x[0] =~ s/[{]/&\x23123;/gxsm; $x[0] =~ s/[}]/&\x23125;/gxsm; return $x[0]; } sub shellaccess { # Parse the arguments my (%arguments); foreach my $i ( 0 .. $#ARGV ) { if ( $ARGV[$i] =~ /\A\-/xsm ) { my ( $option, $value ); $option = $ARGV[$i]; $option =~ s/\A\-\-?//xsm; ( $option, $value ) = split /\=/xsm, $option; $arguments{$option} = $value || q{}; if ( !defined $arguments{$option} ) { $arguments{$option} = 1; } } } ### Requirements and Errors ### require Paths; require Variables::Settings; require Sources::Subs; require Sources::DateTime; require Sources::Load; load_cookie(); # Load the user's cookie (or set to guest) load_usersettings(); # Load user settings our $language = what_language(); # Figure out which language file we should be using! :D get_forum_master(); require Sources::Security; # Is RSS disabled? if ($rss_disabled) { rss_error('rss_disabled'); } $gzcomp = 0; # Disable gzip so we can talk clearly # Map %arguments to %INFO foreach my $var (qw(action board catselect topics)) { $INFO{$var} = $arguments{$var}; } # Run the subroutine require Sources::SubList; my $act = $INFO{'action'}; my ( $file, $sub ) = split /&/xsm, $director{$act}; if ( $file eq 'RSS.pm' ) { &{$sub}(); } exit; } sub get_rss_noaccess { my ($board) = @_; if ( access_check( $currentboard, q{}, $boardperms ) ne 'granted' ) { rss_error('no_access'); } if ( $board && $annboard && $annboard eq $board && !$iamadmin && !$iamgmod ) { rss_error('no_access'); } { no strict qw(refs); if ( ${ $uid . $currentboard }{'brdpasswr'} ) { my $cookiename = "$cookiepassword$currentboard$username"; my $crypass = ${ $uid . $currentboard }{'brdpassw'}; if ( !$staff && $yy_cookies{$cookiename} ne $crypass ) { rss_error('no_access'); } } } return; } sub get_rss_mins { my ( $curnum, $msub, $category ) = @_; my $mins = q~ ~ . rss_description_trim($msub) . q~ ~; $mins .= rss_description_trim("$scripturl?num=$curnum") . q~ ~ . rss_description_trim($category) . q~ ~; $mins .= rss_description_trim("$scripturl?num=$curnum") . q~ ~; if ( $accept_permalink || $accept_permafull ) { my $permdate = permtimer($curnum); $mins = q~ ~ . rss_description_trim($msub) . q~ ~ . rss_description_trim( "$perm_domain/$symlink/$permdate/$currentboard/$curnum") . q~ ~ . rss_description_trim($category) . q~ ~ . rss_description_trim( "$perm_domain/$symlink/$permdate/$currentboard/$curnum") . q~ ~; } return $mins; } sub get_rss_aut { my ($usr) = @_; my $aut = q{}; no strict qw(refs); if ($showauthor) { if ( -e "$memberdir/$usr.vars" ) { load_user($usr); if ( !${ $uid . $usr }{'hidemail'} ) { $aut = q~~ . rss_description_trim( "${ $uid . $usr }{'email'} (${ $uid . $usr }{'realname'})") . q~~; } else { $aut = q~ ~ . rss_description_trim( "$rssemail (${ $uid . $usr }{'realname'})") . qq~\n~; } } } return $aut; } sub get_rss_msg { my ( $message, $curnum, $ns, $usr ) = @_; my $mess = q{}; if ($message) { ( $message, undef ) = split_splice_move( $message, $curnum ); $message =~ s/\[code\s*(.*?)\]\n*(.+?)\n*\[\/code\]/$maintxt{'rsscode'}/eigxsm; { no strict qw(refs); if ( $enable_ubbc && !$ns ) { load_user($usr); my $displayname = ${ $uid . $usr }{'realname'}; $message = do_ubbc( $message, q{}, $displayname ); } } $message = from_html($message); $message = to_chars($message); $message = do_censor($message); $mess = q~ ~ . rss_description_trim($message) . q~ ~; } return $mess; } sub get_rss_threadlist { my ($topics) = @_; my @threadlist; my (%boardname); foreach my $catid (@categoryorder) { my @bdlist = @{ $cat{$catid} }; my ( $catname, $catperms ) = @{ $catinfo{$catid} }; my $cataccess = cat_access($catperms); if ( !$cataccess ) { next; } if ( $INFO{'catselect'} ) { $yytitle = $catname; $mydesc = $catname; } my ($boardview); local *get_subboards = sub { my @brd = @_; foreach my $brd (@brd) { ( $boardname{$brd}, $boardperms, $boardview ) = @{ $board{$brd} }; my $access = access_check( $brd, q{}, $boardperms ); if ( !$iamadmin && $access ne 'granted' ) { next; } { no strict qw(refs); if ( !$iamguest && ${ $uid . $brd }{'brdpasswr'} ) { my $cookiename = "$cookiepassword$brd$username"; my $crypass = ${ $uid . $brd }{'brdpassw'}; if ( !$staff && ( !$yy_cookies{$cookiename} || $yy_cookies{$cookiename} ne $crypass ) ) { next; } } } our ($BOARD); fopen( 'BOARD', '<', "$boardsdir/$brd.txt", 1 ) || rss_error( 'cannot_open', "$boardsdir/$brd.txt", 1 ); foreach my $i ( 0 .. ( $topics - 1 ) ) { my $buffer = <$BOARD>; if ( !$buffer ) { last; } chomp $buffer; my ( $mnum, undef, undef, undef, $mdate, undef, undef, undef, $mstate ) = split /[|]/xsm, $buffer; $mdate ||= $mnum; if ( $rss_message == 2 ) { $mdate = $mnum; } # Sort by topic creation if requested. $mdate = sprintf '%010d', $mdate; # Check if it's hidden. If so, don't show it if ( $mstate && $mstate =~ /h/xsm && !$iamadmin && !$iamgmod ) { next; } # Add it to an array, using $mdate as the first value so we can easily sort push @threadlist, "$mdate|$brd|$buffer"; } close $BOARD or croak "$croak{'close'} $brd.txt"; if ( $subboard{$brd} ) { get_subboards( @{ $subboard{$brd} } ); } } }; foreach my $curbrd (@bdlist) { get_subboards($curbrd); } } @threadlist = reverse sort @threadlist; return ( \@threadlist, \%boardname ); } sub get_rss_showdate { my ( $curnum, $mdate ) = @_; my $rssd = q{}; if ($showdate) { if ( $rss_message == 2 ) { $mdate = $curnum; } # Sort by topic creation if requested. # Get the date how the user wants it. my $realdate = rfc822date($mdate); $rssd = qq~ $realdate\n~; } return $rssd; } sub get_rss_mess { my ( $message, $curnum, $ns, $usr ) = @_; my $mess = q{}; no strict qw(refs); if ($message) { ( $message, undef ) = split_splice_move( $message, $curnum ); $message =~ s/\[code\s*(.*?)\]\n*(.+?)\n*\[\/code\]/$maintxt{'rsscode'}/eigxsm; if ( $enable_ubbc && !$ns ) { load_user($usr); my $displayname = ${ $uid . $usr }{'realname'}; $message = do_ubbc( $message, q{}, $displayname ); } $message = from_html($message); $message = to_chars($message); $message = do_censor($message); $mess = q~ ~ . rss_description_trim($message) . qq~\n~; } return $mess; } 1;