diff --git a/src/main/resources/update/gitbucket-core_4.36.xml b/src/main/resources/update/gitbucket-core_4.36.xml new file mode 100644 index 0000000..8e7c9bf --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.36.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 3d63b0c..a1adfd6 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -118,4 +118,5 @@ new Version("4.35.1"), new Version("4.35.2"), new Version("4.35.3"), + new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")) ) diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index 9b3f0e4..7a650f4 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -253,7 +253,7 @@ repository: RepositoryService.RepositoryInfo ): Unit = { JGitUtil.getObjectLoaderFromId(git, objectId) { loader => - contentType = FileUtil.getSafeMimeType(path) + contentType = FileUtil.getSafeMimeType(path, repository.repository.options.safeMode) if (loader.isLarge) { response.setContentLength(loader.getSize.toInt) diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index 36a4775..e66b1ee 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -40,7 +40,7 @@ .writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get()) session += Keys.Session.Upload(fileId) -> file.name }, - FileUtil.isImage + FileUtil.isImage(_) ) } @@ -191,9 +191,9 @@ } } - private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = + private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: (String) => Boolean) = fileParams.get("file") match { - case Some(file) if mimeTypeChcker(file.name) => + case Some(file) if mimeTypeChecker(file.name) => val fileId = FileUtil.generateFileId f(file, fileId) contentType = "text/plain" diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 7a4d08d..2d57129 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -57,7 +57,8 @@ externalWikiUrl: Option[String], allowFork: Boolean, mergeOptions: Seq[String], - defaultMergeOption: String + defaultMergeOption: String, + safeMode: Boolean ) val optionsForm = mapping( @@ -69,7 +70,8 @@ "externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))), "allowFork" -> trim(label("Allow Forking", boolean())), "mergeOptions" -> mergeOptions, - "defaultMergeOption" -> trim(label("Default merge strategy", text(required))) + "defaultMergeOption" -> trim(label("Default merge strategy", text(required))), + "safeMode" -> trim(label("XSS protection", boolean())) )(OptionsForm.apply).verifying { form => if (!form.mergeOptions.contains(form.defaultMergeOption)) { Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.") @@ -150,7 +152,8 @@ form.externalWikiUrl, form.allowFork, form.mergeOptions, - form.defaultMergeOption + form.defaultMergeOption, + form.safeMode ) flash.update("info", "Repository settings has been updated.") redirect(s"/${repository.owner}/${repository.name}/settings/options") diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 7660665..e57fdaf 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -423,7 +423,7 @@ repository = repository, pathList = paths.take(paths.size - 1).toList, fileName = Some(paths.last), - content = JGitUtil.getContentInfo(git, path, objectId), + content = JGitUtil.getContentInfo(git, path, objectId, repository.repository.options.safeMode), protectedBranch = protectedBranch, commit = revCommit.getName, newLineMode = info.newLineMode, @@ -448,7 +448,7 @@ repository = repository, pathList = paths.take(paths.size - 1).toList, fileName = paths.last, - content = JGitUtil.getContentInfo(git, path, objectId), + content = JGitUtil.getContentInfo(git, path, objectId, repository.repository.options.safeMode), commit = revCommit.getName ) } getOrElse NotFound() @@ -693,7 +693,7 @@ branch = id, repository = repository, pathList = path.split("/").toList, - content = JGitUtil.getContentInfo(git, path, objectId), + content = JGitUtil.getContentInfo(git, path, objectId, repository.repository.options.safeMode), latestCommit = new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), isBlame = request.paths(2) == "blame", diff --git a/src/main/scala/gitbucket/core/model/Repository.scala b/src/main/scala/gitbucket/core/model/Repository.scala index b536c82..358a889 100644 --- a/src/main/scala/gitbucket/core/model/Repository.scala +++ b/src/main/scala/gitbucket/core/model/Repository.scala @@ -24,6 +24,7 @@ val allowFork = column[Boolean]("ALLOW_FORK") val mergeOptions = column[String]("MERGE_OPTIONS") val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION") + val safeMode = column[Boolean]("SAFE_MODE") def * = ( @@ -41,7 +42,16 @@ parentUserName.?, parentRepositoryName.? ), - (issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption) + ( + issuesOption, + externalIssuesUrl.?, + wikiOption, + externalWikiUrl.?, + allowFork, + mergeOptions, + defaultMergeOption, + safeMode + ) ).shaped.<>( { case (repository, options) => @@ -112,5 +122,6 @@ externalWikiUrl: Option[String], allowFork: Boolean, mergeOptions: String, - defaultMergeOption: String + defaultMergeOption: String, + safeMode: Boolean ) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 7b11310..da7ef91 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -60,7 +60,8 @@ externalWikiUrl = None, allowFork = true, mergeOptions = "merge-commit,squash,rebase", - defaultMergeOption = "merge-commit" + defaultMergeOption = "merge-commit", + safeMode = true ) ) @@ -460,7 +461,7 @@ .filter { case (t1, t2) => t2.removed === false.bind } .map { case (t1, t2) => t1 } // for Normal Users - case Some(x) if (!x.isAdmin || limit) => + case Some(x) => Repositories .join(Accounts) .on(_.userName === _.userName) @@ -550,7 +551,8 @@ externalWikiUrl: Option[String], allowFork: Boolean, mergeOptions: Seq[String], - defaultMergeOption: String + defaultMergeOption: String, + safeMode: Boolean )(implicit s: Session): Unit = { Repositories @@ -566,6 +568,7 @@ r.allowFork, r.mergeOptions, r.defaultMergeOption, + r.safeMode, r.updatedDate ) } @@ -579,6 +582,7 @@ allowFork, mergeOptions.mkString(","), defaultMergeOption, + safeMode, currentDate ) } diff --git a/src/main/scala/gitbucket/core/util/FileUtil.scala b/src/main/scala/gitbucket/core/util/FileUtil.scala index 8b4780a..04be98d 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -24,13 +24,18 @@ } } - def getSafeMimeType(name: String): String = { - getMimeType(name) + def getSafeMimeType(name: String, safeMode: Boolean = true): String = { + val mimeType = getMimeType(name) .replace("text/html", "text/plain") - .replace("image/svg+xml", "text/plain; charset=UTF-8") + + if (safeMode) { + mimeType.replace("image/svg+xml", "text/plain; charset=UTF-8") + } else { + mimeType + } } - def isImage(name: String): Boolean = getSafeMimeType(name).startsWith("image/") + def isImage(name: String, safeMode: Boolean = true): Boolean = getSafeMimeType(name, safeMode).startsWith("image/") def isLarge(size: Long): Boolean = (size > 1024 * 1000) diff --git a/src/main/scala/gitbucket/core/util/JGitUtil.scala b/src/main/scala/gitbucket/core/util/JGitUtil.scala index 74864de..05fe9fb 100644 --- a/src/main/scala/gitbucket/core/util/JGitUtil.scala +++ b/src/main/scala/gitbucket/core/util/JGitUtil.scala @@ -1047,13 +1047,13 @@ !loader.isLarge && new String(loader.getBytes(), "UTF-8").startsWith("version https://git-lfs.github.com/spec/v1") } - def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = { + def getContentInfo(git: Git, path: String, objectId: ObjectId, safeMode: Boolean): ContentInfo = { // Viewer Using.resource(git.getRepository.getObjectDatabase) { db => val loader = db.open(objectId) val isLfs = isLfsPointer(loader) val large = FileUtil.isLarge(loader.getSize) - val viewer = if (FileUtil.isImage(path)) "image" else if (large) "large" else "other" + val viewer = if (FileUtil.isImage(path, safeMode)) "image" else if (large) "large" else "other" val bytes = if (viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None val size = Some(getContentSize(loader)) diff --git a/src/main/twirl/gitbucket/core/settings/options.scala.html b/src/main/twirl/gitbucket/core/settings/options.scala.html index 504b615..e967ef3 100644 --- a/src/main/twirl/gitbucket/core/settings/options.scala.html +++ b/src/main/twirl/gitbucket/core/settings/options.scala.html @@ -33,8 +33,6 @@ You choose who can see and commit to this repository. - -
+
diff --git a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala index 237d8d0..781f34b 100644 --- a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala +++ b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala @@ -2,7 +2,6 @@ import java.util.{Calendar, Date, TimeZone} -import gitbucket.core.api.ApiBranchProtection.EnforcementLevel import gitbucket.core.model._ import gitbucket.core.plugin.PluginInfo import gitbucket.core.service.ProtectedBranchService.ProtectedBranchInfo @@ -69,7 +68,8 @@ externalWikiUrl = Some("https://external.com/gitbucket"), allowFork = true, mergeOptions = "merge-commit,squash,rebase", - defaultMergeOption = "merge-commit" + defaultMergeOption = "merge-commit", + safeMode = true ) ) diff --git a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala index 027f595..a6e2c6b 100644 --- a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala @@ -239,7 +239,8 @@ externalWikiUrl = None, allowFork = true, mergeOptions = "merge-commit,squash,rebase", - defaultMergeOption = "merge-commit" + defaultMergeOption = "merge-commit", + safeMode = true ) ), issueCount = 0, diff --git a/src/test/scala/gitbucket/core/util/FileUtilSpec.scala b/src/test/scala/gitbucket/core/util/FileUtilSpec.scala new file mode 100644 index 0000000..2eb65e5 --- /dev/null +++ b/src/test/scala/gitbucket/core/util/FileUtilSpec.scala @@ -0,0 +1,15 @@ +package gitbucket.core.util + +import org.scalatest.funsuite.AnyFunSuite + +class FileUtilSpec extends AnyFunSuite { + + test("getSafeMimeType") { + val contentType1 = FileUtil.getSafeMimeType("test.svg", true) + assert(contentType1 == "text/plain; charset=UTF-8") + + val contentType2 = FileUtil.getSafeMimeType("test.svg", false) + assert(contentType2 == "image/svg+xml") + } + +} diff --git a/src/test/scala/gitbucket/core/util/GitSpecUtil.scala b/src/test/scala/gitbucket/core/util/GitSpecUtil.scala index d556817..11ebdd5 100644 --- a/src/test/scala/gitbucket/core/util/GitSpecUtil.scala +++ b/src/test/scala/gitbucket/core/util/GitSpecUtil.scala @@ -93,7 +93,7 @@ } _getPathObjectId } - JGitUtil.getContentInfo(git, path, objectId) + JGitUtil.getContentInfo(git, path, objectId, true) } def mergeAndCommit(git: Git, into: String, branch: String, message: String = null): Unit = {