~ubuntu-branches/ubuntu/edgy/bugzilla/edgy

« back to all changes in this revision

Viewing changes to attachment.cgi

  • Committer: Bazaar Package Importer
  • Author(s): Alexis Sukrieh
  • Date: 2005-10-03 16:51:01 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051003165101-38n0y5qofd68vole
Tags: 2.18.4-1
* New upstream minor release
  + Fixed a security issue: It was possible to bypass the "user
    visibility groups" restrictions if user-matching was turned on
    in "substring" mode.
  + Fixed a security issue: config.cgi exposed information to users who
    weren't logged in, even when "requirelogin" was turned on in Bugzilla.
  (closes: #331206)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bonsaitools/bin/perl -wT
 
1
#!/usr/bin/perl -wT
2
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
3
3
#
4
4
# The contents of this file are subject to the Mozilla Public
20
20
#
21
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
22
22
#                 Myk Melez <myk@mozilla.org>
 
23
#                 Daniel Raichle <draichle@gmx.net>
 
24
#                 Dave Miller <justdave@syndicomm.com>
 
25
#                 Alexander J. Vincent <ajvincent@juno.com>
23
26
 
24
27
################################################################################
25
28
# Script Initialization
26
29
################################################################################
27
30
 
28
31
# Make it harder for us to do dangerous things in Perl.
29
 
use diagnostics;
30
32
use strict;
31
33
 
32
 
use lib '/usr/share/bugzilla/lib/';
 
34
use lib qw(.);
33
35
 
34
36
use vars qw(
35
37
  $template
39
41
# Include the Bugzilla CGI and general utility library.
40
42
require "CGI.pl";
41
43
 
42
 
# Establish a connection to the database backend.
43
 
ConnectToDatabase();
 
44
# Use these modules to handle flags.
 
45
use Bugzilla::Constants;
 
46
use Bugzilla::Flag; 
 
47
use Bugzilla::FlagType; 
 
48
use Bugzilla::User;
 
49
use Bugzilla::Util;
44
50
 
45
51
# Check whether or not the user is logged in and, if so, set the $::userid 
46
 
# and $::usergroupset variables.
47
 
quietly_check_login();
 
52
Bugzilla->login();
 
53
 
 
54
# The ID of the bug to which the attachment is attached.  Gets set
 
55
# by validateID() (which validates the attachment ID, not the bug ID, but has
 
56
# to check if the user is authorized to access this attachment) and is used 
 
57
# by Flag:: and FlagType::validate() to ensure the requestee (if any) for a 
 
58
# requested flag is authorized to see the bug in question.  Note: This should 
 
59
# really be defined just above validateID() itself, but it's used in the main 
 
60
# body of the script before that function is defined, so we define it up here 
 
61
# instead.  We should move the validation into each function and then move this
 
62
# to just above validateID().
 
63
my $bugid;
 
64
 
 
65
my $cgi = Bugzilla->cgi;
48
66
 
49
67
################################################################################
50
68
# Main Body Execution
51
69
################################################################################
52
70
 
53
 
# All calls to this script should contain an "action" variable whose value
54
 
# determines what the user wants to do.  The code below checks the value of
55
 
# that variable and runs the appropriate code.
 
71
# All calls to this script should contain an "action" variable whose
 
72
# value determines what the user wants to do.  The code below checks
 
73
# the value of that variable and runs the appropriate code. If none is
 
74
# supplied, we default to 'view'.
56
75
 
57
76
# Determine whether to use the action specified by the user or the default.
58
77
my $action = $::FORM{'action'} || 'view';
59
78
 
60
79
if ($action eq "view")  
61
 
62
 
  validateID();
63
 
  view(); 
 
80
{
 
81
  validateID();
 
82
  view();
 
83
}
 
84
elsif ($action eq "interdiff")
 
85
{
 
86
  validateID('oldid');
 
87
  validateID('newid');
 
88
  validateFormat("html", "raw");
 
89
  validateContext();
 
90
  interdiff();
 
91
}
 
92
elsif ($action eq "diff")
 
93
{
 
94
  validateID();
 
95
  validateFormat("html", "raw");
 
96
  validateContext();
 
97
  diff();
64
98
}
65
99
elsif ($action eq "viewall") 
66
100
69
103
}
70
104
elsif ($action eq "enter") 
71
105
72
 
  confirm_login();
 
106
  Bugzilla->login(LOGIN_REQUIRED);
73
107
  ValidateBugID($::FORM{'bugid'});
 
108
  validateCanChangeBug($::FORM{'bugid'});
74
109
  enter(); 
75
110
}
76
111
elsif ($action eq "insert")
77
112
{
78
 
  confirm_login();
 
113
  Bugzilla->login(LOGIN_REQUIRED);
79
114
  ValidateBugID($::FORM{'bugid'});
 
115
  validateCanChangeBug($::FORM{'bugid'});
80
116
  ValidateComment($::FORM{'comment'});
81
117
  validateFilename();
82
 
  validateData();
 
118
  validateIsPatch();
 
119
  my $data = validateData();
83
120
  validateDescription();
84
 
  validateIsPatch();
85
121
  validateContentType() unless $::FORM{'ispatch'};
86
122
  validateObsolete() if $::FORM{'obsolete'};
87
 
  insert();
 
123
  insert($data);
88
124
}
89
125
elsif ($action eq "edit") 
90
126
91
 
  quietly_check_login();
92
127
  validateID();
93
128
  validateCanEdit($::FORM{'id'});
94
129
  edit(); 
95
130
}
96
131
elsif ($action eq "update") 
97
132
98
 
  confirm_login();
 
133
  Bugzilla->login(LOGIN_REQUIRED);
99
134
  ValidateComment($::FORM{'comment'});
100
135
  validateID();
101
136
  validateCanEdit($::FORM{'id'});
 
137
  validateCanChangeAttachment($::FORM{'id'});
102
138
  validateDescription();
103
139
  validateIsPatch();
104
140
  validateContentType() unless $::FORM{'ispatch'};
105
141
  validateIsObsolete();
106
 
  validateStatuses();
 
142
  validatePrivate();
 
143
  
 
144
  # The order of these function calls is important, as both Flag::validate
 
145
  # and FlagType::validate assume User::match_field has ensured that the values
 
146
  # in the requestee fields are legitimate user email addresses.
 
147
  Bugzilla::User::match_field({ '^requestee(_type)?-(\d+)$' => 
 
148
                                    { 'type' => 'single' } });
 
149
  Bugzilla::Flag::validate(\%::FORM, $bugid, $::FORM{'id'});
 
150
  Bugzilla::FlagType::validate(\%::FORM, $bugid, $::FORM{'id'});
 
151
  
107
152
  update();
108
153
}
109
154
else 
110
155
111
 
  DisplayError("I could not figure out what you wanted to do.")
 
156
  ThrowCodeError("unknown_action", { action => $action });
112
157
}
113
158
 
114
159
exit;
119
164
 
120
165
sub validateID
121
166
{
122
 
  # Validate the value of the "id" form field, which must contain an
123
 
  # integer that is the ID of an existing attachment.
124
 
 
125
 
  detaint_natural($::FORM{'id'})
126
 
    || DisplayError("You did not enter a valid attachment number.") 
127
 
      && exit;
 
167
    my $param = @_ ? $_[0] : 'id';
 
168
 
 
169
    # If we're not doing interdiffs, check if id wasn't specified and
 
170
    # prompt them with a page that allows them to choose an attachment.
 
171
    # Happens when calling plain attachment.cgi from the urlbar directly
 
172
    if ($param eq 'id' && !$cgi->param('id')) {
 
173
 
 
174
        print Bugzilla->cgi->header();
 
175
        $template->process("attachment/choose.html.tmpl", $vars) ||
 
176
            ThrowTemplateError($template->error());
 
177
        exit;
 
178
    }
 
179
    
 
180
    my $attach_id = $cgi->param($param);
 
181
 
 
182
    # Validate the specified attachment id. detaint kills $attach_id if
 
183
    # non-natural, so use the original value from $cgi in our exception
 
184
    # message here.
 
185
    detaint_natural($attach_id)
 
186
     || ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
128
187
  
129
 
  # Make sure the attachment exists in the database.
130
 
  SendSQL("SELECT bug_id FROM attachments WHERE attach_id = $::FORM{'id'}");
131
 
  MoreSQLData()
132
 
    || DisplayError("Attachment #$::FORM{'id'} does not exist.") 
133
 
    && exit;
134
 
 
135
 
  # Make sure the user is authorized to access this attachment's bug.
136
 
  my ($bugid) = FetchSQLData();
137
 
  ValidateBugID($bugid);
 
188
    # Make sure the attachment exists in the database.
 
189
    SendSQL("SELECT bug_id, isprivate FROM attachments WHERE attach_id = $attach_id");
 
190
    MoreSQLData()
 
191
      || ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
 
192
 
 
193
    # Make sure the user is authorized to access this attachment's bug.
 
194
    ($bugid, my $isprivate) = FetchSQLData();
 
195
    ValidateBugID($bugid);
 
196
    if (($isprivate > 0 ) && Param("insidergroup") && 
 
197
        !(UserInGroup(Param("insidergroup")))) {
 
198
        ThrowUserError("attachment_access_denied");
 
199
    }
 
200
 
 
201
    # XXX shim code, kill $::FORM
 
202
    $::FORM{$param} = $attach_id;
 
203
}
 
