All Downloads are FREE. Search and download functionalities are using the official Maven repository.

coursier.core.compatibility.package.scala Maven / Gradle / Ivy

The newest version!
package coursier.core

import java.io.CharArrayReader
import java.util.Locale
import javax.xml.parsers.ParserConfigurationException
import javax.xml.parsers.SAXParserFactory
import javax.xml.XMLConstants

import coursier.util.{SaxHandler, Xml}
import org.xml.sax
import org.xml.sax.InputSource
import org.xml.sax.helpers.DefaultHandler

import scala.collection.compat.immutable.LazyList
import scala.xml.{Attribute, Elem, MetaData, Null}

package object compatibility {

  implicit class RichChar(val c: Char) extends AnyVal {
    def letterOrDigit = c.isLetterOrDigit
    def letter        = c.isLetter
  }

  private val utf8Bom = "\ufeff"

  private lazy val throwExceptions = java.lang.Boolean.getBoolean("coursier.core.throw-exceptions")

  private def entityIdx(s: String, fromIdx: Int = 0): Option[(Int, Int)] = {

    var i     = fromIdx
    var found = Option.empty[(Int, Int)]
    while (found.isEmpty && i < s.length)
      if (s.charAt(i) == '&') {
        val start = i
        i += 1
        var isAlpha = true
        while (isAlpha && i < s.length) {
          val c = s.charAt(i)
          if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z'))
            isAlpha = false
          else
            i += 1
        }
        if (start + 1 < i && i < s.length) {
          assert(!isAlpha)
          if (s.charAt(i) == ';') {
            i += 1
            found = Some((start, i))
          }
        }
      }
      else
        i += 1

    found
  }

  private def substituteEntities(s: String): String = {

    val b      = new StringBuilder
    lazy val a = s.toCharArray

    var i = 0

    var j = 0
    while (j < s.length && j < utf8Bom.length && s.charAt(i) == utf8Bom.charAt(j))
      j += 1

    if (j == utf8Bom.length)
      i = j

    var found = Option.empty[(Int, Int)]
    while ({
      found = entityIdx(s, i)
      found.nonEmpty
    }) {
      val from = found.get._1
      val to   = found.get._2

      b.appendAll(a, i, from - i)

      val name        = s.substring(from, to)
      val replacement = Entities.map.getOrElse(name, name)
      b.appendAll(replacement)

      i = to
    }

    if (i == 0)
      s
    else
      b.appendAll(a, i, s.length - i).result()
  }

  def xmlPreprocess(s: String): String =
    substituteEntities(s)

  private final class XmlHandler(handler: SaxHandler) extends DefaultHandler {
    override def startElement(
      uri: String,
      localName: String,
      qName: String,
      attributes: sax.Attributes
    ): Unit =
      handler.startElement(qName)
    override def characters(ch: Array[Char], start: Int, length: Int): Unit =
      handler.characters(ch, start, length)
    override def endElement(uri: String, localName: String, qName: String): Unit =
      handler.endElement(qName)
  }

  private lazy val spf = {
    val spf0 = SAXParserFactory.newInstance()
    spf0.setNamespaceAware(false)

    // Fixing CVE-2022-46751: External Entity Reference Vulnerability
    def trySetFeature(feature: String, value: Boolean): Unit = {
      try spf0.setFeature(feature, value)
      catch {
        case _: ParserConfigurationException | _: sax.SAXNotRecognizedException | _: sax.SAXNotSupportedException =>
          ()
      }
    }
    // Allow doctype processing
    trySetFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
    // Process XML in accordance with the XML specification to avoid conditions such as denial of service attacks
    trySetFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
    // Disallow external entities
    trySetFeature("http://xml.org/sax/features/external-general-entities", false)
    trySetFeature("http://xml.org/sax/features/external-parameter-entities", false)
    // Allow external dtd
    trySetFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true)
    // Disallow XInclude processing
    try spf0.setXIncludeAware(false)
    catch {
      case e: UnsupportedOperationException => ()
    }

    spf0
  }

  def xmlParseSax(str: String, handler: SaxHandler): handler.type = {

    val str0 = xmlPreprocess(str)

    val saxParser = spf.newSAXParser()
    val xmlReader = saxParser.getXMLReader
    xmlReader.setContentHandler(new XmlHandler(handler))
    xmlReader.parse(new InputSource(new CharArrayReader(str0.toCharArray)))
    handler
  }

  def xmlParseDom(s: String): Either[String, Xml.Node] = {

    val content = xmlPreprocess(s)

    val parse =
      try Right(scala.xml.XML.loadString(content))
      catch {
        case e: Exception if !throwExceptions =>
          Left(e.toString + Option(e.getMessage).fold("")(" (" + _ + ")"))
      }

    parse.map(xmlFromElem)
  }

  def xmlFromElem(elem: Elem): Xml.Node = {

    def fromNode(node: scala.xml.Node): Xml.Node =
      new Xml.Node {
        lazy val attributes = {
          def helper(m: MetaData): LazyList[(String, String, String)] =
            m match {
              case Null => LazyList.empty
              case attr =>
                val pre = attr match {
                  case a: Attribute => Option(node.getNamespace(a.pre)).getOrElse("")
                  case _            => ""
                }

                val value = attr.value.collect {
                  case scala.xml.Text(t) => t
                }.mkString("")

                (pre, attr.key, value) #:: helper(m.next)
            }

          helper(node.attributes).toVector
        }
        def label       = node.label
        def children    = node.child.map(fromNode).toSeq
        def isText      = node match { case _: scala.xml.Text => true; case _ => false }
        def textContent = node.text
        def isElement   = node match { case _: scala.xml.Elem => true; case _ => false }

        override def toString = node.toString
      }

    fromNode(elem)
  }

  def xmlParse(s: String): Either[String, Xml.Node] = {

    val content = substituteEntities(s)

    val parse =
      try Right(scala.xml.XML.loadString(content))
      catch {
        case e: Exception if !throwExceptions =>
          Left(e.toString + Option(e.getMessage).fold("")(" (" + _ + ")"))
      }

    parse.map(xmlFromElem)
  }

  def encodeURIComponent(s: String): String =
    new java.net.URI(null, null, null, -1, s, null, null).toASCIIString

  def regexLookbehind: String = "<="

  def coloredOutput: Boolean =
    // Same as coursier.paths.Util.useColorOutput()
    System.getenv("INSIDE_EMACS") == null &&
    Option(System.getenv("COURSIER_PROGRESS"))
      .map(_.toLowerCase(Locale.ROOT))
      .collect {
        case "true" | "1" | "enable"   => true
        case "false" | "0" | "disable" => false
      }
      .getOrElse {
        System.getenv("COURSIER_NO_TERM") == null
      }

  @deprecated("Unused internally, likely to be removed in the future", "2.0.0-RC6-19")
  def hasConsole: Boolean =
    System.console() != null

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy