From 7e1100e9e939c9178b2aa3969349e9e8d34488bf Mon Sep 17 00:00:00 2001
From: Matt McCutchen <matt@mattmccutchen.net>
Date: Sat, 7 Feb 2009 19:00:09 -0500
Subject: [PATCH] gitweb: add $prevent_xss option to prevent XSS by repository
 content

Add a gitweb configuration variable $prevent_xss that disables features
to prevent content in repositories from launching cross-site scripting
(XSS) attacks in the gitweb domain.  Currently, this option makes gitweb
ignore README.html (a better solution may be worked out in the future)
and serve a blob_plain file of an untrusted type with
"Content-Disposition: attachment", which tells the browser not to show
the file at its original URL.

The XSS prevention is currently off by default.

Signed-off-by: Matt McCutchen <matt@mattmccutchen.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 gitweb/README      |  9 ++++++++-
 gitweb/gitweb.perl | 21 +++++++++++++++++++--
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/gitweb/README b/gitweb/README
index 825162a0b6..19ae28ef9b 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -214,6 +214,11 @@ not include variables usually directly set during build):
    Rename detection options for git-diff and git-diff-tree. By default
    ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
    set it to () if you don't want to have renames detection.
+ * $prevent_xss
+   If true, some gitweb features are disabled to prevent content in
+   repositories from launching cross-site scripting (XSS) attacks.  Set this
+   to true if you don't trust the content of your repositories. The default
+   is false.
 
 
 Projects list file format
@@ -260,7 +265,9 @@ You can use the following files in repository:
    A .html file (HTML fragment) which is included on the gitweb project
    summary page inside <div> block element. You can use it for longer
    description of a project, to provide links (for example to project's
-   homepage), etc.
+   homepage), etc. This is recognized only if XSS prevention is off
+   ($prevent_xss is false); a way to include a readme safely when XSS
+   prevention is on may be worked out in the future.
  * description (or gitweb.description)
    Short (shortened by default to 25 characters in the projects list page)
    single line description of a project (of a repository). Plain text file;
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 99f71b47c2..bdaa4e9463 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -132,6 +132,10 @@ our $fallback_encoding = 'latin1';
 # - one might want to include '-B' option, e.g. '-B', '-M'
 our @diff_opts = ('-M'); # taken from git_commit
 
+# Disables features that would allow repository owners to inject script into
+# the gitweb domain.
+our $prevent_xss = 0;
+
 # information about snapshot formats that gitweb is capable of serving
 our %known_snapshot_formats = (
 	# name => {
@@ -4494,7 +4498,9 @@ sub git_summary {
 
 	print "</table>\n";
 
-	if (-s "$projectroot/$project/README.html") {
+	# If XSS prevention is on, we don't include README.html.
+	# TODO: Allow a readme in some safe format.
+	if (!$prevent_xss && -s "$projectroot/$project/README.html") {
 		print "<div class=\"title\">readme</div>\n" .
 		      "<div class=\"readme\">\n";
 		insert_file("$projectroot/$project/README.html");
@@ -4739,10 +4745,21 @@ sub git_blob_plain {
 		$save_as .= '.txt';
 	}
 
+	# With XSS prevention on, blobs of all types except a few known safe
+	# ones are served with "Content-Disposition: attachment" to make sure
+	# they don't run in our security domain.  For certain image types,
+	# blob view writes an <img> tag referring to blob_plain view, and we
+	# want to be sure not to break that by serving the image as an
+	# attachment (though Firefox 3 doesn't seem to care).
+	my $sandbox = $prevent_xss &&
+		$type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+
 	print $cgi->header(
 		-type => $type,
 		-expires => $expires,
-		-content_disposition => 'inline; filename="' . $save_as . '"');
+		-content_disposition =>
+			($sandbox ? 'attachment' : 'inline')
+			. '; filename="' . $save_as . '"');
 	undef $/;
 	binmode STDOUT, ':raw';
 	print <$fd>;