~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/UI.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# See bottom of file for license and copyright information
 
2
=begin TML
 
3
 
 
4
---+!! package Foswiki::UI
 
5
 
 
6
Coordinator of execution flow and service functions used by the UI packages
 
7
 
 
8
=cut
 
9
 
 
10
package Foswiki::UI;
 
11
 
 
12
use strict;
 
13
 
 
14
BEGIN {
 
15
    $Foswiki::cfg{SwitchBoard} ||= {};
 
16
 
 
17
    # package - perl package that contains the method for this request
 
18
    # function - name of the function in package
 
19
    # context - hash of context vars to define
 
20
    # allow - hash of HTTP methods to allow (all others are denied)
 
21
    # deny - hash of HTTP methods that are denied (all others are allowed)
 
22
    # 'deny' is not tested if 'allow' is defined
 
23
 
 
24
    # The switchboard can contain entries either as hashes or as arrays.
 
25
    # The array format specifies [0] package, [1] function, [2] context
 
26
    # and should be used when declaring scripts from plugins that must work
 
27
    # with Foswiki 1.0.0 and 1.0.4.
 
28
 
 
29
    $Foswiki::cfg{SwitchBoard}{attach} = {
 
30
        package => 'Foswiki::UI::Upload',
 
31
        function => 'attach',
 
32
        context => { attach => 1 },
 
33
    };
 
34
    $Foswiki::cfg{SwitchBoard}{changes} = {
 
35
        package => 'Foswiki::UI::Changes',
 
36
        function => 'changes',
 
37
        context => { changes => 1 },
 
38
    };
 
39
    $Foswiki::cfg{SwitchBoard}{edit} = {
 
40
        package => 'Foswiki::UI::Edit',
 
41
        function => 'edit',
 
42
        context => { edit => 1 },
 
43
    };
 
44
    $Foswiki::cfg{SwitchBoard}{login} = {
 
45
        package => undef,
 
46
        function => 'logon',
 
47
        context => { ( login => 1, logon => 1 ) },
 
48
    };
 
49
    $Foswiki::cfg{SwitchBoard}{logon} = {
 
50
        package => undef,
 
51
        function => 'logon',
 
52
        context => { ( login => 1, logon => 1 ) },
 
53
    };
 
54
    $Foswiki::cfg{SwitchBoard}{manage} = {
 
55
        package => 'Foswiki::UI::Manage',
 
56
        function => 'manage',
 
57
        context => { manage => 1 },
 
58
        allow => { POST => 1 },
 
59
    };
 
60
    $Foswiki::cfg{SwitchBoard}{oops} = {
 
61
        package => 'Foswiki::UI::Oops',
 
62
        function => 'oops_cgi',
 
63
        context => { oops => 1 },
 
64
    };
 
65
    $Foswiki::cfg{SwitchBoard}{preview} = {
 
66
        package => 'Foswiki::UI::Preview',
 
67
        function => 'preview',
 
68
        context => { preview => 1 },
 
69
    };
 
70
    $Foswiki::cfg{SwitchBoard}{rdiffauth} = {
 
71
        package => 'Foswiki::UI::RDiff',
 
72
        function => 'diff',
 
73
        context => { diff => 1 },
 
74
    };
 
75
    $Foswiki::cfg{SwitchBoard}{rdiff} = {
 
76
        package => 'Foswiki::UI::RDiff',
 
77
        function => 'diff',
 
78
        context => { diff => 1 },
 
79
    };
 
80
    $Foswiki::cfg{SwitchBoard}{register} = {
 
81
        package => 'Foswiki::UI::Register',
 
82
        function => 'register_cgi',
 
83
        context => { register => 1 },
 
84
        # method verify must allow GET; protect in Foswiki::UI::Register
 
85
        #allow => { POST => 1 },
 
86
    };
 
87
    $Foswiki::cfg{SwitchBoard}{rename} = {
 
88
        package => 'Foswiki::UI::Manage',
 
89
        function => 'rename',
 
90
        context => { rename => 1 },
 
91
        # Rename is 2 stage; protect in Foswiki::UI::Rename
 
92
        #allow => { POST => 1 },
 
93
    };
 
94
    $Foswiki::cfg{SwitchBoard}{resetpasswd} = {
 
95
        package => 'Foswiki::UI::Register',
 
96
        function => 'resetPassword',
 
97
        context => { resetpasswd => 1 },
 
98
        allow => { POST => 1 },
 
99
    };
 
100
    $Foswiki::cfg{SwitchBoard}{rest} = {
 
101
        package => 'Foswiki::UI::Rest',
 
102
        function => 'rest',
 
103
        context => { rest => 1 },
 
104
    };
 
105
    $Foswiki::cfg{SwitchBoard}{save} = {
 
106
        package => 'Foswiki::UI::Save',
 
107
        function => 'save',
 
108
        context => { save => 1 },
 
109
        allow => { POST => 1 },
 
110
    };
 
111
    $Foswiki::cfg{SwitchBoard}{search} = {
 
112
        package => 'Foswiki::UI::Search',
 
113
        function => 'search',
 
114
        context => { search => 1 },
 
115
    };
 
116
    $Foswiki::cfg{SwitchBoard}{statistics} = {
 
117
        package => 'Foswiki::UI::Statistics',
 
118
        function => 'statistics',
 
119
        context => { statistics => 1 },
 
120
    };
 
121
    $Foswiki::cfg{SwitchBoard}{upload} = {
 
122
        package => 'Foswiki::UI::Upload',
 
123
        function => 'upload',
 
124
        context => { upload => 1 },
 
125
        allow => { POST => 1 },
 
126
    };
 
127
    $Foswiki::cfg{SwitchBoard}{viewauth} = {
 
128
        package => 'Foswiki::UI::View',
 
129
        function => 'view',
 
130
        context => { view => 1 },
 
131
    };
 
132
    $Foswiki::cfg{SwitchBoard}{viewfile} = {
 
133
        package => 'Foswiki::UI::View',
 
134
        function => 'viewfile',
 
135
        context => { viewfile => 1 },
 
136
    };
 
137
    $Foswiki::cfg{SwitchBoard}{view} = {
 
138
        package => 'Foswiki::UI::View',
 
139
        function => 'view',
 
140
        context => { view => 1 },
 
141
    };
 
142
}
 
