<commit_before>/**
 * Copyright (c) 2012 <NAME> (born 1979)
 */

package controllers

import com.debiki.v0._
import debiki._
import debiki.DebikiHttp._
import com.debiki.v0.{liftweb => lw}
import java.{util => ju}
import play.api._
import play.api.data._
import play.api.data.Forms._
import play.api.libs.json.JsValue
import play.api.mvc.{Action => _, _}
import PageActions._
import Prelude._

object Utils extends Results with http.ContentTypes {


  /**
   * Prefixes `<!DOCTYPE html>` to the reply, otherwise Internet Explorer
   * enters the terrible Quirks mode. Also sets the Content-Type header.
   */
  def OkHtml(htmlNode: xml.NodeSeq) =
    Ok(serializeHtml(htmlNode)) as HTML

  def OkHtmlBody(bodyNodes: xml.NodeSeq) =
    OkHtml(<body>{bodyNodes}</body>)

  def ForbiddenHtml(htmlNode: xml.NodeSeq) =
    Forbidden(serializeHtml(htmlNode)) as HTML

  def BadReqHtml(htmlNode: xml.NodeSeq) =
    BadRequest(serializeHtml(htmlNode)) as HTML

  /**
   * Adds doctype and serializes to html using a real HTML5 writer.
   *
   * Some pros with using a real HTML5 writer: it won't escape '"' when found
   * inside script tags (which is very annoying when you e.g. copy-paste
   * Twitter's Follow Button <script> elem).
   */
  def serializeHtml(htmlNode: xml.NodeSeq): String = {
    require(htmlNode.size == 1)
    "<!DOCTYPE html>\n"+ lw.Html5.toString(htmlNode.head)
  }


  /**
   * Prefixes the JSON string with characters that prevents the JSON
   * from being parsed as Javascript from a <script> tag.
   * This supposedly thwarts a JSON vulnerability that allows third
   * party websites to turn your JSON resource URL into JSONP
   * request under some conditions, see:
   *   "JSON Vulnerability Protection", here:
   *      http://docs.angularjs.org/api/ng.$http
   *   and:
   *     http://haacked.com/archive/2008/11/20/
   *        anatomy-of-a-subtle-json-vulnerability.aspx
   * Debiki's Javascript, and AngularJS, strips the ")]}'," prefix before
   * parsing the JSON.
   */
  def OkSafeJson(json: JsValue) =
    Ok(")]}',\n" + json.toString) as JSON


