]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Plugin/external.pm
Merge commit 'upstream/master'
[git.ikiwiki.info.git] / IkiWiki / Plugin / external.pm
index e3504a30d510b83124516171a307b0a5656488cb..4ce9c8babb62d45bacb3bb76a4773bd27cb09b5c 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 # Support for external plugins written in other languages.
 #!/usr/bin/perl
 # Support for external plugins written in other languages.
-# Communication via XML RPC a pipe.
+# Communication via XML RPC to a pipe.
 # See externaldemo for an example of a plugin that uses this.
 package IkiWiki::Plugin::external;
 
 # See externaldemo for an example of a plugin that uses this.
 package IkiWiki::Plugin::external;
 
@@ -20,7 +20,8 @@ sub import { #{{{
        return unless defined $plugin;
 
        my ($plugin_read, $plugin_write);
        return unless defined $plugin;
 
        my ($plugin_read, $plugin_write);
-       my $pid = open2($plugin_read, $plugin_write, $plugin);
+       my $pid = open2($plugin_read, $plugin_write,
+               IkiWiki::possibly_foolish_untaint($plugin));
 
        # open2 doesn't respect "use open ':utf8'"
        binmode($plugin_read, ':utf8');
 
        # open2 doesn't respect "use open ':utf8'"
        binmode($plugin_read, ':utf8');
@@ -28,6 +29,7 @@ sub import { #{{{
 
        $plugins{$plugin}={in => $plugin_read, out => $plugin_write, pid => $pid,
                accum => ""};
 
        $plugins{$plugin}={in => $plugin_read, out => $plugin_write, pid => $pid,
                accum => ""};
+       $RPC::XML::ENCODING="utf-8";
 
        rpc_call($plugins{$plugin}, "import");
 } #}}}
 
        rpc_call($plugins{$plugin}, "import");
 } #}}}
@@ -57,14 +59,31 @@ sub rpc_call ($$;@) { #{{{
                        error("XML RPC parser failure: $r") unless ref $r;
                        if ($r->isa('RPC::XML::response')) {
                                my $value=$r->value;
                        error("XML RPC parser failure: $r") unless ref $r;
                        if ($r->isa('RPC::XML::response')) {
                                my $value=$r->value;
-                               if ($value->isa('RPC::XML::array')) {
+                               if ($r->is_fault($value)) {
+                                       # throw the error as best we can
+                                       print STDERR $value->string."\n";
+                                       return "";
+                               }
+                               elsif ($value->isa('RPC::XML::array')) {
                                        return @{$value->value};
                                }
                                elsif ($value->isa('RPC::XML::struct')) {
                                        return @{$value->value};
                                }
                                elsif ($value->isa('RPC::XML::struct')) {
-                                       return %{$value->value};
-                               }
-                               elsif ($value->isa('RPC::XML::fault')) {
-                                       die $value->string;
+                                       my %hash=%{$value->value};
+
+                                       # XML-RPC v1 does not allow for
+                                       # nil/null/None/undef values to be
+                                       # transmitted, so until
+                                       # XML::RPC::Parser honours v2
+                                       # (<nil/>), external plugins send
+                                       # a hash with one key "null" pointing
+                                       # to an empty string.
+                                       if (exists $hash{null} &&
+                                           $hash{null} eq "" &&
+                                           int(keys(%hash)) == 1) {
+                                               return undef;
+                                       }
+
+                                       return %hash;
                                }
                                else {
                                        return $value->value;
                                }
                                else {
                                        return $value->value;
@@ -88,6 +107,14 @@ sub rpc_call ($$;@) { #{{{
                                error("XML RPC call error, unknown function: $name");
                        }
 
                                error("XML RPC call error, unknown function: $name");
                        }
 
+                       # XML-RPC v1 does not allow for nil/null/None/undef
+                       # values to be transmitted, so until XML::RPC::Parser
+                       # honours v2 (<nil/>), send a hash with one key "null"
+                       # pointing to an empty string.
+                       if (! defined $ret) {
+                               $ret={"null" => ""};
+                       }
+
                        my $string=eval { RPC::XML::response->new($ret)->as_string };
                        if ($@ && ref $ret) {
                                # One common reason for serialisation to
                        my $string=eval { RPC::XML::response->new($ret)->as_string };
                        if ($@ && ref $ret) {
                                # One common reason for serialisation to
@@ -107,6 +134,7 @@ sub rpc_call ($$;@) { #{{{
 } #}}}
 
 package IkiWiki::RPC::XML;
 } #}}}
 
 package IkiWiki::RPC::XML;
+use Memoize;
 
 sub getvar ($$$) { #{{{
        my $plugin=shift;
 
 sub getvar ($$$) { #{{{
        my $plugin=shift;
@@ -123,13 +151,46 @@ sub setvar ($$$;@) { #{{{
        my $plugin=shift;
        my $varname="IkiWiki::".shift;
        my $key=shift;
        my $plugin=shift;
        my $varname="IkiWiki::".shift;
        my $key=shift;
+       my $value=shift;
 
        no strict 'refs';
 
        no strict 'refs';
-       my $ret=$varname->{$key}=@_;
+       my $ret=$varname->{$key}=$value;
        use strict 'refs';
        return $ret;
 } #}}}
 
        use strict 'refs';
        return $ret;
 } #}}}
 
+sub getstate ($$$$) { #{{{
+       my $plugin=shift;
+       my $page=shift;
+       my $id=shift;
+       my $key=shift;
+
+       return $IkiWiki::pagestate{$page}{$id}{$key};
+} #}}}
+
+sub setstate ($$$$;@) { #{{{
+       my $plugin=shift;
+       my $page=shift;
+       my $id=shift;
+       my $key=shift;
+       my $value=shift;
+
+       return $IkiWiki::pagestate{$page}{$id}{$key}=$value;
+} #}}}
+
+sub getargv ($) { #{{{
+       my $plugin=shift;
+
+       return \@ARGV;
+} #}}}
+
+sub setargv ($@) { #{{{
+       my $plugin=shift;
+       my $array=shift;
+
+       @ARGV=@$array;
+} #}}}
+
 sub inject ($@) { #{{{
        # Bind a given perl function name to a particular RPC request.
        my $plugin=shift;
 sub inject ($@) { #{{{
        # Bind a given perl function name to a particular RPC request.
        my $plugin=shift;
@@ -141,7 +202,16 @@ sub inject ($@) { #{{{
        my $sub = sub {
                IkiWiki::Plugin::external::rpc_call($plugin, $params{call}, @_)
        };
        my $sub = sub {
                IkiWiki::Plugin::external::rpc_call($plugin, $params{call}, @_)
        };
+       $sub=memoize($sub) if $params{memoize};
+
+       # This will add it to the symbol table even if not present.
+       no warnings;
        eval qq{*$params{name}=\$sub};
        eval qq{*$params{name}=\$sub};
+       use warnings;
+
+       # This will ensure that everywhere it was exported to sees
+       # the injected version.
+       IkiWiki::inject(name => $params{name}, call => $sub);
        return 1;
 } #}}}
 
        return 1;
 } #}}}
 
@@ -155,7 +225,7 @@ sub hook ($@) { #{{{
        delete $params{call};
 
        IkiWiki::hook(%params, call => sub {
        delete $params{call};
 
        IkiWiki::hook(%params, call => sub {
-               IkiWiki::Plugin::external::rpc_call($plugin, $callback, @_)
+               IkiWiki::Plugin::external::rpc_call($plugin, $callback, @_);
        });
 } #}}}
 
        });
 } #}}}