143
 
 
144
use Error qw(:try);
 
145
use Assert;
 
146
use CGI ();
 
147
 
 
148
use Foswiki                         ();
 
149
use Foswiki::Request                ();
 
150
use Foswiki::Response               ();
 
151
use Foswiki::OopsException          ();
 
152
use Foswiki::EngineException        ();
 
153
use Foswiki::ValidationException    ();
 
154
use Foswiki::AccessControlException ();
 
155
use Foswiki::Validation             ();
 
156
 
 
157
# Used to lazily load UI handler modules
 
158
our %isInitialized = ();
 
159
 
 
160
sub TRACE_PASSTHRU {
 
161
 
 
162
    # Change to a 1 to trace passthrough
 
163
    0;
 
164
}
 
165
 
 
166
=begin TML
 
167
 
 
168
---++ StaticMethod handleRequest($req) -> $res
 
169
 
 
170
Main coordinator of request-process-response cycle.
 
171
 
 
172
=cut
 
173
 
 
174
sub handleRequest {
 
175
    my $req = shift;
 
176
 
 
177
    my $res;
 
178
    my $dispatcher = $Foswiki::cfg{SwitchBoard}{ $req->action() };
 
179
    unless ( defined $dispatcher ) {
 
180
        $res = new Foswiki::Response();
 
181
        $res->header( -type => 'text/html', -status => '404' );
 
182
        my $html = CGI::start_html('404 Not Found');
 
183
        $html .= CGI::h1('Not Found');
 
184
        $html .=
 
185
          CGI::p( "The requested URL "
 
186
              . $req->uri
 
187
              . " was not found on this server." );
 
188
        $html .= CGI::end_html();
 
189
        $res->print($html);
 
190
        return $res;
 
191
    }
 
192
 
 
193
    if (ref($dispatcher) eq 'ARRAY') {
 
194
        # Old-style array entry in switchboard from a plugin
 
195
        my @array = @$dispatcher;
 
196
        $dispatcher = {
 
197
            package  => $array[0],
 
198
            function => $array[1],
 
199
            context  => $array[2],
 
200
        };
 
201
    }
 
202
 
 
203
    if ( $dispatcher->{package} && !$isInitialized{$dispatcher->{package}} ) {
 
204
        eval qq(use $dispatcher->{package});
 
205
        die $@ if $@;
 
206
        $isInitialized{$dispatcher->{package}} = 1;
 
207
    }
 
208
 
 
209
    my $sub;
 
210
    $sub = $dispatcher->{package} . '::' if $dispatcher->{package};
 
211
    $sub .= $dispatcher->{function};
 
212
 
 
213
    # Get the params cache from the path
 
214
    my $cache;
 
215
    my $path_info = $req->path_info();
 
216
    if ($path_info =~ s#/foswiki_redirect_cache/([a-f0-9]{32})$##) {
 
217
        $cache = $1;
 
218
        $req->path_info( $path_info );
 
219
    }
 
220
 
 
221
    if ( $cache ) {
 
222
 
 
223
        # Read cached post parameters
 
224
        my $passthruFilename =
 
225
          $Foswiki::cfg{WorkingDir} . '/tmp/passthru_' . $cache;
 
226
        if ( open( F, '<', $passthruFilename ) ) {
 
227
            local $/;
 
228
            if (TRACE_PASSTHRU) {
 
229
                print STDERR "Passthru: Loading cache for ", $req->url(),
 
230
                  '?', $req->query_string(), "\n";
 
231
                print STDERR <F>, "\n";
 
232
                close(F);
 
233
                open( F, '<' . $passthruFilename );
 
234
            }
 
235
            $req->load( \*F );
 
236
            close(F);
 
237
            unlink($passthruFilename);
 
238
            $req->delete('foswiki_redirect_cache');
 
239
            print STDERR "Passthru: Loaded and unlinked $passthruFilename\n"
 
240
              if TRACE_PASSTHRU;
 
241
 
 
242
            $req->method('POST');
 
243
        }
 
244
        else {
 
245
            print STDERR "Passthru: Could not find $passthruFilename\n"
 
246
              if TRACE_PASSTHRU;
 
247
        }
 
248
    }
 
249
    #print STDERR "INCOMING ".$req->method()." ".$req->url." -> ".$sub."\n";
 
250
    #print STDERR "Validation: ".($req->param('validation_key')||'no key')."\n";
 
251
    #require Data::Dumper;
 
252
    #print STDERR Data::Dumper->Dump([$req]);
 
253
    if ( UNIVERSAL::isa( $Foswiki::engine, 'Foswiki::Engine::CLI' ) ) {
 
254
        $dispatcher->{context}->{command_line} = 1;
 
255
    } elsif ( defined $req->method()
 
256
              && (
 
257
                ( defined $dispatcher->{allow}
 
258
                  && !$dispatcher->{allow}->{uc($req->method())} )
 
259
                ||
 
260
                ( defined $dispatcher->{deny}
 
261
                  && $dispatcher->{deny}->{uc($req->method())} )
 
262
              )
 
263
            ) {
 
264
        $res = new Foswiki::Response();
 
265
        $res->header( -type => 'text/html', -status => '405' );
 
266
        $res->print('Bad Request: '.uc($req->method()).' denied for '
 
267
                      .$req->action());
 
268
        return $res;
 
269
    }
 
270
    $res = _execute( $req, \&$sub, %{$dispatcher->{context}} );
 
271
    return $res;
 
272
}
 
