1
# See bottom of file for license and copyright information
5
---+ package Foswiki::Net
7
Object that brokers access to network resources.
11
# This module is used by configure, and as such must *not* 'use Foswiki',
12
# or any other module that uses it. Always run configure to test after
13
# changing the module.
22
our $noHTTPResponse; # if set, forces local impl of HTTP::Response
24
# note that the session is *optional*
26
my ( $class, $session ) = @_;
27
my $this = bless( { session => $session }, $class );
29
$this->{mailHandler} = undef;
36
---++ ObjectMethod finish()
37
Break circular references.
41
# Note to developers; please undef *all* fields in the object explicitly,
42
# whether they are references or not. That way this method is "golden
43
# documentation" of the live fields in the object.
46
undef $this->{mailHandler};
47
undef $this->{HELLO_HOST};
48
undef $this->{MAIL_HOST};
49
undef $this->{session};
54
---+++ getExternalResource( $url ) -> $response
56
Get whatever is at the other end of a URL (using an HTTP GET request). Will
57
only work for encrypted protocols such as =https= if the =LWP= CPAN module is
60
Note that the =$url= may have an optional user and password, as specified by
61
the relevant RFC. Any proxy set in =configure= is honoured.
63
The =$response= is an object that is known to implement the following subset of
64
the methods of =LWP::Response=. It may in fact be an =LWP::Response= object,
65
but it may also not be if =LWP= is not available, so callers may only assume
66
the following subset of methods is available:
74
Note that if LWP is *not* available, this function:
75
1 can only really be trusted for HTTP/1.0 urls. If HTTP/1.1 or another
76
protocol is required, you are *strongly* recommended to =require LWP=.
77
1 Will not parse multipart content
79
In the event of the server returning an error, then =is_error()= will return
80
true, =code()= will return a valid HTTP status code
81
as specified in RFC 2616 and RFC 2518, and =message()= will return the
82
message that was received from
83
the server. In the event of a client-side error (e.g. an unparseable URL)
84
then =is_error()= will return true and =message()= will return an explanatory
85
message. =code()= will return 400 (BAD REQUEST).
87
Note: Callers can easily check the availability of other HTTP::Response methods
91
my $response = Foswiki::Func::getExternalResource($url);
92
if (!$response->is_error() && $response->isa('HTTP::Response')) {
93
... other methods of HTTP::Response may be called
95
... only the methods listed above may be called
101
sub getExternalResource {
102
my ( $this, $url ) = @_;
105
if ( $url =~ m!^([a-z]+):! ) {
109
require Foswiki::Net::HTTPResponse;
110
return new Foswiki::Net::HTTPResponse("Bad URL: $url");
113
# Don't remove $LWPAvailable; it is required to disable LWP when unit
115
unless ( defined $LWPAvailable ) {
117
$LWPAvailable = ($@) ? 0 : 1;
120
return _GETUsingLWP( $this, $url );
124
if ( $protocol ne 'http' ) {
125
require Foswiki::Net::HTTPResponse;
126
return new Foswiki::Net::HTTPResponse(
127
"LWP not available for handling protocol: $url");
132
$url =~ s!^\w+://!!; # remove protocol
134
if ( $url =~ s!([^/\@:]+)(?::([^/\@:]+))?@!! ) {
135
( $user, $pass ) = ( $1, $2 || '' );
138
unless ( $url =~ s!([^:/]+)(?::([0-9]+))?!! ) {
141
my ( $host, $port ) = ( $1, $2 || 80 );
144
import Socket qw(:all);
146
$url = '/' unless ($url);
147
my $req = "GET $url HTTP/1.0\r\n";
149
$req .= "Host: $host:$port\r\n";
152
# Use MIME::Base64 at run-time if using outbound proxy with
154
require MIME::Base64;
155
import MIME::Base64();
156
my $base64 = encode_base64( "$user:$pass", "\r\n" );
157
$req .= "Authorization: Basic $base64";
160
# SMELL: Reference to Foswiki variables used for compatibility
161
my ( $proxyHost, $proxyPort );
162
if ( $this->{session} && $this->{session}->{prefs} ) {
163
my $prefs = $this->{session}->{prefs};
164
$proxyHost = $prefs->getPreferencesValue('PROXYHOST');
165
$proxyPort = $prefs->getPreferencesValue('PROXYPORT');
167
$proxyHost ||= $Foswiki::cfg{PROXY}{HOST};
168
$proxyPort ||= $Foswiki::cfg{PROXY}{PORT};
169
if ( $proxyHost && $proxyPort ) {
170
$req = "GET http://$host:$port$url HTTP/1.0\r\n";
175
'$Rev: 4272 (2009-06-21) $' =~ /([0-9]+)/;
178
$req .= 'User-Agent: Foswiki::Net/' . $revstr . "\r\n";
181
my ( $iaddr, $paddr, $proto );
182
$iaddr = inet_aton($host);
183
die "Could not find IP address for $host" unless $iaddr;
185
$paddr = sockaddr_in( $port, $iaddr );
186
$proto = getprotobyname('tcp');
187
unless ( socket( *SOCK, &PF_INET, &SOCK_STREAM, $proto ) ) {
188
die "socket failed: $!";
190
unless ( connect( *SOCK, $paddr ) ) {
191
die "connect failed: $!";
199
unless ( close(SOCK) ) {
200
die "close faied: $!";
204
# No LWP, but may have HTTP::Response which would make life easier
205
# (it has a much more thorough parser)
206
eval 'require HTTP::Response';
207
if ($@ || $noHTTPResponse) {
209
# Nope, no HTTP::Response, have to do things the hard way :-(
210
require Foswiki::Net::HTTPResponse;
211
$response = Foswiki::Net::HTTPResponse->parse($result);
214
$response = HTTP::Response->parse($result);
217
catch Error::Simple with {
218
require Foswiki::Net::HTTPResponse;
219
$response = new Foswiki::Net::HTTPResponse(shift);
225
my ( $this, $url ) = @_;
228
if ( $url =~ s!([^/\@:]+)(?::([^/\@:]+))?@!! ) {
229
( $user, $pass ) = ( $1, $2 );
232
require HTTP::Request;
233
$request = HTTP::Request->new( GET => $url );
234
'$Rev: 4272 (2009-06-21) $' =~ /([0-9]+)/;
236
$request->header( 'User-Agent' => 'Foswiki::Net/'
238
. " libwww-perl/$LWP::VERSION" );
239
require Foswiki::Net::UserCredAgent;
240
my $ua = new Foswiki::Net::UserCredAgent( $user, $pass );
241
my $response = $ua->request($request);
245
# pick a default mail handler
246
sub _installMailHandler {
248
my $handler = 0; # Not undef
249
if ( $this->{session} && $this->{session}->{prefs} ) {
250
my $prefs = $this->{session}->{prefs};
251
$this->{MAIL_HOST} = $prefs->getPreferencesValue('SMTPMAILHOST');
252
$this->{HELLO_HOST} = $prefs->getPreferencesValue('SMTPSENDERHOST');
255
$this->{MAIL_HOST} ||= $Foswiki::cfg{SMTP}{MAILHOST};
256
$this->{HELLO_HOST} ||= $Foswiki::cfg{SMTP}{SENDERHOST};
258
if ( $this->{MAIL_HOST} ) {
260
# See Codev.RegisterFailureInsecureDependencyCygwin for why
261
# this must be untainted
262
# SMELL: That topic tells me nothing - AFAICT this untaint is not
264
require Foswiki::Sandbox;
266
Foswiki::Sandbox::untaintUnchecked( $this->{MAIL_HOST} );
267
eval { # May fail if Net::SMTP not installed
271
$this->{session}->logger->log('warning', "SMTP not available: $@")
272
if ( $this->{session} );
275
$handler = \&_sendEmailByNetSMTP;
279
if ( !$handler && $Foswiki::cfg{MailProgram} ) {
280
$handler = \&_sendEmailBySendmail;
283
$this->setMailHandler($handler) if $handler;
288
---++ setMailHandler( \&fn )
290
* =\&fn= - reference to a function($) (see _sendEmailBySendmail for proto)
291
Install a handler function to take over mail sending from the default
292
SMTP or sendmail methods. This is provided mainly for tests that
293
need to be told when a mail is sent, without actually sending it. It
294
may also be useful in the event that someone needs to plug in an
295
alternative mail handling method.
300
my ( $this, $fnref ) = @_;
301
$this->{mailHandler} = $fnref;
306
---++ ObjectMethod sendEmail ( $text, $retries ) -> $error
308
* =$text= - text of the mail, including MIME headers
309
* =$retries= - number of times to retry the send (default 1)
311
Send an email specified as MIME format content.
312
Date: ...\nFrom: ...\nTo: ...\nCC: ...\nSubject: ...\n\nMailBody...
317
my ( $this, $text, $retries ) = @_;
320
unless ( $Foswiki::cfg{EnableEmail} ) {
321
return 'Trying to send email while email functionality is disabled';
324
unless ( defined $this->{mailHandler} ) {
325
_installMailHandler($this);
328
return 'No mail handler available' unless $this->{mailHandler};
330
# Put in a Date header, mainly for Qmail
331
require Foswiki::Time;
332
my $dateStr = Foswiki::Time::formatTime( time, '$email' );
333
$text = "Date: " . $dateStr . "\n" . $text;
335
my $back_off = 1; # seconds, doubles on each retry
336
while ( $retries-- ) {
338
&{ $this->{mailHandler} }( $this, $text );
341
catch Error::Simple with {
342
my $e = shift->stringify();
343
$this->{session}->logger->log('warning', $e);
345
# be nasty to errors that we didn't throw. They may be
346
# caused by SMTP or perl, and give away info about the
347
# install that we don't want to share.
348
$e = join( "\n", grep( /^ERROR/, split( /\n/, $e ) ) );
350
unless ( $e =~ /^ERROR/ ) {
351
$e = "Mail could not be sent - please ask your %WIKIWEBMASTER% to look at the Foswiki warning log.";
353
$errors .= $e . "\n";
356
$errors .= "Too many failures sending mail"
367
# split up header lines that are too long
368
$addrs =~ s/(.{60}[^,]*,\s*)/$1\n /go;
369
$addrs =~ s/\n\s*$//gos;
373
sub _sendEmailBySendmail {
374
my ( $this, $text ) = @_;
377
my ( $header, $body ) = split( "\n\n", $text, 2 );
379
s/([\n\r])(From|To|CC|BCC)(\:\s*)([^\n\r]*)/$1.$2.$3._fixLineLength($4)/geois;
380
$text = "$header\n\n$body"; # rebuild message
382
open( MAIL, '|' . $Foswiki::cfg{MailProgram} )
383
|| die "ERROR: Can't send mail using Foswiki::cfg{MailProgram}";
386
#SMELL: this is bizzare. on a freeBSD server, I've seen sendmail return 17152
387
#(17152 >> 8) == 67 == EX_NOUSER - however, the mail log says that the error was
388
#EX_TEMPFAIL == 75, and that (as per oreilly book) the email is cued. The email
389
#does reach the user, but they are very confused because they were told that the
390
#rego failed completely.
391
#Sven has ameneded the oops_message for the verify emails to be less positive that
392
#everything has failed, but.
393
die "ERROR: Exit code ".($? << 8)." ($?) from Foswiki::cfg{MailProgram}" if $?;
396
sub _sendEmailByNetSMTP {
397
my ( $this, $text ) = @_;
402
my ( $header, $body ) = split( "\n\n", $text, 2 );
403
my @headerlines = split( /\r?\n/, $header );
404
$header =~ s/\nBCC\:[^\n]*//os; #remove BCC line from header
406
s/([\n\r])(From|To|CC|BCC)(\:\s*)([^\n\r]*)/$1 . $2 . $3 . _fixLineLength( $4 )/geois;
407
$text = "$header\n\n$body"; # rebuild message
410
my @arr = grep( /^From: /i, @headerlines );
411
if ( scalar(@arr) ) {
413
$from =~ s/^From:\s*//io;
415
s/.*<(.*?)>.*/$1/o; # extract "user@host" out of "Name <user@host>"
419
# SMELL: should be a Foswiki::inlineAlert
420
die "ERROR: Can't send mail, missing 'From:'";
423
# extract @to from 'To:', 'CC:', 'BCC:'
424
@arr = grep( /^To: /i, @headerlines );
426
if ( scalar(@arr) ) {
428
$tmp =~ s/^To:\s*//io;
429
@arr = split( /,\s*/, $tmp );
432
@arr = grep( /^CC: /i, @headerlines );
433
if ( scalar(@arr) ) {
435
$tmp =~ s/^CC:\s*//io;
436
@arr = split( /,\s*/, $tmp );
439
@arr = grep( /^BCC: /i, @headerlines );
440
if ( scalar(@arr) ) {
442
$tmp =~ s/^BCC:\s*//io;
443
@arr = split( /,\s*/, $tmp );
446
if ( !( scalar(@to) ) ) {
448
# SMELL: should be a Foswiki::inlineAlert
449
die "ERROR: Can't send mail, missing recipient";
452
return undef unless ( scalar @to );
454
# Change SMTP protocol recipient format from
455
# "User Name <userid@domain>" to "userid@domain"
456
# for those SMTP hosts that need it just that way.
462
if ( $this->{HELLO_HOST} ) {
463
$smtp = Net::SMTP->new(
465
Hello => $this->{HELLO_HOST},
466
Debug => $Foswiki::cfg{SMTP}{Debug} || 0
471
Net::SMTP->new( $this->{MAIL_HOST},
472
Debug => $Foswiki::cfg{SMTP}{Debug} || 0 );
475
my $mess = "ERROR: Can't send mail using Net::SMTP. ";
476
die $mess . "Can't connect to '$this->{MAIL_HOST}'" unless $smtp;
478
if ( $Foswiki::cfg{SMTP}{Username} ) {
479
$smtp->auth( $Foswiki::cfg{SMTP}{Username}, $Foswiki::cfg{SMTP}{Password} );
481
$smtp->mail($from) || die $mess . $smtp->message;
482
$smtp->to( @to, { SkipBad => 1 } ) || die $mess . $smtp->message;
483
$smtp->data($text) || die $mess . $smtp->message;
484
$smtp->dataend() || die $mess . $smtp->message;
491
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/
493
Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
494
are listed in the AUTHORS file in the root of this distribution.
496
NOTE: Please extend that file, not this notice.
497
Additional copyrights apply to some or all of the code in this
500
Copyright (C) 2001-2007 Peter Thoeny, peter@thoeny.org
501
and TWiki Contributors. All Rights Reserved. TWiki Contributors
502
are listed in the AUTHORS file in the root of this distribution.
504
This program is free software; you can redistribute it and/or
505
modify it under the terms of the GNU General Public License
506
as published by the Free Software Foundation; either version 2
507
of the License, or (at your option) any later version. For
508
more details read LICENSE in the root of this distribution.
510
This program is distributed in the hope that it will be useful,
511
but WITHOUT ANY WARRANTY; without even the implied warranty of
512
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
514
As per the GPL, removal of this notice is prohibited.