diff --git a/build.sbt b/build.sbt index e67253b5f4..f96b113ab5 100644 --- a/build.sbt +++ b/build.sbt @@ -98,11 +98,9 @@ lazy val util = joda_time, joda_convert, commons_codec, - javamail, log4j, htmlparser, xerces, - jbcrypt, json4s_native, ) ) diff --git a/core/util/src/main/java/net/liftweb/util/BCrypt.java b/core/util/src/main/java/net/liftweb/util/BCrypt.java deleted file mode 100644 index 6eb74f63ad..0000000000 --- a/core/util/src/main/java/net/liftweb/util/BCrypt.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2007-2011 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. - */ - -// --------------- -// This is a derivative work of jBCrypt, distributed under BSD licence -// and copyrighted as follows: -// -// Copyright (c) 2006 Damien Miller -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -package net.liftweb.util; - -import java.io.UnsupportedEncodingException; - -import java.security.SecureRandom; - -/** - * This class is a passthrough to org.mindrot.jbcrypt.BCrypt, provided for - * backwards compatibility as we kept a copy in the Lift codebase before it was - * available for package dependencies. Prefer using org.mindrot.jbcrypt.BCrypt. - */ -@Deprecated -public class BCrypt { - /** - * @see org.mindrot.jbcrypt.BCrypt#hashpw(String,String) - */ - @Deprecated - public static String hashpw(String password, String salt) { - return org.mindrot.jbcrypt.BCrypt.hashpw(password, salt); - } - - /** - * @see org.mindrot.jbcrypt.BCrypt#gensalt(int,SecureRandom) - */ - @Deprecated - public static String gensalt(int log_rounds, SecureRandom random) { - return org.mindrot.jbcrypt.BCrypt.gensalt(log_rounds, random); - } - - /** - * @see org.mindrot.jbcrypt.BCrypt#gensalt(int) - */ - @Deprecated - public static String gensalt(int log_rounds) { - return org.mindrot.jbcrypt.BCrypt.gensalt(log_rounds); - } - - /** - * @see org.mindrot.jbcrypt.BCrypt#gensalt() - */ - @Deprecated - public static String gensalt() { - return org.mindrot.jbcrypt.BCrypt.gensalt(); - } - - /** - * @see org.mindrot.jbcrypt.BCrypt#checkpw(String,String) - */ - @Deprecated - public static boolean checkpw(String plaintext, String hashed) { - return org.mindrot.jbcrypt.BCrypt.checkpw(plaintext, hashed); - } -} diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala deleted file mode 100644 index 8c1758755c..0000000000 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright 2006-2011 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 util - -import javax.mail._ -import javax.mail.internet._ -import javax.naming.{Context, InitialContext} -import java.util.Properties - -import scala.language.implicitConversions -import scala.xml.{Text, Elem, Node, NodeSeq} - -import common._ -import actor._ - - -/** - * Utilities for sending email. - */ -object Mailer extends Mailer { - - sealed abstract class MailTypes - /** - * Add message headers to outgoing messages - */ - final case class MessageHeader(name: String, value: String) extends MailTypes - abstract class MailBodyType extends MailTypes - final case class PlusImageHolder(name: String, mimeType: String, bytes: Array[Byte], attachment: Boolean = false) - - /** - * Represents a text/plain mail body. The given text will - * be encoded as UTF-8 when sent. - */ - final case class PlainMailBodyType(text: String) extends MailBodyType - - /** - * Represents a text/plain mail body that is encoded with the - * specified charset - */ - final case class PlainPlusBodyType(text: String, charset: String) extends MailBodyType - - final case class XHTMLMailBodyType(text: NodeSeq) extends MailBodyType - final case class XHTMLPlusImages(text: NodeSeq, items: PlusImageHolder*) extends MailBodyType - - - sealed abstract class RoutingType extends MailTypes - sealed abstract class AddressType extends RoutingType { - def address: String - def name: Box[String] - } - final case class From(address: String, name: Box[String] = Empty) extends AddressType - final case class To(address: String, name: Box[String] = Empty) extends AddressType - final case class CC(address: String, name: Box[String] = Empty) extends AddressType - final case class Subject(subject: String) extends RoutingType - final case class BCC(address: String, name: Box[String] = Empty) extends AddressType - final case class ReplyTo(address: String, name: Box[String] = Empty) extends AddressType - - final case class MessageInfo(from: From, subject: Subject, info: List[MailTypes]) -} - -/** - * This trait implmenets the mail sending. You can create subclasses of this class/trait and - * implement your own mailer functionality - */ -trait Mailer extends SimpleInjector { - import Mailer._ - private val logger = Logger(classOf[Mailer]) - - implicit def xmlToMailBodyType(html: NodeSeq): MailBodyType = XHTMLMailBodyType(html) - - implicit def addressToAddress(in: AddressType): Address = { - val ret = new InternetAddress(in.address) - in.name.foreach{n => ret.setPersonal(n)} - ret - } - - implicit def adListToAdArray(in: List[AddressType]): Array[Address] = in.map(addressToAddress).toArray - - /** - * Passwords cannot be accessed via System.getProperty. Instead, we - * provide a means of explicitlysetting the authenticator. - */ - //def authenticator = authenticatorFunc - var authenticator: Box[Authenticator] = Empty - - /** - * Use the mailer resource in your container by specifying the JNDI name - */ - var jndiName: Box[String] = Empty - - /** - * Custom properties for the JNDI session - */ - var customProperties: Map[String, String] = Map() - - lazy val jndiSession: Box[Session] = - for{ - name <- jndiName - contextObj <- Helpers.tryo(new InitialContext().lookup("java:comp/env")) - context <- Box.asA[Context](contextObj) - sessionObj <- Helpers.tryo(context.lookup(name)) - session <- Box.asA[Session](sessionObj) - } yield session - - lazy val properties: Properties = { - val p = System.getProperties.clone.asInstanceOf[Properties] - customProperties.foreach {case (name, value) => p.put(name, value)} - // allow the properties file to set/override system properties - - Props.props.foreach { - case (name, value) => - p.setProperty(name, value) - } - p - } - - /** - * The host that should be used to send mail. - */ - def host = hostFunc() - - /** - * To change the way the host is calculated, set this to the function that calcualtes the host name. - * By default: System.getProperty("mail.smtp.host") - */ - var hostFunc: () => String = () => _host - - private def _host: String = properties.getProperty("mail.smtp.host") match { - case null => "localhost" - case s => s - } - - def buildProps: Properties = { - val p = properties.clone.asInstanceOf[Properties] - p.getProperty("mail.smtp.host") match { - case null => p.put("mail.smtp.host", host) - case _ => - } - - p - } - - /** - * Set the mail.charset property to something other than UTF-8 for non-UTF-8 - * mail. - */ - lazy val charSet = properties.getProperty("mail.charset") match { - case null => "UTF-8" - case x => x - } - - // def host_=(hostname: String) = System.setProperty("mail.smtp.host", hostname) - - protected class MsgSender extends SpecializedLiftActor[MessageInfo] { - protected def messageHandler = { - case MessageInfo(from, subject, info) => - try { - msgSendImpl(from, subject, info) - } catch { - case e: Exception => logger.error("Couldn't send mail", e) - } - } - } - - protected def performTransportSend(msg: MimeMessage) = { - import Props.RunModes._ - (Props.mode match { - case Development => devModeSend.vend - case Test => testModeSend.vend - case Staging => stagingModeSend.vend - case Production => productionModeSend.vend - case Pilot => pilotModeSend.vend - case Profile => profileModeSend.vend - }).apply(msg) - } - - /** - * How to send a message in dev mode. By default, use Transport.send(msg) - */ - lazy val devModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {} - - /** - * How to send a message in test mode. By default, log the message - */ - lazy val testModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => logger.info("Sending Mime Message: "+m)) {} - - /** - * How to send a message in staging mode. By default, use Transport.send(msg) - */ - lazy val stagingModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {} - - /** - * How to send a message in production mode. By default, use Transport.send(msg) - */ - lazy val productionModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {} - - /** - * How to send a message in pilot mode. By default, use Transport.send(msg) - */ - lazy val pilotModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {} - - /** - * How to send a message in profile mode. By default, use Transport.send(msg) - */ - lazy val profileModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {} - - /** - * Synchronously send an email. - */ - def blockingSendMail(from: From, subject: Subject, rest: MailTypes*): Unit = { - msgSendImpl(from, subject, rest.toList) - } - - def msgSendImpl(from: From, subject: Subject, info: List[MailTypes]): Unit = { - val session = authenticator match { - case Full(a) => jndiSession openOr Session.getInstance(buildProps, a) - case _ => jndiSession openOr Session.getInstance(buildProps) - } - val subj = MimeUtility.encodeText(subject.subject, "utf-8", "Q") - val message = new MimeMessage(session) - message.setFrom(from) - message.setRecipients(Message.RecipientType.TO, info.flatMap {case x: To => Some[To](x) case _ => None}) - message.setRecipients(Message.RecipientType.CC, info.flatMap {case x: CC => Some[CC](x) case _ => None}) - message.setRecipients(Message.RecipientType.BCC, info.flatMap {case x: BCC => Some[BCC](x) case _ => None}) - message.setSentDate(new java.util.Date()) - // message.setReplyTo(filter[MailTypes, ReplyTo](info, {case x @ ReplyTo(_) => Some(x); case _ => None})) - message.setReplyTo(info.flatMap {case x: ReplyTo => Some[ReplyTo](x) case _ => None}) - message.setSubject(subj) - info.foreach { - case MessageHeader(name, value) => message.addHeader(name, value) - case _ => - } - - val bodyTypes = info.flatMap {case x: MailBodyType => Some[MailBodyType](x); case _ => None} - bodyTypes match { - case PlainMailBodyType(txt) :: Nil => - message.setText(txt) - - case _ => - val multiPart = new MimeMultipart("alternative") - bodyTypes.foreach { - tab => - val bp = buildMailBody(tab) - multiPart.addBodyPart(bp) - } - message.setContent(multiPart); - } - - Mailer.this.performTransportSend(message) - } - - protected lazy val msgSender = new MsgSender - - /** - * The default mechanism for encoding a NodeSeq to a String representing HTML. By default, use Html5.toString(node) - */ - protected def encodeHtmlBodyPart(in: NodeSeq): String = Html5.toString(firstNode(in)) - - protected def firstNode(in: NodeSeq): Node = in match { - case n: Node => n - case ns => ns.toList.collect { - case e: Elem => e - } match { - case Nil => if (ns.length == 0) Text("") else ns(0) - case x :: xs => x - } - } - - /** - * Given a MailBodyType, convert it to a javax.mail.BodyPart. You can override this method if you - * add custom MailBodyTypes - */ - protected def buildMailBody(tab: MailBodyType): BodyPart = { - val bp = new MimeBodyPart - - tab match { - case PlainMailBodyType(txt) => - bp.setText(txt, "UTF-8") - - case PlainPlusBodyType(txt, charset) => - bp.setText(txt, charset) - - case XHTMLMailBodyType(html) => - bp.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) - - case XHTMLPlusImages(html, img@_*) => - val (attachments, images) = img.partition(_.attachment) - val relatedMultipart = new MimeMultipart("related") - - val htmlBodyPart = new MimeBodyPart - htmlBodyPart.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) - relatedMultipart.addBodyPart(htmlBodyPart) - - images.foreach { image => - relatedMultipart.addBodyPart(buildAttachment(image)) - } - - if (attachments.isEmpty) { - bp.setContent(relatedMultipart) - } else { - // Some old versions of Exchange server will not behave correclty without - // a mixed multipart wrapping file attachments. This appears to be linked to - // specific versions of Exchange and Outlook. See the discussion at - // https://github.com/lift/framework/pull/1569 for more details. - val mixedMultipart = new MimeMultipart("mixed") - - val relatedMultipartBodypart = new MimeBodyPart - relatedMultipartBodypart.setContent(relatedMultipart) - mixedMultipart.addBodyPart(relatedMultipartBodypart) - - attachments.foreach { attachment => - mixedMultipart.addBodyPart(buildAttachment(attachment)) - } - - bp.setContent(mixedMultipart) - } - } - - bp - } - - private def buildAttachment(holder: PlusImageHolder) = { - val part = new MimeBodyPart - - part.setFileName(holder.name) - part.setContentID(holder.name) - part.setDisposition(if (holder.attachment) Part.ATTACHMENT else Part.INLINE) - part.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource { - def getContentType = holder.mimeType - def getInputStream = new java.io.ByteArrayInputStream(holder.bytes) - def getName = holder.name - def getOutputStream = throw new java.io.IOException("Unable to write to item") - })) - - part - } - - - /** - * Asynchronously send an email. - */ - def sendMail(from: From, subject: Subject, rest: MailTypes*): Unit = { - // forward it to an actor so there's no time on this thread spent sending the message - msgSender ! MessageInfo(from, subject, rest.toList) - } -} diff --git a/core/util/src/main/scala/net/liftweb/util/Schedule.scala b/core/util/src/main/scala/net/liftweb/util/Schedule.scala index 9c5da1baf4..cb0fff3d32 100644 --- a/core/util/src/main/scala/net/liftweb/util/Schedule.scala +++ b/core/util/src/main/scala/net/liftweb/util/Schedule.scala @@ -58,7 +58,7 @@ sealed trait Schedule extends Loggable { */ @volatile var blockingQueueSize: Box[Int] = Full(200000) - @volatile var buildExecutor: () => ThreadPoolExecutor = + @volatile var buildExecutor: () => ExecutorService = () => new ThreadPoolExecutor(threadPoolSize, maxThreadPoolSize, 60, diff --git a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala index f5f8a6b075..e03ef99878 100644 --- a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala @@ -17,20 +17,14 @@ package net.liftweb package util -import java.io._ -import java.security._ -import javax.crypto._ -import javax.crypto.spec._ -import javax.xml.parsers.SAXParserFactory -import javax.xml.XMLConstants - -import scala.xml.{Elem, XML} -import scala.xml.factory.XMLLoader - import org.apache.commons.codec.binary.Base64 import org.apache.xerces.impl.Constants -import common._ +import java.security._ +import javax.xml.XMLConstants +import javax.xml.parsers.SAXParserFactory +import scala.xml.factory.XMLLoader +import scala.xml.{Elem, XML} object SecurityHelpers extends StringHelpers with IoHelpers with SecurityHelpers diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala deleted file mode 100644 index a5cb0ba238..0000000000 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2006-2011 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 util - -import javax.mail.internet.{MimeMessage, MimeMultipart} - -import org.specs2.mutable.Specification - -import common._ - -import Mailer.{From, To, Subject, PlainMailBodyType, XHTMLMailBodyType, XHTMLPlusImages, PlusImageHolder} - -import scala.io.Source - -trait MailerForTesting { - def lastMessage_=(message: Box[MimeMessage]): Unit - def lastMessage: Box[MimeMessage] -} - -/** - * Systems under specification for Lift Mailer. - */ -class MailerSpec extends Specification { - "Mailer Specification".title - sequential - - Props.mode // touch the lazy val so it's detected correctly - - val myMailer = new Mailer with MailerForTesting { - @volatile var lastMessage: Box[MimeMessage] = Empty - - testModeSend.default.set((msg: MimeMessage) => { - lastMessage = Full(msg) - }) - } - - import myMailer._ - - private def doNewMessage(send: => Unit): MimeMessage = { - lastMessage = Empty - - send - - eventually { - lastMessage.isEmpty must_== false - } - lastMessage openOrThrowException("Checked") - } - - "A Mailer" should { - - "deliver simple messages as simple messages" in { - val msg = doNewMessage { - sendMail( - From("sender@nowhere.com"), - Subject("This is a simple email"), - To("recipient@nowhere.com"), - PlainMailBodyType("Here is some plain text.") - ) - } - - msg.getContent must beAnInstanceOf[String] - } - - "deliver multipart messages as multipart" in { - val msg = doNewMessage { - sendMail( - From("sender@nowhere.com"), - Subject("This is a multipart email"), - To("recipient@nowhere.com"), - PlainMailBodyType("Here is some plain text."), - PlainMailBodyType("Here is some more plain text.") - ) - } - - msg.getContent must beAnInstanceOf[MimeMultipart] - } - - "deliver rich messages as multipart" in { - val msg = doNewMessage { - sendMail( - From("sender@nowhere.com"), - Subject("This is a rich email"), - To("recipient@nowhere.com"), - XHTMLMailBodyType( Here is some rich text ) - ) - } - - msg.getContent must beAnInstanceOf[MimeMultipart] - } - - "deliver emails with attachments as mixed multipart" in { - val attachmentBytes = Source.fromInputStream( - getClass.getClassLoader.getResourceAsStream("net/liftweb/util/Html5ParserSpec.page1.html") - ).map(_.toByte).toArray - val msg = doNewMessage { - sendMail( - From("sender@nowhere.com"), - Subject("This is a mixed email"), - To("recipient@nowhere.com"), - XHTMLPlusImages( - Here is some rich text , - PlusImageHolder("awesome.pdf", "text/html", attachmentBytes, true) - ) - ) - } - - msg.getContent must beLike { - case mp: MimeMultipart => - mp.getContentType.substring(0, 21) must_== "multipart/alternative" - - mp.getBodyPart(0).getContent must beLike { - case mp2: MimeMultipart => - mp2.getContentType.substring(0, 15) must_== "multipart/mixed" - } - } - } - } -} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7e7745e5f2..6ac93a6c12 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -25,29 +25,27 @@ object Dependencies { // Compile scope: // Scope available in all classpath, transitive by default. - lazy val commons_codec = "commons-codec" % "commons-codec" % "1.11" - lazy val commons_fileupload = "org.apache.commons" % "commons-fileupload2-jakarta-servlet5" % "2.0.0-M2" + lazy val commons_codec = "commons-codec" % "commons-codec" % "1.19.0" + lazy val commons_fileupload = "org.apache.commons" % "commons-fileupload2-jakarta-servlet6" % "2.0.0-M4" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" - lazy val javamail = "javax.mail" % "mail" % "1.4.7" - lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" - lazy val joda_time = "joda-time" % "joda-time" % "2.10" - lazy val joda_convert = "org.joda" % "joda-convert" % "2.1" + lazy val joda_time = "joda-time" % "joda-time" % "2.14.0" + lazy val joda_convert = "org.joda" % "joda-convert" % "3.0.1" lazy val json4s_ext = "org.json4s" %% "json4s-ext" % "4.0.7" lazy val json4s_native = "org.json4s" %% "json4s-native" % "4.0.7" lazy val json4s_xml = "org.json4s" %% "json4s-xml" % "4.0.7" - lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.12" + lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.16" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.12.7" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.12.7" - lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" + lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8.3" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ - lazy val scalaz7_core = "org.scalaz" %% "scalaz-core" % "7.2.28" + lazy val scalaz7_core = "org.scalaz" %% "scalaz-core" % "7.3.8" lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "2.1.0" + lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "2.4.0" lazy val scala_parallel_collections = "org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0" lazy val rhino = "org.mozilla" % "rhino" % "1.7.15" lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "2.4.0" - lazy val xerces = "xerces" % "xercesImpl" % "2.11.0" + lazy val xerces = "xerces" % "xercesImpl" % "2.12.0" lazy val scala_compiler: ModuleMap = (version: String) => { if (version.startsWith("2")) { @@ -67,8 +65,7 @@ object Dependencies { lazy val logback = "ch.qos.logback" % "logback-classic" % "1.2.13" % Provided lazy val log4j = "log4j" % "log4j" % "1.2.17" % Provided lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % Provided - lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0.2" % Provided - lazy val servlet_api = "jakarta.servlet" % "jakarta.servlet-api" % "5.0.0" % Provided + lazy val servlet_api = "jakarta.servlet" % "jakarta.servlet-api" % "6.1.0" % Provided lazy val jquery = "org.webjars.bower" % "jquery" % "1.11.3" % Provided lazy val jasmineCore = "org.webjars.bower" % "jasmine-core" % "2.4.1" % Provided lazy val jasmineAjax = "org.webjars.bower" % "jasmine-ajax" % "3.2.0" % Provided diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala index 508d9cbf53..163113a0d4 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala @@ -574,5 +574,11 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = def changeSessionId(): String = null def getContentLengthLong(): Long = body.length + override def getRequestId: String = ??? + + override def getProtocolRequestId: String = ??? + + override def getServletConnection: ServletConnection = ??? + def upgrade[T <: jakarta.servlet.http.HttpUpgradeHandler](x$1: Class[T]): T = ??? } diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala index e9ca60e6e0..4efc312a86 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala @@ -96,7 +96,7 @@ class MockHttpServletResponse(var writer: PrintWriter, var outputStream: Servlet setHeader(s, (new Date(l)).toString) } - def sendRedirect(uri: String): Unit = { + override def sendRedirect(uri: String): Unit = { // Send back a 301 to the URL mentioned statusCode = 301 addHeader("Location", uri) @@ -149,4 +149,6 @@ class MockHttpServletResponse(var writer: PrintWriter, var outputStream: Servlet def getContentType(): String = contentType def getCharacterEncoding(): String = charEncoding def setContentLengthLong(l: Long): Unit = contentType = l.toString + + override def sendRedirect(location: String, sc: Int, clearBuffer: Boolean): Unit = ??? } diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala index 3cab0a7008..dd24a4739b 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala @@ -228,8 +228,6 @@ class MockHttpSession extends HttpSession { def hasMoreElements() = keys.hasNext def nextElement(): String = keys.next() } - @nowarn("msg=trait HttpSessionContext in package http is deprecated") - def getSessionContext(): HttpSessionContext = null def getMaxInactiveInterval(): Int = maxii def setMaxInactiveInterval(i: Int): Unit = maxii = i def getServletContext(): ServletContext = null diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 192cbe588b..a720887f1b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -44,8 +44,8 @@ private[this] case class HtmlState( private[http] trait LiftMerge { self: LiftSession => - private def scriptUrl(scriptFile: String) = { - S.encodeURL(s"${LiftRules.liftPath}/$scriptFile") + private def pageJsUrl = { + S.encodeURL(s"${S.contextPath}/${LiftRules.pageJsFunc().mkString("/")}/${RenderVersion.get}.js") } // Gather all page-specific JS into one JsCmd. @@ -63,7 +63,7 @@ private[http] trait LiftMerge { private def pageScopedScriptFileWith(cmd: JsCmd) = { pageScript(Full(JavaScriptResponse(cmd, Nil, Nil, 200))) - + } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index d6722dfa2a..98705a63f7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -358,9 +358,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ private def _getLiftSession(req: Req): LiftSession = { val wp = req.path.wholePath - val LiftPath = LiftRules.liftContextRelativePath + val LiftPath = LiftRules.liftContextRelativePath() val cometSessionId = wp match { - case LiftPath :: "comet" :: _ :: session :: _ => Full(session) + case LiftPath :+ "comet" :+ session :+ _ => Full(session) case _ => Empty } @@ -1357,13 +1357,15 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * scoped. It does not include the context path and should not begin with a * /. */ - @volatile var liftContextRelativePath = "lift" + @volatile var liftContextRelativePath: () => List[String] = () => List("lift") + + @volatile var pageJsFunc: () => List[String] = () => liftContextRelativePath() :+ "page" /** * Returns a complete URI, including the context path, under which all * built-in Lift-handled requests are scoped. */ - def liftPath: String = S.contextPath + "/" + liftContextRelativePath + def liftPath: String = S.contextPath + "/" + liftContextRelativePath().mkString("/") /** * If there is an alternative way of calculating the context path diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index e6d2678bdd..e7e996ed7e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -318,26 +318,28 @@ class LiftServlet extends Loggable { } def cometOrAjax_?(req: Req): (Boolean, Boolean) = { - lazy val ajaxPath = LiftRules.liftContextRelativePath :: "ajax" :: Nil - lazy val cometPath = LiftRules.liftContextRelativePath :: "comet" :: Nil + lazy val ajaxPath = LiftRules.liftContextRelativePath() :+ "ajax" + lazy val cometPath = LiftRules.liftContextRelativePath() :+ "comet" + lazy val cometPathLength = cometPath.length + lazy val ajaxPathLength = ajaxPath.length val wp = req.path.wholePath val pathLen = wp.length def isComet: Boolean = { - if (pathLen < 3) { + if (pathLen <= cometPathLength) { false } else { - val kindaComet = wp.take(2) == cometPath + val kindaComet = wp.take(cometPathLength) == cometPath kindaComet && req.acceptsJavaScript_? } } def isAjax: Boolean = { - if (pathLen < 3) { + if (pathLen <= ajaxPathLength) { false } else { - val kindaAjax = wp.take(2) == ajaxPath + val kindaAjax = wp.take(ajaxPathLength) == ajaxPath kindaAjax && req.acceptsJavaScript_? } @@ -578,11 +580,11 @@ class LiftServlet extends Loggable { * The requestVersion is passed to the function that is passed in. */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { - val LiftPath = LiftRules.liftContextRelativePath + val LiftPath = LiftRules.liftContextRelativePath() path match { - case LiftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + case LiftPath :+ "ajax" :+ AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :+ _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) - case LiftPath :: "ajax" :: renderVersion :: _ => + case LiftPath :+ "ajax" :+ renderVersion :+ _ => RenderVersion.doWith(renderVersion)(f(Empty)) case _ => f(Empty) } @@ -1042,11 +1044,11 @@ class LiftServlet extends Loggable { import net.liftweb.http.provider.servlet._ private class SessionIdCalc(req: Req) { - private val LiftPath = LiftRules.liftContextRelativePath + private val LiftPath = LiftRules.liftContextRelativePath() lazy val id: Box[String] = req.request.sessionId match { case Full(id) => Full(id) case _ => req.path.wholePath match { - case LiftPath :: "comet" :: _ :: id :: _ :: _ => Full(id) + case LiftPath :+ "comet" :+ _ :+ id :+ _ :+ _ => Full(id) case _ => Empty } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 597ad9e086..89cf32c28f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2496,9 +2496,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * set up, then returned. */ private[http] def findOrCreateComet[T <: LiftCometActor]( - cometName: Box[String], - cometHtml: NodeSeq, - cometAttributes: Map[String, String] + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], )(implicit cometManifest: Manifest[T]): Box[T] = { val castClass = cometManifest.runtimeClass.asInstanceOf[Class[T]] val typeName = castClass.getSimpleName @@ -2508,7 +2508,59 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri findOrBuildComet( creationInfo, - buildAndStoreComet(buildCometByClass(castClass)) + buildAndStoreComet(buildCometByClass(castClass, (a: T) => a)) + ) + } + + /** + * Find or build a comet actor of the given type `T` with the given + * configuration parameters. If a comet of that type with that name already + * exists, it is returned; otherwise, a new one of that type is created and + * set up, then returned. + * @param cometInitFunc a function that will be applied to the newly created comet actor + * + */ + private[http] def findOrCreateComet[T <: LiftCometActor]( + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + cometInitFunc: T => T + )(implicit cometManifest: Manifest[T]): Box[T] = { + val castClass = cometManifest.runtimeClass.asInstanceOf[Class[T]] + val typeName = castClass.getSimpleName + + val creationInfo = + CometCreationInfo(typeName, cometName, cometHtml, cometAttributes, this) + + findOrBuildComet( + creationInfo, + buildAndStoreComet(buildCometByClass(castClass, cometInitFunc)) + ) + } + + /** + * Find or build a comet actor of the given type `T` with the given + * configuration parameters. If a comet of that type with that name already + * exists, it is returned; otherwise, a new one of that type is created and + * set up, then returned. + * + * @param cometFactoryFunc a function that will be used to create the new comet actor + */ + private[http] def findOrCreateCometByFactoryFunc[T <: LiftCometActor]( + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + cometFactoryFunc: () => T + )(implicit cometManifest: Manifest[T]): Box[T] = { + val castClass = cometManifest.runtimeClass.asInstanceOf[Class[T]] + val typeName = castClass.getSimpleName + + val creationInfo = + CometCreationInfo(typeName, cometName, cometHtml, cometAttributes, this) + + findOrBuildComet( + creationInfo, + buildAndStoreComet(buildCometByFactoryFunc(cometFactoryFunc)) ) } @@ -2520,20 +2572,19 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * `LiftRules.buildPackage("comet")`. */ private[http] def findOrCreateComet( - cometType: String, - cometName: Box[String] = Empty, - cometHtml: NodeSeq = NodeSeq.Empty, - cometAttributes: Map[String, String] = Map.empty + cometType: String, + cometName: Box[String] = Empty, + cometHtml: NodeSeq = NodeSeq.Empty, + cometAttributes: Map[String, String] = Map.empty ): Box[LiftCometActor] = { val creationInfo = CometCreationInfo(cometType, cometName, cometHtml, cometAttributes, this) findOrBuildComet( creationInfo, - buildAndStoreComet(buildCometByCreationInfo) _ + buildAndStoreComet(buildCometByCreationInfo(_, a => a)) ) } - // Private helper function to do common comet setup handling for // `findOrBuildComet` overloads. private def findOrBuildComet[T <: LiftCometActor]( @@ -2596,15 +2647,27 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } } + /** + * Build and initialize the CometActor by the specified `buildFunc` + */ + private def buildCometByFactoryFunc[T <: LiftCometActor](buildFunc: () => T)(creationInfo: CometCreationInfo): Box[T] = { + tryo{buildFunc()} match { + case r@Full(comet) => + comet.callInitCometActor(creationInfo) + r + case other => other + } + } + // Given a comet creation info, build a comet based on the comet type, first // attempting to use `LiftRules.cometCreationFactory` and then attempting to // find a match in `LiftRules.cometCreation`. Failing those, this will build it by // class name. Return a descriptive Failure if it's all gone sideways. // // Runs some base setup tasks before returning the comet. - private def buildCometByCreationInfo(creationInfo: CometCreationInfo): Box[LiftCometActor] = { + private def buildCometByCreationInfo(creationInfo: CometCreationInfo, cometInitFunc: LiftCometActor => LiftCometActor): Box[LiftCometActor] = { LiftRules.cometCreationFactory.vend.apply(creationInfo) or - NamedPF.applyBox(creationInfo, LiftRules.cometCreation.toList) or { + NamedPF.applyBox(creationInfo, LiftRules.cometCreation.toList) or { val cometType = findType[LiftCometActor]( creationInfo.cometType, @@ -2612,19 +2675,19 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri ) cometType.flatMap { cometClass => - buildCometByClass(cometClass)(creationInfo) + buildCometByClass(cometClass, cometInitFunc)(creationInfo) } ?~ s"Failed to find specified comet class ${creationInfo.cometType}." } } // Given a comet Class and CometCreationInfo, instantiate the given - // comet and run setup tasks. Return a descriptive Failure if it's all - // gone sideways. - private def buildCometByClass[T <: LiftCometActor](cometClass: Class[T])(creationInfo: CometCreationInfo): Box[T] = { + // comet, apply `cometInitFunc` to it, and run setup tasks. Return a + // descriptive Failure if it's all gone sideways. + private def buildCometByClass[T <: LiftCometActor](cometClass: Class[T], cometInitFunc: T => T)(creationInfo: CometCreationInfo): Box[T] = { def buildWithNoArgConstructor = { val constructor = cometClass.getConstructor() - val comet = constructor.newInstance().asInstanceOf[T] + val comet = cometInitFunc(constructor.newInstance()) comet.callInitCometActor(creationInfo) comet @@ -2635,7 +2698,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri val CometCreationInfo(_, name, defaultXml, attributes, _) = creationInfo - constructor.newInstance(this, name, defaultXml, attributes).asInstanceOf[T] + cometInitFunc(constructor.newInstance(this, name, defaultXml, attributes)) } // We first attempt to use the no argument constructor. If we get a NoSuchMethodException, diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 600dbb285f..700a7be5e0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -884,10 +884,7 @@ class Req(val path: ParsePath, /** * A request that is neither Ajax or Comet */ - lazy val standardRequest_? : Boolean = path.partPath match { - case x :: _ if x == LiftRules.liftContextRelativePath => false - case _ => true - } + lazy val standardRequest_? : Boolean = !path.partPath.startsWith(LiftRules.liftContextRelativePath()) /** * Make the servlet session go away diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index ad36f86582..165694ea8f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -861,14 +861,83 @@ trait S extends HasParams with Loggable with UserAgentCalculator { * receive updates like this using {S.addComet}. */ def findOrCreateComet[T <: LiftCometActor]( - cometName: Box[String], - cometHtml: NodeSeq, - cometAttributes: Map[String, String], - receiveUpdatesOnPage: Boolean + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + receiveUpdatesOnPage: Boolean )(implicit cometManifest: Manifest[T]): Box[T] = { for { session <- session ?~ "Comet lookup and creation requires a session." - cometActor <- session.findOrCreateComet[T](cometName, cometHtml, cometAttributes) + cometActor <- session.findOrCreateComet[T](cometName, cometHtml, cometAttributes, (a: T) => a) + } yield { + if (receiveUpdatesOnPage) + addComet(cometActor) + + cometActor + } + } + + /** + * Find or build a comet actor of the given type `T` with the given + * configuration parameters. If a comet of that type with that name already + * exists, it is returned; otherwise, a new one of that type is created and + * set up, then returned. + * + * The `cometInitFunc` is applied to the comet actor after it is created and + * before it is returned. + * + * If `receiveUpdates` is `true`, updates to this comet will be pushed to + * the page currently being rendered or to the page that is currently + * invoking an AJAX callback. You can also separately register a comet to + * receive updates like this using {S.addComet}. + * + * @param cometName The name of the comet actor. + * @param cometHtml The HTML to be rendered by the comet actor. + * @param cometAttributes The attributes to be passed to the comet actor. + * @param receiveUpdatesOnPage Whether to register the comet to receive updates on the current page. + * @param cometInitFunc A function to initialize the comet actor after creation. + */ + + def findOrCreateCometWithInitFunc[T <: LiftCometActor]( + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + receiveUpdatesOnPage: Boolean, + cometInitFunc: T => T + )(implicit cometManifest: Manifest[T]): Box[T] = { + for { + session <- session ?~ "Comet lookup and creation requires a session." + cometActor <- session.findOrCreateComet[T](cometName, cometHtml, cometAttributes, cometInitFunc) + } yield { + if (receiveUpdatesOnPage) + addComet(cometActor) + + cometActor + } + } + + /** + * Find or build a comet actor of the given type `T` with the given + * configuration parameters. If a comet of that type with that name already + * exists, it is returned; otherwise, a new one of that type is created + * by `cometFactoryFunc` and + * set up, then returned. + * + * If `receiveUpdates` is `true`, updates to this comet will be pushed to + * the page currently being rendered or to the page that is currently + * invoking an AJAX callback. You can also separately register a comet to + * receive updates like this using {S.addComet}. + */ + def findOrCreateCometByFactoryFunc[T <: LiftCometActor]( + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + receiveUpdatesOnPage: Boolean, + cometFactoryFunc: () => T + )(implicit cometManifest: Manifest[T]): Box[T] = { + for { + session <- session ?~ "Comet lookup and creation requires a session." + cometActor <- session.findOrCreateCometByFactoryFunc[T](cometName, cometHtml, cometAttributes, cometFactoryFunc) } yield { if (receiveUpdatesOnPage) addComet(cometActor) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala b/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala index 3c9f778e7d..dab090003e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala @@ -337,7 +337,7 @@ object ContentSecurityPolicyViolation extends LazyLoggable { private[this] implicit val formats: DefaultFormats.type = DefaultFormats def defaultViolationHandler: DispatchPF = { - case request @ Req(start :: "content-security-policy-report" :: Nil, _, _) if start == LiftRules.liftContextRelativePath => + case request @ Req(start :+ "content-security-policy-report", _, _) if start == LiftRules.liftContextRelativePath() => val violation = for { requestJson <- request.forcedBodyAsJson diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index c8624f8c53..598e04a8da 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -37,15 +37,15 @@ object LiftJavaScript { object PageJs { def unapply(req: Req): Option[JavaScriptResponse] = { val suffixedPath = req.path.wholePath - val LiftPath = LiftRules.liftContextRelativePath + val PagePath = LiftRules.pageJsFunc() val renderVersion = "([^.]+)\\.js".r suffixedPath match { - case LiftPath :: "page" :: renderVersion(version) :: Nil => + case PagePath :+ renderVersion(version) => RenderVersion.doWith(version) { pageScript.is.toOption } - case other => + case _ => None } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/encoder/CookieEncoder.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/encoder/CookieEncoder.scala index 787bb45eda..cf2d5a1743 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/encoder/CookieEncoder.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/encoder/CookieEncoder.scala @@ -179,7 +179,7 @@ object CookieEncoder { } private def stripTrailingSeparator(buf: StringBuilder) = { - if (buf.length() > 0) { + if (buf.nonEmpty) { buf.setLength(buf.length() - 2); } buf.toString() diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index bcfc4ce319..b057ab8474 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -22,7 +22,7 @@ package servlet import java.io.InputStream import java.util.Locale import jakarta.servlet.http.HttpServletRequest -import org.apache.commons.fileupload2.jakarta.servlet5._ +import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload import org.apache.commons.fileupload2.core.ProgressListener import net.liftweb.common._ import net.liftweb.util._