File Coverage

blib/lib/HTTP/Proxy/GreaseMonkey.pm
Criterion Covered Total %
statement 58 62 93.5
branch 7 14 50.0
condition 5 9 55.5
subroutine 16 17 94.1
pod 10 10 100.0
total 96 112 85.7


line stmt bran cond sub pod time code
1             package HTTP::Proxy::GreaseMonkey;
2              
3 3     3   162142 use warnings;
  3         8  
  3         123  
4 3     3   18 use strict;
  3         7  
  3         143  
5 3     3   17 use Carp;
  3         6  
  3         197  
6 3     3   1184 use HTTP::Proxy::GreaseMonkey::Script;
  3         7  
  3         87  
7 3     3   3287 use Data::UUID;
  3         2951  
  3         240  
8              
9 3     3   20 use base qw( HTTP::Proxy::BodyFilter );
  3         4  
  3         3342  
10              
11             =head1 NAME
12              
13             HTTP::Proxy::GreaseMonkey - Run GreaseMonkey scripts in any browser
14              
15             =head1 VERSION
16              
17             This document describes HTTP::Proxy::GreaseMonkey version 0.05
18              
19             =cut
20              
21             our $VERSION = '0.05';
22              
23             =head1 SYNOPSIS
24              
25             use HTTP::Proxy;
26             use HTTP::Proxy::GreaseMonkey;
27              
28             my $proxy = HTTP::Proxy->new( port => 8030 );
29             my $gm = HTTP::Proxy::GreaseMonkey->new;
30             $gm->add_script( 'gm/myscript.js' );
31             $proxy->push_filter(
32             mime => 'text/html',
33             response => $gm
34             );
35             $proxy->start;
36            
37             =head1 DESCRIPTION
38              
39             GreaseMonkey allows arbitrary user defined Javascript to be run against
40             specific pages. Unfortunately GreaseMonkey only works with FireFox.
41              
42             C creates a local HTTP proxy that allows
43             GreaseMonkey user scripts to be used with any browser.
44              
45             When you install C a program called
46             F is installed in your default bin directory. To launch the
47             GreaseMonkey proxy issue a command something like this:
48              
49             $ gmproxy ~/.userscripts
50              
51             By default the proxy will listen on port 8030. The supplied directory is
52             scanned before each request; any scripts that have been updated or added
53             will be reloaded and any that have been deleted will be discarded.
54              
55             =head2 Mac OS
56              
57             On MacOS F is created in the project home
58             directory. Create a directory called F<~/.userscripts> and then add gmproxy
59             as a launch item:
60              
61             $ cp net.hexten.gmproxy.plist ~/Library/LaunchAgents
62             $ launchctl load ~/Library/LaunchAgents/net.hexten.gmproxy.plist
63             $ launchctl start net.hexten.gmproxy
64              
65             Then change your network settings to route HTTP through proxy
66             localhost:8030. Once this is done F will load automatically
67             when you log in.
68              
69             Important: As of 2007-12-17 PubSubAgent crashes periodically (actually
70             during .mac synchronisation) when HTTP is proxied. The solution appears
71             to be to add *.mac.com to the list of domains that bypass the proxy. As
72             far as I'm aware this is a Mac OS problem that has nothing specifically
73             to do with HTTP::Proxy::GreaseMonkey.
74              
75             =head2 Other Platforms
76              
77             Patches welcome from anyone who has equivalent instructions for other
78             platforms.
79              
80             =head2 Compatibility
81              
82             For maximum GreaseMonkey compatibility this module must be used in
83             conjunction with L which provides
84             compatibility services within the proxy. The easiest way to achieve this
85             is to use the C command line program. If you're rolling your
86             own proxy use something like this to install the necessary filters:
87              
88             my $proxy = HTTP::Proxy->new(
89             port => $self->port,
90             start_servers => $self->servers
91             );
92             my $gm = HTTP::Proxy::GreaseMonkey::ScriptHome->new;
93             $gm->verbose( $self->verbose );
94             my @dirs = map glob, @args;
95             $gm->add_dir( @dirs );
96             $proxy->push_filter(
97             mime => 'text/html',
98             response => $gm
99             );
100             # Make the redirector
101             my $redir = HTTP::Proxy::GreaseMonkey::Redirector->new;
102             $redir->passthru( $gm->get_passthru_key );
103             $redir->state_file(
104             File::Spec->catfile( $dirs[0], 'state.yml' ) )
105             if @dirs;
106             $proxy->push_filter( request => $redir, );
107             $proxy->start;
108              
109             =head3 Supported Functions
110              
111             The C function is not supported; it makes no
112             sense in a proxied environment.
113              
114             C and C operate on a YAML encoded state file
115             which, by default, is stored in the first named user scripts directory.
116              
117             C outputs log messages to any TTY that the proxy is attached to.
118             Log output does not appear in the browser.
119              
120             C forwards requests via the proxy to bypass the
121             browser's cross site scripting policy.
122              
123             =head3 Performance
124              
125             C, C and C talk to the proxy using
126             synchronous JSONRPC - so they're a little slow. It remains to be seen
127             whether this is a problem for typical GreaseMonkey scripts.
128              
129             =head2 Security
130              
131             I believe it would be possible for a specially crafted page that was
132             aware of this implementation to access the C backdoor
133             and make cross-site HTTP requests.
134              
135             I'll attempt to plug that security hole in a future release.
136              
137             =head1 INTERFACE
138              
139             =head2 C<< add_script( $script ) >>
140              
141             Add a GM script to the proxy. The argument may be the filename of a
142             script or an existing L.
143              
144             =cut
145              
146             sub add_script {
147 4     4 1 169 my ( $self, $script ) = @_;
148              
149             $script = HTTP::Proxy::GreaseMonkey::Script->new( $script )
150 4 50       7 unless eval { $script->can( 'script' ) };
  4         103  
151              
152 4         9 push @{ $self->{script} }, $script;
  4         16  
153             }
154              
155             =head2 C<< verbose >>
156              
157             Set / get verbosity.
158              
159             =cut
160              
161             sub verbose {
162 5     5 1 10 my $self = shift;
163 5 50       15 $self->{verbose} = shift if @_;
164 5         33 return $self->{verbose};
165             }
166              
167             =head2 C<< get_passthru_key >>
168              
169             Get the passthru key that is used to signal to the proxy that it should
170             rewrite request URLs.
171              
172             =cut
173              
174             sub get_passthru_key {
175 3     3 1 91 my $self = shift;
176 3   66     728 return $self->{_key} ||= Data::UUID->new->create_str;
177             }
178              
179             =head2 C<< get_gm_globals >>
180              
181             Return a block of Javascript that initialises various globals that are
182             required by the GreaseMonkey environment.
183              
184             =cut
185              
186             sub get_gm_globals {
187 1     1 1 2 my $self = shift;
188 1   33     7 my $h = $self->{_html} ||= HTML::Tiny->new;
189 1         55 return 'var GM__global = '
190             . $h->json_encode(
191             {
192             host => $self->{uri}->host,
193             passthru => $self->get_passthru_key
194             }
195             ) . ";\n";
196             }
197              
198             =head2 C<< get_support_script >>
199              
200             Returns a block of Javascript that is injected before any user scripts.
201             Typically this code provides the GM_* support functions.
202              
203             =cut
204              
205             sub get_support_script {
206 3     3 1 128 my $self = shift;
207              
208 3   66     30 return $self->{_support_js} ||= do { local $/; };
  2         6  
  2         76  
209             }
210              
211             =head2 C<< init >>
212              
213             Called to initialise the filter.
214              
215             =cut
216              
217             sub init {
218 2     2 1 77 my $self = shift;
219             # Bodge: Do this now because it seems to fail after forking.
220 2         12 $self->get_support_script;
221 2         13 $self->get_passthru_key;
222             }
223              
224             =head2 C<< will_modify >>
225              
226             Will this filter modify content? Called by L.
227              
228             =cut
229              
230 1     1 1 2 sub will_modify { scalar @{ shift->{to_run} } }
  1         10  