  /**
   * Prefixes `<?xml version=...>` to the post data.
   */
  def OkXml(xmlNode: xml.NodeSeq, contentType: String = "text/xml") =
    Ok("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ xmlNode) as contentType


  def renderOrRedirect(pageReq: PageRequest[_]): PlainResult = {
    if (isAjax(pageReq.request)) {
      val pageHtml = pageReq.dao.renderTemplate(pageReq)
      Ok(pageHtml) as HTML
    } else {
      val queryString =
         queryStringAndHashToView(pageReq.pageRoot, pageReq.pageVersion)
      Redirect(pageReq.pagePath.path + queryString)
    }
  }


  def formHtml(pageReq: PageRequest[_]) =
    HtmlForms(
      newUrlConfig(pageReq), pageReq.xsrfToken.value,
      pageReq.pageRoot, pageReq.permsOnPage)


  def queryStringAndHashToView(pageRoot: PageRoot, pageVersion: PageVersion,
        actionId: Option[String] = None, forceQuery: Boolean = false)
        : String = {
    var params = List[String]()
    if (!pageVersion.approved) params ::= "unapproved"
    if (!pageVersion.isLatest) params ::= "version="+ pageVersion.datiIsoStr
    if (pageRoot.isDefault && params.nonEmpty) params ::= "?view"
    else if (!pageRoot.isDefault) params ::= "?view=" + pageRoot.subId
    var queryString = params.mkString("&")
    if (queryString.isEmpty && forceQuery) queryString = "?"
    val hash = actionId.map("#post-"+ _) getOrElse ""
    queryString + hash
  }


  def localUrlTo(action: ViAc): String = {
    // - Add `?view=3` for templates, since they're on their own virtual page
    // not connected to the root post.
    // - Add `?view` to paths that end with .js or .css or Debiki will
    // render the page as text, not html. Currently done for all non-template
    // pages.
    val fragment = "/-"+ action.page.id
    val query =
      if (action.id == Page.TemplateId) "?view="+ Page.TemplateId
      else "?view"
    val hash = action match {
      case post: ViPo => "#post-"+ action.id
      case other: ViAc =>
        "" // SHOULD be: "#post-"+ action.target.id  -- but not implemented
    }
    fragment + query + hash
  }


  // COULD move to new object debiki.Utils?
  def isPublicArticlePage(pagePath: PagePath): Boolean =
    !isPrivatePage(pagePath) && !pagePath.isFolderOrIndexPage


  def isPrivatePage(pagePath: PagePath): Boolean =
    pagePath.isCodePage || pagePath.isTemplatePage || pagePath.isHiddenPage


  /**
   * Loads a person from the database, given a login id.
   * Verifies that the loaded ids match the ids encoded in the session identifier,
   * and throws a LoginNotFoundException on mismatch (happens e.g. if
   * I've connected the server to another backend, or access many backends
   * via the same hostname but different ports).
   */
  def loadIdentityAndUserOrThrow(sid: SidStatus, dao: TenantDao)
        : (Option[Identity], Option[User]) = {
    val identityAndUser = sid.loginId match {
      case None => (None, None)
      case Some(loginId) =>
        dao.loadIdtyAndUser(forLoginId = loginId)
        match {
          case Some((identity, user)) =>
            if (Some(user.id) == sid.roleId) {
              // Fine.
              (Some(identity), Some(user))
            }
            else {
              // Sometimes I access different databases via different ports,
              // but from the same host name. Browsers, however, usually ignore
              // port numbers when sending cookies. So they sometimes send
              // the wrong login-id and user-id to the server.
              Logger.warn(<commit_msg>Fix SidStatus.roleId/userId bug.<commit_after>/**
 * Copyright (c) 2012 <NAME> (born 1979)
 */

package controllers

import com.debiki.v0._
import debiki._
import debiki.DebikiHttp._
import com.debiki.v0.{liftweb => lw}
import java.{util => ju}
import play.api._
import play.api.data._
import play.api.data.Forms._
import play.api.libs.json.JsValue
import play.api.mvc.{Action => _, _}
import PageActions._
import Prelude._

object Utils extends Results with http.ContentTypes {


  /**
   * Prefixes `<!DOCTYPE html>` to the reply, otherwise Internet Explorer
   * enters the terrible Quirks mode. Also sets the Content-Type header.
   */
  def OkHtml(htmlNode: xml.NodeSeq) =
    Ok(serializeHtml(htmlNode)) as HTML

  def OkHtmlBody(bodyNodes: xml.NodeSeq) =
    OkHtml(<body>{bodyNodes}</body>)

  def ForbiddenHtml(htmlNode: xml.NodeSeq) =
    Forbidden(serializeHtml(htmlNode)) as HTML

  def BadReqHtml(htmlNode: xml.NodeSeq) =
    BadRequest(serializeHtml(htmlNode)) as HTML

  /**
   * Adds doctype and serializes to html using a real HTML5 writer.
   *
   * Some pros with using a real HTML5 writer: it won't escape '"' when found
   * inside script tags (which is very annoying when you e.g. copy-paste
   * Twitter's Follow Button <script> elem).
   */
  def serializeHtml(htmlNode: xml.NodeSeq): String = {
    require(htmlNode.size == 1)
    "<!DOCTYPE html>\n"+ lw.Html5.toString(htmlNode.head)
  }


  /**
   * Prefixes the JSON string with characters that prevents the JSON
   * from being parsed as Javascript from a <script> tag.
   * This supposedly thwarts a JSON vulnerability that allows third
   * party websites to turn your JSON resource URL into JSONP
   * request under some conditions, see:
   *   "JSON Vulnerability Protection", here:
   *      http://docs.angularjs.org/api/ng.$http
   *   and:
   *     http://haacked.com/archive/2008/11/20/
   *        anatomy-of-a-subtle-json-vulnerability.aspx
   * Debiki's Javascript, and AngularJS, strips the ")]}'," prefix before
   * parsing the JSON.
   */
  def OkSafeJson(json: JsValue) =
    Ok(")]}',\n" + json.toString) as JSON


  /**
   * Prefixes `<?xml version=...>` to the post data.
   */
  def OkXml(xmlNode: xml.NodeSeq, contentType: String = "text/xml") =
    Ok("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ xmlNode) as contentType


  def renderOrRedirect(pageReq: PageRequest[_]): PlainResult = {
    if (isAjax(pageReq.request)) {
      val pageHtml = pageReq.dao.renderTemplate(pageReq)
      Ok(pageHtml) as HTML
    } else {
      val queryString =
         queryStringAndHashToView(pageReq.pageRoot, pageReq.pageVersion)
      Redirect(pageReq.pagePath.path + queryString)
    }
  }


  def formHtml(pageReq: PageRequest[_]) =
    HtmlForms(
      newUrlConfig(pageReq), pageReq.xsrfToken.value,
      pageReq.pageRoot, pageReq.permsOnPage)


  def queryStringAndHashToView(pageRoot: PageRoot, pageVersion: PageVersion,
        actionId: Option[String] = None, forceQuery: Boolean = false)
        : String = {
    var params = List[String]()
    if (!pageVersion.approved) params ::= "unapproved"
    if (!pageVersion.isLatest) params ::= "version="+ pageVersion.datiIsoStr
    if (pageRoot.isDefault && params.nonEmpty) params ::= "?view"
    else if (!pageRoot.isDefault) params ::= "?view=" + pageRoot.subId
    var queryString = params.mkString("&")
    if (queryString.isEmpty && forceQuery) queryString = "?"
    val hash = actionId.map("#post-"+ _) getOrElse ""
    queryString + hash
  }


  def localUrlTo(action: ViAc): String = {
    // - Add `?view=3` for templates, since they're on their own virtual page
    // not connected to the root post.
    // - Add `?view` to paths that end with .js or .css or Debiki will
    // render the page as text, not html. Currently done for all non-template
    // pages.
    val fragment = "/-"+ action.page.id
    val query =
      if (action.id == Page.TemplateId) "?view="+ Page.TemplateId
      else "?view"
    val hash = action match {
      case post: ViPo => "#post-"+ action.id
      case other: ViAc =>
        "" // SHOULD be: "#post-"+ action.target.id  -- but not implemented
    }
    fragment + query + hash
  }


  // COULD move to new object debiki.Utils?
  def isPublicArticlePage(pagePath: PagePath): Boolean =
    !isPrivatePage(pagePath) && !pagePath.isFolderOrIndexPage


  def isPrivatePage(pagePath: PagePath): Boolean =
    pagePath.isCodePage || pagePath.isTemplatePage || pagePath.isHiddenPage


  /**
   * Loads a person from the database, given a login id.
   * Verifies that the loaded ids match the ids encoded in the session identifier,
   * and throws a LoginNotFoundException on mismatch (happens e.g. if
   * I've connected the server to another backend, or access many backends
   * via the same hostname but different ports).
   */
  def loadIdentityAndUserOrThrow(sid: SidStatus, dao: TenantDao)
        : (Option[Identity], Option[User]) = {
    val identityAndUser = sid.loginId match {
      case None => (None, None)
      case Some(loginId) =>
        dao.loadIdtyAndUser(forLoginId = loginId)
        match {
          case Some((identity, user)) =>
            if (Some(user.id) == sid.userId) {
              // Fine.
              (Some(identity), Some(user))
            }
            else {
              // Sometimes I access different databases via different ports,
              // but from the same host name. Browsers, however, usually ignore
              // port numbers when sending cookies. So they sometimes send
              // the wrong login-id and user-id to the server.
              Logger.warn(
{'fraction_non_alphanumeric': 0.11751583831533863, 'fraction_numerical': 0.004639957169626127, 'mean_word_length': 2.9106769016050245, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 4, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 22}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3366480', 'n_tokens_mistral': 3548, 'n_tokens_neox': 3386, 'n_words': 1021}