273
 
 
274
=begin TML
 
275
 
 
276
---++ StaticMethod _execute($req, $sub, %initialContext) -> $res
 
277
 
 
278
Creates a Foswiki session object with %initalContext and calls
 
279
$sub method. Returns the Foswiki::Response object generated
 
280
 
 
281
=cut
 
282
 
 
283
sub _execute {
 
284
    my ( $req, $sub, %initialContext ) = @_;
 
285
 
 
286
    # DO NOT pass in $req->remoteUser here (even though it appears to be right)
 
287
    # because it may occlude the login manager.
 
288
    my $session = new Foswiki( undef, $req, \%initialContext );
 
289
    my $res = $session->{response};
 
290
 
 
291
    $res->pushHeader( 'X-FoswikiAction' => $req->action() );
 
292
    $res->pushHeader( 'X-FoswikiURI'    => $req->uri() );
 
293
 
 
294
    unless ( defined $session->{response}->status()
 
295
        && $session->{response}->status() =~ /^\s*3\d\d/ )
 
296
    {
 
297
        try {
 
298
            $session->{users}->{loginManager}->checkAccess();
 
299
            &$sub($session);
 
300
        }
 
301
        catch Foswiki::ValidationException with {
 
302
            my $query = $session->{request};
 
303
            # Redirect with passthrough so we don't lose the
 
304
            # original query params. We use the login script for
 
305
            # validation because it already has the correct criteria
 
306
            # in httpd.conf for Apache login.
 
307
            my $url     = $session->getScriptUrl(
 
308
                0, 'login', $session->{webName}, $session->{topicName} );
 
309
            $query->param( -name => 'action',
 
310
                           -value => 'validate' );
 
311
            $query->param( -name => 'origurl',
 
312
                           -value => $session->{request}->uri );
 
313
            $session->redirect( $url, 1 );    # with passthrough
 
314
        }
 
315
        catch Foswiki::AccessControlException with {
 
316
            my $e = shift;
 
317
            unless ( $session->{users}->{loginManager}->forceAuthentication() )
 
318
            {
 
319
 
 
320
                # Login manager did not want to authenticate, perhaps because
 
321
                # we are already authenticated.
 
322
                my $exception = new Foswiki::OopsException(
 
323
                    'accessdenied', status => 403,
 
324
                    web    => $e->{web},
 
325
                    topic  => $e->{topic},
 
326
                    def    => 'topic_access',
 
327
                    params => [ $e->{mode}, $e->{reason} ]
 
328
                );
 
329
 
 
330
                $exception->generate($session);
 
331
            }
 
332
        }
 
333
        catch Foswiki::OopsException with {
 
334
            shift->generate($session);
 
335
        }
 
336
        catch Error::Simple with {
 
337
            my $e = shift;
 
338
            $res = new Foswiki::Response;
 
339
            $res->header( -type => 'text/plain' );
 
340
            if (DEBUG) {
 
341
 
 
342
                # output the full message and stacktrace to the browser
 
343
                $res->print( $e->stringify() );
 
344
            }
 
345
            else {
 
346
                my $mess = $e->stringify();
 
347
                print STDERR $mess;
 
348
                $session->logger->log('warning',$mess);
 
349
 
 
350
                # tell the browser where to look for more help
 
351
                my $text =
 
352
'Foswiki detected an internal error - please check your Foswiki logs and webserver logs for more information.'
 
353
                  . "\n\n";
 
354
                $mess =~ s/ at .*$//s;
 
355
 
 
356
                # cut out pathnames from public announcement
 
357
                $mess =~ s#/[\w./]+#path#g;
 
358
                $text .= $mess;
 
359
                $res->print($text);
 
360
            }
 
361
        }
 
362
        catch Foswiki::EngineException with {
 
363
            my $e   = shift;
 
364
            my $res = $e->{response};
 
365
            unless ( defined $res ) {
 
366
                $res = new Foswiki::Response();
 
367
                $res->header( -type => 'text/html', -status => $e->{status} );
 
368
                my $html = CGI::start_html( $e->{status} . ' Bad Request' );
 
369
                $html .= CGI::h1('Bad Request');
 
370
                $html .= CGI::p( $e->{reason} );
 
371
                $html .= CGI::end_html();
 
372
                $res->print($html);
 
373
            }
 
374
            $Foswiki::engine->finalizeError($res);
 
375
            return $e->{status};
 
376
        }
 
377
        otherwise {
 
378
            $res = new Foswiki::Response;
 
379
            $res->header( -type => 'text/plain' );
 
380
            $res->print("Unspecified error");
 
381
        };
 
382
    }
 
383
 
 
384
    $session->finish();
 
385
    return $res;
 
386
}
 