204
 
 
205
sub validateFormat
 
206
{
 
207
  # receives a list of legal formats; first item is a default
 
208
  my $format = $cgi->param('format') || $_[0];
 
209
  if ( lsearch(\@_, $format) == -1)
 
210
  {
 
211
     ThrowUserError("invalid_format", { format  => $format, formats => \@_ });
 
212
  }
 
213
 
 
214
  # XXX shim code, kill $::FORM
 
215
  $::FORM{'format'} = $format;
 
216
}
 
217
 
 
218
sub validateContext
 
219
{
 
220
  my $context = $cgi->param('context') || "patch";
 
221
  if ($context ne "file" && $context ne "patch") {
 
222
    detaint_natural($context)
 
223
      || ThrowUserError("invalid_context", { context => $cgi->param('context') });
 
224
  }
 
225
  # XXX shim code, kill $::FORM
 
226
  $::FORM{'context'} = $context;
138
227
}
139
228
 
140
229
sub validateCanEdit
142
231
    my ($attach_id) = (@_);
143
232
 
144
233
    # If the user is not logged in, claim that they can edit. This allows
145
 
    # the edit scrren to be displayed to people who aren't logged in.
 
234
    # the edit screen to be displayed to people who aren't logged in.
146
235
    # People not logged in can't actually commit changes, because that code
147
 
    # calls confirm_login, not quietly_check_login, before calling this sub
 
236
    # calls Bugzilla->login with LOGIN_REQUIRED, not with LOGIN_NORMAL,
 
237
    # before calling this sub
148
238
    return if $::userid == 0;
149
239
 
150
240
    # People in editbugs can edit all attachments
155
245
            "attach_id = $attach_id AND submitter_id = $::userid");
156
246
 
157
247
    FetchSQLData()
158
 
      || DisplayError("You are not authorised to edit attachment #$attach_id")
159
 
      && exit;
 
248
      || ThrowUserError("illegal_attachment_edit",
 
249
                        { attach_id => $attach_id });
 
250
}
 
251
 
 
252
sub validateCanChangeAttachment 
 
