/
App.pm
3941 lines (3151 loc) · 115 KB
/
App.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# See bottom of file for license and copyright information
package Foswiki::App;
=begin TML
---+!! Class Foswiki::App
The core class of the project responsible for low-level and code glue
functionality.
=cut
use constant TRACE_REQUEST => 0;
use Assert;
use Cwd;
use Try::Tiny;
use Storable qw(dclone);
# SMELL CGI is only used for generating a simple error page using HTML tags
# shortcut functions. Must be replaced with something more reasonable.
use CGI ();
use Compress::Zlib;
use Foswiki::Extensions;
use Foswiki::FeatureSet qw(:all);
use Foswiki::Engine;
use Foswiki::Templates;
use Foswiki::Exception;
use Foswiki::Sandbox;
use Foswiki::WebFilter;
use Foswiki::Time;
use Foswiki qw(%regex load_package load_class isTrue);
use Foswiki::Class qw(callbacks);
extends qw(Foswiki::Object);
callback_names qw(handleRequestException postConfig);
features_provided
MOO => [
2.99, undef, undef,
-proposal => 'ImproveOOModel',
-desc => 'Support for Moo-based OO core',
],
PARA_INDENT => [ undef, undef, undef ],
PREF_SET_URLS => [ undef, undef, undef ],
PSGI => [ 2.99, undef, undef, -desc => 'PSGI support', ],
UNICODE => [ 2.0, undef, undef, -desc => 'Unicode core', ],
OOSPECS => [
2.99, undef, undef,
-desc => "Perl Specs",
-proposal => "OOConfigSpecsFormat",
],
;
has access => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
isa =>
Foswiki::Object::isaCLASS( 'access', 'Foswiki::Access', noUndef => 1, ),
default => sub {
my $this = shift;
my $accessClass = $this->cfg->data->{AccessControl}
|| 'Foswiki::Access::TopicACLAccess';
return $this->create($accessClass);
},
);
has attach => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub { $_[0]->create('Foswiki::Attach'); },
);
has cache => (
is => 'rw',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
my $this = shift;
my $cfg = $this->cfg;
if ( $cfg->data->{Cache}{Enabled}
&& $cfg->data->{Cache}{Implementation} )
{
load_class( $cfg->data->{Cache}{Implementation} );
ASSERT( !$@, $@ ) if DEBUG;
return $this->create( $cfg->data->{Cache}{Implementation} );
}
return undef;
},
);
=begin TML
---++ ObjectAttribute cfg
This attribute stores application configuration object - a =Foswiki::Config=
instance.
=cut
has cfg => (
is => 'rw',
lazy => 1,
predicate => 1,
clearer => 1,
builder => 'prepareCfg',
isa => Foswiki::Object::isaCLASS( 'cfg', 'Foswiki::Config', noUndef => 1, ),
);
has env => (
is => 'rw',
required => 1,
);
has extensions => (
is => 'ro',
lazy => 1,
predicate => 1,
clearer => 1,
builder => 'prepareExtensions',
);
has forms => (
is => 'ro',
lazy => 1,
clearer => 1,
default => sub { {} },
);
has logger => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
my $this = shift;
my $cfg = $this->cfg;
my $loggerClass = 'Foswiki::Logger';
if ( $cfg->data->{Log}{Implementation} ne 'none' ) {
$loggerClass = $cfg->data->{Log}{Implementation};
}
return $this->create($loggerClass);
},
);
has engine => (
is => 'rw',
lazy => 1,
predicate => 1,
builder => 'prepareEngine',
isa =>
Foswiki::Object::isaCLASS( 'engine', 'Foswiki::Engine', noUndef => 1, ),
);
# Heap is to be used for data persistent over session lifetime.
# Usage: $sessiom->heap->{key} = <your data>;
has heap => (
is => 'rw',
clearer => 1,
lazy => 1,
default => sub { {} },
);
has i18n => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
# language information; must be loaded after
# *all possible preferences sources* are available
$_[0]->create('Foswiki::I18N');
},
);
has net => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub { return $_[0]->create('Foswiki::Net'); },
);
has plugins => (
is => 'rw',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub { return $_[0]->create('Foswiki::Plugins'); },
);
has prefs => (
is => 'ro',
lazy => 1,
predicate => 1,
clearer => 1,
builder => 'preparePrefs',
);
has renderer => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
return $_[0]->create('Foswiki::Render');
},
);
has request => (
is => 'rw',
lazy => 1,
builder => 'prepareRequest',
isa =>
Foswiki::Object::isaCLASS( 'request', 'Foswiki::Request', noUndef => 1, ),
);
has response => (
is => 'rw',
lazy => 1,
clearer => 1,
default => sub { $_[0]->create('Foswiki::Response') },
isa => Foswiki::Object::isaCLASS(
'response', 'Foswiki::Response', noUndef => 1,
),
);
has search => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
return $_[0]->create('Foswiki::Search');
},
);
has store => (
is => 'rw',
lazy => 1,
clearer => 1,
predicate => 1,
isa =>
Foswiki::Object::isaCLASS( 'store', 'Foswiki::Store', noUndef => 1, ),
default => sub {
my $storeClass = $Foswiki::cfg{Store}{Implementation}
|| 'Foswiki::Store::PlainFile';
ASSERT( $storeClass, "Foswiki::store base class is not defined" )
if DEBUG;
return $_[0]->create($storeClass);
},
);
has templates => (
is => 'ro',
lazy => 1,
predicate => 1,
clearer => 1,
default => sub { return $_[0]->create('Foswiki::Templates'); },
);
has macros => (
is => 'rw',
lazy => 1,
default => sub { return $_[0]->create('Foswiki::Macros'); },
isa =>
Foswiki::Object::isaCLASS( 'macros', 'Foswiki::Macros', noUndef => 1, ),
);
has context => (
is => 'rw',
lazy => 1,
clearer => 1,
builder => 'prepareContext',
);
has ui => (
is => 'rw',
lazy => 1,
default => sub {
return $_[0]->create('Foswiki::UI');
},
);
has remoteUser => (
is => 'rw',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub {
my $this = shift;
return $this->users->loadSession( $this->engine->user );
},
);
# XXX user attribute must have complicated options like lazy or builder.
# Otherwise there is high risk of Perl or Moo optimizing RO-calls to this
# attribute leading to corrupt stack if passed over as a method attribute and
# then set to a different value deep in the call stack. For example if there is
# a call: $app->addUserToGroup($app->user, ...) - it may cause corrupt stack
# because TopicUserMapping temporarily escalates current user to Admin
# privileges.
has user => (
is => 'rw',
lazy => 1,
builder => 'prepareUser',
);
has users => (
is => 'rw',
lazy => 1,
predicate => 1,
clearer => 1,
default => sub { return $_[0]->create('Foswiki::Users'); },
);
has zones => (
is => 'ro',
lazy => 1,
clearer => 1,
predicate => 1,
default => sub { return $_[0]->create('Foswiki::Render::Zones'); },
);
has _dispatcherAttrs => (
is => 'rw',
isa => Foswiki::Object::isaHASH( '_dispatcherAttrs', noUndef => 1 ),
);
# List of system messages to be displayed to user. Could be used to display non-critical errors or important warnings.
has system_messages => (
is => 'rw',
lazy => 1,
clearer => 1,
default => sub { [] },
isa => Foswiki::Object::isaARRAY( 'system_messages', noUndef => 1, ),
);
has inUnitTestMode => (
is => 'rw',
lazy => 1,
default => sub {
my $this = shift;
my $inTest = $Foswiki::inUnitTestMode
|| ( $this->has_engine && ref( $this->engine ) =~ /::Test$/ );
return $inTest;
},
);
# App init stage.
has initStage => ( is => 'rw', );
=begin TML
---++ ClassMethod new([%parameters])
The following keys could be defined in =%parameters= hash:
|*Key*|*Type*|*Description*|
|=env=|hashref|Environment hash such as shell environment or PSGI env|
=cut
sub BUILD {
my $this = shift;
my $params = shift;
$Foswiki::app = $this;
for my $stNum ( 1 .. 2 ) {
$this->clear_cfg;
$this->initStage( 'readConfig' . $stNum );
unless ( $this->cfg->data->{isVALID} ) {
$this->cfg->bootstrapSystemSettings;
}
$this->initStage( 'loadExtensions' . $stNum );
# Reload extensions based on the configuration information.
$this->clear_extensions;
$this->extensions->initialize;
}
$this->initStage('postConfig');
$this->callback('postConfig');
my $cfgData = $this->cfg->data;
if ( $cfgData->{Store}{overrideUmask} && $cfgData->{OS} ne 'WINDOWS' ) {
# Note: The addition of zero is required to force dirPermission and filePermission
# to be numeric. Without the additition, certain values of the permissions cause
# runtime errors about illegal characters in subtraction. "and" with 777 to prevent
# sticky-bits from breaking the umask.
my $oldUmask = umask(
(
oct(777) - (
(
$cfgData->{Store}{dirPermission} + 0 |
$cfgData->{Store}{filePermission} + 0
)
) & oct(777)
)
);
#my $umask = sprintf('%04o', umask() );
#$oldUmask = sprintf('%04o', $oldUmask );
#my $dirPerm = sprintf('%04o', $Foswiki::cfg{Store}{dirPermission}+0 );
#my $filePerm = sprintf('%04o', $Foswiki::cfg{Store}{filePermission}+0 );
#print STDERR " ENGINE changes $oldUmask to $umask from $dirPerm and $filePerm \n";
}
# Enforce some shell environment variables.
# SMELL Would it be tolerated in PSGI?
$CGI::TMPDIRECTORY = $ENV{TMPDIR} = $ENV{TEMP} = $ENV{TMP} =
$this->cfg->data->{TempfileDir};
# Make %ENV safer, preventing hijack of the search path. The
# environment is set per-query, so this can't be done in a BEGIN.
# This MUST be done before any external programs are run via Sandbox.
# or it will fail with taint errors. See Item13237
if ( defined $cfgData->{SafeEnvPath} ) {
$ENV{PATH} = $cfgData->{SafeEnvPath};
}
else {
# Default $ENV{PATH} must be untainted because
# Foswiki may be run with the -T flag.
# SMELL: how can we validate the PATH?
# Configure now warns, suppress the broadcast warning.
# $this->systemMessage(
#"Unsafe shell variable PATH is used, consider setting SafeEnvPath configuration parameter."
# );
$ENV{PATH} = Foswiki::Sandbox::untaintUnchecked( $ENV{PATH} );
}
delete @ENV{qw( IFS CDPATH ENV BASH_ENV )};
# TODO It's not clear yet as how to deal with logger configuration - see Foswiki::BUILDARGS().
unless ( defined $this->engine ) {
Foswiki::Exception::Fatal->throw( text => "Cannot initialize engine" );
}
$this->_prepareDispatcher;
$this->_checkBootstrapStage2;
# Override user to be admin if no configuration exists.
# Do this really early, so that later changes in isBOOTSTRAPPING can't
# change Foswiki's behavior.
if ( $cfgData->{isBOOTSTRAPPING} ) {
$this->engine->user('admin');
}
else {
my $plogin = $this->plugins->load;
$this->engine->user($plogin) if $plogin;
}
$this->user( $this->users->initialiseUser( $this->remoteUser ) );
# Read preferences which may depend on user being authenticated.
$this->_readPrefs;
}
sub DEMOLISH {
my $this = shift;
my ($in_global) = @_;
# Make sure not to do this if incomplete initialization happened or we're
# doomed for "(in cleanup)" messages.
# Skip it over global destruction stage too.
$this->users->loginManager->complete
if !$in_global
&& $this->has_users
&& $this->users
&& $this->users->has_loginManager
&& $this->users->loginManager;
}
=begin TML
---++ StaticMethod run([%parameters])
Starts application, prepares and initiates request processing. The following
keys could be defined in =%parameters= hash:
|*Key*|*Type*|*Description*|
|=env=|hashref|Environment hash such as shell environment or PSGI env|
=cut
sub run {
my $class = shift;
my %params = @_;
# Do nice in shared code environment, localize ALL request-related globals.
local $Foswiki::app;
local %Foswiki::cfg;
# Before localizing shell environment we need to preserve and restore it.
local %ENV = %ENV;
my ( $app, $rc );
# We use shell environment by default. PSGI would supply its own env
# hashref. Because PSGI env is not the same as shell env we must clone the
# latter in order to avoid any side effects related to situations when
# changes to the env hashref are gettin' translated back onto the shell env.
$params{env} //= dclone( \%ENV );
# Use current working dir for fetching the initial setlib.cfg
$params{env}{PWD} //= getcwd;
try {
local $SIG{__DIE__} = sub {
# Somehow overriding of __DIE__ clashes with remote perl debugger in
# Komodo unless we die again instantly.
die $_[0] if (caller)[0] =~ /^DB::/;
Foswiki::Exception::Fatal->rethrow( $_[0] );
};
local $SIG{__WARN__} = sub {
Foswiki::Exception::Fatal->rethrow( $_[0] );
}
if DEBUG;
$app = $class->new(%params);
$rc = $app->handleRequest;
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
if ( defined $app && defined $app->logger ) {
$app->logger->log( 'error', $e->stringify, );
}
my $errStr = Foswiki::Exception::errorStr($e);
# Low-level report of errors to user.
if ( defined $app && $app->has_engine ) {
$errStr = '<pre>' . Foswiki::entityEncode($errStr) . '</pre>';
# Send error output to user using the initialized engine.
$rc = $app->engine->finalizeReturn(
[
500,
[
'Content-Type' => 'text/html; charset=utf-8',
'Content-Length' => length($errStr),
],
[$errStr]
]
);
}
else {
# Propagade the error using the most primitive way.
die $errStr;
}
};
return $rc;
}
sub handleRequest {
my $this = shift;
my $req = $this->request;
my $res = $this->response;
my $rc;
try {
$this->_checkTickle;
$this->_checkReqCache;
if (TRACE_REQUEST) {
print STDERR "INCOMING "
. $req->method() . " "
. $req->url . " -> "
. $this->_dispatcherAttrs->{method} . "\n";
print STDERR "validation_key: "
. ( $req->param('validation_key') || 'no key' ) . "\n";
#require Data::Dumper;
#print STDERR Data::Dumper->Dump([$req]);
}
$this->_checkActionAccess;
# Set both isadmin and authenticated contexts. If the current user is
# admin, then they either authenticated, or we are in bootstrap.
if ( $this->users->isAdmin( $this->user ) ) {
$this->context->{authenticated} = 1;
$this->context->{isadmin} = 1;
}
# Finish plugin initialization - register handlers
$this->plugins->enable();
my $method = $this->_dispatcherAttrs->{method};
$this->ui->$method;
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
$this->callback( 'handleRequestException', { exception => $e, } );
# SMELL TODO At this stage we shall be able to display any exception in
# a pretty HTMLized way if engine is HTTPCompliant. Rethrowing of an
# exception is just a temporary stub.
if ( $e->isa('Foswiki::AccessControlException') ) {
unless ( $this->users->getLoginManager->forceAuthentication ) {
# Login manager did not want to authenticate, perhaps because
# we are already authenticated.
my $exception = $this->create(
'Foswiki::OopsException',
template => 'accessdenied',
status => 403,
web => $e->web,
topic => $e->topic,
def => 'topic_access',
params => [ $e->mode, $e->reason ]
);
$exception->generate;
}
}
elsif ( $e->isa('Foswiki::OopsException') ) {
$e->generate;
}
elsif ( $e->isa('Foswiki::EngineException') ) {
$res->header( -type => 'text/html', );
$res->status( $e->status );
my $html = CGI::start_html( $e->status . ' Bad Request' );
$html .= CGI::h1( {}, 'Bad Request' );
$html .= CGI::p( {}, $e->reason );
$html .= CGI::end_html();
$res->print( Foswiki::encode_utf8($html) );
}
else {
Foswiki::Exception::Fatal->rethrow($e);
}
};
my $return = $res->as_array;
$rc = $this->engine->finalizeReturn($return);
# Clean up sessions before we finish.
# SMELL Not sure if it really belongs here but being called in DEMOLISH()
# it fails because users attribute gets destroyed by the time.
$this->users->loginManager->complete;
return $rc;
}
=begin TML
---++ ObjectMethod create($className, %initArgs)
Creates a new object of class =$className=. This method must always be used for
creating new objects of classes rooted on =Foswiki::Object=.
This methods does the following:
1. Loads =$className= module.
1. Maps =$className= into a replacement class if an extension registered for class overriding.
1. Adds =app= parameter key pointing to the application object to class constructor arguments.
=cut
sub create {
my $this = shift;
my $class = shift;
$class = ref($class) || $class;
Foswiki::load_class($class);
if ( $this->has_extensions ) {
# Avoid referring to extensions if we're on very early stages of the app
# existance.
$class = $this->extensions->mapClass($class);
}
my $object;
if ( $class->isa('Foswiki::Object') ) {
$object = $class->new( app => $this, @_ );
}
else {
$object = $class->new(@_);
}
return $object;
}
=begin TML
---++ ObjectMethod deepWebList($filter, $web) -> @list
Deep list subwebs of the named web. $filter is a =Foswiki::WebFilter=
object that is used to filter the list. The listing of subwebs is
dependent on $Foswiki::cfg{EnableHierarchicalWebs} being true.
Webs are returned as absolute web pathnames.
=cut
sub deepWebList {
my ( $this, $filter, $rootWeb ) = @_;
my @list;
my $webObject = $this->create( 'Foswiki::Meta', web => $rootWeb );
my $it = $webObject->eachWeb( $this->cfg->data->{EnableHierarchicalWebs} );
return $it->all() unless $filter;
while ( $it->hasNext() ) {
my $w = $rootWeb || '';
$w .= '/' if $w;
$w .= $it->next();
if ( $filter->ok( $this, $w ) ) {
push( @list, $w );
}
}
return @list;
}
=begin TML
---++ ObjectMethod enterContext( $id, $val )
Add the context id $id into the set of active contexts. The $val
can be anything you like, but should always evaluate to boolean
TRUE.
An example of the use of contexts is in the use of tag
expansion. The commonTagsHandler in plugins is called every
time tags need to be expanded, and the context of that expansion
is signalled by the expanding module using a context id. So the
forms module adds the context id "form" before invoking common
tags expansion.
Contexts are not just useful for tag expansion; they are also
relevant when rendering.
Contexts are intended for use mainly by plugins. Core modules can
use $session->inContext( $id ) to determine if a context is active.
=cut
sub enterContext {
my ( $this, $id, $val ) = @_;
$val ||= 1;
$this->context->{$id} = $val;
}
=begin TML
---++ ObjectMethod leaveContext( $id )
Remove the context id $id from the set of active contexts.
(see =enterContext= for more information on contexts)
=cut
sub leaveContext {
my ( $this, $id ) = @_;
my $res = $this->context->{$id};
delete $this->context->{$id};
return $res;
}
=begin TML
---++ ObjectMethod inContext( $id )
Return the value for the given context id
(see =enterContext= for more information on contexts)
=cut
sub inContext {
my ( $this, $id ) = @_;
return $this->context->{$id};
}
=begin TML
---++ ObjectMethod inlineAlert($template, $def, ... ) -> $string
Format an error for inline inclusion in rendered output. The message string
is obtained from the template 'oops'.$template, and the DEF $def is
selected. The parameters (...) are used to populate %PARAM1%..%PARAMn%
=cut
sub inlineAlert {
my $this = shift;
my $template = shift;
my $def = shift;
my $req = $this->request;
# web and topic can be anything; they are not used
my $topicObject = $this->create(
'Foswiki::Meta',
web => $req->web,
topic => $req->topic,
);
my $text = $this->templates->readTemplate( 'oops' . $template );
if ($text) {
my $blah = $this->templates->expandTemplate($def);
$text =~ s/%INSTANTIATE%/$blah/;
$text = $topicObject->expandMacros($text);
my $n = 1;
while ( defined( my $param = shift ) ) {
$text =~ s/%PARAM$n%/$param/g;
$n++;
}
# Suppress missing params
$text =~ s/%PARAM\d+%//g;
# Suppress missing params
$text =~ s/%PARAM\d+%//g;
}
else {
# Error in the template system.
$text = $topicObject->renderTML(<<MESSAGE);
---+ Foswiki Installation Error
Template 'oops$template' not found or returned no text, expanding $def.
Check your configuration settings for {TemplateDir} and {TemplatePath}
or check for syntax errors in templates, or a missing TMPL:END.
MESSAGE
}
return $text;
}
=begin TML
---++ ObjectMethod redirect( $url, $passthrough, $status )
* $url - url or topic to redirect to
* $passthrough - (optional) parameter to pass through current query
parameters (see below)
* $status - HTTP status code (30x) to redirect with. Defaults to 302.
Redirects the request to =$url=, *unless*
1 It is overridden by a plugin declaring a =redirectCgiQueryHandler=
(a dangerous, deprecated handler!)
1 =$session->{request}= is =undef=
Thus a redirect is only generated when in a CGI context.
Normally this method will ignore parameters to the current query. Sometimes,
for example when redirecting to a login page during authentication (and then
again from the login page to the original requested URL), you want to make
sure all parameters are passed on, and for this $passthrough should be set to
true. In this case it will pass all parameters that were passed to the
current query on to the redirect target. If the request_method for the
current query was GET, then all parameters will be passed by encoding them
in the URL (after ?). If the request_method was POST, then there is a risk the
URL would be too big for the receiver, so it caches the form data and passes
over a cache reference in the redirect GET.
NOTE: Passthrough is only meaningful if the redirect target is on the same
server.
=cut
sub redirect {
my $this = shift;
my ( $url, $passthru, $status ) = @_;
ASSERT( defined $url ) if DEBUG;
my $req = $this->request;
( $url, my $anchor ) = Foswiki::splitAnchorFromUrl($url);
if ( $passthru && defined $req->method() ) {
my $existing = '';
if ( $url =~ s/\?(.*)$// ) {
$existing = $1; # implicit untaint OK; recombined later
}
if ( uc( $req->method() ) eq 'POST' ) {
# Redirecting from a post to a get
my $cache = $req->cacheQuery;
if ($cache) {
if ( $url eq '/' ) {
$url = $this->cfg->getScriptUrl( 1, 'view' );
}
$url .= $cache;
}
}
else {
# Redirecting a get to a get; no need to use passthru
if ( $req->query_string() ) {
$url .= '?' . $req->query_string();
}
if ($existing) {
if ( $url =~ m/\?/ ) {
$url .= ';';
}
else {
$url .= '?';
}
$url .= $existing;
}
}
}
# prevent phishing by only allowing redirect to configured host
# do this check as late as possible to catch _any_ last minute hacks
# TODO: this should really use URI
if ( !$this->_isRedirectSafe($url) ) {
# goto oops if URL is trying to take us somewhere dangerous
$url = $this->cfg->getScriptUrl(
1, 'oops',
$this->request->web || $Foswiki::cfg{UsersWebName},
$this->request->topic || $Foswiki::cfg{HomeTopicName},
template => 'oopsredirectdenied',
def => 'redirect_denied',
param1 => "$url",
param2 => "$Foswiki::cfg{DefaultUrlHost}",
);
}
$url .= $anchor if $anchor;
# Dangerous, deprecated handler! Might work, probably won't.
return
if (
$this->plugins->dispatch(
'redirectCgiQueryHandler', $this->response, $url
)
);
$url = $this->users->getLoginManager->rewriteRedirectUrl($url);
# Foswiki::Response::redirect doesn't automatically pass on the cookies
# for us, so we have to do it explicitly; otherwise the session cookie
# won't get passed on.
$this->response->redirect(
-url => $url,
-cookies => $this->response->cookies,
-status => $status,
);
}
=begin TML
---++ ObjectMethod redirectto($url) -> $url
If the CGI parameter 'redirectto' is present on the query, then will validate
that it is a legal redirection target (url or topic name). If 'redirectto'
is not present on the query, performs the same steps on $url.
Returns undef if the target is not valid, and the target URL otherwise.
=cut
sub redirectto {
my ( $this, $url ) = @_;
my $req = $this->request;
my $redirecturl = $req->param('redirectto');
$redirecturl = $url unless $redirecturl;
return unless $redirecturl;
if ( $redirecturl =~ m#^$regex{linkProtocolPattern}://# ) {
# assuming URL
return $redirecturl if $this->_isRedirectSafe($redirecturl);
return;
}
my @attrs = ();
# capture anchor
if ( $redirecturl =~ s/#(.*)// ) {
push( @attrs, '#' => $1 );
}
# capture params
if ( $redirecturl =~ s/\?(.*)// ) {
push( @attrs, map { split( '=', $_, 2 ) } split( /[;&]/, $1 ) );
}
# assuming 'web.topic' or 'topic'
my ( $w, $t ) = $req->normalizeWebTopicName( $req->web, $redirecturl );
return $this->cfg->getScriptUrl( 0, 'view', $w, $t, @attrs );
}
=begin TML
---++ ObjectMethod satisfiedByCache( $action, $web, $topic ) -> $boolean
Try and satisfy the current request for the given web.topic from the cache, given
the current action (view, edit, rest etc).
If the action is satisfied, the cache content is written to the output and
true is returned. Otherwise ntohing is written, and false is returned.
Designed for calling from Foswiki::UI::*
=cut
sub satisfiedByCache {
my ( $this, $action, $web, $topic ) = @_;