File Coverage

blib/lib/Perlbal/Plugin/Vhosts.pm
Criterion Covered Total %
statement 54 80 67.5
branch 23 34 67.6
condition 12 19 63.1
subroutine 8 11 72.7
pod 0 6 0.0
total 97 150 64.6


line stmt bran cond sub pod time code
1             ###########################################################################
2             # plugin to do name-based virtual hosts
3             ###########################################################################
4              
5             # things to test:
6             # one persistent connection, first to a docs plugin, then to web proxy... see if it returns us to our base class after end of request
7             # PUTing a large file to a selector, seeing if it is put correctly to the PUT-enabled web_server proxy
8             # obvious cases: non-existent domains, default domains (*), proper matching (foo.brad.lj before *.brad.lj)
9             #
10              
11             package Perlbal::Plugin::Vhosts;
12              
13 4     4   1730 use strict;
  4         9  
  4         159  
14 4     4   31 use warnings;
  4         9  
  4         150  
15 4     4   20 no warnings qw(deprecated);
  4         9  
  4         10323  
16              
17             our %Services; # service_name => $svc
18              
19             # when "LOAD" directive loads us up
20             sub load {
21 3     3 0 10 my $class = shift;
22              
23             Perlbal::register_global_hook('manage_command.vhost', sub {
24 12     12   94 my $mc = shift->parse(qr/^vhost\s+(?:(\w+)\s+)?(\S+)\s*=\s*(\w+)$/,
25             "usage: VHOST [] = ");
26 10         50 my ($selname, $host, $target) = $mc->args;
27 10 50 66     61 unless ($selname ||= $mc->{ctx}{last_created}) {
28 0         0 return $mc->err("omitted service name not implied from context");
29             }
30              
31 10         52 my $ss = Perlbal->service($selname);
32 10 100 66     196 return $mc->err("Service '$selname' is not a selector service")
33             unless $ss && $ss->{role} eq "selector";
34              
35 9         22 $host = lc $host;
36 9 100       55 return $mc->err("invalid host pattern: '$host'")
37             unless $host =~ /^[\w\-\_\.\*\;\:]+$/;
38              
39 8   50     29 $ss->{extra_config}->{_vhosts} ||= {};
40 8         46 $ss->{extra_config}->{_vhosts}{$host} = $target;
41              
42 8         32 return $mc->ok;
43 3         29 });
44 3         77 return 1;
45             }
46              
47             # unload our global commands, clear our service object
48             sub unload {
49 0     0 0 0 my $class = shift;
50              
51 0         0 Perlbal::unregister_global_hook('manage_command.vhost');
52 0         0 unregister($class, $_) foreach (values %Services);
53 0         0 return 1;
54             }
55              
56             # called when we're being added to a service
57             sub register {
58 4     4 0 10 my ($class, $svc) = @_;
59 4 50 33     34 unless ($svc && $svc->{role} eq "selector") {
60 0         0 die "You can't load the vhost plugin on a service not of role selector.\n";
61             }
62              
63 4         26 $svc->selector(\&vhost_selector);
64 4         13 $svc->{extra_config}->{_vhosts} = {};
65              
66 4         18 $Services{"$svc"} = $svc;
67 4         34 return 1;
68             }
69              
70             # called when we're no longer active on a service
71             sub unregister {
72 0     0 0 0 my ($class, $svc) = @_;
73 0         0 $svc->selector(undef);
74 0         0 delete $Services{"$svc"};
75 0         0 return 1;
76             }
77              
78             sub dumpconfig {
79 0     0 0 0 my ($class, $svc) = @_;
80              
81 0         0 my $vhosts = $svc->{extra_config}->{_vhosts};
82              
83 0 0       0 return unless $vhosts;
84              
85 0         0 my @return;
86              
87 0         0 while (my ($vhost, $target) = each %$vhosts) {
88 0         0 push @return, "VHOST $vhost = $target";
89             }
90              
91 0         0 return @return;
92             }
93              
94             # call back from Service via ClientHTTPBase's event_read calling service->select_new_service(Perlbal::ClientHTTPBase)
95             sub vhost_selector {
96 89     89 0 257 my Perlbal::ClientHTTPBase $cb = shift;
97              
98 89         542 my $req = $cb->{req_headers};
99 89 50       238 return $cb->_simple_response(404, "Not Found (no reqheaders)") unless $req;
100              
101 89         671 my $vhost = $req->header("Host");
102              
103             # Browsers and the Apache API considers 'www.example.com.' == 'www.example.com'
104 89 100       296 $vhost and $vhost =~ s/\.$//;
105              
106 89         638 my $uri = $req->request_uri;
107 89   50     586 my $maps = $cb->{service}{extra_config}{_vhosts} ||= {};
108              
109             # ability to ask for one host, but actually use another. (for
110             # circumventing javascript/java/browser host restrictions when you
111             # actually control two domains).
112 89 50 66     2039 if ($vhost && $uri =~ m!^/__using/([\w\.]+)(?:/\w+)(?:\?.*)?$!) {
113 0         0 my $alt_host = $1;
114              
115             # update our request object's Host header, if we ended up switching them
116             # around with /__using/...
117 0         0 my $svc_name = $maps->{"$vhost;using:$alt_host"};
118 0 0       0 my $svc = $svc_name ? Perlbal->service($svc_name) : undef;
119 0 0       0 unless ($svc) {
120 0         0 $cb->_simple_response(404, "Vhost twiddling not configured for requested pair.");
121 0         0 return 1;
122             }
123              
124 0         0 $req->header("Host", $alt_host);
125 0         0 $svc->adopt_base_client($cb);
126 0         0 return 1;
127             }
128              
129             # returns 1 if done with client, 0 if no action taken
130             my $map_using = sub {
131 103     103   469 my ($match_on, $force) = @_;
132              
133 103         414 my $map_name = $maps->{$match_on};
134 103 100       714 my $svc = $map_name ? Perlbal->service($map_name) : undef;
135              
136 103 100 100     872 return 0 unless $svc || $force;
137              
138 89 100       320 unless ($svc) {
139 2         14 $cb->_simple_response(404, "Not Found (no configured vhost)");
140 2         32 return 1;
141             }
142              
143 87         422 $svc->adopt_base_client($cb);
144 87         1969 return 1;
145 89         887 };
146              
147             # foo.site.com should match:
148             # foo.site.com
149             # *.foo.site.com -- this one's questionable, but might as well?
150             # *.site.com
151             # *.com
152             # *
153              
154             # if no vhost, just try the * mapping
155 89 100       662 return $map_using->("*", 1) unless $vhost;
156              
157             # Strip off the :portnumber, if any
158 17         43 $vhost =~ s/:\d+$//;
159              
160             # try the literal mapping
161 17 100       154 return if $map_using->($vhost);
162              
163             # and now try wildcard mappings, removing one part of the domain
164             # at a time until we find something, or end up at "*"
165              
166             # first wildcard, prepending the "*."
167 5         19 my $wild = "*.$vhost";
168 5 50       16 return if $map_using->($wild);
169              
170             # now peel away subdomains
171 5         40 while ($wild =~ s/^\*\.[\w\-\_]+/*/) {
172 7 100       18 return if $map_using->($wild);
173             }
174              
175             # last option: use the "*" wildcard
176 2         9 return $map_using->("*", 1);
177             }
178              
179             1;