| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package WWW::Noss::Curl; |
|
2
|
1
|
|
|
1
|
|
22
|
use 5.016; |
|
|
1
|
|
|
|
|
5
|
|
|
3
|
1
|
|
|
1
|
|
7
|
use strict; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
71
|
|
|
4
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
95
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '2.02'; |
|
6
|
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
7
|
use Exporter qw(import); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
66
|
|
|
8
|
|
|
|
|
|
|
our @EXPORT_OK = qw(curl curl_error http_status_string parse_http_header); |
|
9
|
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
7
|
use File::Temp qw(tempfile); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1662
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# https://curl.se/docs/manpage.html |
|
13
|
|
|
|
|
|
|
our %CODES = ( |
|
14
|
|
|
|
|
|
|
-1 => 'Failed to execute curl', |
|
15
|
|
|
|
|
|
|
0 => 'Success', |
|
16
|
|
|
|
|
|
|
1 => 'Unsupported protocol', |
|
17
|
|
|
|
|
|
|
2 => 'Failed to initialize curl', |
|
18
|
|
|
|
|
|
|
3 => 'Malformed URL', |
|
19
|
|
|
|
|
|
|
4 => 'Required feature disabled', |
|
20
|
|
|
|
|
|
|
5 => 'Could not resolve proxy', |
|
21
|
|
|
|
|
|
|
6 => 'Could not resolve host', |
|
22
|
|
|
|
|
|
|
7 => 'Failed to connect to host', |
|
23
|
|
|
|
|
|
|
8 => 'Weird server reply', |
|
24
|
|
|
|
|
|
|
9 => 'FTP access denied', |
|
25
|
|
|
|
|
|
|
10 => 'FTP accept failed', |
|
26
|
|
|
|
|
|
|
11 => 'Weird FTP PASS reply', |
|
27
|
|
|
|
|
|
|
12 => 'FTP session timeout expired', |
|
28
|
|
|
|
|
|
|
13 => 'Weird FTP PASV reply', |
|
29
|
|
|
|
|
|
|
14 => 'Weird FTP 227 reply', |
|
30
|
|
|
|
|
|
|
15 => 'Cannot use FTP host', |
|
31
|
|
|
|
|
|
|
16 => 'HTTP/2 error', |
|
32
|
|
|
|
|
|
|
17 => 'Failed to set FTP transfer to binary', |
|
33
|
|
|
|
|
|
|
18 => 'Could only receive partial file', |
|
34
|
|
|
|
|
|
|
19 => 'FTP file access error', |
|
35
|
|
|
|
|
|
|
21 => 'FTP quote error', |
|
36
|
|
|
|
|
|
|
22 => 'HTTP page not retrieved', |
|
37
|
|
|
|
|
|
|
23 => 'Write error', |
|
38
|
|
|
|
|
|
|
25 => 'FTP STOR error', |
|
39
|
|
|
|
|
|
|
26 => 'Read error', |
|
40
|
|
|
|
|
|
|
27 => 'Out of memory', |
|
41
|
|
|
|
|
|
|
28 => 'Operation timeout', |
|
42
|
|
|
|
|
|
|
30 => 'FTP PORT failed', |
|
43
|
|
|
|
|
|
|
31 => 'FTP REST failed', |
|
44
|
|
|
|
|
|
|
33 => 'HTTP range error', |
|
45
|
|
|
|
|
|
|
34 => 'HTTP post error', |
|
46
|
|
|
|
|
|
|
35 => 'SSL connection failed', |
|
47
|
|
|
|
|
|
|
36 => 'Failed to resume download', |
|
48
|
|
|
|
|
|
|
37 => 'Failed to open file', |
|
49
|
|
|
|
|
|
|
38 => 'LDAP bind failed', |
|
50
|
|
|
|
|
|
|
39 => 'LDAP search failed', |
|
51
|
|
|
|
|
|
|
41 => 'LDAP function not found', |
|
52
|
|
|
|
|
|
|
42 => 'Aborted', |
|
53
|
|
|
|
|
|
|
43 => 'Internal error', |
|
54
|
|
|
|
|
|
|
45 => 'Interface error', |
|
55
|
|
|
|
|
|
|
47 => 'Too many redirects', |
|
56
|
|
|
|
|
|
|
48 => 'Unknown option passed to libcurl', |
|
57
|
|
|
|
|
|
|
49 => 'Mailformed telnet option', |
|
58
|
|
|
|
|
|
|
52 => 'No server reply', |
|
59
|
|
|
|
|
|
|
53 => 'SSL crypto engine not found', |
|
60
|
|
|
|
|
|
|
54 => 'Cannot set SSL crypto engine as default', |
|
61
|
|
|
|
|
|
|
55 => 'Failed sending network data', |
|
62
|
|
|
|
|
|
|
56 => 'Failed to receive network data', |
|
63
|
|
|
|
|
|
|
58 => 'Problem with local certificate', |
|
64
|
|
|
|
|
|
|
59 => 'Could not use specified SSL cipher', |
|
65
|
|
|
|
|
|
|
60 => 'Peer certificate cannot be authenticated with known CA certificates', |
|
66
|
|
|
|
|
|
|
61 => 'Unrecognized transfer encoding', |
|
67
|
|
|
|
|
|
|
63 => 'Maximum file size exceeded', |
|
68
|
|
|
|
|
|
|
64 => 'Requested FTP SSL level failed', |
|
69
|
|
|
|
|
|
|
65 => 'Rewind failed', |
|
70
|
|
|
|
|
|
|
66 => 'Failed to initialize SSL engine', |
|
71
|
|
|
|
|
|
|
67 => 'Username or password not accepted', |
|
72
|
|
|
|
|
|
|
68 => 'TFTP file not found', |
|
73
|
|
|
|
|
|
|
69 => 'TFTP permission error', |
|
74
|
|
|
|
|
|
|
70 => 'TFTP out of disk space', |
|
75
|
|
|
|
|
|
|
71 => 'Illegal TFTP operation', |
|
76
|
|
|
|
|
|
|
72 => 'Unknown TFTP transfer ID', |
|
77
|
|
|
|
|
|
|
73 => 'TFTP file already exists', |
|
78
|
|
|
|
|
|
|
74 => 'TFTP no such user', |
|
79
|
|
|
|
|
|
|
77 => 'Problem reading SSL CA cert', |
|
80
|
|
|
|
|
|
|
78 => 'Resource referenced in URL does not exist', |
|
81
|
|
|
|
|
|
|
79 => 'Unspecified error occurred during SSH session', |
|
82
|
|
|
|
|
|
|
80 => 'Failed to shut down SSL connection', |
|
83
|
|
|
|
|
|
|
82 => 'Could not load CRL file', |
|
84
|
|
|
|
|
|
|
83 => 'Issuer check failed', |
|
85
|
|
|
|
|
|
|
84 => 'FTP PRET failed', |
|
86
|
|
|
|
|
|
|
85 => 'RTSP CSeq number mismatch', |
|
87
|
|
|
|
|
|
|
86 => 'RTSP Session ID mismatch', |
|
88
|
|
|
|
|
|
|
87 => 'Failed to parse FTP file list', |
|
89
|
|
|
|
|
|
|
88 => 'FTP chunk callback error', |
|
90
|
|
|
|
|
|
|
89 => 'No connection available', |
|
91
|
|
|
|
|
|
|
90 => 'SSL public key does not match pinned public key', |
|
92
|
|
|
|
|
|
|
91 => 'Invalid SSL certificate status', |
|
93
|
|
|
|
|
|
|
92 => 'Stream error in HTTP/2 framing layer', |
|
94
|
|
|
|
|
|
|
93 => 'API function called from inside callback', |
|
95
|
|
|
|
|
|
|
94 => 'Authentication function returned error', |
|
96
|
|
|
|
|
|
|
95 => 'Error detected in HTTP/3 layer', |
|
97
|
|
|
|
|
|
|
96 => 'QUIC connection error', |
|
98
|
|
|
|
|
|
|
97 => 'Proxy handshake error', |
|
99
|
|
|
|
|
|
|
98 => 'Client-side certificate required to complete TLS handshake', |
|
100
|
|
|
|
|
|
|
99 => 'Poll or select returned fatal error', |
|
101
|
|
|
|
|
|
|
100 => 'Value or data field grew larger than allowed', |
|
102
|
|
|
|
|
|
|
# and maybe some more in the future... |
|
103
|
|
|
|
|
|
|
); |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status |
|
106
|
|
|
|
|
|
|
our %HTTP_CODES = ( |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
100 => 'Continue', |
|
109
|
|
|
|
|
|
|
101 => 'Switching Protocols', |
|
110
|
|
|
|
|
|
|
102 => 'Processing', |
|
111
|
|
|
|
|
|
|
103 => 'Early Hints', |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
200 => 'OK', |
|
114
|
|
|
|
|
|
|
201 => 'Created', |
|
115
|
|
|
|
|
|
|
202 => 'Accepted', |
|
116
|
|
|
|
|
|
|
203 => 'Non-Authoritative Information', |
|
117
|
|
|
|
|
|
|
204 => 'No Content', |
|
118
|
|
|
|
|
|
|
205 => 'Reset Content', |
|
119
|
|
|
|
|
|
|
206 => 'Partial Content', |
|
120
|
|
|
|
|
|
|
207 => 'Multi-Status', |
|
121
|
|
|
|
|
|
|
208 => 'Already Reported', |
|
122
|
|
|
|
|
|
|
209 => 'IM Used', |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
300 => 'Multiple Choices', |
|
125
|
|
|
|
|
|
|
301 => 'Moved Permanently', |
|
126
|
|
|
|
|
|
|
302 => 'Found', |
|
127
|
|
|
|
|
|
|
303 => 'See Other', |
|
128
|
|
|
|
|
|
|
304 => 'Not Modified', |
|
129
|
|
|
|
|
|
|
305 => 'Use Proxy', |
|
130
|
|
|
|
|
|
|
306 => 'unused', |
|
131
|
|
|
|
|
|
|
307 => 'Temporary Redirect', |
|
132
|
|
|
|
|
|
|
308 => 'Permanent Redirect', |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
400 => 'Bad Request', |
|
135
|
|
|
|
|
|
|
401 => 'Unauthorized', |
|
136
|
|
|
|
|
|
|
402 => 'Payment Required', |
|
137
|
|
|
|
|
|
|
403 => 'Forbidden', |
|
138
|
|
|
|
|
|
|
404 => 'Not Found', |
|
139
|
|
|
|
|
|
|
405 => 'Method Not Allowed', |
|
140
|
|
|
|
|
|
|
406 => 'Not Acceptable', |
|
141
|
|
|
|
|
|
|
407 => 'Proxy Authentication Required', |
|
142
|
|
|
|
|
|
|
408 => 'Request Timeout', |
|
143
|
|
|
|
|
|
|
409 => 'Conflict', |
|
144
|
|
|
|
|
|
|
410 => 'Gone', |
|
145
|
|
|
|
|
|
|
411 => 'Length Required', |
|
146
|
|
|
|
|
|
|
412 => 'Precondition Failed', |
|
147
|
|
|
|
|
|
|
413 => 'Content Too Large', |
|
148
|
|
|
|
|
|
|
414 => 'URI Too Long', |
|
149
|
|
|
|
|
|
|
415 => 'Unsupported Media Type', |
|
150
|
|
|
|
|
|
|
416 => 'Range Not Satisfiable', |
|
151
|
|
|
|
|
|
|
417 => 'Exception Failed', |
|
152
|
|
|
|
|
|
|
418 => q{I'm a teapot}, |
|
153
|
|
|
|
|
|
|
421 => 'Misdirected Request', |
|
154
|
|
|
|
|
|
|
422 => 'Unprocessable Content', |
|
155
|
|
|
|
|
|
|
423 => 'Locked', |
|
156
|
|
|
|
|
|
|
424 => 'Failed Dependency', |
|
157
|
|
|
|
|
|
|
425 => 'Too Early', |
|
158
|
|
|
|
|
|
|
426 => 'Upgrade Required', |
|
159
|
|
|
|
|
|
|
428 => 'Precondition Required', |
|
160
|
|
|
|
|
|
|
429 => 'Too Many Requests', |
|
161
|
|
|
|
|
|
|
431 => 'Request Header Fields Too Large', |
|
162
|
|
|
|
|
|
|
451 => 'Unavailable For Legal Reasons', |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
500 => 'Internal Server Error', |
|
165
|
|
|
|
|
|
|
501 => 'Not Implemented', |
|
166
|
|
|
|
|
|
|
502 => 'Bad Gateway', |
|
167
|
|
|
|
|
|
|
503 => 'Service Unavailable', |
|
168
|
|
|
|
|
|
|
504 => 'Gateway Timeout', |
|
169
|
|
|
|
|
|
|
505 => 'HTTP Version Not Supported', |
|
170
|
|
|
|
|
|
|
506 => 'Variant Also Negotiates', |
|
171
|
|
|
|
|
|
|
507 => 'Insufficient Storage', |
|
172
|
|
|
|
|
|
|
508 => 'Loop Detected', |
|
173
|
|
|
|
|
|
|
510 => 'Not Extended', |
|
174
|
|
|
|
|
|
|
511 => 'Network Authentication Required', |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
); |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub curl_error { |
|
179
|
|
|
|
|
|
|
|
|
180
|
0
|
|
|
0
|
1
|
|
my ($code) = @_; |
|
181
|
|
|
|
|
|
|
|
|
182
|
0
|
|
0
|
|
|
|
return $CODES{ $code } // 'Unknown error'; |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
} |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub curl { |
|
187
|
|
|
|
|
|
|
|
|
188
|
0
|
|
|
0
|
1
|
|
my ($link, $output, %param) = @_; |
|
189
|
0
|
|
0
|
|
|
|
my $verbose = $param{ verbose } // 0; |
|
190
|
0
|
|
0
|
|
|
|
my $agent = $param{ agent } // undef; |
|
191
|
0
|
|
0
|
|
|
|
my $time_cond = $param{ time_cond } // undef; |
|
192
|
0
|
|
0
|
|
|
|
my $remote_time = $param{ remote_time } // 0; |
|
193
|
0
|
|
0
|
|
|
|
my $etag_save = $param{ etag_save } // undef; |
|
194
|
0
|
|
0
|
|
|
|
my $etag_compare = $param{ etag_compare } // undef; |
|
195
|
0
|
|
0
|
|
|
|
my $limit_rate = $param{ limit_rate } // undef; |
|
196
|
0
|
|
0
|
|
|
|
my $user_agent = $param{ user_agent } // undef; |
|
197
|
0
|
|
0
|
|
|
|
my $timeout = $param{ timeout } // undef; |
|
198
|
0
|
|
0
|
|
|
|
my $fail = $param{ fail } // 0; |
|
199
|
0
|
|
0
|
|
|
|
my $proxy = $param{ proxy } // undef; |
|
200
|
0
|
|
0
|
|
|
|
my $proxy_user = $param{ proxy_user } // undef; |
|
201
|
0
|
|
0
|
|
|
|
my $redirect = $param{ redirect } // 0; |
|
202
|
0
|
|
0
|
|
|
|
my $compressed = $param{ compressed } // 0; |
|
203
|
|
|
|
|
|
|
|
|
204
|
0
|
|
|
|
|
|
my $tmp = do { |
|
205
|
0
|
|
|
|
|
|
my ($h, $p) = tempfile(UNLINK => 1); |
|
206
|
0
|
|
|
|
|
|
close $h; |
|
207
|
0
|
|
|
|
|
|
$p; |
|
208
|
|
|
|
|
|
|
}; |
|
209
|
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
|
my @cmd = ('curl', '-D', $tmp, '-o', $output); |
|
211
|
|
|
|
|
|
|
|
|
212
|
0
|
0
|
|
|
|
|
if (!$verbose) { |
|
213
|
0
|
|
|
|
|
|
push @cmd, '-s'; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
0
|
0
|
|
|
|
|
if (defined $agent) { |
|
217
|
0
|
|
|
|
|
|
push @cmd, '-A', $agent; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
0
|
0
|
|
|
|
|
if (defined $time_cond) { |
|
221
|
0
|
|
|
|
|
|
push @cmd, '-z', $time_cond; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
0
|
0
|
|
|
|
|
if ($remote_time) { |
|
225
|
0
|
|
|
|
|
|
push @cmd, '-R'; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
0
|
0
|
|
|
|
|
if (defined $etag_save) { |
|
229
|
0
|
|
|
|
|
|
push @cmd, '--etag-save', $etag_save; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
0
|
0
|
|
|
|
|
if (defined $etag_compare) { |
|
233
|
0
|
|
|
|
|
|
push @cmd, '--etag-compare', $etag_compare; |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
0
|
0
|
|
|
|
|
if (defined $limit_rate) { |
|
237
|
0
|
|
|
|
|
|
push @cmd, '--limit-rate', $limit_rate; |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
|
|
240
|
0
|
0
|
|
|
|
|
if (defined $user_agent) { |
|
241
|
0
|
|
|
|
|
|
push @cmd, '-A', $user_agent; |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
|
|
244
|
0
|
0
|
|
|
|
|
if (defined $timeout) { |
|
245
|
0
|
|
|
|
|
|
push @cmd, '-m', $timeout; |
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
|
|
248
|
0
|
0
|
|
|
|
|
if ($fail) { |
|
249
|
0
|
|
|
|
|
|
push @cmd, '-f'; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
0
|
0
|
|
|
|
|
if (defined $proxy) { |
|
253
|
0
|
|
|
|
|
|
push @cmd, '-x', $proxy; |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
0
|
0
|
|
|
|
|
if (defined $proxy_user) { |
|
257
|
0
|
|
|
|
|
|
push @cmd, '-U', $proxy_user; |
|
258
|
|
|
|
|
|
|
} |
|
259
|
|
|
|
|
|
|
|
|
260
|
0
|
0
|
|
|
|
|
if (defined $redirect) { |
|
261
|
0
|
|
|
|
|
|
push @cmd, '-L'; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
|
|
264
|
0
|
0
|
|
|
|
|
if ($compressed) { |
|
265
|
0
|
|
|
|
|
|
push @cmd, '--compressed'; |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
|
push @cmd, $link; |
|
269
|
|
|
|
|
|
|
|
|
270
|
0
|
|
|
|
|
|
system @cmd; |
|
271
|
|
|
|
|
|
|
|
|
272
|
0
|
0
|
|
|
|
|
my $exit = $? == -1 ? $? : $? >> 8; |
|
273
|
|
|
|
|
|
|
my ($resp, $head) = ( |
|
274
|
|
|
|
|
|
|
-s $tmp |
|
275
|
|
|
|
|
|
|
# If there are multiple headers, only parse the last one. |
|
276
|
0
|
0
|
|
|
|
|
? do { |
|
277
|
0
|
0
|
|
|
|
|
open my $fh, '<', $tmp or die "Failed to open $tmp for reading: $!\n"; |
|
278
|
0
|
|
|
|
|
|
binmode $fh; |
|
279
|
0
|
|
|
|
|
|
my $slurp = do { local $/; <$fh> }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
280
|
0
|
|
|
|
|
|
close $fh; |
|
281
|
0
|
|
|
|
|
|
$slurp =~ s/(?:\r\n)*$//; |
|
282
|
0
|
|
|
|
|
|
my $headstr = (split /(?:\r\n){2,}/, $slurp)[-1]; |
|
283
|
0
|
|
|
|
|
|
parse_http_header($headstr); |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
: (undef, {}) |
|
286
|
|
|
|
|
|
|
); |
|
287
|
|
|
|
|
|
|
|
|
288
|
0
|
|
|
|
|
|
return ($exit, $resp, $head); |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub parse_http_header { |
|
293
|
|
|
|
|
|
|
|
|
294
|
0
|
|
|
0
|
1
|
|
my ($header) = @_; |
|
295
|
|
|
|
|
|
|
|
|
296
|
0
|
|
|
|
|
|
my ($resp, @lines) = split /\r\n/, $header; |
|
297
|
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
$resp =~ s/\r\n$//; |
|
299
|
0
|
|
|
|
|
|
my @resp_parts = split /\s+/, $resp, 3; |
|
300
|
0
|
0
|
|
|
|
|
if (@resp_parts == 2) { |
|
|
|
0
|
|
|
|
|
|
|
301
|
0
|
|
|
|
|
|
$resp_parts[2] = ''; |
|
302
|
|
|
|
|
|
|
} elsif (@resp_parts != 3) { |
|
303
|
0
|
|
|
|
|
|
die "invalid HTTP response header"; |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
0
|
|
|
|
|
|
my %head; |
|
307
|
0
|
|
|
|
|
|
for my $l (@lines) { |
|
308
|
0
|
|
|
|
|
|
$l =~ s/\r\n$//; |
|
309
|
0
|
0
|
|
|
|
|
if ($l eq '') { |
|
310
|
0
|
|
|
|
|
|
last; |
|
311
|
|
|
|
|
|
|
} |
|
312
|
0
|
|
|
|
|
|
my ($k, $v) = split /:/, $l, 2; |
|
313
|
0
|
0
|
|
|
|
|
if (not defined $v) { |
|
314
|
0
|
|
|
|
|
|
die "invalid HTTP response header"; |
|
315
|
|
|
|
|
|
|
} |
|
316
|
0
|
|
|
|
|
|
$v =~ s/^\s+|\s+$//g; |
|
317
|
0
|
|
|
|
|
|
$head{ lc $k } = $v; |
|
318
|
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
|
|
320
|
0
|
|
|
|
|
|
return (\@resp_parts, \%head); |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
sub http_status_string { |
|
325
|
|
|
|
|
|
|
|
|
326
|
0
|
|
|
0
|
1
|
|
my ($status) = @_; |
|
327
|
|
|
|
|
|
|
|
|
328
|
0
|
0
|
|
|
|
|
if (not exists $HTTP_CODES{ $status }) { |
|
329
|
0
|
|
|
|
|
|
die "'$status' is not a valid HTTP status code"; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
|
return $HTTP_CODES{ $status }; |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
1; |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=head1 NAME |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
WWW::Noss::Curl - Interface to curl command |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=head1 USAGE |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
use WWW::Noss::Curl qw(curl); |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
curl('https://url', 'output'); |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
B is a module that provides an interface to the L |
|
351
|
|
|
|
|
|
|
command for fetching network resources. This is a private module, please |
|
352
|
|
|
|
|
|
|
consult the L manual for user documentation. |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=head1 SUBROUTINES |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
Subroutines are not automatically exported. |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=over 4 |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item ($rt, \@resp, \%head) = curl($link, $output, [ %param ]) |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
L C<$link> and download it to C<$output>. C<%param> is an optional |
|
363
|
|
|
|
|
|
|
hash argument of additional parameters to pass. |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
Returns the exit code of L. A return value of C<0> means |
|
366
|
|
|
|
|
|
|
success, non-zero means failure. C can be used to describe |
|
367
|
|
|
|
|
|
|
the return value. Also returns the return values of C on |
|
368
|
|
|
|
|
|
|
the reponse's header. |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
The following are valid fields for the C<%param> hash: |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=over 4 |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=item verbose |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Boolean determining whether to enable verbose output or not. Corresponds to |
|
377
|
|
|
|
|
|
|
L's C<--silent> option. Defaults to false. |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=item agent |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
String to use as user agent. Corresponds to L's C<--user-agent> option. |
|
382
|
|
|
|
|
|
|
Defaults to none. |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=item time_cond |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
Only download a file if it has been modified past the given time. Can either |
|
387
|
|
|
|
|
|
|
be a timestamp or file. Corresponds to L's C<--time-cond> option. |
|
388
|
|
|
|
|
|
|
Defaults to none. |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=item remote_time |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
Copy the remote file's modification time when downloading a file. Corresponds |
|
393
|
|
|
|
|
|
|
to L's C<--remote-time> option. Defaults to false. |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=item etag_save |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Path to file to write remote file's etag to, if it has one. Corresponds to |
|
398
|
|
|
|
|
|
|
L's C<--etag-save> option. Defaults to none. |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=item etag_compare |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Path to file to compare remote file's etag to, only downloading the remote file |
|
403
|
|
|
|
|
|
|
if the etags differ. Corresponds to L's C<--etag-compare> option. |
|
404
|
|
|
|
|
|
|
Defaults to none. |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=item limit_rate |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Download rate to limit L to. Corresponds to L's |
|
409
|
|
|
|
|
|
|
C<--limit-rate> option. Defaults to none. |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=item user_agent |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
User agent string to send to server. Corresponds to L's |
|
414
|
|
|
|
|
|
|
C<--user-agent> option. Defaults to none. |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=item timeout |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
Maximum time in seconds a transfer is allowed to take. Corresponds to |
|
419
|
|
|
|
|
|
|
L's C<--max-time> option. Defaults to no timeout. |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=item fail |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
Boolean determining if L should fail with no output on server errors. |
|
424
|
|
|
|
|
|
|
Corresponds to L's C<--fail> option. Defaults to false. |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=item proxy |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
Host to use as proxy. Corresponds to L's C<--proxy> option. Defaults |
|
429
|
|
|
|
|
|
|
to none. |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=item proxy_user |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
Username and password to use for proxy, separated by a colon (C). |
|
434
|
|
|
|
|
|
|
Corresponds to L's C<--proxy-user> option. Defaults to none. |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=item redirect |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Boolean determining whether to following redirections. Corresponds to |
|
439
|
|
|
|
|
|
|
L's C<--location> option. Defaults to false. |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=item compressed |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
Boolean determining whether to request compressed content and automatically |
|
444
|
|
|
|
|
|
|
decompress it. Corresponds to L's C<--compressed> option. Defaults to |
|
445
|
|
|
|
|
|
|
false. |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=back |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=item $desc = curl_error($rt) |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
Returns the string description of the C exit code C<$rt>. |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=item (\@response, \%head) = parse_http_header($header) |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Parses the HTTP header in C<$header>. C<\@response> is an array ref containing |
|
456
|
|
|
|
|
|
|
the reponses's information. The first element is the protocol information, the |
|
457
|
|
|
|
|
|
|
second is the HTTP status code, and the third is a string providing extra |
|
458
|
|
|
|
|
|
|
information about the reponse. C<\%head> is a hash of data provided by the |
|
459
|
|
|
|
|
|
|
response's header fields. |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=item $desc = http_status_string($code) |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Returns the string description of the HTTP status code C<$code>. Dies if |
|
464
|
|
|
|
|
|
|
C<$code> is not a valid status code. |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=back |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=head1 GLOBAL VARIABLES |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=over 4 |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=item %WWW::Noss::Curl::CODES |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
Hash of C exit codes and their corresponding string descriptions. |
|
475
|
|
|
|
|
|
|
Use of the C function is preferable. |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=back |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=head1 AUTHOR |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Written by Samuel Young, Esamyoung12788@gmail.comE. |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
This project's source can be found on its |
|
484
|
|
|
|
|
|
|
L. Comments and pull |
|
485
|
|
|
|
|
|
|
requests are welcome! |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
Copyright (C) 2025-2026 Samuel Young |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify |
|
492
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
|
493
|
|
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or |
|
494
|
|
|
|
|
|
|
(at your option) any later version. |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
L, L |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=cut |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
# vim: expandtab shiftwidth=4 |