line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Authen::Simple::WebForm; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
105150
|
use warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
82
|
|
4
|
2
|
|
|
2
|
|
11
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
73
|
|
5
|
2
|
|
|
2
|
|
13
|
use base 'Authen::Simple::Adapter'; |
|
2
|
|
|
|
|
16
|
|
|
2
|
|
|
|
|
4615
|
|
6
|
|
|
|
|
|
|
|
7
|
2
|
|
|
2
|
|
225834
|
use URI; |
|
2
|
|
|
|
|
20324
|
|
|
2
|
|
|
|
|
79
|
|
8
|
2
|
|
|
2
|
|
2331
|
use LWP; |
|
2
|
|
|
|
|
187062
|
|
|
2
|
|
|
|
|
5481
|
|
9
|
2
|
|
|
2
|
|
12116
|
use LWP::ConnCache; |
|
2
|
|
|
|
|
5321
|
|
|
2
|
|
|
|
|
94
|
|
10
|
2
|
|
|
2
|
|
22
|
use Params::Validate qw[]; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
22436
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Authen::Simple::WebForm - Simple authentication against existing web based forms. |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 VERSION |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
Version 0.02 |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=cut |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
__PACKAGE__->options({ |
25
|
|
|
|
|
|
|
initial_url => { |
26
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
27
|
|
|
|
|
|
|
default => '', |
28
|
|
|
|
|
|
|
optional => 1, |
29
|
|
|
|
|
|
|
}, |
30
|
|
|
|
|
|
|
# compiled regex or string |
31
|
|
|
|
|
|
|
initial_expect => { |
32
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
33
|
|
|
|
|
|
|
default => '', |
34
|
|
|
|
|
|
|
optional => 1, |
35
|
|
|
|
|
|
|
}, |
36
|
|
|
|
|
|
|
# compiled regex or string |
37
|
|
|
|
|
|
|
initial_expect_cookie => { |
38
|
|
|
|
|
|
|
type => Params::Validate::SCALAR | Params::Validate::SCALARREF, |
39
|
|
|
|
|
|
|
default => '', |
40
|
|
|
|
|
|
|
optional => 1, |
41
|
|
|
|
|
|
|
}, |
42
|
|
|
|
|
|
|
check_initial_status_code => { |
43
|
|
|
|
|
|
|
type => Params::Validate::BOOLEAN, |
44
|
|
|
|
|
|
|
default => 1, |
45
|
|
|
|
|
|
|
optional => 1, |
46
|
|
|
|
|
|
|
}, |
47
|
|
|
|
|
|
|
initial_request_method => { |
48
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
49
|
|
|
|
|
|
|
default => 'GET', |
50
|
|
|
|
|
|
|
optional => 1, |
51
|
|
|
|
|
|
|
}, |
52
|
|
|
|
|
|
|
login_url => { |
53
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
54
|
|
|
|
|
|
|
default => '', |
55
|
|
|
|
|
|
|
optional => 0, |
56
|
|
|
|
|
|
|
}, |
57
|
|
|
|
|
|
|
# compiled regex or string |
58
|
|
|
|
|
|
|
login_expect => { |
59
|
|
|
|
|
|
|
type => Params::Validate::SCALAR | Params::Validate::SCALARREF, |
60
|
|
|
|
|
|
|
default => '', |
61
|
|
|
|
|
|
|
optional => 1, |
62
|
|
|
|
|
|
|
}, |
63
|
|
|
|
|
|
|
# compiled regex or string |
64
|
|
|
|
|
|
|
login_expect_cookie => { |
65
|
|
|
|
|
|
|
type => Params::Validate::SCALAR | Params::Validate::SCALARREF, |
66
|
|
|
|
|
|
|
default => '', |
67
|
|
|
|
|
|
|
optional => 1, |
68
|
|
|
|
|
|
|
}, |
69
|
|
|
|
|
|
|
check_login_status_code => { |
70
|
|
|
|
|
|
|
type => Params::Validate::BOOLEAN, |
71
|
|
|
|
|
|
|
default => 1, |
72
|
|
|
|
|
|
|
optional => 1, |
73
|
|
|
|
|
|
|
}, |
74
|
|
|
|
|
|
|
login_request_method => { |
75
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
76
|
|
|
|
|
|
|
default => 'POST', |
77
|
|
|
|
|
|
|
optional => 1, |
78
|
|
|
|
|
|
|
}, |
79
|
|
|
|
|
|
|
# for "domain\" if needed |
80
|
|
|
|
|
|
|
username_prefix => { |
81
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
82
|
|
|
|
|
|
|
default => '', |
83
|
|
|
|
|
|
|
optional => 1, |
84
|
|
|
|
|
|
|
}, |
85
|
|
|
|
|
|
|
username_field => { |
86
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
87
|
|
|
|
|
|
|
default => 'username', |
88
|
|
|
|
|
|
|
optional => 1, |
89
|
|
|
|
|
|
|
}, |
90
|
|
|
|
|
|
|
password_field => { |
91
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
92
|
|
|
|
|
|
|
default => 'password', |
93
|
|
|
|
|
|
|
optional => 1, |
94
|
|
|
|
|
|
|
}, |
95
|
|
|
|
|
|
|
lwp_user_agent => { |
96
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
97
|
|
|
|
|
|
|
default => 'Authen::Simple::WebForm/'.$VERSION, |
98
|
|
|
|
|
|
|
optional => 1, |
99
|
|
|
|
|
|
|
}, |
100
|
|
|
|
|
|
|
lwp_timeout => { |
101
|
|
|
|
|
|
|
type => Params::Validate::SCALAR, |
102
|
|
|
|
|
|
|
default => '15', |
103
|
|
|
|
|
|
|
optional => 1, |
104
|
|
|
|
|
|
|
}, |
105
|
|
|
|
|
|
|
lwp_protocols_allowed => { |
106
|
|
|
|
|
|
|
type => Params::Validate::ARRAYREF, |
107
|
|
|
|
|
|
|
default => ['http', 'https'], |
108
|
|
|
|
|
|
|
optional => 1, |
109
|
|
|
|
|
|
|
}, |
110
|
|
|
|
|
|
|
lwp_use_conn_cache => { |
111
|
|
|
|
|
|
|
type => Params::Validate::BOOLEAN, |
112
|
|
|
|
|
|
|
default => 1, |
113
|
|
|
|
|
|
|
optional => 1, |
114
|
|
|
|
|
|
|
}, |
115
|
|
|
|
|
|
|
lwp_requests_redirectable => { |
116
|
|
|
|
|
|
|
type => Params::Validate::ARRAYREF, |
117
|
|
|
|
|
|
|
default => ['GET', 'POST'], |
118
|
|
|
|
|
|
|
optional => 1, |
119
|
|
|
|
|
|
|
}, |
120
|
|
|
|
|
|
|
# yes, this looks like a hash, but it's not (allows keys to show up twice) |
121
|
|
|
|
|
|
|
# [ field => value, field => value ] |
122
|
|
|
|
|
|
|
extra_fields => { |
123
|
|
|
|
|
|
|
type => Params::Validate::ARRAYREF, |
124
|
|
|
|
|
|
|
default => [], |
125
|
|
|
|
|
|
|
optional => 1, |
126
|
|
|
|
|
|
|
}, |
127
|
|
|
|
|
|
|
# yes, this looks like a hash, but it's not (allows keys to show up twice) |
128
|
|
|
|
|
|
|
# [ field => value, field => value ] |
129
|
|
|
|
|
|
|
extra_headers => { |
130
|
|
|
|
|
|
|
type => Params::Validate::ARRAYREF, |
131
|
|
|
|
|
|
|
default => [], |
132
|
|
|
|
|
|
|
optional => 1, |
133
|
|
|
|
|
|
|
}, |
134
|
|
|
|
|
|
|
trace => { |
135
|
|
|
|
|
|
|
type => Params::Validate::BOOLEAN, |
136
|
|
|
|
|
|
|
default => 0, |
137
|
|
|
|
|
|
|
optional => 1, |
138
|
|
|
|
|
|
|
}, |
139
|
|
|
|
|
|
|
}); |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head1 SYNOPSIS |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
use Authen::Simple::WebForm; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
my $webform = Authen::Simple::WebForm->new( |
147
|
|
|
|
|
|
|
login_url => 'http://host.company.com/login.pl', |
148
|
|
|
|
|
|
|
login_expect => 'Successful Login', |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
if ($webform->authenticate( $username, $password ) ) { |
152
|
|
|
|
|
|
|
# successful authentication |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# or as a mod_perl Authen handler |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
PerlModule Authen::Simple::Apache |
158
|
|
|
|
|
|
|
PerlModule Authen::Simple::WebForm |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
PerlSetVar AuthenSimpleWebForm_login_url "http://host.company.com/login.pl" |
161
|
|
|
|
|
|
|
PerlSetVar AuthenSimpleWebForm_login_expect "Successful Login" |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
PerlAuthenHandler Authen::Simple::WebForm |
165
|
|
|
|
|
|
|
AuthType Basic |
166
|
|
|
|
|
|
|
AuthName "Protected Area" |
167
|
|
|
|
|
|
|
Require valid-user |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head1 DESCRIPTION |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Authentication against a variety of login forms. This wraps up the LWP (libwww-perl) |
173
|
|
|
|
|
|
|
calls needed to attempt a login to a site that uses an HTML form for logins. It supports |
174
|
|
|
|
|
|
|
logins that require cookies, various form variables, special headers, and more. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
You can also subclass this to make it easier to setup, such as the |
177
|
|
|
|
|
|
|
L module. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
There are a log of options, but they all have sane defaults. In most cases, you'll only need to use the following: |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=over |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=item login_url |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=item login_expect |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=item uesrname_field |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item password_field |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=item extra_fields |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=back |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Also helpful may be the "trace" option, which may help you to configure |
197
|
|
|
|
|
|
|
your settings. It will print out the response code, cookies, and the resulting |
198
|
|
|
|
|
|
|
page to STDERR. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head1 INSTALLATION |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
To install this module, run the following commands: |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
perl Makefile.PL |
206
|
|
|
|
|
|
|
make |
207
|
|
|
|
|
|
|
make test |
208
|
|
|
|
|
|
|
make install |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=head1 METHODS |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head2 new |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
This method takes a hash of parameters. The following options are accepted: |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=over |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=item initial_url |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
A URL to go to prior to logging in. |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
If the login page requires you to go to some page prior to posting, use this. |
224
|
|
|
|
|
|
|
It will accept and store any cookies returned, and use this page as the |
225
|
|
|
|
|
|
|
referrer when submitting to the login form. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Off by default. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item initial_expect |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
String or a compiled regex (eg. C). |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
If you want to make sure the page you got is the login form, you can set |
235
|
|
|
|
|
|
|
a string here to check for. The page content will be tested against this, |
236
|
|
|
|
|
|
|
and authentication will fail (with a logged error) if this doesn't match. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
With this, you can make sure the server isn't returning a sorry server page, or similar. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Off by default. |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=item initial_expect_cookie |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
String or a compiled regex (eg. C). |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
Similar to initial_expect, but checks the cookies returned by the page. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
NOTE: this matches the cookie key, and the value must simple have some length. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
Off by default. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=item check_initial_status_code |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Boolean, set to 0 to disable. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
Set to undef to skip checking the response status code from the initial page. Otherwise, it must match HTTP::Status->is_success. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Defaults to enabled (1). |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=item initial_request_method |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
This can be either "GET" or "POST". |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
How the initial url will be sent to the server, either via HTTP GET request, or HTTP POST. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Defaults to "GET". |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=item login_url |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
REQUIRED |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
The URL to which the login credentials will be submitted. |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
For example: https://host.company.com/login.pl |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=item login_expect |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
String or a compiled regex (eg. C). |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Set to a unique string to expect in the resulting page when the login was successful. |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
Be default, this is not turned on. If you do not set this, then as long as the |
288
|
|
|
|
|
|
|
server returns a successful status code (see HTTP::Status::is_success), then |
289
|
|
|
|
|
|
|
the user will be authenticated. Most form based login systems return a successful |
290
|
|
|
|
|
|
|
status code even when the login fails, so you'll probably want to set this. |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
A notable exception is the use of something like L, which |
293
|
|
|
|
|
|
|
will return a 403 Forbidden error code when authentication fails. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Off by default. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=item login_expect_cookie |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
String or a compiled regex (eg. C). |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
Similar to login_expect, but checks the cookies returned by the page. If you are also using "initial_url", please be aware that an cookies set by that page will also test true here (ie. this checks our cookie jar, not the content of the page). The cookie jar is reset on every authentication request, so you don't have to worry about stale cookies from previous authentication attempts. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
NOTE: this matches the cookie key, and the value must simple have some length. |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
Off by default. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=item check_login_status_code |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Boolean, set to 0 to disable. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
Set to undef to skip checking the response status code from the login page. Otherwise, it must match HTTP::Status->is_success. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Defaults to enabled (1). |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=item login_request_method |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
This can be either "GET" or "POST". |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
How the initial url will be sent to the server, either via HTTP GET request, or HTTP POST. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Defaults to "POST". |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=item username_prefix |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
Username prefix string. |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
With this, you can automatically prefix your the submitted username with |
332
|
|
|
|
|
|
|
some string. This can can be useful if loging into a windows domain, for |
333
|
|
|
|
|
|
|
example. In that case, you would set it to something like "MyDomain\". |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
Off be default. |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=item username_field |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
Form field name for the username. |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
Defaults to "username". |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=item password_field |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Form field name for the password. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
Defaults to "password". |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=item extra_fields |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
Array reference of key => value pairs, representing additional form fields to submit. |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
Often when submitting to a login form, other form fields are expected by the |
357
|
|
|
|
|
|
|
login script. You may specify any number of them, and their repsective values, |
358
|
|
|
|
|
|
|
using this option. |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
Example: |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
extra_fields => [ |
363
|
|
|
|
|
|
|
'language' => 'en_US', |
364
|
|
|
|
|
|
|
'trusted' => 1 |
365
|
|
|
|
|
|
|
], |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
None submitted by default. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=item extra_headers |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
Array reference of key => value pairs, representing additional HTTP headers. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
You can use this if you need to further mask your client to appear as |
375
|
|
|
|
|
|
|
a popular web browser. Some misbehaved servers may reject your script |
376
|
|
|
|
|
|
|
if these are not set. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
Example: (pose as netscape) |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
extra_headers => [ |
381
|
|
|
|
|
|
|
'User-Agent' => 'Mozilla/4.76 [en] (Win98; U)', |
382
|
|
|
|
|
|
|
'Accept' => 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*', |
383
|
|
|
|
|
|
|
'Accept-Charset' => 'iso-8859-1,*,utf-8', |
384
|
|
|
|
|
|
|
'Accept-Language' => 'en-US' |
385
|
|
|
|
|
|
|
], |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
None submitted by default. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=item lwp_user_agent |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
The HTTP User Agent string to submit to the server in the HTTP headers. |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
Some servers may restrict access to certain user agents (ie. limit only |
395
|
|
|
|
|
|
|
to MS Internet Explorer and Mozilla clients). You can forge a user agent |
396
|
|
|
|
|
|
|
string with this. |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
Example: |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
lwp_user_agent => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14', |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Defaults to "Authen::Simple::WebForm/$VERSION". |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=item lwp_timeout |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Timeout in seconds. Set to zero to disable. |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
This is how long the script will wait for a response for each page fetch. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Defaults to "15" seconds. |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=item lwp_protocols_allowed |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
Array reference of protocols to allow. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
This will limit what protocols will be fetched. You're already setting the |
419
|
|
|
|
|
|
|
URLS that will be loaded, but if you allow redirects (via lwp_requests_redirectable) |
420
|
|
|
|
|
|
|
then those may go to a different protocol. For example, you may submit to an |
421
|
|
|
|
|
|
|
SSL protected site (https) but be redirected to an unprotected page (http). |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
Defaults to ["http", "https"] |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=item lwp_use_conn_cache |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
Boolean, set to 0 to disable. |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Whether to use connection caching. See L for details, as well as the "conn_cache" option to L. |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
Defaults to enabled (1). |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
=item lwp_requests_redirectable |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
Array reference of request names for which we will automatically redirect. |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
See L option requests_redirectable for details. This affects the responses |
440
|
|
|
|
|
|
|
we get from the server. For example, if you are posting form data |
441
|
|
|
|
|
|
|
(login_request_method == POST), and the successful login page returns a redirect |
442
|
|
|
|
|
|
|
to some other page, "POST" would be needed here. We allow GET and POST by |
443
|
|
|
|
|
|
|
default, so you only need to set this is if do not want this behavior. |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
Defaults to ["GET", "POST"] |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=item trace |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Boolean, set to 1 to enable. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
If set to true, the data we recieve will be dumped out to STDERR. |
453
|
|
|
|
|
|
|
This can be useful while you're trying to determine what fields need |
454
|
|
|
|
|
|
|
passed, and what might be going wrong. When running your test scripts, |
455
|
|
|
|
|
|
|
assuming your are starting from a test script, simply dump STDERR |
456
|
|
|
|
|
|
|
to a file: |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
perl test.pl 2>somefile.txt |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
Defaults to disabled (0). |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=back |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=head2 log |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
Any object that supports C, C, C and C. |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
log => Log::Log4perl->get_logger('Authen::Simple::WebForm') |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
See L for a simple logging class you may use, |
473
|
|
|
|
|
|
|
or L for more advanced logging. |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=head2 authenticate( $username, $password ) |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
Returns true on success and false on failure. |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=head2 check($user, $pass) |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
Internal method used to do the actual authentication check. |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=cut |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
sub check |
488
|
|
|
|
|
|
|
{ |
489
|
0
|
|
|
0
|
1
|
|
my ($self, $username, $password) = @_; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# prepend prefix. If none set, or blank, this will just be $username |
492
|
0
|
|
|
|
|
|
my $full_username = join('', ($self->username_prefix, $username)); |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# prep any additional headers we might need |
495
|
0
|
|
|
|
|
|
my @headers; |
496
|
0
|
|
|
|
|
|
my $extra_headers = $self->extra_headers; |
497
|
0
|
0
|
0
|
|
|
|
if (ref($extra_headers) eq 'ARRAY' && @$extra_headers) |
498
|
|
|
|
|
|
|
{ |
499
|
0
|
0
|
|
|
|
|
if ((@$extra_headers % 2) == 0) |
500
|
|
|
|
|
|
|
{ |
501
|
0
|
|
|
|
|
|
push(@headers, @$extra_headers); |
502
|
|
|
|
|
|
|
} else { |
503
|
0
|
0
|
|
|
|
|
$self->log->error("Invalid extra_headers option.") if $self->log; |
504
|
0
|
|
|
|
|
|
return 0; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
# determine request method |
509
|
0
|
|
0
|
|
|
|
my $initial_req_method = uc($self->initial_request_method || 'GET'); |
510
|
0
|
0
|
|
|
|
|
unless ($initial_req_method =~ /^(GET|POST)$/i) { |
511
|
0
|
0
|
|
|
|
|
$self->log->error("Invalid initial_request_method.") if $self->log; |
512
|
0
|
|
|
|
|
|
return 0; |
513
|
|
|
|
|
|
|
} |
514
|
0
|
|
0
|
|
|
|
my $login_req_method = uc($self->login_request_method || 'GET'); |
515
|
0
|
0
|
|
|
|
|
unless ($login_req_method =~ /^(GET|POST)$/i) { |
516
|
0
|
0
|
|
|
|
|
$self->log->error("Invalid login_request_method.") if $self->log; |
517
|
0
|
|
|
|
|
|
return 0; |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
# initialize the user agent |
521
|
0
|
0
|
|
|
|
|
my $ua = LWP::UserAgent->new() or die "Unable to init LWP::UserAgent : $@"; |
522
|
|
|
|
|
|
|
# keep in memory cookie jar |
523
|
0
|
|
|
|
|
|
$ua->cookie_jar({}); |
524
|
0
|
0
|
|
|
|
|
$ua->agent($self->lwp_user_agent) if $self->lwp_user_agent; |
525
|
0
|
0
|
|
|
|
|
$ua->timeout( $self->lwp_timeout ) if $self->lwp_timeout; |
526
|
0
|
0
|
|
|
|
|
$ua->conn_cache(LWP::ConnCache->new()) if $self->lwp_use_conn_cache; |
527
|
|
|
|
|
|
|
|
528
|
0
|
|
|
|
|
|
my $req_redirectable = $self->lwp_requests_redirectable; |
529
|
0
|
0
|
0
|
|
|
|
if (ref($req_redirectable) eq 'ARRAY' && @$req_redirectable) { |
530
|
0
|
|
|
|
|
|
push @{$ua->requests_redirectable}, @$req_redirectable; |
|
0
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# get an inital page? |
534
|
0
|
0
|
|
|
|
|
if ($self->initial_url) |
535
|
|
|
|
|
|
|
{ |
536
|
0
|
0
|
|
|
|
|
my $res = ($initial_req_method eq 'GET') ? $ua->get($self->initial_url, @headers): |
537
|
|
|
|
|
|
|
$ua->post($self->initial_url, @headers); |
538
|
0
|
0
|
|
|
|
|
if ($self->trace) |
539
|
|
|
|
|
|
|
{ |
540
|
0
|
|
|
|
|
|
print STDERR ("-"x80)."\n"; |
541
|
0
|
|
|
|
|
|
print STDERR "TRACE: initial response, response code [".$res->code."]\n"; |
542
|
0
|
|
|
|
|
|
print STDERR "TRACE: initial response, cookies [".$ua->cookie_jar->as_string()."]\n"; |
543
|
0
|
|
|
|
|
|
print STDERR $res->decoded_content; |
544
|
0
|
|
|
|
|
|
print STDERR "\n\n\n"; |
545
|
0
|
|
|
|
|
|
print STDERR ("-"x80)."\n"; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
# make sure status code is ok? |
548
|
0
|
0
|
|
|
|
|
if ($self->check_initial_status_code) |
549
|
|
|
|
|
|
|
{ |
550
|
0
|
0
|
|
|
|
|
unless ($res->is_success) |
551
|
|
|
|
|
|
|
{ |
552
|
0
|
0
|
|
|
|
|
$self->log->error("Can't get ".$self->initial_url." -- ".$res->status_line) |
553
|
|
|
|
|
|
|
if $self->log; |
554
|
0
|
|
|
|
|
|
return 0; |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
# do we care to check the content? |
559
|
0
|
0
|
|
|
|
|
if ($self->initial_expect) |
560
|
|
|
|
|
|
|
{ |
561
|
0
|
|
|
|
|
|
my $expect = $self->initial_expect; |
562
|
0
|
0
|
|
|
|
|
unless (ref($expect) eq 'Regexp') { |
563
|
0
|
|
|
|
|
|
$expect = qr/\Q$expect\E/; |
564
|
|
|
|
|
|
|
} |
565
|
0
|
0
|
|
|
|
|
unless ($res->decoded_content =~ /$expect/) |
566
|
|
|
|
|
|
|
{ |
567
|
0
|
0
|
|
|
|
|
$self->log->error("Initial url didn't return expected results.") if $self->log; |
568
|
0
|
|
|
|
|
|
return 0; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# do we care to check for a cookie |
573
|
0
|
0
|
|
|
|
|
if ($self->initial_expect_cookie) |
574
|
|
|
|
|
|
|
{ |
575
|
0
|
|
|
|
|
|
my $expect = $self->initial_expect_cookie; |
576
|
|
|
|
|
|
|
|
577
|
0
|
|
|
|
|
|
my $found = 0; |
578
|
0
|
|
|
|
|
|
my $search; # cookie_jar search callback |
579
|
|
|
|
|
|
|
|
580
|
0
|
0
|
|
|
|
|
if (ref($expect) eq 'Regexp') |
581
|
|
|
|
|
|
|
{ |
582
|
0
|
0
|
0
|
0
|
|
|
$search = sub { $found++ if $_[1] =~ /$expect/ && length($_[2]); }; |
|
0
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
} else { |
584
|
0
|
0
|
0
|
0
|
|
|
$search = sub { $found++ if $_[1] eq $expect && length($_[2]); }; |
|
0
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
# search the cookie jar |
588
|
0
|
|
|
|
|
|
$ua->cookie_jar->scan($search); |
589
|
0
|
0
|
|
|
|
|
unless ($found) |
590
|
|
|
|
|
|
|
{ |
591
|
0
|
0
|
|
|
|
|
$self->log->debug("Failed to authenticate user '$full_username'. Reason: Initial Cookie $expect was not found.") |
592
|
|
|
|
|
|
|
if $self->log; |
593
|
0
|
|
|
|
|
|
return 0; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# build data to post |
600
|
0
|
|
|
|
|
|
my @data = ( |
601
|
|
|
|
|
|
|
$self->username_field => $full_username, |
602
|
|
|
|
|
|
|
$self->password_field => $password |
603
|
|
|
|
|
|
|
); |
604
|
|
|
|
|
|
|
# add an extra fields to submit |
605
|
0
|
|
|
|
|
|
my $extra_fields = $self->extra_fields; |
606
|
0
|
0
|
0
|
|
|
|
if (ref($extra_fields) eq 'ARRAY' && @$extra_fields) |
607
|
|
|
|
|
|
|
{ |
608
|
0
|
0
|
|
|
|
|
if ((@$extra_fields % 2) == 0) |
609
|
|
|
|
|
|
|
{ |
610
|
0
|
|
|
|
|
|
push(@data, @$extra_fields); |
611
|
|
|
|
|
|
|
} else { |
612
|
0
|
0
|
|
|
|
|
$self->log->error("Invalid extra_fields option.") if $self->log; |
613
|
0
|
|
|
|
|
|
return 0; |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
} |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
# attempt to login |
618
|
0
|
|
|
|
|
|
my $res; |
619
|
0
|
0
|
|
|
|
|
if ($login_req_method eq 'GET') |
620
|
|
|
|
|
|
|
{ |
621
|
0
|
|
|
|
|
|
my $url = URI->new($self->login_url); |
622
|
0
|
0
|
|
|
|
|
unless ($url) { |
623
|
0
|
0
|
|
|
|
|
$self->log->error("Unable to parse login_url. $@") if $self->log; |
624
|
0
|
|
|
|
|
|
return 0; |
625
|
|
|
|
|
|
|
} |
626
|
0
|
|
|
|
|
|
$url->query_form( \@data ); |
627
|
0
|
|
|
|
|
|
$res = $ua->get($url, @headers); |
628
|
|
|
|
|
|
|
} else { # POST |
629
|
0
|
|
|
|
|
|
$res = $ua->post($self->login_url, \@data, @headers); |
630
|
|
|
|
|
|
|
} |
631
|
0
|
0
|
|
|
|
|
if ($self->trace) |
632
|
|
|
|
|
|
|
{ |
633
|
0
|
|
|
|
|
|
print STDERR ("-"x80)."\n"; |
634
|
0
|
|
|
|
|
|
print STDERR "TRACE: initial response, response code [".$res->code."]\n"; |
635
|
0
|
|
|
|
|
|
print STDERR "TRACE: initial response, cookies [".$ua->cookie_jar->as_string()."]\n"; |
636
|
0
|
|
|
|
|
|
print STDERR $res->decoded_content; |
637
|
0
|
|
|
|
|
|
print STDERR "\n\n\n"; |
638
|
0
|
|
|
|
|
|
print STDERR ("-"x80)."\n"; |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
# make sure status code is ok? |
642
|
0
|
0
|
|
|
|
|
if ($self->check_login_status_code) |
643
|
|
|
|
|
|
|
{ |
644
|
0
|
0
|
|
|
|
|
unless ($res->is_success) |
645
|
|
|
|
|
|
|
{ |
646
|
0
|
0
|
|
|
|
|
if ($res->is_redirect) |
647
|
|
|
|
|
|
|
{ |
648
|
0
|
0
|
|
|
|
|
$self->log->debug("Failed to authenticate user '$full_username'. Reason: Login page returned redirect status code '".$res->code."'. You may wish to enable lwp_requests_redirectable -- ".$res->status_line) |
649
|
|
|
|
|
|
|
if $self->log; |
650
|
|
|
|
|
|
|
} else { |
651
|
0
|
0
|
|
|
|
|
$self->log->debug("Failed to authenticate user '$full_username'. Reason: Login page returned invalid status code '".$res->code."' -- ".$res->status_line) |
652
|
|
|
|
|
|
|
if $self->log; |
653
|
|
|
|
|
|
|
} |
654
|
0
|
|
|
|
|
|
return 0; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
# do we care to check the content? |
659
|
0
|
0
|
|
|
|
|
if ($self->login_expect) |
660
|
|
|
|
|
|
|
{ |
661
|
0
|
|
|
|
|
|
my $expect = $self->login_expect; |
662
|
0
|
0
|
|
|
|
|
unless (ref($expect) eq 'Regexp') { |
663
|
0
|
|
|
|
|
|
$expect = qr/\Q$expect\E/; |
664
|
|
|
|
|
|
|
} |
665
|
0
|
0
|
|
|
|
|
unless ($res->decoded_content =~ /$expect/) |
666
|
|
|
|
|
|
|
{ |
667
|
0
|
0
|
|
|
|
|
$self->log->debug("Failed to authenticate user '$full_username'. Reason: Login page response did not match expected value.") |
668
|
|
|
|
|
|
|
if $self->log; |
669
|
0
|
|
|
|
|
|
return 0; |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
# do we care to check for a cookie |
674
|
0
|
0
|
|
|
|
|
if ($self->login_expect_cookie) |
675
|
|
|
|
|
|
|
{ |
676
|
0
|
|
|
|
|
|
my $expect = $self->login_expect_cookie; |
677
|
|
|
|
|
|
|
|
678
|
0
|
|
|
|
|
|
my $found = 0; |
679
|
0
|
|
|
|
|
|
my $search; # cookie_jar search callback |
680
|
|
|
|
|
|
|
|
681
|
0
|
0
|
|
|
|
|
if (ref($expect) eq 'Regexp') |
682
|
|
|
|
|
|
|
{ |
683
|
0
|
0
|
0
|
0
|
|
|
$search = sub { $found++ if $_[1] =~ /$expect/ && length($_[2]); }; |
|
0
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
} else { |
685
|
0
|
0
|
0
|
0
|
|
|
$search = sub { $found++ if $_[1] eq $expect && length($_[2]); }; |
|
0
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
# search the cookie jar |
689
|
0
|
|
|
|
|
|
$ua->cookie_jar->scan($search); |
690
|
0
|
0
|
|
|
|
|
unless ($found) |
691
|
|
|
|
|
|
|
{ |
692
|
0
|
0
|
|
|
|
|
$self->log->debug("Failed to authenticate user '$full_username'. Reason: Login Cookie $expect was not found.") |
693
|
|
|
|
|
|
|
if $self->log; |
694
|
0
|
|
|
|
|
|
return 0; |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
0
|
0
|
|
|
|
|
$self->log->debug("Successfully authenticated user '$full_username'.") if $self->log; |
699
|
0
|
|
|
|
|
|
return 1; |
700
|
|
|
|
|
|
|
} |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=head1 TODO |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
Add lwp_cookie_jar option(s) so that it may use a file. |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
Add a debug mode. It's often difficult to determine what content is being returned, and what to look for. The debug mode should print each step out to STDERR, and include the relevant response information from the page. |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
Write tests using HTTP::Daemon as a local webserver. See LWP test t/local/http.t and t/local/chunked.t for example. |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
=head1 AUTHOR |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
Joshua I. Miller, C<< >> |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
=head1 BUGS |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
717
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
718
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=head1 SUPPORT |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
perldoc Authen::Simple::WebForm |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
You can also look for information at: |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
=over 4 |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
L |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
L |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
=item * CPAN Ratings |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
L |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
=item * Search CPAN |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
L |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
=back |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
=head1 SEE ALSO |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
L |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
L |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
examples/ex1.pl (an example that can be used to auth against freshmeat.net). |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
L |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
Copyright 2009 Joshua I. Miller, all rights reserved. |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
768
|
|
|
|
|
|
|
under the same terms as Perl itself. |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
=cut |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
1; # End of Authen::Simple::WebForm |