]> sipb.mit.edu Git - ikiwiki.git/commitdiff
Merge commit 'upstream/master' into prv/po
authorintrigeri <intrigeri@boum.org>
Sat, 17 Jan 2009 13:01:53 +0000 (14:01 +0100)
committerintrigeri <intrigeri@boum.org>
Sat, 17 Jan 2009 13:01:53 +0000 (14:01 +0100)
Conflicts:

IkiWiki/Plugin/editpage.pm
IkiWiki/Plugin/skeleton.pm.example
doc/plugins/write.mdwn
t/syntax.t

13 files changed:
IkiWiki/Plugin/blogspam.pm [new file with mode: 0644]
IkiWiki/Plugin/comments.pm
IkiWiki/Plugin/editpage.pm
IkiWiki/Plugin/skeleton.pm.example
auto-blog.setup
debian/changelog
doc/download.mdwn
doc/ikiwikiusers.mdwn
doc/plugins/blogspam.mdwn [new file with mode: 0644]
doc/plugins/write.mdwn
doc/sandbox.mdwn
doc/todo/anti-spam_protection.mdwn
t/syntax.t

diff --git a/IkiWiki/Plugin/blogspam.pm b/IkiWiki/Plugin/blogspam.pm
new file mode 100644 (file)
index 0000000..6e68a98
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::blogspam;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+require RPC::XML;
+require RPC::XML::Client;
+
+my $defaulturl='http://test.blogspam.net:8888/';
+
+sub import {
+       hook(type => "getsetup", id => "blogspam",  call => \&getsetup);
+       hook(type => "checkcontent", id => "blogspam", call => \&checkcontent);
+}
+
+sub getsetup () {
+       return
+               plugin => {
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_pagespec => {
+                       type => 'pagespec',
+                       example => 'postcomment(*)',
+                       description => 'PageSpec of pages to check for spam',
+                       link => 'ikiwiki/PageSpec',
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_options => {
+                       type => "string",
+                       example => "blacklist=1.2.3.4,blacklist=8.7.6.5,max-links=10",
+                       description => "options to send to blogspam server",
+                       link => "http://blogspam.net/api/testComment.html#options",
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_server => {
+                       type => "string",
+                       default => $defaulturl,
+                       description => "blogspam server XML-RPC url",
+                       safe => 1,
+                       rebuild => 0,
+               },
+}
+
+sub checkcontent (@) {
+       my %params=@_;
+       
+       if (exists $config{blogspam_pagespec}) {
+               return undef
+                       if ! pagespec_match($params{page}, $config{blogspam_pagespec},
+                               location => $params{page});
+       }
+
+       my $url=$defaulturl;
+       $url = $params{blogspam_server} if exists $params{blogspam_server};
+       my $client = RPC::XML::Client->new($url);
+
+       my @options = split(",", $params{blogspam_options})
+               if exists $params{blogspam_options};
+
+       # Allow short comments and whitespace-only edits, unless the user
+       # has overridden min-words themselves.
+       push @options, "min-words=0"
+               unless grep /^min-words=/i, @options;
+       # Wiki pages can have a lot of urls, unless the user specifically
+       # wants to limit them.
+       push @options, "exclude=lotsaurls"
+               unless grep /^max-links/i, @options;
+       # Unless the user specified a size check, disable such checking.
+       push @options, "exclude=size"
+               unless grep /^(?:max|min)-size/i, @options;
+       # This test has absurd false positives on words like "alpha"
+       # and "buy".
+       push @options, "exclude=stopwords";
+
+       # blogspam API does not have a field for author url, so put it in
+       # the content to be checked.
+       if (exists $params{url}) {
+               $params{content}.="\n".$params{url};
+       }
+
+       my $res = $client->send_request('testComment', {
+               ip => $ENV{REMOTE_ADDR},
+               comment => $params{content},
+               subject => defined $params{subject} ? $params{subject} : "",
+               name => defined $params{author} ? $params{author} : "",
+               options => join(",", @options),
+               site => $config{url},
+               version => "ikiwiki ".$IkiWiki::version,
+       });
+
+       if (! ref $res || ! defined $res->value) {
+               debug("failed to get response from blogspam server ($url)");
+               return undef;
+       }
+       elsif ($res->value =~ /^SPAM:(.*)/) {
+               return gettext("Sorry, but that looks like spam to <a href=\"http://blogspam.net/\">blogspam</a>: ").$1;
+       }
+       elsif ($res->value ne 'OK') {
+               debug(gettext("blogspam server failure: ").$res->value);
+               return undef;
+       }
+       else {
+               return undef;
+       }
+}
+
+1
index 1c4ab4895f0e16f90d33b4dbb347ecc7fbc9192c..833bedf25dcbad59a6f1dc3faed0f5c764b03db9 100644 (file)
@@ -343,8 +343,6 @@ sub sessioncgi ($$) {
                error(gettext("bad page name"));
        }
 
-       # FIXME: is this right? Or should we be using the candidate subpage
-       # (whatever that might mean) as the base URL?
        my $baseurl = urlto($page, undef, 1);
 
        $form->title(sprintf(gettext("commenting on %s"),
@@ -469,9 +467,21 @@ sub sessioncgi ($$) {
        }
 
        if ($form->submitted eq POST_COMMENT && $form->validate) {
-               my $file = "$location._comment";
-
                IkiWiki::checksessionexpiry($cgi, $session);
+               
+               $postcomment=1;
+               IkiWiki::check_content(content => $form->field('editcontent'),
+                       subject => $form->field('subject'),
+                       $config{comments_allowauthor} ? (
+                               author => $form->field('author'),
+                               url => $form->field('url'),
+                       ) : (),
+                       page => $location,
+                       cgi => $cgi, session => $session
+               );
+               $postcomment=0;
+               
+               my $file = "$location._comment";
 
                # FIXME: could probably do some sort of graceful retry
                # on error? Would require significant unwinding though
index 91d125a5ca1ca30e836706a6e5e2aa43df6eced2..658d664af38d76b330ae9c27b74e77e5f28172a6 100644 (file)
@@ -78,7 +78,43 @@ sub check_canedit ($$$;$) {
                        }
                }
        });
-       return $canedit;
+       return defined $canedit ? $canedit : 1;
+}
+
+sub check_content (@) {
+       my %params=@_;
+       
+       return 1 if ! exists $hooks{checkcontent}; # optimisation
+
+       if (exists $pagesources{$params{page}}) {
+               my @diff;
+               my %old=map { $_ => 1 }
+                       split("\n", readfile(srcfile($pagesources{$params{page}})));
+               foreach my $line (split("\n", $params{content})) {
+                       push @diff, $line if ! exists $old{$_};
+               }
+               $params{content}=join("\n", @diff);
+       }
+
+       my $ok;
+       run_hooks(checkcontent => sub {
+               return if defined $ok;
+               my $ret=shift->(%params);
+               if (defined $ret) {
+                       if ($ret eq "") {
+                               $ok=1;
+                       }
+                       elsif (ref $ret eq 'CODE') {
+                               $ret->();
+                               $ok=0;
+                       }
+                       elsif (defined $ret) {
+                               error($ret);
+                       }
+               }
+
+       });
+       return defined $ok ? $ok : 1;
 }
 
 sub check_cansave ($$$$) {
@@ -395,9 +431,18 @@ sub cgi_editpage ($$) {
                        showform($form, \@buttons, $session, $q, forcebaseurl => $baseurl);
                        exit;
                }
+                       
+               my $message="";
+               if (defined $form->field('comments') &&
+                   length $form->field('comments')) {
+                       $message=$form->field('comments');
+               }
                
                my $content=$form->field('editcontent');
                check_cansave($page, $content, $q, $session);
+               check_content(content => $content, page => $page,
+                       cgi => $q, session => $session,
+                       subject => $message);
                run_hooks(editcontent => sub {
                        $content=shift->(
                                content => $content,
@@ -431,12 +476,6 @@ sub cgi_editpage ($$) {
                
                my $conflict;
                if ($config{rcs}) {
-                       my $message="";
-                       if (defined $form->field('comments') &&
-                           length $form->field('comments')) {
-                               $message=$form->field('comments');
-                       }
-                       
                        if (! $exists) {
                                rcs_add($file);
                        }
index 0bd5edeef748b328cfd869c2104860a1c0b6794f..30c8dbd5c82d06dde62bc7a219fbcbf77bd64177 100644 (file)
@@ -31,6 +31,7 @@ sub import {
        hook(type => "sessioncgi", id => "skeleton", call => \&sessioncgi);
        hook(type => "canedit", id => "skeleton", call => \&canedit);
        hook(type => "cansave", id => "skeleton", call => \&cansave);
+       hook(type => "checkcontent", id => "skeleton", call => \&checkcontent);
        hook(type => "editcontent", id => "skeleton", call => \&editcontent);
        hook(type => "formbuilder_setup", id => "skeleton", call => \&formbuilder_setup);
        hook(type => "formbuilder", id => "skeleton", call => \&formbuilder);
@@ -181,6 +182,7 @@ sub canedit ($$$) {
        debug("skeleton plugin running in canedit");
 }
 
+<<<<<<< HEAD:IkiWiki/Plugin/skeleton.pm.example
 sub cansave ($$$$) {
        my $page=shift;
        my $content=shift;
@@ -204,6 +206,12 @@ sub canrename ($$$) {
        my $session=shift;
 
        debug("skeleton plugin running in canrename");
+=======
+sub checkcontent (@) {
+       my %params=@_;
+
+       debug("skeleton plugin running in checkcontent");
+>>>>>>> upstream/master:IkiWiki/Plugin/skeleton.pm.example
 }
 
 sub editcontent ($$$) {
index fde7f17f8ca4ec7527e540acb9de96e0f9ecc5e3..3ef734b39a41a9213cccd4ec5e5f71f2aed2b3a2 100644 (file)
@@ -35,7 +35,7 @@ IkiWiki::Setup::Automator->import(
        cgiurl => "http://$domain/~$ENV{USER}/$wikiname/ikiwiki.cgi",
        cgi_wrapper => "$ENV{HOME}/public_html/$wikiname/ikiwiki.cgi",
        adminemail => "$ENV{USER}\@$domain",
-       add_plugins => [qw{goodstuff websetup comments}],
+       add_plugins => [qw{goodstuff websetup comments blogspam}],
        disable_plugins => [qw{}],
        libdir => "$ENV{HOME}/.ikiwiki",
        rss => 1,
@@ -44,5 +44,6 @@ IkiWiki::Setup::Automator->import(
        
        example => "blog",
        comments_pagespec => "posts/* and !*/Discussion",
+       blogspam_pagespec => "postcomment(*)",
        discussion => 0,
 )
index f35606148353cd863b65070e3487cca13033a6d5..6384c8df99d657f48db3f0942d6983e1d6ba484f 100644 (file)
@@ -1,5 +1,11 @@
 ikiwiki (3.02) UNRELEASED; urgency=low
 
+  * blogspam: New plugin, adding spam filtering for page editing / comment
+    posting using the BlogSpam.net API.
+  * Add auto-blog.setup, which will set up an ikiwiki instance tuned for use
+    in blogging.
+  * checkcontent: New hook, can be used to implement arbitrary content
+    filters, including spam filters.
   * table: Fix misparsed links in external files.
   * table: Find links in external files in scan pass.
   * rename: Show full names of affected pages.
@@ -16,8 +22,6 @@ ikiwiki (3.02) UNRELEASED; urgency=low
     the nostromo web server.
   * Recommend libterm-readline-gnu-perl since that makes auto.setup
     behave better.
-  * Add auto-blog.setup, which will set up an ikiwiki instance tuned for use
-    in blogging.
 
  -- Joey Hess <joeyh@debian.org>  Tue, 06 Jan 2009 15:02:52 -0500
 
index 067938f87118f4816e0954d78ae4d7314f5c9483..354929052d5136baad73821bd67c933e00630df6 100644 (file)
@@ -17,7 +17,7 @@ ikiwiki, there have been lots of enhancements and bug fixes since those
 versions.
 """]]
 
-        apt-get install ikiwiki
+        aptitude install ikiwiki
 
 Or download the deb from <http://packages.debian.org/unstable/web/ikiwiki>.
 
index ade15d08002b37d5e6bee229665520413bf8920f..2a51dfd9dc19e9f1510c92f5c16ca245815c325d 100644 (file)
@@ -39,6 +39,7 @@ Projects
 * [monkeysphere](http://web.monkeysphere.info/)
 * [The Walden Effect](http://www.waldeneffect.org/)
 * The [Fortran Wiki](http://fortranwiki.org/)
+* [Monotone](http://monotone.ca/wiki/FrontPage/)
 
 Personal sites and blogs
 ========================
diff --git a/doc/plugins/blogspam.mdwn b/doc/plugins/blogspam.mdwn
new file mode 100644 (file)
index 0000000..a090f9c
--- /dev/null
@@ -0,0 +1,25 @@
+[[!template id=plugin name=blogspam author="[[Joey]]"]]
+[[!tag type/auth]]
+
+This plugin adds antispam support to ikiwiki, using the
+[blogspam.net](http://blogspam.net/) API. Both page edits and
+[[comment|comments]] postings can be checked for spam. Currently,
+detected spam is not saved for human review, it is just rejected.
+
+The plugin requires the [[!cpan RPC::XML]] perl module.
+
+You can control how content is tested via the `blogspam_options` setting.
+The list of options is [here](http://blogspam.net/api/testComment.html#options).
+By default, the options are configured in a way that is appropriate for
+wiki content. This includes turning off some of the more problimatic tests.
+
+The `blogspam_pagespec` setting is a [[ikiwiki/PageSpec]] that can be
+used to configure which pages are checked for spam. The default is to check
+all edits. If you only want to check [[comments]] (not wiki page edits),
+set it to "postcomment(*)".
+
+By default, the blogspam.net server is used to do the spam checking. To
+change this, the `blogspam_server` option can be set to the url for a
+different server implementing the same API. Note that content is sent
+unencrypted over the internet to the server, and the server sees
+the full text of the content.
index 4db02b532557addd28922fcf4c26ce1eda458586..ef14f18dbb13fe81b0f85d1fcb2e85fd6fa63db1 100644 (file)
@@ -303,7 +303,7 @@ can check if the session object has a "name" parameter set.
 
 ### canedit
 
-       hook(type => "canedit", id => "foo", call => \&pagelocked);
+       hook(type => "canedit", id => "foo", call => \&canedit);
 
 This hook can be used to implement arbitrary access methods to control when
 a page can be edited using the web interface (commits from revision control
@@ -356,6 +356,26 @@ but is passed:
 * a session object
 * the named parameters `src`, `srcfile`, `dest` and `destfile`.
 
+### checkcontent
+       
+       hook(type => "checkcontent", id => "foo", call => \&checkcontent);
+
+This hook is called to check the content a user has entered on a page,
+before it is saved, and decide if it should be allowed.
+
+It is passed named parameters: `content`, `page`, `cgi`, and `session`. If
+the content the user has entered is a comment, it may also be passed some
+additional parameters: `author`, `url`, and `subject`. The `subject`
+parameter may also be filled with the user's comment about the change.
+
+Note: When the user edits an existing wiki page, the passed `content` will
+include only the lines that they added to the page, or modified.
+
+The hook should return `undef` on success. If the content is disallowed, it
+should return a message stating what the problem is, or a function
+that can be run to perform whatever action is necessary to allow the user
+to post the content.
+
 ### editcontent
 
        hook(type => "editcontent", id => "foo", call => \&editcontent);
index 00b57815d9e81ef58ede30243529196e01aed311..d39b5971583eeef6e65c0ac7d838ddcd3fc92c34 100644 (file)
@@ -1,4 +1,4 @@
-This is the SandBox, a page anyone can edit to try out ikiwiki.
+This is the [[SandBox]], a page anyone can edit to try out ikiwiki.
 
 hello
 
@@ -6,6 +6,10 @@ testing 1..2..3!!
 
 ----
 
+I am testing the edit box provided through ikiwiki.cgi.
+
+----
+
 Here's a paragraph.
 
 The following code block is pre-formatted:
index cb45faee5d4b245712c10a9aeb9914af8a77eb80..b0524be5fcabfba5b66ef671ddebab6eb0645eb4 100644 (file)
@@ -17,3 +17,14 @@ Cheers,
 You might look at the Wikipedia page on "Spam\_in\_blogs" for more ideas.  In particular, would it be possible to force a subset of the pages (by regex, but you'd choose the regex to match those pages which are publicly writable) to use rel="nofollow" in all links.
 
 > I just wanted to leave a link here to the [[todo/require_CAPTCHA_to_edit]] plugin patch.  Unfortunately that plugin currently interacts badly with the openid plugin. -- [[Will]]
+
+
+---
+
+Ikiwiki now has a checkcontent hook that plugins can use to see content
+that is being entered and check it for spam/whatever.
+
+There is a blogspam plugin that uses the blogspam.org service
+to check for common spam signatures. --[[Joey]] 
+
+[[done]]
index 8c96d1d848014e9145739515e974f0b5aac271ac..ddb0da073c9c4dddd7389f4d7cabc2b29a8fa948 100755 (executable)
@@ -5,8 +5,8 @@ use Test::More;
 
 my @progs="ikiwiki.in";
 my @libs="IkiWiki.pm";
-# monotone, external, amazon_s3 skipped since they need perl modules
-push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v monotone.pm | grep -v external.pm | grep -v amazon_s3.pm | grep -v po.pm`;
+# monotone, external, blogspam, amazon_s3, po skipped since they need perl modules
+push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v monotone.pm | grep -v external.pm | grep -v blogspam.pm | grep -v amazon_s3.pm | grep -v po.pm`;
 push @libs, 'IkiWiki/Plugin/skeleton.pm.example';
 
 plan(tests => (@progs + @libs));