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/&/&#38;/gsm;
584    $x[0] =~ s/"/&#34;/gsm;      #";
585    $x[0] =~ s/'/&#39;/gsm;      #';
586    $x[0] =~ s/  / &#160;/gsm;
587    $x[0] =~ s/</&#60;/gsm;
588    $x[0] =~ s/>/&#62;/gsm;
589    $x[0] =~ s/\|/&#124;/gsm;
590    $x[0] =~ s/\{/&#123;/gsm;
591    $x[0] =~ s/\}/&#125;/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