387
 
 
388
=begin TML
 
389
 
 
390
---++ StaticMethod logon($session)
 
391
 
 
392
Handler to "logon" action.
 
393
   * =$session= is a Foswiki session object
 
394
 
 
395
=cut
 
396
 
 
397
sub logon {
 
398
    my $session = shift;
 
399
    if (($session->{request}->param('action') ||'') eq 'validate'
 
400
          # Force login if not recognisably authenticated
 
401
          && $session->inContext('authenticated')) {
 
402
        Foswiki::Validation::validate( $session );
 
403
    } else {
 
404
        $session->{users}->{loginManager}->login(
 
405
            $session->{request}, $session );
 
406
    }
 
407
}
 
408
 
 
409
=begin TML
 
410
 
 
411
---++ StaticMethod checkWebExists( $session, $web, $topic, $op )
 
412
 
 
413
Check if the web exists. If it doesn't, will throw an oops exception.
 
414
 $op is the user operation being performed.
 
415
 
 
416
=cut
 
417
 
 
418
sub checkWebExists {
 
419
    my ( $session, $webName, $topic, $op ) = @_;
 
420
    ASSERT( $session->isa('Foswiki') ) if DEBUG;
 
421
 
 
422
    unless ( $session->{store}->webExists($webName) ) {
 
423
        throw Foswiki::OopsException(
 
424
            'accessdenied', status => 403,
 
425
            def    => 'no_such_web',
 
426
            web    => $webName,
 
427
            topic  => $topic,
 
428
            params => [$op]
 
429
        );
 
430
    }
 
431
}
 
