# Send appropriate charset
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
+ # Redirect to urlbase/sslbase if we are not viewing an attachment.
+ if (use_attachbase() && i_am_cgi()) {
+ my $cgi_file = $self->url('-path_info' => 0, '-query' => 0, '-relative' => 1);
+ $cgi_file =~ s/\?$//;
+ my $urlbase = Bugzilla->params->{'urlbase'};
+ my $sslbase = Bugzilla->params->{'sslbase'};
+ my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/;
+ if ($cgi_file ne 'attachment.cgi' && $self->self_url !~ /$path_regexp/) {
+ $self->redirect_to_urlbase;
+ }
+ }
+
# Check for errors
# All of the Bugzilla code wants to do this, so do it here instead of
# in each script
exit;
}
+# Redirect to the urlbase version of the current URL.
+sub redirect_to_urlbase {
+ my $self = shift;
+ my $path = $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
+ print $self->redirect('-location' => correct_urlbase() . $path);
+ exit;
+}
+
1;
__END__
It takes an optional argument which will be used as the base URL. If $baseurl
is not provided, the current URL is used.
+=item C<redirect_to_urlbase>
+
+Redirects from the current URL to one prefixed by the urlbase parameter.
+
=back
=head1 SEE ALSO
# Greg Hendricks <ghendricks@novell.com>
# Frédéric Buclin <LpSolit@gmail.com>
# Marc Schumann <wurblzap@gmail.com>
+# Byron Jones <bugzilla@glob.com.au>
################################################################################
# Script Initialization
use Bugzilla::Token;
use Bugzilla::Keyword;
-Bugzilla->login();
-
# For most scripts we don't make $cgi and $template global variables. But
# when preparing Bugzilla for mod_perl, this script used these
# variables in so many subroutines that it was easier to just
# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'view';
+# You must use the appropriate urlbase/sslbase param when doing anything
+# but viewing an attachment.
+if ($action ne 'view') {
+ my $urlbase = Bugzilla->params->{'urlbase'};
+ my $sslbase = Bugzilla->params->{'sslbase'};
+ my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/;
+ if (use_attachbase() && $cgi->self_url !~ /$path_regexp/) {
+ $cgi->redirect_to_urlbase;
+ }
+ Bugzilla->login();
+}
+
# Determine if PatchReader is installed
eval {
require PatchReader;
$vars->{'patchviewerinstalled'} = 1;
};
-if ($action eq "view")
+# When viewing an attachment, do not request credentials if we are on
+# the alternate host. Let view() decide when to call Bugzilla->login.
+if ($action eq "view")
{
view();
}
# Validates an attachment ID. Optionally takes a parameter of a form
# variable name that contains the ID to be validated. If not specified,
# uses 'id'.
-#
+# If the second parameter is true, the attachment ID will be validated,
+# however the current user's access to the attachment will not be checked.
# Will throw an error if 1) attachment ID is not a valid number,
# 2) attachment does not exist, or 3) user isn't allowed to access the
# attachment.
# Returns an attachment object.
sub validateID {
- my $param = @_ ? $_[0] : 'id';
- my $user = Bugzilla->user;
+ my($param, $dont_validate_access) = @_;
+ $param ||= 'id';
# If we're not doing interdiffs, check if id wasn't specified and
# prompt them with a page that allows them to choose an attachment.
my $attachment = Bugzilla::Attachment->get($attach_id)
|| ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
+ return $attachment if ($dont_validate_access || check_can_access($attachment));
+}
+
+# Make sure the current user has access to the specified attachment.
+sub check_can_access {
+ my $attachment = shift;
+ my $user = Bugzilla->user;
+
# Make sure the user is authorized to access this attachment's bug.
ValidateBugID($attachment->bug_id);
if ($attachment->isprivate && $user->id != $attachment->attacher->id && !$user->is_insider) {
ThrowUserError('auth_failure', {action => 'access',
object => 'attachment'});
}
- return $attachment;
+ return 1;
+}
+
+# Determines if the attachment is public -- that is, if users who are
+# not logged in have access to the attachment
+sub attachmentIsPublic {
+ my $attachment = shift;
+
+ return 0 if Bugzilla->params->{'requirelogin'};
+ return 0 if $attachment->isprivate;
+
+ my $anon_user = new Bugzilla::User;
+ return $anon_user->can_see_bug($attachment->bug_id);
}
# Validates format of a diff/interdiff. Takes a list as an parameter, which
# Display an attachment.
sub view {
- # Retrieve and validate parameters
- my $attachment = validateID();
+ my $attachment;
+
+ if (use_attachbase()) {
+ $attachment = validateID(undef, 1);
+ # Replace %bugid% by the ID of the bug the attachment belongs to, if present.
+ my $attachbase = Bugzilla->params->{'attachment_base'};
+ my $bug_id = $attachment->bug_id;
+ $attachbase =~ s/%bugid%/$bug_id/;
+ my $path = 'attachment.cgi?id=' . $attachment->id;
+
+ # Make sure the attachment is served from the correct server.
+ if ($cgi->self_url !~ /^\Q$attachbase\E/) {
+ # We couldn't call Bugzilla->login earlier as we first had to make sure
+ # we were not going to request credentials on the alternate host.
+ Bugzilla->login();
+ if (attachmentIsPublic($attachment)) {
+ # No need for a token; redirect to attachment base.
+ print $cgi->redirect(-location => $attachbase . $path);
+ exit;
+ } else {
+ # Make sure the user can view the attachment.
+ check_can_access($attachment);
+ # Create a token and redirect.
+ my $token = url_quote(issue_session_token($attachment->id));
+ print $cgi->redirect(-location => $attachbase . "$path&t=$token");
+ exit;
+ }
+ } else {
+ # No need to validate the token for public attachments. We cannot request
+ # credentials as we are on the alternate host.
+ if (!attachmentIsPublic($attachment)) {
+ my $token = $cgi->param('t');
+ my ($userid, undef, $token_attach_id) = Bugzilla::Token::GetTokenData($token);
+ unless ($userid
+ && detaint_natural($token_attach_id)
+ && ($token_attach_id == $attachment->id))
+ {
+ # Not a valid token.
+ print $cgi->redirect('-location' => correct_urlbase() . $path);
+ exit;
+ }
+ # Change current user without creating cookies.
+ Bugzilla->set_user(new Bugzilla::User($userid));
+ # Tokens are single use only, delete it.
+ delete_token($token);
+ }
+ }
+ } else {
+ # No alternate host is used. Request credentials if required.
+ Bugzilla->login();
+ $attachment = validateID();
+ }
+
+ # At this point, Bugzilla->login has been called if it had to.
my $contenttype = $attachment->contenttype;
my $filename = $attachment->filename;
%]
[% param_descs = {
+ attachment_base => "It is possible for a malicious attachment to steal your " _
+ "cookies or access other attachments to perform an attack " _
+ "on the user.<p>" _
+ "If you would like additional security on attachments " _
+ "to avoid this, set this parameter to an alternate URL " _
+ "for your $terms.Bugzilla that is not the same as " _
+ "<tt>urlbase</tt> or <tt>sslbase</tt>. That is, a different " _
+ "domain name that resolves to this exact same $terms.Bugzilla " _
+ "installation.<p>" _
+ "For added security, you can insert <tt>%bugid%</tt> into " _
+ "the URL, which will be replaced with the ID of the current " _
+ "$terms.bug that the attachment is on, when you access " _
+ "an attachment. This will limit attachments to accessing " _
+ "only other attachments on the same ${terms.bug}. " _
+ "Remember, though, that all those possible domain names " _
+ "(such as <tt>1234.your.domain.com</tt>) must point to " _
+ "this same $terms.Bugzilla instance."
+
allow_attachment_deletion => "If this option is on, administrators will be able to delete " _
"the content of attachments.",
doc_section = "attachments.html"
%]
+[%# No need to display the Diff button and iframe if the attachment is not a patch. %]
+[% patchviewerinstalled = (patchviewerinstalled && attachment.ispatch) %]
+
<script type="text/javascript">
<!--
var prev_mode = 'raw';
var has_viewed_as_diff = 0;
function editAsComment()
{
- // Get the content of the document as a string.
- var viewFrame = document.getElementById('viewFrame');
- var aSerializer = new XMLSerializer();
- var contentDocument = viewFrame.contentDocument;
- var theContent = aSerializer.serializeToString(contentDocument);
-
- // If this is a plaintext document, remove cruft that Mozilla adds
- // because it treats it as an HTML document with a big PRE section.
- // http://bugzilla.mozilla.org/show_bug.cgi?id=86012
- var contentType = '[% attachment.contenttype FILTER js %]';
- if ( contentType == 'text/plain' )
- {
- theContent = theContent.replace( /^<html><head\/?><body><pre>/i , "" );
- theContent = theContent.replace( /<\/pre><\/body><\/html>$/i , "" );
- theContent = theContent.replace( /</gi , "<" );
- theContent = theContent.replace( />/gi , ">" );
- theContent = theContent.replace( /&/gi , "&" );
- }
-
- // Add mail-style quote indicators (>) to the beginning of each line.
- // ".*\n" matches lines that end with a newline, while ".+" matches
- // the rare situation in which the last line of a file does not end
- // with a newline.
- theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
-
switchToMode('edit');
-
- // Copy the contents of the diff into the textarea
- var editFrame = document.getElementById('editFrame');
- editFrame.value = theContent + "\n\n";
-
has_edited = 1;
}
function undoEditAsComment()
minrows = 10
cols = 80
wrap = 'soft'
+ defaultcontent = (attachment.contenttype.match('^text\/')) ?
+ attachment.data.replace('(.*\n|.+)', '>$1') : undef
%]
<iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" style="height: 400px; width: 100%;">
<b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.