diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 3f12900..e50b583 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -1,5 +1,7 @@ package gitbucket.core.controller +import java.io.File + import gitbucket.core.account.html import gitbucket.core.helper import gitbucket.core.model._ @@ -284,7 +286,9 @@ response.setDateHeader("Last-Modified", account.updatedDate.getTime) account.image .map { image => - Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))) + Some( + RawData(FileUtil.getMimeType(image), new File(getUserUploadDir(userName), FileUtil.checkFilename(image))) + ) } .getOrElse { if (account.isGroupAccount) { diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index b80fa1e..d1b034e 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -1,6 +1,6 @@ package gitbucket.core.controller -import java.io.FileInputStream +import java.io.{File, FileInputStream} import gitbucket.core.api.{ApiError, JsonFormat} import gitbucket.core.model.Account @@ -327,7 +327,7 @@ protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit = if (clearImage) { getAccountByUserName(userName).flatMap(_.image).map { image => - new java.io.File(getUserUploadDir(userName), image).delete() + new File(getUserUploadDir(userName), FileUtil.checkFilename(image)).delete() updateAvatarImage(userName, None) } } else { @@ -338,9 +338,9 @@ uploadDir.mkdirs() } Thumbnails - .of(new java.io.File(getTemporaryDir(session.getId), fileId)) + .of(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId))) .size(324, 324) - .toFile(new java.io.File(uploadDir, filename)) + .toFile(new File(uploadDir, FileUtil.checkFilename(filename))) updateAvatarImage(userName, Some(filename)) } } diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index 3df5d8e..c751776 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -1,7 +1,9 @@ package gitbucket.core.controller +import java.io.File + import gitbucket.core.model.Account -import gitbucket.core.service.{AccountService, RepositoryService, ReleaseService} +import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService} import gitbucket.core.servlet.Database import gitbucket.core.util._ import gitbucket.core.util.SyntaxSugars._ @@ -31,7 +33,8 @@ post("/image") { execute( { (file, fileId) => - FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get) + FileUtils + .writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get) session += Keys.Session.Upload(fileId) -> file.name }, FileUtil.isImage @@ -41,7 +44,8 @@ post("/tmp") { execute( { (file, fileId) => - FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get) + FileUtils + .writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get) session += Keys.Session.Upload(fileId) -> file.name }, _ => true @@ -52,9 +56,9 @@ execute( { (file, fileId) => FileUtils.writeByteArrayToFile( - new java.io.File( + new File( getAttachedDir(params("owner"), params("repository")), - fileId + "." + FileUtil.getExtension(file.getName) + FileUtil.checkFilename(fileId + "." + FileUtil.getExtension(file.getName)) ), file.get ) @@ -129,12 +133,15 @@ val owner = params("owner") val repository = params("repository") val tag = params("tag") - execute({ (file, fileId) => - FileUtils.writeByteArrayToFile( - new java.io.File(getReleaseFilesDir(owner, repository), tag + "/" + fileId), - file.get - ) - }, _ => true) + execute( + { (file, fileId) => + FileUtils.writeByteArrayToFile( + new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)), + file.get + ) + }, + _ => true + ) } .getOrElse(BadRequest()) } diff --git a/src/main/scala/gitbucket/core/controller/ReleasesController.scala b/src/main/scala/gitbucket/core/controller/ReleasesController.scala index 8ffd023..2b37a49 100644 --- a/src/main/scala/gitbucket/core/controller/ReleasesController.scala +++ b/src/main/scala/gitbucket/core/controller/ReleasesController.scala @@ -80,7 +80,7 @@ response.setHeader("Content-Disposition", s"attachment; filename=${asset.label}") RawData( FileUtil.getMimeType(asset.label), - new File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId) + new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(tagName + "/" + fileId)) ) }).getOrElse(NotFound()) }) @@ -111,7 +111,10 @@ files.foreach { case (fileId, fileName) => val size = - new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length + new File( + getReleaseFilesDir(repository.owner, repository.name), + FileUtil.checkFilename(tagName + "/" + fileId) + ).length createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) } @@ -153,15 +156,18 @@ files.foreach { case (fileId, fileName) => val size = - new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length + new File( + getReleaseFilesDir(repository.owner, repository.name), + FileUtil.checkFilename(tagName + "/" + fileId) + ).length createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) } assets.foreach { asset => if (!files.exists { case (fileId, _) => fileId == asset.fileName }) { - val file = new java.io.File( + val file = new File( getReleaseFilesDir(repository.owner, repository.name), - release.tag + "/" + asset.fileName + FileUtil.checkFilename(release.tag + "/" + asset.fileName) ) FileUtils.forceDelete(file) } @@ -175,7 +181,9 @@ post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository => val tagName = params("tag") getRelease(repository.owner, repository.name, tagName).foreach { release => - FileUtils.deleteDirectory(new File(getReleaseFilesDir(repository.owner, repository.name), release.tag)) + FileUtils.deleteDirectory( + new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag)) + ) } deleteRelease(repository.owner, repository.name, tagName) redirect(s"/${repository.owner}/${repository.name}/releases") diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 73b308e..f4660ae 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -896,7 +896,8 @@ } newFiles.foreach { file => - val bytes = FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), file.id)) + val bytes = + FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id))) builder.add( JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)) ) diff --git a/src/main/scala/gitbucket/core/service/CommitsService.scala b/src/main/scala/gitbucket/core/service/CommitsService.scala index 237762d..8f81b76 100644 --- a/src/main/scala/gitbucket/core/service/CommitsService.scala +++ b/src/main/scala/gitbucket/core/service/CommitsService.scala @@ -7,7 +7,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.util.Directory._ -import gitbucket.core.util.StringUtil +import gitbucket.core.util.{FileUtil, StringUtil} import org.apache.commons.io.FileUtils trait CommitsService { @@ -83,7 +83,7 @@ newLine: Option[Int], diffJson: String ): Unit = { - val dir = new java.io.File(getDiffDir(owner, repository), commitId) + val dir = new File(getDiffDir(owner, repository), FileUtil.checkFilename(commitId)) if (!dir.exists) { dir.mkdirs() } @@ -99,7 +99,7 @@ oldLine: Option[Int], newLine: Option[Int] ): Option[String] = { - val dir = new java.io.File(getDiffDir(owner, repository), commitId) + val dir = new File(getDiffDir(owner, repository), FileUtil.checkFilename(commitId)) val file = diffFile(dir, fileName, oldLine, newLine) if (file.exists) { Option(FileUtils.readFileToString(file, "UTF-8")) diff --git a/src/main/scala/gitbucket/core/util/FileUtil.scala b/src/main/scala/gitbucket/core/util/FileUtil.scala index d399f77..0a668fe 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -52,7 +52,7 @@ } def getLfsFilePath(owner: String, repository: String, oid: String): String = - Directory.getLfsDir(owner, repository) + "/" + oid + Directory.getLfsDir(owner, repository) + "/" + checkFilename(oid) def readableSize(size: Long): String = FileUtils.byteCountToDisplaySize(size) @@ -76,6 +76,16 @@ file } + /** + * Create an instance of java.io.File safely. + */ + def checkFilename(name: String): String = { + if (name.contains("..")) { + throw new IllegalArgumentException(s"Invalid file name: ${name}") + } + name + } + lazy val MaxFileSize = if (System.getProperty("gitbucket.maxFileSize") != null) System.getProperty("gitbucket.maxFileSize").toLong