-
Notifications
You must be signed in to change notification settings - Fork 275
/
LiftRules.scala
2297 lines (1940 loc) · 80.1 KB
/
LiftRules.scala
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
/*
* Copyright 2007-2015 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.liftweb
package http
import common._
import util._
import util.Helpers._
import sitemap._
import http.js.JSArtifacts
import http.js.jquery._
import http.provider._
import js._
import JE._
import JsCmds._
import auth._
import json._
import scala.xml._
import java.util.{Locale, TimeZone, ResourceBundle, Date}
import java.io.{InputStream, ByteArrayOutputStream, BufferedReader, StringReader}
import java.util.concurrent.{ConcurrentHashMap => CHash}
import scala.reflect.Manifest
import java.util.concurrent.atomic.AtomicInteger
import net.liftweb.actor.{LiftActor, LAFuture}
class LiftRulesJBridge {
def liftRules: LiftRules = LiftRules
}
sealed trait LiftRulesMocker {
def realInstance: LiftRules
}
object LiftRulesMocker {
implicit def toLiftRules(in: LiftRulesMocker): LiftRules = in.realInstance
/**
* In Dev and Test mode, there's an option to stuff another LiftRules
* instance in here and use that one for mocking
*/
object devTestLiftRulesInstance extends ThreadGlobal[LiftRules]
/**
* This function, in Test and Dev mode will vend the instance of LiftRules.
* If there is an instance set in devTestLiftRulesInstance, that instance
* will be used, otherwise the global instance in LiftRules.prodInstance
* will be used.
*/
@volatile var calcLiftRulesInstance: () => LiftRules =
() => devTestLiftRulesInstance.box.openOr( LiftRules.prodInstance)
}
/**
* The data structure that contains information to determine if the
* request should be treated as a stateful or stateless request
*/
final case class StatelessReqTest(path: List[String], httpReq: HTTPRequest)
/**
* Sometimes we're going to have to surface more data from one of these requests
* than we might like (for example, extra info about continuing the computation on
* a different thread), so we'll start off right by having an Answer trait
* that will have some subclasses and implicit conversions
*/
sealed trait DataAttributeProcessorAnswer
/**
* The companion object that has the implicit conversions
*/
object DataAttributeProcessorAnswer {
implicit def nodesToAnswer(in: NodeSeq): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerNodes(in)
implicit def nodeFuncToAnswer(in: () => NodeSeq): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerFork(in)
implicit def nodeFutureToAnswer(in: LAFuture[NodeSeq]): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerFuture(in)
implicit def setNodeToAnswer(in: Seq[Node]): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerNodes(in)
}
/**
* Yep... just a bunch of nodes.
* @param nodes
*/
final case class DataAttributeProcessorAnswerNodes(nodes: NodeSeq) extends DataAttributeProcessorAnswer
/**
* A function that returns a bunch of nodes... run it on a different thread
* @param nodeFunc
*/
final case class DataAttributeProcessorAnswerFork(nodeFunc: () => NodeSeq) extends DataAttributeProcessorAnswer
/**
* A future that returns nodes... run them on a different thread
* @param nodeFuture the future of the NodeSeq
*/
final case class DataAttributeProcessorAnswerFuture(nodeFuture: LAFuture[NodeSeq]) extends DataAttributeProcessorAnswer
/**
* The Lift configuration singleton
*/
object LiftRules extends LiftRulesMocker {
lazy val prodInstance: LiftRules = new LiftRules()
private[this] val devOrTest = Props.devMode || Props.testMode
/**
* Get the real instance of LiftRules
*/
def realInstance: LiftRules = if (devOrTest) {
LiftRulesMocker.calcLiftRulesInstance()
} else prodInstance
type DispatchPF = PartialFunction[Req, () => Box[LiftResponse]];
/**
* A partial function that allows processing of any attribute on an Elem
* if the attribute begins with "data-"
*/
type DataAttributeProcessor = PartialFunction[(String, String, Elem, LiftSession), DataAttributeProcessorAnswer]
/**
* The pattern/PartialFunction for matching tags in Lift
*/
type TagProcessor = PartialFunction[(String, Elem, LiftSession), DataAttributeProcessorAnswer]
/**
* The test between the path of a request and whether that path
* should result in stateless servicing of that path
*/
type StatelessTestPF = PartialFunction[List[String], Boolean]
/**
* The test between the path of a request, the HTTP request, and whether that path
* should result in stateless servicing of that path
*/
type StatelessReqTestPF = PartialFunction[StatelessReqTest, Boolean]
type RewritePF = PartialFunction[RewriteRequest, RewriteResponse]
type SnippetPF = PartialFunction[List[String], NodeSeq => NodeSeq]
type LiftTagPF = PartialFunction[(String, Elem, MetaData, NodeSeq, String), NodeSeq]
type URINotFoundPF = PartialFunction[(Req, Box[Failure]), NotFound]
type URLDecoratorPF = PartialFunction[String, String]
type SnippetDispatchPF = PartialFunction[String, DispatchSnippet]
type ViewDispatchPF = PartialFunction[List[String], Either[() => Box[NodeSeq], LiftView]]
type HttpAuthProtectedResourcePF = PartialFunction[Req, Box[Role]]
type ExceptionHandlerPF = PartialFunction[(Props.RunModes.Value, Req, Throwable), LiftResponse]
type ResourceBundleFactoryPF = PartialFunction[(String, Locale), ResourceBundle]
type SplitSuffixPF = PartialFunction[List[String], (List[String], String)]
type CometCreationPF = PartialFunction[CometCreationInfo, LiftCometActor]
/**
* A partial function that allows the application to define requests that should be
* handled by lift rather than the default handler
*/
type LiftRequestPF = PartialFunction[Req, Boolean]
/*
private[this] var _doneBoot = false
private[http] def doneBoot = _doneBoot
private[http] def doneBoot_=(in: Boolean) {_doneBoot = in}
*/
/**
* Holds the failure information when a snippet can not be executed.
*/
case class SnippetFailure(page: String, typeName: Box[String], failure: SnippetFailures.Value)
object SnippetFailures extends Enumeration {
val NoTypeDefined = Value(1, "No Type Defined")
val ClassNotFound = Value(2, "Class Not Found")
val StatefulDispatchNotMatched = Value(3, "Stateful Snippet: Dispatch Not Matched")
val MethodNotFound = Value(4, "Method Not Found")
val NoNameSpecified = Value(5, "No Snippet Name Specified")
val InstantiationException = Value(6, "Exception During Snippet Instantiation")
val DispatchSnippetNotMatched = Value(7, "Dispatch Snippet: Dispatch Not Matched")
val StateInStateless = Value(8, "Access to Lift's statefull features from Stateless mode")
val CometTimeout = Value(9, "Comet Component did not response to requests")
val CometNotFound = Value(10, "Comet Component not found")
val ExecutionFailure = Value(11, "Execution Failure")
val NoCometType = Value(12, "Comet Type not specified")
}
def defaultFuncNameGenerator(runMode: Props.RunModes.Value): () => String = {
runMode match {
case Props.RunModes.Test => S.generateTestFuncName _
case _ => S.generateFuncName _
}
}
}
/**
* LiftRules is the global object that holds all of Lift's configuration.
*/
class LiftRules() extends Factory with FormVendor with LazyLoggable {
import LiftRules._
private var _doneBoot = false
/**
* Does the LiftRules instance think it's done booting?
*/
def doneBoot = _doneBoot
def noticesContainerId = "lift__noticesContainer__"
/**
* If you want to make the Lift inactivity timeout shorter than
* the container inactivity timeout, set the inactivity timeout here
*/
val sessionInactivityTimeout = new FactoryMaker[Box[Long]](Empty){}
/**
* The function that converts a JValue to
* a String.
*
* By default, use prettyRender for dev mode and compactRender for other modes.
*/
val jsonOutputConverter = new FactoryMaker[JsonAST.JValue => String]({
import json.{prettyRender, compactRender}
if (Props.devMode) {
prettyRender _
} else {
compactRender _
}
}){}
/**
* Set the default fadeout mechanism for Lift notices. Thus you provide a function that take a NoticeType.Value
* and decide the duration after which the fade out will start and the actual fadeout time. This is applicable
* for general notices (not associated with id-s) regardless if they are set for the page rendering, ajax
* response or Comet response.
*/
val noticesAutoFadeOut = new FactoryMaker[(NoticeType.Value) => Box[(TimeSpan, TimeSpan)]]((notice : NoticeType.Value) => Empty){}
/**
* Use this to apply various effects to the notices. The user function receives the NoticeType
* and the id of the element containing the specific notice. Thus it is the function's responsibility to form
* the javascript code for the visual effects. This is applicable for both ajax and non ajax contexts.
* For notices associated with ID's the user type will receive an Empty notice type. That's because the effect
* is applied on the real estate holding the notices for this ID. Typically this contains a single message.
*/
val noticesEffects = new FactoryMaker[(Box[NoticeType.Value], String) => Box[JsCmd]]((notice: Box[NoticeType.Value], id: String) => Empty){}
/**
* Holds user functions that will be executed very early in the request processing. The functions'
* result will be ignored.
*/
val early = RulesSeq[(HTTPRequest) => Any]
/**
* Holds user functions that are executed before sending the response to client. The functions'
* result will be ignored.
*/
val beforeSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any]
private[this] lazy val defaultSecurityRules = SecurityRules()
/**
* The security rules used by Lift to secure this application. These mostly
* relate to HTTPS handling and HTTP `Content-Security-Policy`. See the
* `[[SecurityRules]]` documentation for more.
*
* Once the application has started using these, they are locked in, so make
* sure to set them early in the boot process.
*/
@volatile var securityRules: () => SecurityRules = () => defaultSecurityRules
private[http] lazy val lockedSecurityRules = securityRules()
/**
* Defines the resources that are protected by authentication and authorization. If this function
* is not defined for the input data, the resource is considered unprotected ergo no authentication
* is performed. If this function is defined and returns a Full box, it means that this resource
* is protected by authentication, and authenticated subjed must be assigned to the role returned by
* this function or to a role that is child-of this role. If this function returns Empty it means that
* this resource is protected by authentication but no authorization is performed meaning that roles are
* not verified.
*/
val httpAuthProtectedResource = RulesSeq[HttpAuthProtectedResourcePF]
/**
* The HTTP authentication mechanism that Lift will perform. See <i>LiftRules.protectedResource</i>
*/
@volatile var authentication: HttpAuthentication = NoAuthentication
/**
* A function that takes the HTTPSession and the contextPath as parameters
* and returns a LiftSession reference. This can be used in cases subclassing
* LiftSession is necessary.
*/
@volatile var sessionCreator: (HTTPSession, String) => LiftSession = {
case (httpSession, contextPath) => new LiftSession(contextPath, httpSession.sessionId, Full(httpSession))
}
/**
* A method that returns a function to create migratory sessions. If you want migratory sessions for your
* application, <code>LiftRules.sessionCreator = LiftRules.sessionCreatorForMigratorySessions</code>
*/
def sessionCreatorForMigratorySessions: (HTTPSession, String) => LiftSession = {
case (httpSession, contextPath) => new LiftSession(contextPath, httpSession.sessionId, Full(httpSession)) with MigratorySession
}
@volatile var enableContainerSessions = true
@volatile var getLiftSession: (Req) => LiftSession = (req) => _getLiftSession(req)
// Unique identifier for this particular instance of Lift, used for
// tagging resources below in attachResourceId.
private val instanceResourceId = "instance-" + Helpers.nextFuncName
/**
* Attaches an ID entity for resource URI specified in
* link or script tags. This allows controlling browser
* resource caching. By default this just adds a query string
* parameter unique per application lifetime. More complex
* implementation could user per resource MD5 sequences thus
* "forcing" browsers to refresh the resource only when the resource
* file changes. Users can define other rules as well. Inside user's
* function it is safe to use S context as attachResourceId is called
* from inside the <lift:with-resource-id> snippet
*
*/
@volatile var attachResourceId: (String) => String = (name) => {
name + (if (name contains ("?")) "&" else "?") + instanceResourceId + "=_"
}
/**
* Returns a LiftSession instance.
*/
private def _getLiftSession(req: Req): LiftSession = {
val wp = req.path.wholePath
val LiftPath = LiftRules.liftContextRelativePath
val cometSessionId = wp match {
case LiftPath :: "comet" :: _ :: session :: _ => Full(session)
case _ => Empty
}
val ret = SessionMaster.getSession(req, cometSessionId) match {
case Full(ret) =>
ret.fixSessionTime()
ret
case Failure(_, _, _) =>
LiftRules.statelessSession.vend.apply(req)
case _ =>
val ret = LiftSession(req)
ret.fixSessionTime()
SessionMaster.addSession(ret, req,
req.request.userAgent,
SessionMaster.getIpFromReq(req))
ret
}
makeCometBreakoutDecision(ret, req)
ret
}
/**
* A function that takes appropriate action in breaking out of any
* existing comet requests based on the request, browser type, etc.
*/
@volatile var makeCometBreakoutDecision: (LiftSession, Req) => Unit =
(session, req) => {
// get the open sessions to the host (this means that any DNS wildcarded
// Comet requests will not be counted), as well as all invalid/expired
// sessions
val (which, invalid) = session.cometForHost(req.hostAndPath)
// get the maximum requests given the browser type
val max = maxConcurrentRequests.vend(req) - 2 // this request and any open comet requests
// dump the oldest requests
which.drop(max).foreach {
case (actor, req) => actor ! BreakOut()
}
invalid.foreach {
case (actor, req) => actor ! BreakOut()
}
}
/**
* The path to handle served resources
*/
@volatile var resourceServerPath = "classpath"
/**
* Holds the JS library specific UI artifacts. By default it uses JQuery's artifacts
*
* Please note that currently any setting other than `JQueryArtifacts` will switch
* you to using Lift's liftVanilla implementation, which is meant to work independent
* of any framework. '''This implementation is experimental in Lift 3.0, so use it at
* your own risk and make sure you test your application!'''
*/
@volatile var jsArtifacts: JSArtifacts = JQueryArtifacts
/**
* Use this PartialFunction to to automatically add static URL parameters
* to any URL reference from the markup of Ajax request.
*/
val urlDecorate = RulesSeq[URLDecoratorPF]
/**
* Should the JSESSIONID be encoded in the URL if cookies are
* not supported
*/
@volatile var encodeJSessionIdInUrl_? = false
/**
* Partial function to allow you to build a CometActor from code rather than via reflection
*/
val cometCreation = RulesSeq[CometCreationPF]
private def noComet(ignore: CometCreationInfo): Box[LiftCometActor] = Empty
/**
* A factory that will vend comet creators
*/
val cometCreationFactory: FactoryMaker[CometCreationInfo => Box[LiftCometActor]] =
new FactoryMaker(() => noComet _) {}
/**
* Should codes that represent entities be converted to XML
* entities when rendered?
*/
val convertToEntity: FactoryMaker[Boolean] = new FactoryMaker(false) {}
/**
* Certain paths and requests within your application can be marked as stateless
* and if there is access to Lift's stateful facilities (setting
* SessionVars, updating function tables, etc.) the developer will
* receive a notice and the operation will not complete.
*/
val statelessReqTest = RulesSeq[StatelessReqTestPF]
val statelessSession: FactoryMaker[Req => LiftSession with StatelessSession] =
new FactoryMaker((req: Req) => new LiftSession(req.contextPath,
Helpers.nextFuncName,
Empty) with
StatelessSession) {}
/**
* Holds user functions that are executed after the response is sent to client. The functions' result
* will be ignored.
*/
val afterSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any]
/**
* Calculate the Comet Server (by default, the server that
* the request was made on, but can do the multi-server thing
* as well)
*/
@volatile var cometServer: () => Option[String] = () => None
/**
* The maximum concurrent requests. If this number of
* requests are being serviced for a given session, messages
* will be sent to all Comet requests to terminate
*/
val maxConcurrentRequests: FactoryMaker[Req => Int] = new FactoryMaker((x: Req) => x match {
case r if r.isIPad || r.isIPhone => 1
case r if r.isFirefox35_+ || r.isIE8 || r.isIE9 || r.isChrome3_+ || r.isOpera9 || r.isSafari3_+ => 4
case _ => 2
}) {}
/**
* A partial function that determines content type based on an incoming
* Req and Accept header
*/
@volatile var determineContentType: PartialFunction[(Box[Req], Box[String]), String] = {
case (_, Full(accept)) if this.useXhtmlMimeType && accept.toLowerCase.contains("application/xhtml+xml") =>
"application/xhtml+xml; charset=utf-8"
case _ => "text/html; charset=utf-8"
}
lazy val liftVersion: String = {
val cn = """\.""".r.replaceAllIn(LiftRules.getClass.getName, "/")
val ret: Box[String] =
for{
url <- Box !! LiftRules.getClass.getResource("/" + cn + ".class")
newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF")
str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8"))
ma <- """Implementation-Version: (.*)""".r.findFirstMatchIn(str)
} yield ma.group(1)
ret openOr "Unknown Lift Version"
}
lazy val liftBuildDate: Date = {
val cn = """\.""".r.replaceAllIn(LiftRules.getClass.getName, "/")
val ret: Box[Date] =
for{
url <- Box !! LiftRules.getClass.getResource("/" + cn + ".class")
newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF")
str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8"))
ma <- """Built-Time: (.*)""".r.findFirstMatchIn(str)
asLong <- asLong(ma.group(1))
} yield new Date(asLong)
ret openOr new Date(0L)
}
/**
* Hooks to be run when LiftServlet.destroy is called.
*/
val unloadHooks = RulesSeq[() => Unit]
/**
* For each unload hook registered, run them during destroy()
*/
private[http] def runUnloadHooks() {
unloadHooks.toList.foreach{f =>
tryo{f()}
}
}
/**
* The maximum allowed size of a complete mime multi-part POST. Default 8MB
*/
@volatile var maxMimeSize: Long = 8 * 1024 * 1024
/**
* Should pages that are not found be passed along the request processing chain to the
* next handler outside Lift?
*/
@volatile var passNotFoundToChain = false
/**
* The maximum allowed size of a single file in a mime multi-part POST.
* Default 7MB
*/
@volatile var maxMimeFileSize: Long = 7 * 1024 * 1024
/**
* The function referenced here is called if there's a localization lookup failure
*/
@volatile var localizationLookupFailureNotice: Box[(String, Locale) => Unit] = Empty
/**
* When a parameter is received either via POST or GET and does not have a
* corresponding mapping on the server, the function provided by this
* FactoryMaker will be called with the req and parameter name.
*
* By default, if the parameter looks Lift-like (i.e., it starts with an F),
* then we log a warning with the given parameter name and URI.
*/
val handleUnmappedParameter = new FactoryMaker[(Req,String)=>Unit](
() => { (req: Req, parameterName: String) =>
if (parameterName.startsWith("F"))
logger.warn("Unmapped Lift-like parameter seen in request [%s]: %s".format(req.uri, parameterName))
}
) {}
/**
* Set to false if you want to have 404's handled the same way in dev and production mode
*/
@volatile var displayHelpfulSiteMapMessages_? = true
/**
* Enables or disables event attribute and script element extraction.
*
* Lift can extract script elements and event attributes like onclick,
* onchange, etc, and attach the event handlers in a separate JavaScript file
* that is generated per-page. This allows for populating these types of
* JavaScript in your snippets via CSS selector transforms, without needing
* to allow inline scripts in your content security policy (see
* `[[securityRules]]`).
*
* However, there are certain scenarios where event attribute extraction
* cannot provide a 1-to-1 reproduction of the behavior you'd get with inline
* attributes or scripts; if your application hits these scenarios and you
* would prefer not to adjust them to work with a restrictive content
* security policy, you can allow inline scripts and set
* `extractEventAttributes` to false to disable event extraction.
*/
@volatile var extractInlineJavaScript: Boolean = false
/**
* The attribute used to expose the names of event attributes that were
* removed from a given element for separate processing in JS (when
* `extractInlineJavaScript` is `true`). By default, Lift removes event
* attributes and attaches those behaviors via a separate JS file, to avoid
* inline JS invocations so that a restrictive content security policy can be
* used.
*
* You can set this variable so that the resulting HTML will have
* attribute information about the removed attributes, in case you
* need them for e.g. CSS selector matching. The attribute will
* contain a space-separated list of JS attributes that were removed
* by Lift's processing.
*
* For example, if you needed to match elements with an `onclick`
* attribute in CSS, you would usually do:
*
* {{{
* [onclick] {
* text-decoration: underline;
* }
* }}}
*
* And have an element:
*
* {{{
* <span onclick="jsCode()">Do something!</span>
* }}}
*
* In Lift 3, this would not work, as the onclick attribute would be
* removed before HTML serialization, so your HTML would be:
*
* {{{
* <span id="lift-event-js-F827001738725NKMEQNF">Do something!</span>
* }}}
*
* Instead, you could set:
*
* {{{
* LiftRules.attributeForRemovedEventAttributes = Some("data-lift-removed-attributes")
* }}}
*
* The HTML Lift emitted would then look like:
*
* {{{
* <span id="lift-event-js-F827001738725NKMEQNF"
* data-lift-removed-attributes="onclick">Do something!</span>
* }}}
*
* This makes it possible to replace the old CSS with with similar
* matching for the `data-lift-removed-attributes` attribute:
*
* {{{
* [data-lift-removed-attributes~=onclick] {
* text-decoration: underline;
* }
* }}}
*/
@volatile var attributeForRemovedEventAttributes: Option[String] = None
/**
* The default location to send people if SiteMap access control fails. The path is
* expressed a a List[String]
*/
@volatile var siteMapFailRedirectLocation: List[String] = List()
private[http] def notFoundOrIgnore(requestState: Req, session: Box[LiftSession]): Box[LiftResponse] = {
if (passNotFoundToChain) Empty
else session match {
case Full(session) => Full(session.checkRedirect(requestState.createNotFound))
case _ => Full(requestState.createNotFound)
}
}
/**
* Allows user adding additional Lift tags (the tags must be prefixed by lift namespace such as <lift:xxxx/>).
* Each LiftTagPF function will be called with the following parameters:
* <pre>
* - Element label,
* - The Element itselft,
* - The attributes
* - The child nodes
* - The page name
* </pre>
*/
val liftTagProcessing = RulesSeq[LiftTagPF]
/**
* If you don't want lift to send the application/xhtml+xml mime type to those browsers
* that understand it, then set this to { @code false }
*/
@volatile var useXhtmlMimeType: Boolean = true
private def _stringToXml(s: String): NodeSeq = Text(s)
/**
* A function that defines how a String should be converted to XML
* for the localization stuff. By default, Text(s) is returned,
* but you can change this to attempt to parse the XML in the String and
* return the NodeSeq.
*/
@volatile var localizeStringToXml: String => NodeSeq = _stringToXml _
/**
* The base name of the resource bundle
*/
@volatile var resourceNames: List[String] = List("lift")
/**
* This function is called to convert the current set of Notices into
* a JsCmd that will be executed on the client to display the Notices.
*
* @see net.liftweb.builtin.snippet.Msgs
*/
@volatile var noticesToJsCmd: () => JsCmd = () => {
import builtin.snippet.{Msg,Msgs,MsgErrorMeta,MsgNoticeMeta,MsgWarningMeta}
// A "wrapper" that simply returns the javascript
val passJs = (in : JsCmd) => in
// Delegate to Msgs for fadeout and effects
def noticesFadeOut(noticeType: NoticeType.Value): JsCmd =
Msgs.noticesFadeOut(noticeType, Noop, passJs)
def groupEffects(noticeType: NoticeType.Value): JsCmd =
Msgs.effects(Full(noticeType), noticeType.id, Noop, passJs)
def idEffects(id : String): JsCmd =
Msgs.effects(Empty, id, Noop, passJs)
// Compute the global notices first
val groupMessages = Msgs.renderNotices() match {
case NodeSeq.Empty => JsCmds.Noop
case xml => LiftRules.jsArtifacts.setHtml(LiftRules.noticesContainerId, xml) &
noticesFadeOut(NoticeType.Notice) &
noticesFadeOut(NoticeType.Warning) &
noticesFadeOut(NoticeType.Error) &
groupEffects(NoticeType.Notice) &
groupEffects(NoticeType.Warning) &
groupEffects(NoticeType.Error)
}
// We need to determine the full set of IDs that need messages rendered.
val idSet = (S.idMessages((S.errors)) ++
S.idMessages((S.warnings)) ++
S.idMessages((S.notices))).map(_._1).distinct
// Merge each Id's messages and effects into the JsCmd chain
idSet.foldLeft(groupMessages) {
(chain,id) => chain &
LiftRules.jsArtifacts.setHtml(id, Msg.renderIdMsgs(id)) &
idEffects(id)
}
}
/**
* The base name of the resource bundle of the lift core code
*/
@volatile var liftCoreResourceName = "i18n.lift-core"
/**
* The JsCmd to execute when the comet session is lost. The comet
* session is considered lost when either (a) a comet request comes
* in for a session that does not exist on the server or (b) a comet
* request comes in for a session that has no associated comet actors
* (this typically happens when the server restarts).
*
* By default, we invoke lift.cometOnSessionLost, which can be
* overridden client-side for more complex work.
* lift.cometOnSessionLost reloads the current page by default.
*/
val noCometSessionCmd = new FactoryMaker[JsCmd](
() => JsCmds.Run("lift.cometOnSessionLost()")
) {}
/**
* The JsCmd to execute when the ajax session is lost. The ajax
* session is considered lost when either an ajax request comes in for
* a session that does not exist on the server.
*
* By default, we invoke lift.ajaxOnSessionLost, which can be
* overridden client-side for more complex work.
* lift.ajaxOnSessionLost reloads the page by default.
*/
val noAjaxSessionCmd = new FactoryMaker[JsCmd](
() => JsCmds.Run("lift.ajaxOnSessionLost()")
) {}
/**
* Server-side actors that represent client-side
* actor endpoints (client actors, Round Trips) need
* a lifespan. By default, it's 60 seconds, but you might
* want to make it longer if the client is going to get
* delayed by long computations that bar it from re-establishing
* the long polling connection
*/
val clientActorLifespan = new FactoryMaker[LiftActor => Long](
() => (actor: LiftActor) => (30.minutes): Long
){}
/**
* Put a function that will calculate the request timeout based on the
* incoming request.
*/
@volatile var calcRequestTimeout: Box[Req => Int] = Empty
/**
* If you want the standard (non-AJAX) request timeout to be something other than
* 10 seconds, put the value here
*/
@volatile var stdRequestTimeout: Box[Int] = Empty
/**
* If you want the AJAX request timeout to be something other than 120 seconds, put the value here
*/
@volatile var cometRequestTimeout: Box[Int] = Empty
/**
* If a Comet request fails timeout for this period of time. Default value is 10 seconds
*/
@volatile var cometFailureRetryTimeout: Long = 10.seconds
/**
* The timeout in milliseconds of a comet ajax-request. Defaults to 5000 ms.
*/
@volatile var cometProcessingTimeout: Long = 5.seconds
/**
* The timeout in milliseconds of a comet render-request. Defaults to 30000 ms.
*/
@volatile var cometRenderTimeout: Long = 30.seconds
/**
* The dispatcher that takes a Snippet and converts it to a
* DispatchSnippet instance
*/
val snippetDispatch = RulesSeq[SnippetDispatchPF]
/**
* Function that generates variants on snippet names to search for, given the name from the template.
* The default implementation just returns name :: Nil (e.g. no change).
* The names are searched in order.
* See also searchSnippetsWithRequestPath for an implementation.
*/
@volatile var snippetNamesToSearch: FactoryMaker[String => List[String]] =
new FactoryMaker(() => (name: String) => name :: Nil) {}
/**
* Implementation for snippetNamesToSearch that looks first in a package named by taking the current template path.
* For example, suppose the following is configured in Boot:
* LiftRules.snippetNamesToSearch.default.set(() => LiftRules.searchSnippetsWithRequestPath)
* LiftRules.addToPackages("com.mycompany.myapp")
* LiftRules.addToPackages("com.mycompany.mylib")
* The tag <lift:MySnippet> in template foo/bar/baz.html would search for the snippet in the following locations:
* - com.mycompany.myapp.snippet.foo.bar.MySnippet
* - com.mycompany.myapp.snippet.MySnippet
* - com.mycompany.mylib.snippet.foo.bar.MySnippet
* - com.mycompany.mylib.snippet.MySnippet
* - and then the Lift builtin snippet packages
*/
def searchSnippetsWithRequestPath(name: String): List[String] =
S.request.map(_.path.partPath.dropRight(1)) match {
case Full(xs) if !xs.isEmpty => (xs.mkString(".") + "." + name) :: name :: Nil
case _ => name :: Nil
}
/**
* Change this variable to set view dispatching
*/
val viewDispatch = RulesSeq[ViewDispatchPF]
private[http] def snippet(name: String): Box[DispatchSnippet] = NamedPF.applyBox(name, snippetDispatch.toList)
/**
* If the request times out (or returns a non-Response) you can
* intercept the response here and create your own response
*/
@volatile var requestTimedOut: Box[(Req, Any) => Box[LiftResponse]] = Empty
/**
* A function that takes the current HTTP request and returns the current
*/
@volatile var timeZoneCalculator: Box[HTTPRequest] => TimeZone = defaultTimeZoneCalculator _
def defaultTimeZoneCalculator(request: Box[HTTPRequest]): TimeZone = TimeZone.getDefault
/**
* How many times do we retry an Ajax command before calling it a failure?
*/
@volatile var ajaxRetryCount: Box[Int] = Empty
/**
* The JavaScript to execute at the beginning of an
* Ajax request (for example, showing the spinning working thingy)
*/
@volatile var ajaxStart: Box[() => JsCmd] = Empty
import FuncJBridge._
/**
* Set the Ajax end JavaScript function. The
* Java-callable alternative to assigning the var ajaxStart
*/
def setAjaxStart(f: Func0[JsCmd]): Unit = {
ajaxStart = Full(f: () => JsCmd)
}
/**
* The function that calculates if the response should be rendered in
* IE6/7/8 compatibility mode
*/
@volatile var calcIEMode: () => Boolean =
() => (for (r <- S.request) yield r.isIE6 || r.isIE7 ||
r.isIE8) openOr true
/**
* The JavaScript to execute to log a message on the client side when
* lift.logError is called.
*
* If Empty no logging is performed
* The default when running in DevMode is to call lift.logError which
* will use JavaScript console if available or alert otherwise.
*
* To always use alert set:
*
* LiftRules.jsLogFunc = Full(v => JE.Call("alert",v).cmd)
*/
@volatile var jsLogFunc: Box[JsVar => JsCmd] =
if (Props.devMode) Full(v => JE.Call("lift.defaultLogError", v))
else Empty
/**
* The JavaScript to execute at the end of an
* Ajax request (for example, removing the spinning working thingy)
*/
@volatile var ajaxEnd: Box[() => JsCmd] = Empty
/**
* Set the Ajax end JavaScript function. The
* Java-callable alternative to assigning the var ajaxEnd
*/
def setAjaxEnd(f: Func0[JsCmd]): Unit = {
ajaxEnd = Full(f: () => JsCmd)
}
/**
* An XML header is inserted at the very beginning of returned XHTML pages.
* This function defines the cases in which such a header is inserted. The
* function takes a NodeResponse (the LiftResponse that is converting the
* XML to a stream of bytes), the Node (root node of the XML), and
* a Box containing the content type.
*/
@volatile var calculateXmlHeader: (NodeResponse, Node, Box[String]) => String = {
case _ if S.skipXmlHeader => ""
case (_, up: Unparsed, _) => ""
case (_, _, Empty) | (_, _, Failure(_, _, _)) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/html")) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/xml") ||
s.toLowerCase.startsWith("text/xhtml") ||
s.toLowerCase.startsWith("application/xml") ||
s.toLowerCase.startsWith("application/xhtml+xml")) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case _ => ""
}
/**
* The default action to take when the JavaScript action fails
*/
@volatile var ajaxDefaultFailure: Box[() => JsCmd] =
Full(() => JsCmds.Alert(S.?("ajax.error")))
/**
* A function that takes the current HTTP request and returns the current
*/
@volatile var localeCalculator: Box[HTTPRequest] => Locale = defaultLocaleCalculator _
def defaultLocaleCalculator(request: Box[HTTPRequest]) =
request.flatMap(_.locale).openOr(Locale.getDefault())
val resourceBundleFactories = RulesSeq[ResourceBundleFactoryPF]
/**
* Given the current location (based on the Req.path.partPath),
* what are the resource bundles in the templates for the current
* page.
*
* @see DefaultRoutines.resourceForCurrentLoc()
*/
val resourceForCurrentLoc: FactoryMaker[() => List[ResourceBundle]] =
new FactoryMaker(() => () => DefaultRoutines.resourceForCurrentReq()) {}
/**
* Ever wanted to add custom attribute processing to Lift? Here's your chance.
* Every attribute with the data- prefix will be tested against this
* RulesSeq and if there's a match, then use the rule process. Simple, easy, cool.