package controllers
import java.net.URL
import javax.inject.Inject
import com.github.nscala_time.time.Imports._
import com.ysoft.odc.SecureXml
import modules.TemplateCustomization
import org.joda.time.DateTime
import play.api.Configuration
import play.api.i18n.MessagesApi
import play.api.mvc.{Action, AnyContent, Result}
import play.twirl.api.Html
import services.{DependencyNotFoundException, OdcDbService, OdcService, SingleLibraryScanResult}
import views.html.DefaultRequest
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
class LibraryAdvisor @Inject() (
config: Configuration,
odcServiceOption: Option[OdcService],
odcDbService: OdcDbService,
val messagesApi: MessagesApi,
val env: AuthEnv,
val templateCustomization: TemplateCustomization
) (implicit ec: ExecutionContext) extends AuthenticatedController
{
import secureRequestConversion._
private def withOdc(f: OdcService => Future[Result])(implicit defaultRequest: DefaultRequest) = {
odcServiceOption.fold(Future.successful(InternalServerError(views.html.libraryAdvisor.notEnabled())))(odcService =>
f(odcService)
)
}
private val InputParsers = Seq[(OdcService, String) => Option[Either[Future[SingleLibraryScanResult], String]]](
(odcService, xmlString) => {
val triedElem = Try {
SecureXml.loadString(xmlString)
}
triedElem.toOption.map{ xml =>
xml.label match {
case "dependency" =>
/*
Maven POM, e.g.:
com.google.code.gson
gson
2.3.1
*/
val groupId = (xml \ "groupId").text
val artifactId = (xml \ "artifactId").text
val version = (xml \ "version").text
Left(odcService.scanMaven(groupId, artifactId, version))
case other =>
Right(s"Unknown root XML element: $other")
}
}
},
(odcService, urlString) => {
Try{new URL(urlString)}.toOption.map{url =>
url.getHost match {
case "www.mvnrepository.com" | "mvnrepository.com" =>
// https://www.mvnrepository.com/artifact/ch.qos.logback/logback-classic/0.9.10
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic/0.9.10
url.getPath.split('/') match {
case Array("", "artifact", groupId, artifactId, version) =>
Left(odcService.scanMaven(groupId, artifactId, version))
case _ =>
Right("Unknown path for mvnrepository.com: Expected https://mvnrepository.com/artifact///")
}
case "www.nuget.org" | "preview.nuget.org" =>
// https://www.nuget.org/packages/Newtonsoft.Json/9.0.1
url.getPath.split('/') match {
case Array("", "packages", packageName, version) => Left(odcService.scanDotNet(packageName, version))
case _ => Right("Unknown path for nuget.org: Expected https://www.nuget.org/packages//")
}
case otherHost => Right(s"Unknown host – there is no rule how to get library identification from its path: $otherHost")
}
}
}
)
def index(dependency: Option[String]): Action[AnyContent] = ReadAction.async{ implicit req =>
withOdc{ odcService =>
Future.successful(Ok(views.html.libraryAdvisor.scanLibrary(dependency, Seq(
Html("<dependency>…</dependency> – Maven POM format"),
Html("https://mvnrepository.com/artifact/groupId/artifactId/version"),
Html("https://www.nuget.org/packages/package/version")
))))
}
}
//noinspection TypeAnnotation
def scan() = ReadAction.async(parse.json[String]){ implicit req =>
withOdc{ odcService =>
val now = DateTime.now()
val oldDataThreshold = 2.days
val lastDbUpdateFuture = odcDbService.loadLastDbUpdate()
val isOldFuture = lastDbUpdateFuture.map{ lastUpdate => now - oldDataThreshold > lastUpdate}
val response = InputParsers.toStream.map(_(odcService, req.body)).find(_.nonEmpty).flatten match{
case None => Future.successful(Ok(views.html.libraryAdvisor.scanInputError("Unknown input format")))
case Some(Right(message)) => Future.successful(Ok(views.html.libraryAdvisor.scanInputError(s"Unknown input format: $message")))
case Some(Left(resFuture)) =>
for{
res <- resFuture
isOld <- isOldFuture
} yield Ok(views.html.libraryAdvisor.scanResults(isOld, res))
}
response.recover{
case DependencyNotFoundException(dependency) =>
NotFound(views.html.libraryAdvisor.notFound(dependency))
}.map { _.withHeaders("Content-type" -> "text/plain; charset=utf-8")}
}
}
}