File Coverage

blib/lib/Github/Hooks/Receiver.pm
Criterion Covered Total %
statement 57 68 83.8
branch 16 22 72.7
condition 4 4 100.0
subroutine 13 14 92.8
pod 0 4 0.0
total 90 112 80.3


line stmt bran cond sub pod time code
1             package Github::Hooks::Receiver;
2 4     4   91189 use 5.008001;
  4         10  
  4         124  
3 4     4   17 use strict;
  4         4  
  4         152  
4 4     4   18 use warnings;
  4         17  
  4         156  
5              
6             our $VERSION = "0.02";
7              
8 4     4   1227 use Github::Hooks::Receiver::Event;
  4         16  
  4         91  
9              
10 4     4   1986 use JSON;
  4         52076  
  4         23  
11 4     4   2943 use Plack::Request;
  4         183109  
  4         217  
12             use Class::Accessor::Lite (
13 4         34 new => 1,
14             ro => [qw/secret/],
15 4     4   31 );
  4         5  
16 4     4   2701 use Digest::SHA;
  4         12383  
  4         199  
17 4     4   2205 use String::Compare::ConstantTime;
  4         1884  
  4         2107  
18              
19 24   100 24 0 168 sub events { shift->{events} ||= {} }
20              
21             sub to_app {
22 4     4 0 21 my $self = shift;
23             sub {
24 13     13   102652 my $env = shift;
25 13         102 my $req = Plack::Request->new($env);
26 13 50       143 if ($req->method ne 'POST') {
27 0         0 return [400, [], ['BAD REQUEST']];
28             }
29              
30             # verify signature
31 13 100       178 if ($self->secret) {
32 6         66 my $expected = 'sha1=' . Digest::SHA::hmac_sha1_hex($req->content, $self->secret);
33 6   100     3070 my $actual = $req->header('X-Hub-Signature') || '';
34 6 100       1773 if (!String::Compare::ConstantTime::equals($expected, $actual)) {
35 4         54 return [400, [], ['BAD REQUEST']];
36             }
37             }
38              
39             # Parse JSON payload
40 9         66 my $payload_json;
41 9 100       35 if (lc $req->header('content-type') eq 'application/json') {
    100          
42 2         462 $payload_json = $req->content;
43             } elsif (lc $req->header('content-type') eq 'application/x-www-form-urlencoded') {
44 6         1095 $payload_json = $req->param('payload');
45             }
46 9 100       4933 my $payload = eval { decode_json $payload_json }
  9         48  
47             or return [400, [], ['BAD REQUEST']];
48              
49 8         1727 my $event_name = $req->header('X-GitHub-Event');
50 8         408 my $event = Github::Hooks::Receiver::Event->new(
51             payload => $payload,
52             event => $event_name,
53             );
54              
55 8 50       121 if (my $code = $self->events->{''}) {
56 8         35 $code->($event, $req);
57             }
58 8 100       35 if (my $code = $self->events->{$event_name}) {
59 5         16 $code->($event, $req);
60             }
61              
62 8         154 [200, [], ['OK']];
63 4         28 };
64             }
65              
66             sub on {
67 8     8 0 1247 my $self = shift;
68 8         15 my ($event, $code) = @_;
69 8 100       27 if (ref $event eq 'CODE') {
70 4         10 $code = $event;
71 4         9 $event = '';
72             }
73 8         48 $self->events->{$event} = $code;
74             }
75              
76             sub run {
77 0     0 0   my $self = shift;
78 0 0         my %opts = @_ == 1 ? %{$_[0]} : @_;
  0            
79              
80 0           my %server;
81 0           my $server = delete $opts{server};
82 0 0         $server{server} = $server if $server;
83              
84 0           my @options = %opts;
85 0           require Plack::Runner;
86              
87 0           my $runner = Plack::Runner->new(
88             %server,
89             options => \@options,
90             );
91 0           $runner->run($self->to_app);
92             }
93              
94             1;
95             __END__