432
 
 
433
=begin TML
 
434
 
 
435
---++ StaticMethod topicExists( $session, $web, $topic, $op ) => boolean
 
436
 
 
437
Check if the given topic exists, throwing an OopsException
 
438
if it doesn't. $op is the user operation being performed.
 
439
 
 
440
=cut
 
441
 
 
442
sub checkTopicExists {
 
443
    my ( $session, $webName, $topic, $op ) = @_;
 
444
    ASSERT( $session->isa('Foswiki') ) if DEBUG;
 
445
 
 
446
    unless ( $session->{store}->topicExists( $webName, $topic ) ) {
 
447
        throw Foswiki::OopsException(
 
448
            'accessdenied', status => 403,
 
449
            def    => 'no_such_topic',
 
450
            web    => $webName,
 
451
            topic  => $topic,
 
452
            params => [$op]
 
453
        );
 
454
    }
 
455
}
 
456
 
 
457
=pod TML
 
458
 
 
459
---++ StaticMethod checkAccess( $web, $topic, $mode, $user )
 
460
 
 
461
Check if the given mode of access by the given user to the given
 
462
web.topic is permissible, throwing a Foswiki::OopsException if not.
 
463
 
 
464
=cut
 
465
 
 
466
sub checkAccess {
 
467
    my ( $session, $web, $topic, $mode, $user ) = @_;
 
468
    ASSERT( $session->isa('Foswiki') ) if DEBUG;
 
469
 
 
470
    unless (
 
471
        $session->security->checkAccessPermission(
 
472
            $mode, $user, undef, undef, $topic, $web
 
473
        )
 
474
      )
 
475
    {
 
476
        throw Foswiki::OopsException(
 
477
            'accessdenied', status => 403,
 
478
            def    => 'topic_access',
 
479
            web    => $web,
 
480
            topic  => $topic,
 
481
            params => [ $mode, $session->security->getReason() ]
 
482
        );
 
483
    }
 
484
}
 
485
 
 
486
=begin TML
 
487
 
 
488
---++ StaticMethod readTemplateTopic( $session, $theTopicName ) -> ( $meta, $text )
 
489
 
 
490
Read a topic from the Foswiki web, or if that fails from the current
 
491
web.
 
492
 
 
493
=cut
 