231              
232             =head2 C<< begin >>
233              
234             Called at the start of processing.
235              
236             =cut
237              
238             sub begin {
239 1     1 1 10316 my ( $self, $message ) = @_;
240              
241 1         6 my $uri = $self->{uri} = $message->request->uri;
242              
243 1 50       20 print "Proxying $uri\n" if $self->verbose;
244              
245 1         4 $self->{to_run} = [];
246 1         3 for my $script ( @{ $self->{script} } ) {
  1         4  
247 2 50       12 if ( $script->match_uri( $uri ) ) {
248             # Wrap each script in an anon function to give it a
249             # private scope.
250 2         23 push @{ $self->{to_run} },
  2         10  
251             $self->_js_scope( $script->support, $script->script );
252 2 50       7 print " Filtering with ", $script->name, "\n"
253             if $self->verbose;
254             }
255             }
256             }
257              
258             sub _js_scope {
259 3     3   4 my $self = shift;
260 3         46 return join "\n", '( function() {', @_, '} )()';
261             }
262              
263             =head2 C<< filter >>
264              
265             The filter entry point. Called for each chunk of input.
266              
267             =cut
268              
269             sub filter {
270 1     1 1 8 my ( $self, $dataref, $message, $protocol, $buffer ) = @_;
271              
272 1 50       5 if ( $self->will_modify ) {
273 1 50       4 if ( defined $buffer ) {
274 0         0 $$buffer = $$dataref;
275 0         0 $$dataref = "";
276             }
277             else {
278 1         5 my $insert = "\n";
282              
283             # TODO: Fragile - needs a fairly normal looking
284 1         32 $$dataref =~ s{}{$insert}ig;
285             }
286             }
287             }
288              
289             =head2 C<< end >>
290              
291             Finished processing.
292              
293             =cut
294              
295             sub end {
296 0     0 1   my $self = shift;
297 0           $self->{to_run} = [];
298             }
299              
300             1;
301              
302             =head1 CONFIGURATION AND ENVIRONMENT
303            
304             HTTP::Proxy::GreaseMonkey requires no configuration files or environment
305             variables.
306              
307             =head1 DEPENDENCIES
308              
309             None.
310              
311             =head1 INCOMPATIBILITIES
312              
313             None reported.
314              
315             =head1 BUGS AND LIMITATIONS
316              
317             No bugs have been reported.
318              
319             Please report any bugs or feature requests to
320             C, or through the web interface at
321             L.
322              
323             =head1 AUTHOR
324              
325             Andy Armstrong C<< >>
326              
327             =head1 LICENCE AND COPYRIGHT
328              
329             Copyright (c) 2007, Andy Armstrong C<< >>.
330              
331             This module is free software; you can redistribute it and/or
332             modify it under the same terms as Perl itself. See L.
333              
334             =cut
335              
336             __DATA__