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

io.joern.rubysrc2cpg.passes.DependencyPass.scala Maven / Gradle / Ivy

package io.joern.rubysrc2cpg.passes

import io.shiftleft.codepropertygraph.generated.Cpg
import io.shiftleft.codepropertygraph.generated.nodes.{ConfigFile, NewDependency}
import io.shiftleft.passes.ForkJoinParallelCpgPass
import io.shiftleft.semanticcpg.language.*

/** Parses the dependencies from the `Gemfile.lock` and `Gemfile` files. This pass uses a dependency node to store the
  * Ruby Gems resolver with the `name` as `Defines.Resolver` and `version` as the URL.
  * @param cpg
  *   the graph.
  */
class DependencyPass(cpg: Cpg) extends ForkJoinParallelCpgPass[ConfigFile](cpg) {

  /** @return
    *   the Gemfiles, while preferring `Gemfile.lock` files if present.
    */
  override def generateParts(): Array[ConfigFile] = {
    val allGemfiles = cpg.configFile.name(".*Gemfile(\\.lock)?").toArray
    if (allGemfiles.count(_.name.endsWith("Gemfile.lock")) > 0) allGemfiles.filter(_.name.endsWith("Gemfile.lock"))
    else allGemfiles
  }

  override def runOnPart(builder: DiffGraphBuilder, configFile: ConfigFile): Unit = {
    if (configFile.name.endsWith("Gemfile.lock")) parseLockFile(builder, configFile)
    else parseGemfile(builder, configFile)
  }

  private def parseLockFile(builder: DiffGraphBuilder, configFile: ConfigFile): Unit = {
    var inGem       = false
    var inSpecs     = false
    var specTabSize = 0
    val specRegex   = " *([\\w_-]+) \\(([\\d.]+)\\)".r
    configFile.content.linesIterator.foreach {
      // Check if we're under the GEM header
      case "GEM" => inGem = true
      // Check if we've left the GEM header
      case "" if inGem => inGem = false
      // Check if we're under a new header
      case line if !line.isBlank && !line.charAt(0).isWhitespace => inGem = false
      // If we're under the GEM header, process line
      case gemLine if inGem =>
        val currentTabSize = gemLine.takeWhile(_.isWhitespace).length
        gemLine match {
          case remote if remote.trim.startsWith("remote:") =>
            val depNode = NewDependency()
              .name(Defines.Resolver)
              .version(remote.stripSuffix("remote:").trim)
            builder.addNode(depNode)
          case specs if specs.trim.startsWith("specs:") =>
            inSpecs = true
          case specRegex(dep, version) if inSpecs && (specTabSize == currentTabSize || specTabSize == 0) =>
            specTabSize = currentTabSize
            val depNode = NewDependency()
              .name(dep)
              .version(version)
            builder.addNode(depNode)
          case _ => // do nothing
        }
      case _ => // do nothing
    }
  }

  private def parseGemfile(builder: DiffGraphBuilder, configFile: ConfigFile): Unit = {
    val gemRegex    = "gem [\"']([\\w_-]+)[\"'][, ]*(?:[\"']([\\w.]+)[\"'])?".r
    val sourceRegex = "source [\"']([/:\\w\\d_.-]+)[\"']".r
    configFile.content.linesIterator.filterNot(_.isBlank).foreach {
      case gemRegex(dep, null) =>
        builder.addNode(
          NewDependency()
            .name(dep)
            .version("")
        )
      case gemRegex(dep, version) =>
        builder.addNode(
          NewDependency()
            .name(dep)
            .version(version)
        )
      case sourceRegex(url) =>
        builder.addNode(
          NewDependency()
            .name(Defines.Resolver)
            .version(url)
        )
      case _ => // do nothing
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy