e45977d750df87157589371d2d576be1e38f9738
1 package App
::FonBot
::Plugin
::HTTPD
;
3 our $VERSION = '0.000_3';
9 use Apache2
::Authen
::Passphrase qw
/pwcheck/;
10 use HTTP
::Status qw
/HTTP_BAD_REQUEST HTTP_OK HTTP_NO_CONTENT HTTP_FORBIDDEN HTTP_UNAUTHORIZED/;
11 use JSON qw
/encode_json/;
13 use POE
::Component
::Server
::HTTP qw
/RC_OK RC_DENY RC_WAIT/;
16 use MIME
::Base64 qw
/decode_base64/;
17 use Storable qw
/freeze thaw/;
18 use Text
::ParseWords qw
/shellwords/;
20 use App
::FonBot
::Plugin
::Config qw
/$httpd_port/;
21 use App
::FonBot
::Plugin
::Common
;
23 ##################################################
25 my $log=Log
::Log4perl
->get_logger(__PACKAGE__
);
28 my %waiting_userrequests;
32 $log->info('initializing '.__PACKAGE__
);
33 %waiting_requests = ();
34 %waiting_userrequests = ();
35 $httpd = POE
::Component
::Server
::HTTP
->new(
37 PreHandler
=> { '/' => [\
&pre_auth
, \
&pre_get
, \
&pre_userget
], },
38 ContentHandler
=>{ '/send' => \
&on_send
, '/get' => \
&on_get
, '/ok' => \
&on_ok
, '/userget' => \
&on_userget
, '/usersend' => \
&on_usersend
},
39 ErrorHandler
=> { '/' => sub { RC_OK
}},
40 Headers
=> { 'Cache-Control' => 'no-cache' },
45 $log->info('finishing '.__PACKAGE__
);
46 POE
::Kernel
->call($httpd, 'shutdown');
49 ##################################################
52 my ($response,$errstr,$errcode)=@_;
54 $$response->code($errcode // HTTP_BAD_REQUEST
);
55 $$response->header(Content_Type
=> 'text/plain');
56 $$response->message($errstr);
62 my ($request, $response)=@_;
65 my $authorization=$request->header('Authorization') // die 'No Authorization header';
66 $authorization =~ /^Basic (.+)$/ or die 'Invalid Authorization header';
67 my ($user, $password) = decode_base64
($1) =~ /^(.+):(.*)$/ or die 'Invalid Authorization header';
68 eval { pwcheck
$user, $password; 1 } or die 'Invalid user/password combination';
69 $request->header(Username
=> $user);
70 $log->debug("HTTP request from $user to url ".$request->url);
73 $response->code(HTTP_UNAUTHORIZED
);
74 $response->message('Bad username or password');
75 $response->header(Content_Type
=> 'text/plain');
76 $response->header(WWW_Authenticate
=> 'Basic realm="fonbotd"');
77 $response->content('Unauthorized');
78 $log->debug("Request denied: $error");
82 $response->content('');
87 my ($request, $response)=@_;
88 my $user=$request->header('Username');
89 return RC_OK
if $response->code;
90 return RC_OK
unless $user;
91 return RC_OK
unless $request->uri =~ m
,/get
,;
93 unless (exists $commands{$user}) {
94 $log->debug("No pending commands for $user, entering RC_WAIT");
95 $waiting_requests{$user}->continue if exists $waiting_requests{$user};
96 $waiting_requests{$user}=$response;
104 my ($request, $response)=@_;
105 my $user=$request->header('Username');
106 return RC_OK
if $response->code;
107 return RC_OK
unless $user;
108 return RC_OK
unless $request->uri =~ m
,/userget
,;
110 unless (exists $responses{$user}) {
111 $log->debug("No pending responses for $user, entering RC_WAIT");
112 $waiting_userrequests{$user}->continue if exists $waiting_userrequests{$user};
113 $waiting_userrequests{$user}=$response;
121 my ($request, $response)=@_;
122 return RC_OK
if $response->code;
124 $response->code(HTTP_OK
);
129 my ($request, $response)=@_;
130 return RC_OK
if $response->code;
133 my $user=$request->header('Username');
134 $log->debug("on_get from user $user");
136 if (exists $commands{$user}) {
137 my $json=encode_json thaw
$commands{$user};
138 $log->debug("Sending JSON: $json to $user");
139 $response->content($json);
140 $response->code(HTTP_OK
);
141 $response->message('Commands sent');
143 $log->debug("Sending back 204 No Content");
144 $response->code(HTTP_NO_CONTENT
);
145 $response->message('No pending commands');
148 delete $commands{$user}
151 $log->error("ERROR: $@") if $@
&& $@
!~ /^Bad Request /;
157 my ($request, $response)=@_;
158 return RC_OK
if $response->code;
161 my $user=$request->header('Username');
162 $log->debug("on_userget from user $user");
164 if (exists $responses{$user}) {
165 my $json=encode_json
$responses{$user};
166 $log->debug("Sending JSON: $json to $user");
167 $response->content($json);
168 $response->code(HTTP_OK
);
169 $response->message('Responses sent');
171 $log->debug("Sending back 204 No Content");
172 $response->code(HTTP_NO_CONTENT
);
173 $response->message('No pending responses');
176 delete $responses{$user}
179 $log->error("ERROR: $@") if $@
&& $@
!~ /^Bad Request /;
185 my ($request, $response)=@_;
186 return RC_OK
if $response->code;
189 httpdie
$response, 'All requests must use the POST http method' unless $request->method eq 'POST';
190 my $user=$request->header('Username');
192 my $destination=$request->header('X-Destination') // httpdie
$response, 'Missing destination address';
193 my ($driver, $address)=shellwords
$destination;
195 my $content=$request->content // httpdie
$response, 'Content is undef';
197 if ($driver eq 'HTTP') {
198 $responses{$user}//=[];
199 push $responses{$user}, $content;
200 if (exists $waiting_userrequests{$user}) {
201 $waiting_userrequests{$user}->continue;
202 delete $waiting_userrequests{$user}
205 unless ($ok_user_addresses{"$user $driver $address"}) {
206 $response->code(HTTP_FORBIDDEN
);
207 $response->message("$user is not allowed to send messages to $address");
211 POE
::Kernel
->post($driver, 'send_message', $address, $content) or $log->error("Driver not found: $driver");
214 $response->code(HTTP_NO_CONTENT
);
215 $response->message('Message sent');
218 $log->error("ERROR: $@") if $@
&& $@
!~ /^Bad Request /;
219 $log->debug('Responding to send from $user with '.$response->code.' '.$response->message);
224 my ($request, $response)=@_;
225 $log->debug("asdasd asd");
226 return RC_OK
if $response->code;
229 httpdie
$response, 'All requests must use the POST http method' unless $request->method eq 'POST';
230 my $user=$request->header('Username');
232 my $content=$request->content // httpdie
$response, 'Content is undef';
234 sendmsg
$user, $request->header('X-Requestid'), HTTP
=> shellwords
$_ for split '\n', $content;
236 $response->code(HTTP_NO_CONTENT
);
237 $response->message('Command sent');
240 $log->error("ERROR: $@") if $@
&& $@
!~ /^Bad Request /;
241 $log->debug('Responding to usersend from $user with '.$response->code.' '.$response->message);
253 App::FonBot::Plugin::HTTPD - FonBot webserver plugin, used for communication with phones
257 use App::FonBot::Plugin::HTTPD;
258 App::FonBot::Plugin::HTTPD->init;
260 App::FonBot::Plugin::HTTPD->fini;
264 This FonBot plugin provides a webserver for interacting with fonbotd. All requests use Basic access authentication.
266 The available calls are:
272 Returns a JSON array of pending commands for the current user. Uses long polling — the server does not respond immediately if there are no pending commands.
280 Sends a message to an address. The address is given in the C<X-Destination> header. The message is in the POST data.
282 =item GET C</userget>
284 Returns a JSON array of pending messages for the current user. Uses long polling — the server does not respond immediately if there are no pending commands.
286 =item POST C</usersend>
288 Sends a command to the sender's phone. The optional C<X-Requestid> header sets the request ID. The command is in the POST data
292 =head1 CONFIGURATION VARIABLES
298 The HTTPD listens on this port.
304 Marius Gavrilescu C<< marius@ieval.ro >>
306 =head1 COPYRIGHT AND LICENSE
308 Copyright 2013 Marius Gavrilescu
310 This file is part of fonbotd.
312 fonbotd is free software: you can redistribute it and/or modify
313 it under the terms of the GNU Affero General Public License as published by
314 the Free Software Foundation, either version 3 of the License, or
315 (at your option) any later version.
317 fonbotd is distributed in the hope that it will be useful,
318 but WITHOUT ANY WARRANTY; without even the implied warranty of
319 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
320 GNU Affero General Public License for more details.
322 You should have received a copy of the GNU Affero General Public License
323 along with fonbotd. If not, see <http://www.gnu.org/licenses/>
This page took 0.04042 seconds and 3 git commands to generate.