253
{
 
254
    my ($attachid) = @_;
 
255
    SendSQL("SELECT product_id
 
256
             FROM attachments, bugs 
 
257
             WHERE attach_id = $attachid
 
258
             AND bugs.bug_id = attachments.bug_id");
 
259
    my $productid = FetchOneColumn();
 
260
    CanEditProductId($productid)
 
261
      || ThrowUserError("illegal_attachment_edit",
 
262
                        { attach_id => $attachid });
 
263
}
 
264
 
 
265
sub validateCanChangeBug
 
266
{
 
267
    my ($bugid) = @_;
 
268
    SendSQL("SELECT product_id
 
269
             FROM bugs 
 
270
             WHERE bug_id = $bugid");
 
271
    my $productid = FetchOneColumn();
 
272
    CanEditProductId($productid)
 
273
      || ThrowUserError("illegal_attachment_edit_bug",
 
274
                        { bug_id => $bugid });
160
275
}
161
276
 
162
277
sub validateDescription
163
278
{
164
279
  $::FORM{'description'}
165
 
    || DisplayError("You must enter a description for the attachment.")
166
 
      && exit;
 
280
    || ThrowUserError("missing_attachment_description");
167
281
}
168
282
 
169
283
sub validateIsPatch
181
295
{
182
296
  if (!$::FORM{'contenttypemethod'})
183
297
  {
184
 
    DisplayError("You must choose a method for determining the content type,
185
 
      either <em>auto-detect</em>, <em>select from list</em>, or <em>enter 
186
 
      manually</em>.");
187
 
    exit;
 
298
    ThrowUserError("missing_content_type_method");
188
299
  }
189
300
  elsif ($::FORM{'contenttypemethod'} eq 'autodetect')
190
301
  {
 
302
    my $contenttype = $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
191
303
    # The user asked us to auto-detect the content type, so use the type
192
304
    # specified in the HTTP request headers.
193
 
    if ( !$::FILE{'data'}->{'contenttype'} )
 
305
    if ( !$contenttype )
194
306
    {
195
 
      DisplayError("You asked Bugzilla to auto-detect the content type, but
196
 
        your browser did not specify a content type when uploading the file, 
197
 
        so you must enter a content type manually.");
198
 
      exit;
 
307
      ThrowUserError("missing_content_type");
199
308
    }
200
 
    $::FORM{'contenttype'} = $::FILE{'data'}->{'contenttype'};
 
309
    $::FORM{'contenttype'} = $contenttype;
201
310
  }
202
311
  elsif ($::FORM{'contenttypemethod'} eq 'list')
203
312
  {
211
320
  }
212
321
  else
213
322
  {
214
 
    my $htmlcontenttypemethod = html_quote($::FORM{'contenttypemethod'});
215
 
    DisplayError("Your form submission got corrupted somehow.  The <em>content
216
 
      method</em> field, which specifies how the content type gets determined,
217
 
      should have been either <em>autodetect</em>, <em>list</em>, 
218
 
      or <em>manual</em>, but was instead <em>$htmlcontenttypemethod</em>.");
219
 
    exit;
 
323
    ThrowCodeError("illegal_content_type_method",
 
324
                   { contenttypemethod => $::FORM{'contenttypemethod'} });
220
325
  }
221
326
 
222
327
  if ( $::FORM{'contenttype'} !~ /^(application|audio|image|message|model|multipart|text|video)\/.+$/ )
223
328
  {
224
 
    my $htmlcontenttype = html_quote($::FORM{'contenttype'});
225
 
    DisplayError("The content type <em>$htmlcontenttype</em> is invalid.
226
 
      Valid types must be of the form <em>foo/bar</em> where <em>foo</em> 
227
 
      is either <em>application, audio, image, message, model, multipart, 
228
 
      text,</em> or <em>video</em>.");
229
 
    exit;
 
329
    ThrowUserError("invalid_content_type",
 
330
                   { contenttype => $::FORM{'contenttype'} });
230
331
  }
231
332
}
232
333
 
238
339
  $::FORM{'isobsolete'} = $::FORM{'isobsolete'} ? 1 : 0;
239
340
}
240
341
 
241
 
sub validateStatuses
 
342
sub validatePrivate
242
343
{
243
 
  # Get a list of attachment statuses that are valid for this attachment.
244
 
  PushGlobalSQLState();
245
 
  SendSQL("SELECT  attachstatusdefs.id
246
 
           FROM    attachments, bugs, attachstatusdefs
247
 
           WHERE   attachments.attach_id = $::FORM{'id'}
248
 
           AND     attachments.bug_id = bugs.bug_id
249
 
           AND     attachstatusdefs.product = bugs.product");
250
 
  my @statusdefs;
251
 
  push(@statusdefs, FetchSQLData()) while MoreSQLData();
252
 
  PopGlobalSQLState();
253
 
  
254
 
  foreach my $status (@{$::MFORM{'status'}})
255
 
  {
256
 
    grep($_ == $status, @statusdefs)
257
 
      || DisplayError("One of the statuses you entered is not a valid status
258
 
                       for this attachment.")
259
 
        && exit;
260
 
    # We have tested that the status is valid, so it can be detainted
261
 
    detaint_natural($status);
262
 
  }
 
344
    # Set the isprivate flag to zero if it is undefined, since the UI uses
 
345
    # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
 
346
    # do not get sent in HTML requests.
 
347
    $::FORM{'isprivate'} = $::FORM{'isprivate'} ? 1 : 0;
263
348
}
264
349
 
265
350
sub validateData
266
351
{
267
 
  $::FORM{'data'}
268
 
    || DisplayError("The file you are trying to attach is empty!")
269
 
      && exit;
270
 
 
271
 
  my $len = length($::FORM{'data'});
272
 
 
273
 
  my $maxpatchsize = Param('maxpatchsize');
274
 
  my $maxattachmentsize = Param('maxattachmentsize');
275
 
  
276
 
  # Makes sure the attachment does not exceed either the "maxpatchsize" or 
277
 
  # the "maxattachmentsize" parameter.
278
 
  if ( $::FORM{'ispatch'} && $maxpatchsize && $len > $maxpatchsize*1024 )
 
352
  my $maxsize = $::FORM{'ispatch'} ? Param('maxpatchsize') : Param('maxattachmentsize');
 
353
  $maxsize *= 1024; # Convert from K
 
354
 
 
355
  my $fh = $cgi->upload('data');
 
356
  my $data;
 
357
 
 
358
  # We could get away with reading only as much as required, except that then
 
359
  # we wouldn't have a size to print to the error handler below.
279
360
  {
280
 
    my $lenkb = sprintf("%.0f", $len/1024);
281
 
    DisplayError("The file you are trying to attach is ${lenkb} kilobytes (KB) in size.  
282
 
                  Patches cannot be more than ${maxpatchsize}KB in size.
283
 
                  Try breaking your patch into several pieces.");
284
 
    exit;
285
 
  } elsif ( !$::FORM{'ispatch'} && $maxattachmentsize && $len > $maxattachmentsize*1024 ) {
286
 
    my $lenkb = sprintf("%.0f", $len/1024);
287
 
    DisplayError("The file you are trying to attach is ${lenkb} kilobytes (KB) in size.  
288
 
                  Non-patch attachments cannot be more than ${maxattachmentsize}KB.
289
 
                  If your attachment is an image, try converting it to a compressable
290
 
                  format like JPG or PNG, or put it elsewhere on the web and
291
 
                  link to it from the bug's URL field or in a comment on the bug.");
292
 
    exit;
293
 
  }
 
361
      # enable 'slurp' mode
 
362
      local $/;
 
363
      $data = <$fh>;
 
364
  }
 
365
 
 
366
  $data
 
367
    || ThrowUserError("zero_length_file");
 
368
 
 
369
  # Make sure the attachment does not exceed the maximum permitted size
 
370
  my $len = length($data);
 
371
  if ($maxsize && $len > $maxsize) {
 
372
      my $vars = { filesize => sprintf("%.0f", $len/1024) };
 
373
      if ( $::FORM{'ispatch'} ) {
 
374
          ThrowUserError("patch_too_large", $vars);
 
375
      } else {
 
376
          ThrowUserError("file_too_large", $vars);
 
377
      }
 
378
  }
 
379
 
 
380
  return $data;
294
381
}
295
382
 
 
383
my $filename;
296
384
sub validateFilename
297
385
{
298
 
  defined $::FILE{'data'}
299
 
    || DisplayError("You did not specify a file to attach.")
300
 
      && exit;
 
386
  defined $cgi->upload('data')
 
387
    || ThrowUserError("file_not_specified");
 
388
 
 
389
  $filename = $cgi->upload('data');
 
390
  
 
391
  # Remove path info (if any) from the file name.  The browser should do this
 
392
  # for us, but some are buggy.  This may not work on Mac file names and could
 
393
  # mess up file names with slashes in them, but them's the breaks.  We only
 
394
  # use this as a hint to users downloading attachments anyway, so it's not 
 
395
  # a big deal if it munges incorrectly occasionally.
 
396
  $filename =~ s/^.*[\/\\]//;
 
397
 
 
398
  # Truncate the filename to 100 characters, counting from the end of the string
 
399
  # to make sure we keep the filename extension.
 
400
  $filename = substr($filename, -100, 100);
301
401
}
302
402
 
303
403
sub validateObsolete
305
405
  # Make sure the attachment id is valid and the user has permissions to view
306
406
  # the bug to which it is attached.
307
407
  foreach my $attachid (@{$::MFORM{'obsolete'}}) {
 
408
    my $vars = {};
 
409
    $vars->{'attach_id'} = $attachid;
 
410
    
308
411
    detaint_natural($attachid)
309
 
      || DisplayError("The attachment number of one of the attachments 
310
 
           you wanted to obsolete is invalid.") 
311
 
        && exit;
 
412
      || ThrowCodeError("invalid_attach_id_to_obsolete", $vars);
312
413
  
313
414
    SendSQL("SELECT bug_id, isobsolete, description 
314
415
             FROM attachments WHERE attach_id = $attachid");
315
416
 
316
417
    # Make sure the attachment exists in the database.
317
418
    MoreSQLData()
318
 
      || DisplayError("Attachment #$attachid does not exist.") 
319
 
        && exit;
 
419
      || ThrowUserError("invalid_attach_id", $vars);
320
420
 
321
421
    my ($bugid, $isobsolete, $description) = FetchSQLData();
322
422
 
 
423
    $vars->{'description'} = $description;
 
424
    
323
425
    if ($bugid != $::FORM{'bugid'})
324
426
    {
325
 
      $description = html_quote($description);
326
 
      DisplayError("Attachment #$attachid ($description) is attached 
327
 
        to bug #$bugid, but you tried to flag it as obsolete while
328
 
        creating a new attachment to bug #$::FORM{'bugid'}.");
329
 
      exit;
 
427
      $vars->{'my_bug_id'} = $::FORM{'bugid'};
 
428
      $vars->{'attach_bug_id'} = $bugid;
 
429
      ThrowCodeError("mismatched_bug_ids_on_obsolete", $vars);
330
430
    }
331
431
 
332
432
    if ( $isobsolete )
333
433
    {
334
 
      $description = html_quote($description);
335
 
      DisplayError("Attachment #$attachid ($description) is already obsolete.");
336
 
      exit;
 
434
      ThrowCodeError("attachment_already_obsolete", $vars);
337
435
    }
338
436
 
339
437
    # Check that the user can modify this attachment
340
438
    validateCanEdit($attachid);
341
439
  }
342
 
 
 
440
}
 
441
 
 
442
# Returns 1 if the parameter is a content-type viewable in this browser
 
443
# Note that we don't use $cgi->Accept()'s ability to check if a content-type
 
444
# matches, because this will return a value even if it's matched by the generic
 
445
# */* which most browsers add to the end of their Accept: headers.
 
446
sub isViewable
 
447
{
 
448
  my $contenttype = trim(shift);
 
449
    
 
450
  # We assume we can view all text and image types  
 
451
  if ($contenttype =~ /^(text|image)\//) {
 
452
    return 1;
 
453
  }
 
454
  
 
455
  # Mozilla can view XUL. Note the trailing slash on the Gecko detection to
 
456
  # avoid sending XUL to Safari.
 
457
  if (($contenttype =~ /^application\/vnd\.mozilla\./) &&
 
458
      ($cgi->user_agent() =~ /Gecko\//))
 
459
  {
 
460
    return 1;
 
461
  }
 
462
 
 
463
  # If it's not one of the above types, we check the Accept: header for any 
 
464
  # types mentioned explicitly.
 
465
  my $accept = join(",", $cgi->Accept());
 
466
  
 
467
  if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/) {
 
468
    return 1;
 
469
  }
 
470
  
 
471
  return 0;
343
472
}
344
473
 
345
474
################################################################################
348
477
 
349
478
sub view
350
479
{
351
 
  # Display an attachment.
352
 
 
353
 
  # Retrieve the attachment content and its content type from the database.
354
 
  SendSQL("SELECT mimetype, thedata FROM attachments WHERE attach_id = $::FORM{'id'}");
355
 
  my ($contenttype, $thedata) = FetchSQLData();
356
 
    
357
 
  # Return the appropriate HTTP response headers.
358
 
  print "Content-Type: $contenttype\n\n";
359
 
 
360
 
  print $thedata;
361
 
}
362
 
 
 
480
    # Display an attachment.
 
481
 
 
482
    # Retrieve the attachment content and its content type from the database.
 
483
    SendSQL("SELECT mimetype, filename, thedata FROM attachments WHERE attach_id = $::FORM{'id'}");
 
484
    my ($contenttype, $filename, $thedata) = FetchSQLData();
 
485
   
 
486
    # Bug 111522: allow overriding content-type manually in the posted $::FORM.
 
487
    if ($::FORM{'content_type'})
 
488
    {
 
489
        $::FORM{'contenttypemethod'} = 'manual';
 
490
        $::FORM{'contenttypeentry'} = $::FORM{'content_type'};
 
491
        validateContentType();
 
492
        $contenttype = $::FORM{'content_type'};
 
493
    }
 
494
 
 
495
    # Return the appropriate HTTP response headers.
 
496
    $filename =~ s/^.*[\/\\]//;
 
497
    my $filesize = length($thedata);
 
498
 
 
499
    # escape quotes and backslashes in the filename, per RFCs 2045/822
 
500
    $filename =~ s/\\/\\\\/g; # escape backslashes
 
501
    $filename =~ s/"/\\"/g; # escape quotes
 
502
 
 
503
    print Bugzilla->cgi->header(-type=>"$contenttype; name=\"$filename\"",
 
504
                                -content_disposition=> "inline; filename=\"$filename\"",
 
505
                                -content_length => $filesize);
 
506
 
 
507
    print $thedata;
 
508
}
 
509
 
 
510
sub interdiff
 
511
{
 
512
  # Get old patch data
 
513
  my ($old_bugid, $old_description, $old_filename, $old_file_list) =
 
514
      get_unified_diff($::FORM{'oldid'});
 
515
 
 
516
  # Get new patch data
 
517
  my ($new_bugid, $new_description, $new_filename, $new_file_list) =
 
518
      get_unified_diff($::FORM{'newid'});
 
519
 
 
520
  my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
 
521
 
 
522
  #
 
523
  # send through interdiff, send output directly to template
 
524
  #
 
525
  # Must hack path so that interdiff will work.
 
526
  #
 
527
  $ENV{'PATH'} = $::diffpath;
 
528
  open my $interdiff_fh, "$::interdiffbin $old_filename $new_filename|";
 
529
  binmode $interdiff_fh;
 
530
  my ($reader, $last_reader) = setup_patch_readers("");
 
531
  if ($::FORM{'format'} eq "raw")
 
532
  {
 
533
    require PatchReader::DiffPrinter::raw;
 
534
    $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
 
535
    # Actually print out the patch
 
536
    print $cgi->header(-type => 'text/plain',
 
537
                       -expires => '+3M');
 
538
  }
 
539
  else
 
540
  {
 
541
    $vars->{warning} = $warning if $warning;
 
542
    $vars->{bugid} = $new_bugid;
 
543
    $vars->{oldid} = $::FORM{'oldid'};
 
544
    $vars->{old_desc} = $old_description;
 
545
    $vars->{newid} = $::FORM{'newid'};
 
546
    $vars->{new_desc} = $new_description;
 
547
    delete $vars->{attachid};
 
548
    delete $vars->{do_context};
 
549
    delete $vars->{context};
 
550
    setup_template_patch_reader($last_reader);
 
551
  }
 
552
  $reader->iterate_fh($interdiff_fh, "interdiff #$::FORM{'oldid'} #$::FORM{'newid'}");
 
553
  close $interdiff_fh;
 
554
  $ENV{'PATH'} = '';
 
555
 
 
556
  #
 
557
  # Delete temporary files
 
558
  #
 
559
  unlink($old_filename) or warn "Could not unlink $old_filename: $!";
 
560
  unlink($new_filename) or warn "Could not unlink $new_filename: $!";
 
561
}
 
562
 
 
563
sub get_unified_diff
 
564
{
 
565
  my ($id) = @_;
 
566
 
 
567
  # Bring in the modules we need
 
568
  require PatchReader::Raw;
 
569
  require PatchReader::FixPatchRoot;
 
570
  require PatchReader::DiffPrinter::raw;
 
571
  require PatchReader::PatchInfoGrabber;
 
572
  require File::Temp;
 
573
 
 
574
  # Get the patch
 
575
  SendSQL("SELECT bug_id, description, ispatch, thedata FROM attachments WHERE attach_id = $id");
 
576
  my ($bugid, $description, $ispatch, $thedata) = FetchSQLData();
 
577
  if (!$ispatch) {
 
578
    $vars->{'attach_id'} = $id;
 
579
    ThrowCodeError("must_be_patch");
 
580
  }
 
581
 
 
582
  # Reads in the patch, converting to unified diff in a temp file
 
583
  my $reader = new PatchReader::Raw;
 
584
  my $last_reader = $reader;
 
585
 
 
586
  # fixes patch root (makes canonical if possible)
 
587
  if (Param('cvsroot')) {
 
588
    my $fix_patch_root = new PatchReader::FixPatchRoot(Param('cvsroot'));
 
589
    $last_reader->sends_data_to($fix_patch_root);
 
590
    $last_reader = $fix_patch_root;
 
591
  }
 
592
 
 
593
  # Grabs the patch file info
 
594
  my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
 
595
  $last_reader->sends_data_to($patch_info_grabber);
 
596
  $last_reader = $patch_info_grabber;
 
597
 
 
598
  # Prints out to temporary file
 
599
  my ($fh, $filename) = File::Temp::tempfile();
 
600
  my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
 
601
  $last_reader->sends_data_to($raw_printer);
 
602
  $last_reader = $raw_printer;
 
603
 
 
604
  # Iterate!
 
605
  $reader->iterate_string($id, $thedata);
 
606
 
 
607
  return ($bugid, $description, $filename, $patch_info_grabber->patch_info()->{files});
 
608
}
 
609
 
 
610
sub warn_if_interdiff_might_fail {
 
611
  my ($old_file_list, $new_file_list) = @_;
 
612
  # Verify that the list of files diffed is the same
 
613
  my @old_files = sort keys %{$old_file_list};
 
614
  my @new_files = sort keys %{$new_file_list};
 
615
  if (@old_files != @new_files ||
 
616
      join(' ', @old_files) ne join(' ', @new_files)) {
 
617
    return "interdiff1";
 
618
  }
 
619
 
 
620
  # Verify that the revisions in the files are the same
 
621
  foreach my $file (keys %{$old_file_list}) {
 
622
    if ($old_file_list->{$file}{old_revision} ne
 
623
        $new_file_list->{$file}{old_revision}) {
 
624
      return "interdiff2";
 
625
    }
 
626
  }
 
627
 
 
628
  return undef;
 
629
}
 
630
 
 
631
sub setup_patch_readers {
 
632
  my ($diff_root) = @_;
 
633
 
 
634
  #
 
635
  # Parameters:
 
636
  # format=raw|html
 
637
  # context=patch|file|0-n
 
638
  # collapsed=0|1
 
639
  # headers=0|1
 
640
  #
 
641
 
 
642
  # Define the patch readers
 
643
  # The reader that reads the patch in (whatever its format)
 
644
  require PatchReader::Raw;
 
645
  my $reader = new PatchReader::Raw;
 
646
  my $last_reader = $reader;
 
647
  # Fix the patch root if we have a cvs root
 
648
  if (Param('cvsroot'))
 
649
  {
 
650
    require PatchReader::FixPatchRoot;
 
651
    $last_reader->sends_data_to(new PatchReader::FixPatchRoot(Param('cvsroot')));
 
652
    $last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
 
653
    $last_reader = $last_reader->sends_data_to;
 
654
  }
 
655
  # Add in cvs context if we have the necessary info to do it
 
656
  if ($::FORM{'context'} ne "patch" && $::cvsbin && Param('cvsroot_get'))
 
657
  {
 
658
    require PatchReader::AddCVSContext;
 
659
    $last_reader->sends_data_to(
 
660
        new PatchReader::AddCVSContext($::FORM{'context'},
 
661
                                         Param('cvsroot_get')));
 
662
    $last_reader = $last_reader->sends_data_to;
 
663
  }
 
664
  return ($reader, $last_reader);
 
665
}
 
666
 
 
667
sub setup_template_patch_reader
 
668
{
 
669
  my ($last_reader) = @_;
 
670
 
 
671
  require PatchReader::DiffPrinter::template;
 
672
 
 
673
  my $format = $::FORM{'format'};
 
674
 
 
675
  # Define the vars for templates
 
676
  if (defined($::FORM{'headers'})) {
 
677
    $vars->{headers} = $::FORM{'headers'};
 
678
  } else {
 
679
    $vars->{headers} = 1 if !defined($::FORM{'headers'});
 
680
  }
 
681
  $vars->{collapsed} = $::FORM{'collapsed'};
 
682
  $vars->{context} = $::FORM{'context'};
 
683
  $vars->{do_context} = $::cvsbin && Param('cvsroot_get') && !$vars->{'newid'};
 
684
 
 
685
  # Print everything out
 
686
  print $cgi->header(-type => 'text/html',
 
687
                     -expires => '+3M');
 
688
  $last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
 
689
                             "attachment/diff-header.$format.tmpl",
 
690
                             "attachment/diff-file.$format.tmpl",
 
691
                             "attachment/diff-footer.$format.tmpl",
 
692
                             { %{$vars},
 
693
                               bonsai_url => Param('bonsai_url'),
 
694
                               lxr_url => Param('lxr_url'),
 
695
                               lxr_root => Param('lxr_root'),
 
696
                             }));
 
697
}
 
698
 
 
699
sub diff
 
700
{
 
701
  # Get patch data
 
702
  SendSQL("SELECT bug_id, description, ispatch, thedata FROM attachments WHERE attach_id = $::FORM{'id'}");
 
703
  my ($bugid, $description, $ispatch, $thedata) = FetchSQLData();
 
704
 
 
705
  # If it is not a patch, view normally
 
706
  if (!$ispatch)
 
707
  {
 
708
    view();
 
709
    return;
 
710
  }
 
711
 
 
712
  my ($reader, $last_reader) = setup_patch_readers();
 
713
 
 
714
  if ($::FORM{'format'} eq "raw")
 
715
  {
 
716
    require PatchReader::DiffPrinter::raw;
 
717
    $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
 
718
    # Actually print out the patch
 
719
    use vars qw($cgi);
 
720
    print $cgi->header(-type => 'text/plain',
 
721
                       -expires => '+3M');
 
722
    $reader->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
 
723
  }
 
724
  else
 
725
  {
 
726
    $vars->{other_patches} = [];
 
727
    if ($::interdiffbin && $::diffpath) {
 
728
      # Get list of attachments on this bug.
 
729
      # Ignore the current patch, but select the one right before it
 
730
      # chronologically.
 
731
      SendSQL("SELECT attach_id, description FROM attachments WHERE bug_id = $bugid AND ispatch = 1 ORDER BY creation_ts DESC");
 
732
      my $select_next_patch = 0;
 
733
      while (my ($other_id, $other_desc) = FetchSQLData()) {
 
734
        if ($other_id eq $::FORM{'id'}) {
 
735
          $select_next_patch = 1;
 
736
        } else {
 
737
          push @{$vars->{other_patches}}, { id => $other_id, desc => $other_desc, selected => $select_next_patch };
 
738
          if ($select_next_patch) {
 
739
            $select_next_patch = 0;
 
740
          }
 
741
        }
 
742
      }
 
743
    }
 
744
 
 
745
    $vars->{bugid} = $bugid;
 
746
    $vars->{attachid} = $::FORM{'id'};
 
747
    $vars->{description} = $description;
 
748
    setup_template_patch_reader($last_reader);
 
749
    # Actually print out the patch
 
750
    $reader->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
 
751
  }
 
752
}
363
753
 
364
754
sub viewall
365
755
{
367
757
 
368
758
  # Retrieve the attachments from the database and write them into an array
369
759
  # of hashes where each hash represents one attachment.
370
 
  SendSQL("SELECT attach_id, creation_ts, mimetype, description, ispatch, isobsolete 
371
 
           FROM attachments WHERE bug_id = $::FORM{'bugid'} ORDER BY attach_id");
 
760
    my $privacy = "";
 
761
    if (Param("insidergroup") && !(UserInGroup(Param("insidergroup")))) {
 
762
        $privacy = "AND isprivate < 1 ";
 
763
    }
 
764
    SendSQL("SELECT attach_id, DATE_FORMAT(creation_ts, '%Y.%m.%d %H:%i'),
 
765
            mimetype, description, ispatch, isobsolete, isprivate, 
 
766
            LENGTH(thedata)
 
767
            FROM attachments WHERE bug_id = $::FORM{'bugid'} $privacy 
 
768
            ORDER BY attach_id");
372
769
  my @attachments; # the attachments array
373
770
  while (MoreSQLData())
374
771
  {
375
772
    my %a; # the attachment hash
376
773
    ($a{'attachid'}, $a{'date'}, $a{'contenttype'}, 
377
 
     $a{'description'}, $a{'ispatch'}, $a{'isobsolete'}) = FetchSQLData();
378
 
 
379
 
    # Format the attachment's creation/modification date into something readable.
380
 
    if ($a{'date'} =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
381
 
        $a{'date'} = "$3/$4/$2&nbsp;$5:$6";
382
 
    }
383
 
 
384
 
    # Flag attachments as to whether or not they can be viewed (as opposed to
385
 
    # being downloaded).  Currently I decide they are viewable if their MIME type 
386
 
    # is either text/*, image/*, or application/vnd.mozilla.*.
387
 
    # !!! Yuck, what an ugly hack.  Fix it!
388
 
    $a{'isviewable'} = ( $a{'contenttype'} =~ /^(text|image|application\/vnd\.mozilla\.)/ );
389
 
 
390
 
    # Retrieve a list of status flags that have been set on the attachment.
391
 
    PushGlobalSQLState();
392
 
    SendSQL("SELECT    name 
393
 
             FROM      attachstatuses, attachstatusdefs 
394
 
             WHERE     attach_id = $a{'attachid'} 
395
 
             AND       attachstatuses.statusid = attachstatusdefs.id
396
 
             ORDER BY  sortkey");
397
 
    my @statuses;
398
 
    push(@statuses, FetchSQLData()) while MoreSQLData();
399
 
    $a{'statuses'} = \@statuses;
400
 
    PopGlobalSQLState();
 
774
     $a{'description'}, $a{'ispatch'}, $a{'isobsolete'}, $a{'isprivate'},
 
775
     $a{'datasize'}) = FetchSQLData();
 
776
    $a{'isviewable'} = isViewable($a{'contenttype'});
 
777
    $a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'},
 
778
                                          'is_active' => 1 });
401
779
 
402
780
    # Add the hash representing the attachment to the array of attachments.
403
781
    push @attachments, \%a;
404
782
  }
405
783
 
406
 
  # Retrieve the bug summary for displaying on screen.
407
 
  SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $::FORM{'bugid'}");
408
 
  my ($bugsummary) = FetchSQLData();
 
784
  # Retrieve the bug summary (for displaying on screen) and assignee.
 
785
  SendSQL("SELECT short_desc, assigned_to FROM bugs " .
 
786
          "WHERE bug_id = $::FORM{'bugid'}");
 
787
  my ($bugsummary, $assignee_id) = FetchSQLData();
409
788
 
410
789
  # Define the variables and functions that will be passed to the UI template.
411
790
  $vars->{'bugid'} = $::FORM{'bugid'};
 
791
  $vars->{'attachments'} = \@attachments;
 
792
  $vars->{'bugassignee_id'} = $assignee_id;
412
793
  $vars->{'bugsummary'} = $bugsummary;
413
 
  $vars->{'attachments'} = \@attachments;
 
794
  $vars->{'GetBugLink'} = \&GetBugLink;
414
795
 
415
 
  # Return the appropriate HTTP response headers.
416
 
  print "Content-Type: text/html\n\n";
 
796
  print Bugzilla->cgi->header();
417
797
 
418
798
  # Generate and return the UI (HTML page) from the appropriate template.
419
799
  $template->process("attachment/show-multiple.html.tmpl", $vars)
431
811
  if (!UserInGroup("editbugs")) {
432
812
      $canEdit = "AND submitter_id = $::userid";
433
813
  }
434
 
  SendSQL("SELECT attach_id, description 
 
814
  SendSQL("SELECT attach_id, description, isprivate
435
815
           FROM attachments
436
816
           WHERE bug_id = $::FORM{'bugid'}
437
817
           AND isobsolete = 0 $canEdit
439
819
  my @attachments; # the attachments array
440
820
  while ( MoreSQLData() ) {
441
821
    my %a; # the attachment hash
442
 
    ($a{'id'}, $a{'description'}) = FetchSQLData();
 
822
    ($a{'id'}, $a{'description'}, $a{'isprivate'}) = FetchSQLData();
443
823
 
444
824
    # Add the hash representing the attachment to the array of attachments.
445
825
    push @attachments, \%a;
446
826
  }
447
827
 
448
 
  # Retrieve the bug summary for displaying on screen.
449
 
  SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $::FORM{'bugid'}");
450
 
  my ($bugsummary) = FetchSQLData();
 
828
  # Retrieve the bug summary (for displaying on screen) and assignee.
 
829
  SendSQL("SELECT short_desc, assigned_to FROM bugs 
 
830
           WHERE bug_id = $::FORM{'bugid'}");
 
831
  my ($bugsummary, $assignee_id) = FetchSQLData();
451
832
 
452
833
  # Define the variables and functions that will be passed to the UI template.
453
834
  $vars->{'bugid'} = $::FORM{'bugid'};
 
835
  $vars->{'attachments'} = \@attachments;
 
836
  $vars->{'bugassignee_id'} = $assignee_id;
454
837
  $vars->{'bugsummary'} = $bugsummary;
455
 
  $vars->{'attachments'} = \@attachments;
 
838
  $vars->{'GetBugLink'} = \&GetBugLink;
456
839
 
457
 
  # Return the appropriate HTTP response headers.
458
 
  print "Content-Type: text/html\n\n";
 
840
  print Bugzilla->cgi->header();
459
841
 
460
842
  # Generate and return the UI (HTML page) from the appropriate template.
461
843
  $template->process("attachment/create.html.tmpl", $vars)
465
847
 
466
848
sub insert
467
849
{
 
850
  my ($data) = @_;
 
851
 
468
852
  # Insert a new attachment into the database.
469
853
 
470
854
  # Escape characters in strings that will be used in SQL statements.
471
 
  my $filename = SqlQuote($::FILE{'data'}->{'filename'});
 
855
  $filename = SqlQuote($filename);
472
856
  my $description = SqlQuote($::FORM{'description'});
473
857
  my $contenttype = SqlQuote($::FORM{'contenttype'});
474
 
  my $thedata = SqlQuote($::FORM{'data'});
 
858
  my $thedata = SqlQuote($data);
 
859
  my $isprivate = $::FORM{'isprivate'} ? 1 : 0;
 
860
 
 
861
  # Figure out when the changes were made.
 
862
  my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); 
 
863
  my $sql_timestamp = SqlQuote($timestamp); 
475
864
 
476
865
  # Insert the attachment into the database.
477
 
  SendSQL("INSERT INTO attachments (bug_id, filename, description, mimetype, ispatch, submitter_id, thedata) 
478
 
           VALUES ($::FORM{'bugid'}, $filename, $description, $contenttype, $::FORM{'ispatch'}, $::userid, $thedata)");
 
866
  SendSQL("INSERT INTO attachments (bug_id, creation_ts, filename, description, mimetype, ispatch, isprivate, submitter_id, thedata) 
 
867
           VALUES ($::FORM{'bugid'}, $sql_timestamp, $filename, $description, $contenttype, $::FORM{'ispatch'}, $isprivate, $::userid, $thedata)");
479
868
 
480
869
  # Retrieve the ID of the newly created attachment record.
481
870
  SendSQL("SELECT LAST_INSERT_ID()");
492
881
 
493
882
  AppendComment($::FORM{'bugid'}, 
494
883
                $::COOKIE{"Bugzilla_login"},
495
 
                $comment);
 
884
                $comment,
 
885
                $isprivate,
 
886
                $timestamp);
496
887
 
497
888
  # Make existing attachments obsolete.
498
889
  my $fieldid = GetFieldID('attachments.isobsolete');
499
 
  foreach my $attachid (@{$::MFORM{'obsolete'}}) {
500
 
    SendSQL("UPDATE attachments SET isobsolete = 1 WHERE attach_id = $attachid");
501
 
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
502
 
             VALUES ($::FORM{'bugid'}, $attachid, $::userid, NOW(), $fieldid, '0', '1')");
 
890
  foreach my $obsolete_id (@{$::MFORM{'obsolete'}}) {
 
891
      SendSQL("UPDATE attachments SET isobsolete = 1 WHERE attach_id = $obsolete_id");
 
892
      SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
 
893
               VALUES ($::FORM{'bugid'}, $obsolete_id, $::userid, $sql_timestamp, $fieldid, '0', '1')");
 
894
      # If the obsolete attachment has pending flags, migrate them to the new attachment.
 
895
      if (Bugzilla::Flag::count({ 'attach_id' => $obsolete_id , 
 
896
                                  'status' => 'pending',
 
897
                                  'is_active' => 1 })) {
 
898
        Bugzilla::Flag::migrate($obsolete_id, $attachid, $timestamp);
 
899
      }
503
900
  }
504
901
 
505
 
  # Send mail to let people know the attachment has been created.  Uses a 
506
 
  # special syntax of the "open" and "exec" commands to capture the output of 
507
 
  # "processmail", which "system" doesn't allow, without running the command 
508
 
  # through a shell, which backticks (``) do.
509
 
  #system ("./processmail", $bugid , $::userid);
510
 
  #my $mailresults = `./processmail $bugid $::userid`;
511
 
  my $mailresults = '';
512
 
  open(PMAIL, "-|") or exec('/usr/share/bugzilla/lib/processmail', $::FORM{'bugid'}, $::COOKIE{'Bugzilla_login'});
513
 
  $mailresults .= $_ while <PMAIL>;
514
 
  close(PMAIL);
515
 
 
 
902
  # Assign the bug to the user, if they are allowed to take it
 
903
  my $owner = "";
 
904
  
 
905
  if ($::FORM{'takebug'} && UserInGroup("editbugs")) {
 
906
      
 
907
      my @fields = ("assigned_to", "bug_status", "resolution", "login_name");
 
908
      
 
909
      # Get the old values, for the bugs_activity table
 
910
      SendSQL("SELECT " . join(", ", @fields) . " FROM bugs, profiles " .
 
911
              "WHERE bugs.bug_id = $::FORM{'bugid'} " .
 
912
              "AND   profiles.userid = bugs.assigned_to");
 
913
      
 
914
      my @oldvalues = FetchSQLData();
 
915
      my @newvalues = ($::userid, "ASSIGNED", "", DBID_to_name($::userid));
 
916
      
 
917
      # Make sure the person we are taking the bug from gets mail.
 
918
      $owner = $oldvalues[3];  
 
919
                  
 
920
      @oldvalues = map(SqlQuote($_), @oldvalues);
 
921
      @newvalues = map(SqlQuote($_), @newvalues);
 
922
               
 
923
      # Update the bug record. Note that this doesn't involve login_name.
 
924
      SendSQL("UPDATE bugs SET " . 
 
925
              join(", ", map("$fields[$_] = $newvalues[$_]", (0..2))) . 
 
926
              " WHERE bug_id = $::FORM{'bugid'}");
 
927
      
 
928
      # We store email addresses in the bugs_activity table rather than IDs.
 
929
      $oldvalues[0] = $oldvalues[3];
 
930
      $newvalues[0] = $newvalues[3];
 
931
      
 
932
      # Add the changes to the bugs_activity table
 
933
      for (my $i = 0; $i < 3; $i++) {
 
934
          if ($oldvalues[$i] ne $newvalues[$i]) {
 
935
              my $fieldid = GetFieldID($fields[$i]);
 
936
              SendSQL("INSERT INTO bugs_activity " .
 
937
                      "(bug_id, who, bug_when, fieldid, removed, added) " .
 
938
                      " VALUES ($::FORM{'bugid'}, $::userid, " . 
 
939
                      "$sql_timestamp " . 
 
940
                      ", $fieldid, $oldvalues[$i], $newvalues[$i])");
 
941
          }
 
942
      }      
 
943
  }   
 
944
  
516
945
  # Define the variables and functions that will be passed to the UI template.
517
 
  $vars->{'bugid'} = $::FORM{'bugid'};
 
946
  $vars->{'mailrecipients'} =  { 'changer' => $::COOKIE{'Bugzilla_login'},
 
947
                                 'owner'   => $owner };
 
948
  my $bugid = $::FORM{'bugid'};
 
949
  detaint_natural($bugid); # don't bother with error condition, we know it'll work
 
950
                           # because of ValidateBugID above.  This is only needed
 
951
                           # for Perl 5.6.0.  If we ever require Perl 5.6.1 or
 
952
                           # newer, or detaint something other than $::FORM{'bugid'}
 
953
                           # in ValidateBugID above, then this can go away.
 
954
  my $contenttypemethod = $::FORM{'contenttypemethod'} || '';
 
955
  trick_taint($contenttypemethod); # Same Perl 5.6.0 hack as above
 
956
  $contenttype = $::FORM{'contenttype'} || '';
 
957
  trick_taint($contenttype); # Same Perl 5.6.0 hack as above
 
958
  $vars->{'bugid'} = $bugid;
518
959
  $vars->{'attachid'} = $attachid;
519
960
  $vars->{'description'} = $description;
520
 
  $vars->{'mailresults'} = $mailresults;
521
 
  $vars->{'contenttypemethod'} = $::FORM{'contenttypemethod'};
522
 
  $vars->{'contenttype'} = $::FORM{'contenttype'};
 
961
  $vars->{'contenttypemethod'} = $contenttypemethod;
 
962
  $vars->{'contenttype'} = $contenttype;
523
963
 
524
 
  # Return the appropriate HTTP response headers.
525
 
  print "Content-Type: text/html\n\n";
 
964
  print Bugzilla->cgi->header();
526
965
 
527
966
  # Generate and return the UI (HTML page) from the appropriate template.
528
967
  $template->process("attachment/created.html.tmpl", $vars)
539
978
  # Users cannot edit the content of the attachment itself.
540
979
 
541
980
  # Retrieve the attachment from the database.
542
 
  SendSQL("SELECT description, mimetype, bug_id, ispatch, isobsolete 
 
981
  SendSQL("SELECT description, mimetype, filename, bug_id, ispatch, isobsolete, isprivate, LENGTH(thedata)
543
982
           FROM attachments WHERE attach_id = $::FORM{'id'}");
544
 
  my ($description, $contenttype, $bugid, $ispatch, $isobsolete) = FetchSQLData();
545
 
 
546
 
  # Flag attachment as to whether or not it can be viewed (as opposed to
547
 
  # being downloaded).  Currently I decide it is viewable if its content
548
 
  # type is either text/.* or application/vnd.mozilla.*.
549
 
  # !!! Yuck, what an ugly hack.  Fix it!
550
 
  my $isviewable = ( $contenttype =~ /^(text|image|application\/vnd\.mozilla\.)/ );
551
 
 
552
 
  # Retrieve a list of status flags that have been set on the attachment.
553
 
  my %statuses;
554
 
  SendSQL("SELECT  id, name 
555
 
           FROM    attachstatuses JOIN attachstatusdefs 
556
 
           WHERE   attachstatuses.statusid = attachstatusdefs.id 
557
 
           AND     attach_id = $::FORM{'id'}");
558
 
  while ( my ($id, $name) = FetchSQLData() )
559
 
  {
560
 
    $statuses{$id} = $name;
561
 
  }
562
 
 
563
 
  # Retrieve a list of statuses for this bug's product, and build an array 
564
 
  # of hashes in which each hash is a status flag record.
565
 
  # ???: Move this into versioncache or its own routine?
566
 
  my @statusdefs;
567
 
  SendSQL("SELECT   id, name 
568
 
           FROM     attachstatusdefs, bugs 
569
 
           WHERE    bug_id = $bugid 
570
 
           AND      attachstatusdefs.product = bugs.product 
571
 
           ORDER BY sortkey");
572
 
  while ( MoreSQLData() )
573
 
  {
574
 
    my ($id, $name) = FetchSQLData();
575
 
    push @statusdefs, { 'id' => $id , 'name' => $name };
576
 
  }
 
983
  my ($description, $contenttype, $filename, $bugid, $ispatch, $isobsolete, $isprivate, $datasize) = FetchSQLData();
 
984
 
 
985
  my $isviewable = isViewable($contenttype);
577
986
 
578
987
  # Retrieve a list of attachments for this bug as well as a summary of the bug
579
988
  # to use in a navigation bar across the top of the screen.
582
991
  push(@bugattachments, FetchSQLData()) while (MoreSQLData());
583
992
  SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $bugid");
584
993
  my ($bugsummary) = FetchSQLData();
585
 
 
 
994
  
 
995
  # Get a list of flag types that can be set for this attachment.
 
996
  SendSQL("SELECT product_id, component_id FROM bugs WHERE bug_id = $bugid");
 
997
  my ($product_id, $component_id) = FetchSQLData();
 
998
  my $flag_types = Bugzilla::FlagType::match({ 'target_type'  => 'attachment' , 
 
999
                                               'product_id'   => $product_id , 
 
1000
                                               'component_id' => $component_id });
 
1001
  foreach my $flag_type (@$flag_types) {
 
1002
    $flag_type->{'flags'} = Bugzilla::Flag::match({ 'type_id'   => $flag_type->{'id'}, 
 
1003
                                                    'attach_id' => $::FORM{'id'},
 
1004
                                                    'is_active' => 1 });
 
1005
  }
 
1006
  $vars->{'flag_types'} = $flag_types;
 
1007
  $vars->{'any_flags_requesteeble'} = grep($_->{'is_requesteeble'}, @$flag_types);
 
1008
  
586
1009
  # Define the variables and functions that will be passed to the UI template.
587
1010
  $vars->{'attachid'} = $::FORM{'id'}; 
588
1011
  $vars->{'description'} = $description; 
589
1012
  $vars->{'contenttype'} = $contenttype; 
 
1013
  $vars->{'filename'} = $filename;
590
1014
  $vars->{'bugid'} = $bugid; 
591
1015
  $vars->{'bugsummary'} = $bugsummary; 
592
1016
  $vars->{'ispatch'} = $ispatch; 
593
1017
  $vars->{'isobsolete'} = $isobsolete; 
 
1018
  $vars->{'isprivate'} = $isprivate; 
 
1019
  $vars->{'datasize'} = $datasize;
594
1020
  $vars->{'isviewable'} = $isviewable; 
595
 
  $vars->{'statuses'} = \%statuses; 
596
 
  $vars->{'statusdefs'} = \@statusdefs; 
597
1021
  $vars->{'attachments'} = \@bugattachments; 
 
1022
  $vars->{'GetBugLink'} = \&GetBugLink;
598
1023
 
599
 
  # Return the appropriate HTTP response headers.
600
 
  print "Content-Type: text/html\n\n";
 
1024
  # Determine if PatchReader is installed
 
1025
  eval {
 
1026
    require PatchReader;
 
1027
    $vars->{'patchviewerinstalled'} = 1;
 
1028
  };
 
1029
  print Bugzilla->cgi->header();
601
1030
 
602
1031
  # Generate and return the UI (HTML page) from the appropriate template.
603
1032
  $template->process("attachment/edit.html.tmpl", $vars)
607
1036
 
608
1037
sub update
609
1038
{
610
 
  # Update an attachment record.
 
1039
  # Updates an attachment record.
611
1040
 
612
1041
  # Get the bug ID for the bug to which this attachment is attached.
613
1042
  SendSQL("SELECT bug_id FROM attachments WHERE attach_id = $::FORM{'id'}");
614
 
  my $bugid = FetchSQLData() 
615
 
    || DisplayError("Cannot figure out bug number.")
616
 
    && exit;
617
 
 
 
1043
  my $bugid = FetchSQLData();
 
1044
  
618
1045
  # Lock database tables in preparation for updating the attachment.
619
 
  SendSQL("LOCK TABLES attachments WRITE , attachstatuses WRITE , 
620
 
           attachstatusdefs READ , fielddefs READ , bugs_activity WRITE");
621
 
 
 
1046
  SendSQL("LOCK TABLES attachments WRITE , flags WRITE , " . 
 
1047
          "flagtypes READ , fielddefs READ , bugs_activity WRITE, " . 
 
1048
          "flaginclusions AS i READ, flagexclusions AS e READ, " . 
 
1049
          # cc, bug_group_map, user_group_map, and groups are in here so we
 
1050
          # can check the permissions of flag requestees and email addresses
 
1051
          # on the flag type cc: lists via the CanSeeBug
 
1052
          # function call in Flag::notify. group_group_map is in here in case
 
1053
          # Bugzilla::User needs to rederive groups. profiles and 
 
1054
          # user_group_map would be READ locks instead of WRITE locks if it
 
1055
          # weren't for derive_groups, which needs to write to those tables.
 
1056
          "bugs READ, profiles WRITE, " . 
 
1057
          "cc READ, bug_group_map READ, user_group_map WRITE, " . 
 
1058
          "group_group_map READ, groups READ");
 
1059
  
622
1060
  # Get a copy of the attachment record before we make changes
623
1061
  # so we can record those changes in the activity table.
624
 
  SendSQL("SELECT description, mimetype, ispatch, isobsolete 
 
1062
  SendSQL("SELECT description, mimetype, filename, ispatch, isobsolete, isprivate
625
1063
           FROM attachments WHERE attach_id = $::FORM{'id'}");
626
 
  my ($olddescription, $oldcontenttype, $oldispatch, $oldisobsolete) = FetchSQLData();
627
 
 
628
 
  # Get the list of old status flags.
629
 
  SendSQL("SELECT    attachstatusdefs.name 
630
 
           FROM      attachments, attachstatuses, attachstatusdefs
631
 
           WHERE     attachments.attach_id = $::FORM{'id'}
632
 
           AND       attachments.attach_id = attachstatuses.attach_id
633
 
           AND       attachstatuses.statusid = attachstatusdefs.id
634
 
           ORDER BY  attachstatusdefs.sortkey
635
 
          ");
636
 
  my @oldstatuses;
637
 
  while (MoreSQLData()) {
638
 
    push(@oldstatuses, FetchSQLData());
639
 
  }
640
 
  my $oldstatuslist = join(', ', @oldstatuses);
641
 
 
642
 
  # Update the database with the new status flags.
643
 
  SendSQL("DELETE FROM attachstatuses WHERE attach_id = $::FORM{'id'}");
644
 
  foreach my $statusid (@{$::MFORM{'status'}}) 
645
 
  {
646
 
    SendSQL("INSERT INTO attachstatuses (attach_id, statusid) VALUES ($::FORM{'id'}, $statusid)");
647
 
  }
648
 
 
649
 
  # Get the list of new status flags.
650
 
  SendSQL("SELECT    attachstatusdefs.name 
651
 
           FROM      attachments, attachstatuses, attachstatusdefs
652
 
           WHERE     attachments.attach_id = $::FORM{'id'}
653
 
           AND       attachments.attach_id = attachstatuses.attach_id
654
 
           AND       attachstatuses.statusid = attachstatusdefs.id
655
 
           ORDER BY  attachstatusdefs.sortkey
656
 
          ");
657
 
  my @newstatuses;
658
 
  while (MoreSQLData()) {
659
 
    push(@newstatuses, FetchSQLData());
660
 
  }
661
 
  my $newstatuslist = join(', ', @newstatuses);
 
1064
  my ($olddescription, $oldcontenttype, $oldfilename, $oldispatch,
 
1065
      $oldisobsolete, $oldisprivate) = FetchSQLData();
662
1066
 
663
1067
  # Quote the description and content type for use in the SQL UPDATE statement.
664
1068
  my $quoteddescription = SqlQuote($::FORM{'description'});
665
1069
  my $quotedcontenttype = SqlQuote($::FORM{'contenttype'});
 
1070
  my $quotedfilename = SqlQuote($::FORM{'filename'});
666
1071
 
667
1072
  # Update the attachment record in the database.
668
1073
  # Sets the creation timestamp to itself to avoid it being updated automatically.
669
1074
  SendSQL("UPDATE  attachments 
670
1075
           SET     description = $quoteddescription , 
671
1076
                   mimetype = $quotedcontenttype , 
 
1077
                   filename = $quotedfilename ,
672
1078
                   ispatch = $::FORM{'ispatch'} , 
673
 
                   isobsolete = $::FORM{'isobsolete'} , 
674
 
                   creation_ts = creation_ts
 
1079
                   isobsolete = $::FORM{'isobsolete'} ,
 
1080
                   isprivate = $::FORM{'isprivate'} 
675
1081
           WHERE   attach_id = $::FORM{'id'}
676
1082
         ");
677
1083
 
 
1084
  # Figure out when the changes were made.
 
1085
  my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); 
 
1086
  my $sql_timestamp = SqlQuote($timestamp); 
 
1087
    
678
1088
  # Record changes in the activity table.
679
1089
  if ($olddescription ne $::FORM{'description'}) {
680
1090
    my $quotedolddescription = SqlQuote($olddescription);
681
1091
    my $fieldid = GetFieldID('attachments.description');
682
1092
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
683
 
             VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedolddescription, $quoteddescription)");
 
1093
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $quotedolddescription, $quoteddescription)");
684
1094
  }
685
1095
  if ($oldcontenttype ne $::FORM{'contenttype'}) {
686
1096
    my $quotedoldcontenttype = SqlQuote($oldcontenttype);
687
1097
    my $fieldid = GetFieldID('attachments.mimetype');
688
1098
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
689
 
             VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedoldcontenttype, $quotedcontenttype)");
 
1099
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $quotedoldcontenttype, $quotedcontenttype)");
 
1100
  }
 
1101
  if ($oldfilename ne $::FORM{'filename'}) {
 
1102
    my $quotedoldfilename = SqlQuote($oldfilename);
 
1103
    my $fieldid = GetFieldID('attachments.filename');
 
1104
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
 
1105
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $quotedoldfilename, $quotedfilename)");
690
1106
  }
691
1107
  if ($oldispatch ne $::FORM{'ispatch'}) {
692
1108
    my $fieldid = GetFieldID('attachments.ispatch');
693
1109
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
694
 
             VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $oldispatch, $::FORM{'ispatch'})");
 
1110
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $oldispatch, $::FORM{'ispatch'})");
695
1111
  }
696
1112
  if ($oldisobsolete ne $::FORM{'isobsolete'}) {
697
1113
    my $fieldid = GetFieldID('attachments.isobsolete');
698
1114
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
699
 
             VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $oldisobsolete, $::FORM{'isobsolete'})");
 
1115
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $oldisobsolete, $::FORM{'isobsolete'})");
700
1116
  }
701
 
  if ($oldstatuslist ne $newstatuslist) {
702
 
    my ($removed, $added) = DiffStrings($oldstatuslist, $newstatuslist);
703
 
    my $quotedremoved = SqlQuote($removed);
704
 
    my $quotedadded = SqlQuote($added);
705
 
    my $fieldid = GetFieldID('attachstatusdefs.name');
 
1117
  if ($oldisprivate ne $::FORM{'isprivate'}) {
 
1118
    my $fieldid = GetFieldID('attachments.isprivate');
706
1119
    SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added) 
707
 
             VALUES ($bugid, $::FORM{'id'}, $::userid, NOW(), $fieldid, $quotedremoved, $quotedadded)");
 
1120
             VALUES ($bugid, $::FORM{'id'}, $::userid, $sql_timestamp, $fieldid, $oldisprivate, $::FORM{'isprivate'})");
708
1121
  }
709
 
 
 
1122
  
 
1123
  # Update flags.
 
1124
  my $target = Bugzilla::Flag::GetTarget(undef, $::FORM{'id'});
 
1125
  Bugzilla::Flag::process($target, $timestamp, \%::FORM);
 
1126
  
710
1127
  # Unlock all database tables now that we are finished updating the database.
711
1128
  SendSQL("UNLOCK TABLES");
712
1129
 
713
 
  # If this installation has enabled the request manager, let the manager know
714
 
  # an attachment was updated so it can check for requests on that attachment
715
 
  # and fulfill them.  The request manager allows users to request database
716
 
  # changes of other users and tracks the fulfillment of those requests.  When
717
 
  # an attachment record is updated and the request manager is called, it will
718
 
  # fulfill those requests that were requested of the user performing the update
719
 
  # which are requests for the attachment being updated.
720
 
  #my $requests;
721
 
  #if (Param('userequestmanager'))
722
 
  #{
723
 
  #  use Request;
724
 
  #  # Specify the fieldnames that have been updated.
725
 
  #  my @fieldnames = ('description', 'mimetype', 'status', 'ispatch', 'isobsolete');
726
 
  #  # Fulfill pending requests.
727
 
  #  $requests = Request::fulfillRequest('attachment', $::FORM{'id'}, @fieldnames);
728
 
  #  $vars->{'requests'} = $requests; 
729
 
  #}
730
 
 
731
1130
  # If the user submitted a comment while editing the attachment, 
732
1131
  # add the comment to the bug.
733
1132
  if ( $::FORM{'comment'} )
759
1158
    my $neverused = $::userid;
760
1159
 
761
1160
    # Append the comment to the list of comments in the database.
762
 
    AppendComment($bugid, $who, $wrappedcomment);
 
1161
    AppendComment($bugid, $who, $wrappedcomment, $::FORM{'isprivate'}, $timestamp);
763
1162
 
764
1163
  }
765
 
 
766
 
  # Send mail to let people know the bug has changed.  Uses a special syntax
767
 
  # of the "open" and "exec" commands to capture the output of "processmail",
768
 
  # which "system" doesn't allow, without running the command through a shell,
769
 
  # which backticks (``) do.
770
 
  #system ("./processmail", $bugid , $::userid);
771
 
  #my $mailresults = `./processmail $bugid $::userid`;
772
 
  my $mailresults = '';
773
 
  open(PMAIL, "-|") or exec('/usr/share/bugzilla/lib/processmail', $bugid, DBID_to_name($::userid));
774
 
  $mailresults .= $_ while <PMAIL>;
775
 
  close(PMAIL);
776
 
 
 
1164
  
777
1165
  # Define the variables and functions that will be passed to the UI template.
 
1166
  $vars->{'mailrecipients'} = { 'changer' => $::COOKIE{'Bugzilla_login'} };
778
1167
  $vars->{'attachid'} = $::FORM{'id'}; 
779
1168
  $vars->{'bugid'} = $bugid; 
780
 
  $vars->{'mailresults'} = $mailresults; 
781
1169
 
782
 
  # Return the appropriate HTTP response headers.
783
 
  print "Content-Type: text/html\n\n";
 
1170
  print Bugzilla->cgi->header();
784
1171
 
785
1172
  # Generate and return the UI (HTML page) from the appropriate template.
786
1173
  $template->process("attachment/updated.html.tmpl", $vars)