/*
* Copyright 2007-2008 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.http
import _root_.scala.xml.{Node, Unparsed, Group, NodeSeq}
import _root_.net.liftweb.util._
import _root_.javax.servlet.http.Cookie
import js._
import _root_.net.liftweb.util.Helpers._
/**
* 200 response but without body.
*/
case class OkResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 200)
}
trait HeaderStuff {
val headers = S.getHeaders(Nil)
val cookies = S.responseCookies
}
/**
* 201 Created Response
*
* The Resource was created. We then return the resource, post-processing, to
* the client. Usually used with HTTP PUT.
*/
case class CreatedResponse(xml: Node, mime: String) extends NodeResponse {
def docType = Empty
def code = 201
def headers = List("Content-Type" -> mime)
def cookies = Nil
def out = xml
}
/**
* 202 response but without body.
*/
case class AcceptedResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 202)
}
/**
* 204 response but without body.
*/
case class NoContentResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 204)
}
/**
* 205 response but without body.
*/
case class ResetContentResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 205)
}
/**
* 301 Redirect.
*/
case class PermRedirectResponse(uri: String, request: Req, cookies: Cookie*) extends LiftResponse {
def toResponse = InMemoryResponse(Array(), List("Location" -> request.updateWithContextPath(uri)), cookies.toList, 301)
}
/**
* 307 Redirect.
*/
case class TemporaryRedirectResponse(uri: String, request: Req, cookies: Cookie*) extends LiftResponse {
def toResponse = InMemoryResponse(Array(), List("Location" -> request.updateWithContextPath(uri)), cookies.toList, 307)
}
/**
* 400 Bad Request
*
* Your Request was missing an important element. Use this as a last resort if
* the request appears incorrect.
*/
case class BadResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 400)
}
/**
* 401 Unauthorized Response.
*/
case class UnauthorizedResponse(realm: String) extends LiftResponse {
def toResponse = InMemoryResponse(Array(), List("WWW-Authenticate" -> ("Basic realm=\"" + realm + "\"")), Nil, 401)
}
object Qop extends Enumeration(0, "auth", "auth-int", "auth,auth-int") {
val AUTH, AUTH_INT, AUTH_AND_AUTH_INT = Value
}
/**
* 401 Unauthorized Response.
*/
case class UnauthorizedDigestResponse(override val realm: String, qop: Qop.Value, nonce: String, opaque: String) extends UnauthorizedResponse(realm) {
override def toResponse = InMemoryResponse(Array(), List("WWW-Authenticate" -> (
"Digest realm=\"" + realm + "\", " +
"qop=\"" + qop + "\", " +
"nonce=\"" + nonce + "\", " +
"opaque=\"" + opaque + "\""
)), Nil, 401)
}
/**
* 403 Forbidden
*
* The server understood the request, but is refusing to fulfill it.
* Authorization will not help and the request SHOULD NOT be repeated.
*/
case class ForbiddenResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 403)
}
/**
* 404 Not Found
*
* The server has not found anything matching the Request-URI.
*/
case class NotFoundResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 404)
}
/**
* 405 Method Not Allowed
*
* This Resource does not allow this method. Use this when the resource can't
* understand the method no matter the circumstances.
*/
case class MethodNotAllowedResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 405)
}
/**
* 406 Not Acceptable
*
* This Resource does not allow this method. Use this when the resource can't
* understand the method no matter the circumstances.
*/
case class NotAcceptableResponse(msg: String) extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(msg.getBytes("UTF-8"), headers, cookies, 406)
}
object NotAcceptableResponse {
def apply() = new NotAcceptableResponse("")
}
/**
* 410 Resource Gone
*
* The requested Resource used to exist but no longer does.
*/
case class GoneResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 410)
}
/**
* 415 Resource Gone
*
* The requested Resource used to exist but no longer does.
*/
case class UnsupportedMediaTypeResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 415)
}
/**
* 500 Internal Server Error
*
* The server encountered an unexpected condition which prevented
* it from fulfilling the request.
*/
case class InternalServerErrorResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 500)
}
/**
* 501 Not Implemented
*
* The server does not support the functionality required to
* fulfill the request. This is the appropriate response when the
* server does not recognize the request method and is not capable
* of supporting it for any resource.
*/
case class NotImplementedResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 501)
}
/**
* 502 Bad Gateway
*
* The server, while acting as a gateway or proxy, received an invalid
* response from the upstream server it accessed in attempting
* to fulfill the request.
*/
case class BadGatewayResponse() extends LiftResponse with HeaderStuff {
def toResponse = InMemoryResponse(Array(), headers, cookies, 502)
}
/**
* 503 Bad Gateway
*
* The server, while acting as a gateway or proxy, received an invalid
* response from the upstream server it accessed in attempting
* to fulfill the request.
*/
case class ServiceUnavailableResponse(retryAfter: Long) extends LiftResponse {
def toResponse = InMemoryResponse(Array(), List("Retry-After" -> retryAfter.toString), Nil, 503)
}
object JavaScriptResponse {
def apply(js: JsCmd): LiftResponse = JavaScriptResponse(js, S.getHeaders(Nil), S.responseCookies, 200)
}
/**
* Impersonates a HTTP response having Content-Type = text/javascript
*/
case class JavaScriptResponse(js: JsCmd, headers: List[(String, String)], cookies: List[Cookie], code: Int) extends LiftResponse {
def toResponse = {
val bytes = js.toJsCmd.getBytes("UTF-8")
InMemoryResponse(bytes, ("Content-Length", bytes.length.toString) :: ("Content-Type", "text/javascript") :: headers, cookies, code)
}
}
trait LiftResponse {
def toResponse: BasicResponse
}
object JsonResponse extends HeaderStuff {
def apply(json: JsExp): LiftResponse = JsonResponse(json, headers, cookies, 200)
}
case class JsonResponse(json: JsExp, headers: List[(String, String)], cookies: List[Cookie], code: Int) extends LiftResponse {
def toResponse = {
val bytes = json.toJsCmd.getBytes("UTF-8")
InMemoryResponse(bytes, ("Content-Length", bytes.length.toString) :: ("Content-Type", "application/json") :: headers, cookies, code)
}
}
sealed trait BasicResponse extends LiftResponse {
def headers: List[(String, String)]
def cookies: List[Cookie]
def code: Int
def size: Long
}
final case class InMemoryResponse(data: Array[Byte], headers: List[(String, String)], cookies: List[Cookie], code: Int) extends BasicResponse {
def toResponse = this
def size = data.length
override def toString="InMemoryResponse("+(new String(data, "UTF-8"))+", "+headers+", "+cookies+", "+code+")"
}
final case class StreamingResponse(data: {def read(buf: Array[Byte]): Int}, onEnd: () => Unit, size: Long, headers: List[(String, String)], cookies: List[Cookie], code: Int) extends BasicResponse {
def toResponse = this
override def toString="StreamingResponse( steaming_data , "+headers+", "+cookies+", "+code+")"
}
case class RedirectResponse(uri: String, cookies: Cookie*) extends LiftResponse {
// The Location URI is not resolved here, instead it is resolved with context path prior of sending the actual response
def toResponse = InMemoryResponse(Array(0), List("Location" -> uri), cookies toList, 302)
}
object DoRedirectResponse {
def apply(url: String): LiftResponse = RedirectResponse(url, Nil :_*)
}
case class RedirectWithState(override val uri: String, state : RedirectState, override val cookies: Cookie*) extends RedirectResponse(uri, cookies:_*)
object RedirectState {
def apply(f: () => Unit, msgs: (String, NoticeType.Value)*): RedirectState = new RedirectState(Full(f), msgs :_*)
}
case class RedirectState(func: Box[() => Unit], msgs : (String, NoticeType.Value)*)
object MessageState {
implicit def tuple2MessageState(msg : (String, NoticeType.Value)) = MessageState(msg)
}
case class MessageState(override val msgs: (String, NoticeType.Value)*) extends RedirectState(Empty, msgs :_*)
/**
* Stock XHTML doctypes available to the lift programmer.
*/
object DocType {
val xhtmlTransitional = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
val xhtmlStrict = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
val xhtmlFrameset = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"
val xhtml11 = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
val xhtmlMobile = "<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.0//EN\" \"http://www.wapforum.org/DTD/xhtml-mobile10.dtd\">"
}
object ResponseInfo {
var docType: PartialFunction[Req, Box[String]] = {
case _ if S.getDocType._1 => S.getDocType._2
case _ => Full(DocType.xhtmlTransitional)
}
}
object PlainTextResponse {
def apply(text: String): PlainTextResponse = PlainTextResponse(text, Nil, 200)
def apply(text: String, code: Int): PlainTextResponse = PlainTextResponse(text, Nil, code)
}
case class PlainTextResponse(text: String, headers: List[(String, String)], code: Int) extends LiftResponse {
def toResponse = {
val bytes = text.getBytes("UTF-8")
InMemoryResponse(bytes, ("Content-Length", bytes.length.toString) :: ("Content-Type", "text/plain") :: headers, Nil, code)
}
}
object CSSResponse {
def apply(text: String): CSSResponse = CSSResponse(text, Nil, 200)
def apply(text: String, code: Int): CSSResponse = CSSResponse(text, Nil, code)
}
case class CSSResponse(text: String, headers: List[(String, String)], code: Int) extends LiftResponse {
def toResponse = {
val bytes = text.getBytes("UTF-8")
InMemoryResponse(bytes, ("Content-Length", bytes.length.toString) :: ("Content-Type", "text/css") :: headers, Nil, code)
}
}
trait NodeResponse extends LiftResponse {
def out: Node
def headers: List[(String, String)]
def cookies: List[Cookie]
def code: Int
def docType: Box[String]
def renderInIEMode: Boolean = false
def toResponse = {
val encoding: String =
(out, headers.ciGet("Content-Type")) match {
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 _ => ""
}
val doc = docType.map(_ + "\n") openOr ""
val sb = new StringBuilder(64000)
sb.append(encoding)
sb.append(doc)
AltXML.toXML(out, _root_.scala.xml.TopScope,
sb, false, false, renderInIEMode)
sb.append(" \n ")
val ret = sb.toString
InMemoryResponse(ret.getBytes("UTF-8"), headers, cookies, code)
}
}
case class XhtmlResponse(out: Node, docType: Box[String],
headers: List[(String, String)],
cookies: List[Cookie],
code: Int,
override val renderInIEMode: Boolean) extends NodeResponse
/**
* Allows you to create custom 200 responses for clients using different
* Content-Types.
*/
case class XmlMimeResponse(xml: Node, mime: String) extends NodeResponse {
def docType = Empty
def code = 200
def headers = List("Content-Type" -> mime)
def cookies = Nil
def out = xml
}
case class XmlResponse(xml: Node) extends NodeResponse {
def docType = Empty
def code = 200
def headers = List("Content-Type" -> "text/xml")
def cookies = Nil
def out = xml
}
/**
* Returning an Atom document.
*/
case class AtomResponse(xml: Node) extends NodeResponse {
def docType = Empty
def code = 200
def headers = List("Content-Type" -> "application/atom+xml")
def cookies = Nil
def out = xml
}
/**
* Returning an OpenSearch Description Document.
*/
case class OpenSearchResponse(xml: Node) extends NodeResponse {
def docType = Empty
def code = 200
def headers = List("Content-Type" -> "application/opensearchdescription+xml")
def cookies = Nil
def out = xml
}
/**
* The Atom entity was successfully created and is shown to the client.
*/
case class AtomCreatedResponse(xml: Node) extends LiftResponse {
def toResponse = CreatedResponse(xml, "application/atom+xml").toResponse
}
/**
* Returning an Atom category document.
*/
case class AtomCategoryResponse(xml: Node) extends LiftResponse {
def toResponse = XmlMimeResponse(xml, "application/atomcat+xml").toResponse
}
/**
* Returning an Atom Service Document.
*/
case class AtomServiceResponse(xml: Node) extends LiftResponse {
def toResponse = XmlMimeResponse(xml, "application/atomsvc+xml").toResponse
}