494
 
 
495
sub readTemplateTopic {
 
496
    my ( $session, $theTopicName ) = @_;
 
497
    ASSERT( $session->isa('Foswiki') ) if DEBUG;
 
498
 
 
499
    my $web = $Foswiki::cfg{SystemWebName};
 
500
    if ( $session->{store}->topicExists( $session->{webName}, $theTopicName ) )
 
501
    {
 
502
 
 
503
        # try to read from current web, if found
 
504
        $web = $session->{webName};
 
505
    }
 
506
    return $session->{store}
 
507
      ->readTopic( $session->{user}, $web, $theTopicName, undef );
 
508
}
 
509
 
 
510
=pod TML
 
511
 
 
512
---++ StaticMethod checkValidationKey( $session )
 
513
 
 
514
Check the validation key for the given action. Throws an exception
 
515
if the validation key isn't valid (handled in _execute(), above)
 
516
 
 
517
See Foswiki::Validation for more information.
 
518
 
 
519
=cut
 
520
 
 
521
sub checkValidationKey {
 
522
    my ($session) = @_;
 
523
 
 
524
    # Check the nonce before we do anything else
 
525
    my $nonce = $session->{request}->param('validation_key');
 
526
    $session->{request}->delete('validation_key');
 
527
    if ( !defined($nonce)
 
528
        || !Foswiki::Validation::isValidNonce( $session->getCGISession(),
 
529
            $nonce ) )
 
530
    {
 
531
        throw Foswiki::ValidationException();
 
532
    }
 
533
    if ( defined($nonce) ) {
 
534
 
 
535
        # Expire the nonce. If the user tries to use it again, they will
 
536
        # be prompted.
 
537
        Foswiki::Validation::expireValidationKeys(
 
538
            $session->getCGISession(),
 
539
            $Foswiki::cfg{Validation}{ExpireKeyOnUse} ? $nonce : undef );
 
540
    }
 
541
}
 
542
 
 
543
=begin TML
 
544
 
 
545
---++ StaticMethod run( $method, %context )
 
546
 
 
547
Supported for bin scripts that were written for Foswiki < 1.0. The parameters
 
548
are a function reference to the UI method to call and initial context.
 
549
 
 
550
In Foswiki >= 1.0 it should be replaced by a Config.spec entry such as:
 
551
 
 
552
# **PERL H**
 
553
# Bin script registration - do not modify
 
554
$Foswiki::cfg{SwitchBoard}{publish} = [ "Foswiki::Contrib::Publish", "publish", { publishing => 1 } ];
 
555
 
 
556
=cut
 
557
 
 
558
sub run {
 
559
    my ( $method, %context ) = @_;
 
560
    
 
561
    if ( UNIVERSAL::isa( $Foswiki::engine, 'Foswiki::Engine::CLI' ) ) {
 
562
        $context{command_line} = 1;
 
563
    }
 
564
    _execute( Foswiki::Request->new(), \&$method, %context );
 
565
}
 
566
 
 
567
1;
 
568
__DATA__
 
569
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
 
570
#
 
571
# Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
 
572
# are listed in the AUTHORS file in the root of this distribution.
 
573
# NOTE: Please extend that file, not this notice.
 
574
#
 
575
# Additional copyrights apply to some or all of the code in this
 
576
# file as follows:
 
577
#
 
578
# Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org
 
579
# and TWiki Contributors. All Rights Reserved. TWiki Contributors
 
580
# are listed in the AUTHORS file in the root of this distribution.
 
581
# Copyright (C) 2005 Martin at Cleaver.org
 
582
# Copyright (C) 2005-2007 TWiki Contributors
 
583
#
 
584
# and also based/inspired on Catalyst framework, whose Author is
 
585
# Sebastian Riedel. Refer to
 
586
#
 
587
# http://search.cpan.org/~mramberg/Catalyst-Runtime-5.7010/lib/Catalyst.pm
 
588
#
 
589
# for more credit and liscence details.
 
590
#
 
591
# This program is free software; you can redistribute it and/or
 
592
# modify it under the terms of the GNU General Public License
 
593
# as published by the Free Software Foundation; either version 2
 
594
# of the License, or (at your option) any later version. For
 
595
# more details read LICENSE in the root of this distribution.
 
596
#
 
597
# This program is distributed in the hope that it will be useful,
 
598
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
599
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
600
#
 
601
# As per the GPL, removal of this notice is prohibited.