~ubuntu-branches/ubuntu/hardy/bugzilla/hardy-security

« back to all changes in this revision

Viewing changes to attachment.cgi

  • Committer: Bazaar Package Importer
  • Author(s): Rémi Perrot
  • Date: 2004-04-02 01:13:32 UTC
  • Revision ID: james.westby@ubuntu.com-20040402011332-hxrg0n2szimd7d25
Tags: upstream-2.16.5
Import upstream version 2.16.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bonsaitools/bin/perl -wT
 
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
 
3
#
 
4
# The contents of this file are subject to the Mozilla Public
 
5
# License Version 1.1 (the "License"); you may not use this file
 
6
# except in compliance with the License. You may obtain a copy of
 
7
# the License at http://www.mozilla.org/MPL/
 
8
#
 
9
# Software distributed under the License is distributed on an "AS
 
10
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 
11
# implied. See the License for the specific language governing
 
12
# rights and limitations under the License.
 
13
#
 
14
# The Original Code is the Bugzilla Bug Tracking System.
 
15
#
 
16
# The Initial Developer of the Original Code is Netscape Communications
 
17
# Corporation. Portions created by Netscape are
 
18
# Copyright (C) 1998 Netscape Communications Corporation. All
 
19
# Rights Reserved.
 
20
#
 
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
 
22
#                 Myk Melez <myk@mozilla.org>
 
23
 
 
24
################################################################################
 
25
# Script Initialization
 
26
################################################################################
 
27
 
 
28
# Make it harder for us to do dangerous things in Perl.
 
29
use diagnostics;
 
30
use strict;
 
31
 
 
32
use lib qw(.);
 
33
 
 
34
use vars qw(
 
35
  $template
 
36
  $vars
 
37
);
 
38
 
 
39
# Include the Bugzilla CGI and general utility library.
 
40
require "CGI.pl";
 
41
 
 
42
# Establish a connection to the database backend.
 
43
ConnectToDatabase();
 
44
 
 
45
# Check whether or not the user is logged in and, if so, set the $::userid 
 
46
# and $::usergroupset variables.
 
47
quietly_check_login();
 
48
 
 
49
################################################################################
 
50
# Main Body Execution
 
51
################################################################################
 
52
 
 
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.
 
56
 
 
57
# Determine whether to use the action specified by the user or the default.
 
58
my $action = $::FORM{'action'} || 'view';
 
59
 
 
60
if ($action eq "view")  
 
61
 
62
  validateID();
 
63
  view(); 
 
64
}
 
65
elsif ($action eq "viewall") 
 
66
 
67
  ValidateBugID($::FORM{'bugid'});
 
68
  viewall(); 
 
69
}
 
70
elsif ($action eq "enter") 
 
71
 
72
  confirm_login();
 
73
  ValidateBugID($::FORM{'bugid'});
 
74
  enter(); 
 
75
}
 
76
elsif ($action eq "insert")
 
77
{
 
78
  confirm_login();
 
79
  ValidateBugID($::FORM{'bugid'});
 
80
  ValidateComment($::FORM{'comment'});
 
81
  validateFilename();
 
82
  validateData();
 
83
  validateDescription();
 
84
  validateIsPatch();
 
85
  validateContentType() unless $::FORM{'ispatch'};
 
86
  validateObsolete() if $::FORM{'obsolete'};
 
87
  insert();
 
88
}
 
89
elsif ($action eq "edit") 
 
90
 
91
  quietly_check_login();
 
92
  validateID();
 
93
  validateCanEdit($::FORM{'id'});
 
94
  edit(); 
 
95
}
 
96
elsif ($action eq "update") 
 
97
 
98
  confirm_login();
 
99
  ValidateComment($::FORM{'comment'});
 
100
  validateID();
 
101
  validateCanEdit($::FORM{'id'});
 
102
  validateDescription();
 
103
  validateIsPatch();
 
104
  validateContentType() unless $::FORM{'ispatch'};
 
105
  validateIsObsolete();
 
106
  validateStatuses();
 
107
  update();
 
108
}
 
109
else 
 
110
 
111
  DisplayError("I could not figure out what you wanted to do.")
 
112
}
 
113
 
 
114
exit;
 
115
 
 
116
################################################################################
 
117
# Data Validation / Security Authorization
 
118
################################################################################
 
119
 
 
120
sub validateID
 
121
{
 
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;
 
128
  
 
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);
 
138
}
 
139
 
 
140
sub validateCanEdit
 
141
{
 
142
    my ($attach_id) = (@_);
 
143
 
 
144
    # 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.
 
146
    # People not logged in can't actually commit changes, because that code
 
147
    # calls confirm_login, not quietly_check_login, before calling this sub
 
148
    return if $::userid == 0;
 
149
 
 
150
    # People in editbugs can edit all attachments
 
151
    return if UserInGroup("editbugs");
 
152
 
 
153
    # Bug 97729 - the submitter can edit their attachments
 
154
    SendSQL("SELECT attach_id FROM attachments WHERE " .
 
155
            "attach_id = $attach_id AND submitter_id = $::userid");
 
156
 
 
157
    FetchSQLData()
 
158
      || DisplayError("You are not authorised to edit attachment #$attach_id")
 
159
      && exit;
 
160
}
 
161
 
 
162
sub validateDescription
 
163
{
 
164
  $::FORM{'description'}
 
165
    || DisplayError("You must enter a description for the attachment.")
 
166
      && exit;
 
167
}
 
168
 
 
169
sub validateIsPatch
 
170
{
 
171
  # Set the ispatch flag to zero if it is undefined, since the UI uses
 
172
  # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
 
173
  # do not get sent in HTML requests.
 
174
  $::FORM{'ispatch'} = $::FORM{'ispatch'} ? 1 : 0;
 
175
 
 
176
  # Set the content type to text/plain if the attachment is a patch.
 
177
  $::FORM{'contenttype'} = "text/plain" if $::FORM{'ispatch'};
 
178
}
 
179
 
 
180
sub validateContentType
 
181
{
 
182
  if (!$::FORM{'contenttypemethod'})
 
183
  {
 
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;
 
188
  }
 
189
  elsif ($::FORM{'contenttypemethod'} eq 'autodetect')
 
190
  {
 
191
    # The user asked us to auto-detect the content type, so use the type
 
192
    # specified in the HTTP request headers.
 
193
    if ( !$::FILE{'data'}->{'contenttype'} )
 
194
    {
 
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;
 
199
    }
 
200
    $::FORM{'contenttype'} = $::FILE{'data'}->{'contenttype'};
 
201
  }
 
202
  elsif ($::FORM{'contenttypemethod'} eq 'list')
 
203
  {
 
204
    # The user selected a content type from the list, so use their selection.
 
205
    $::FORM{'contenttype'} = $::FORM{'contenttypeselection'};
 
206
  }
 
207
  elsif ($::FORM{'contenttypemethod'} eq 'manual')
 
208
  {
 
209
    # The user entered a content type manually, so use their entry.
 
210
    $::FORM{'contenttype'} = $::FORM{'contenttypeentry'};
 
211
  }
 
212
  else
 
213
  {
 
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;
 
220
  }
 
221
 
 
222
  if ( $::FORM{'contenttype'} !~ /^(application|audio|image|message|model|multipart|text|video)\/.+$/ )
 
223
  {
 
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;
 
230
  }
 
231
}
 
232
 
 
233
sub validateIsObsolete
 
234
{
 
235
  # Set the isobsolete flag to zero if it is undefined, since the UI uses
 
236
  # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
 
237
  # do not get sent in HTML requests.
 
238
  $::FORM{'isobsolete'} = $::FORM{'isobsolete'} ? 1 : 0;
 
239
}
 
240
 
 
241
sub validateStatuses
 
242
{
 
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
  }
 
263
}
 
264
 
 
265
sub validateData
 
266
{
 
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 )
 
279
  {
 
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
  }
 
294
}
 
295
 
 
296
sub validateFilename
 
297
{
 
298
  defined $::FILE{'data'}
 
299
    || DisplayError("You did not specify a file to attach.")
 
300
      && exit;
 
301
}
 
302
 
 
303
sub validateObsolete
 
304
{
 
305
  # Make sure the attachment id is valid and the user has permissions to view
 
306
  # the bug to which it is attached.
 
307
  foreach my $attachid (@{$::MFORM{'obsolete'}}) {
 
308
    detaint_natural($attachid)
 
309
      || DisplayError("The attachment number of one of the attachments 
 
310
           you wanted to obsolete is invalid.") 
 
311
        && exit;
 
312
  
 
313
    SendSQL("SELECT bug_id, isobsolete, description 
 
314
             FROM attachments WHERE attach_id = $attachid");
 
315
 
 
316
    # Make sure the attachment exists in the database.
 
317
    MoreSQLData()
 
318
      || DisplayError("Attachment #$attachid does not exist.") 
 
319
        && exit;
 
320
 
 
321
    my ($bugid, $isobsolete, $description) = FetchSQLData();
 
322
 
 
323
    if ($bugid != $::FORM{'bugid'})
 
324
    {
 
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;
 
330
    }
 
331
 
 
332
    if ( $isobsolete )
 
333
    {
 
334
      $description = html_quote($description);
 
335
      DisplayError("Attachment #$attachid ($description) is already obsolete.");
 
336
      exit;
 
337
    }
 
338
 
 
339
    # Check that the user can modify this attachment
 
340
    validateCanEdit($attachid);
 
341
  }
 
342
 
 
343
}
 
344
 
 
345
################################################################################
 
346
# Functions
 
347
################################################################################
 
348
 
 
349
sub view
 
350
{
 
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
 
 
363
 
 
364
sub viewall
 
365
{
 
366
  # Display all attachments for a given bug in a series of IFRAMEs within one HTML page.
 
367
 
 
368
  # Retrieve the attachments from the database and write them into an array
 
369
  # 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");
 
372
  my @attachments; # the attachments array
 
373
  while (MoreSQLData())
 
374
  {
 
375
    my %a; # the attachment hash
 
376
    ($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();
 
401
 
 
402
    # Add the hash representing the attachment to the array of attachments.
 
403
    push @attachments, \%a;
 
404
  }
 
405
 
 
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();
 
409
 
 
410
  # Define the variables and functions that will be passed to the UI template.
 
411
  $vars->{'bugid'} = $::FORM{'bugid'};
 
412
  $vars->{'bugsummary'} = $bugsummary;
 
413
  $vars->{'attachments'} = \@attachments;
 
414
 
 
415
  # Return the appropriate HTTP response headers.
 
416
  print "Content-Type: text/html\n\n";
 
417
 
 
418
  # Generate and return the UI (HTML page) from the appropriate template.
 
419
  $template->process("attachment/show-multiple.html.tmpl", $vars)
 
420
    || ThrowTemplateError($template->error());
 
421
}
 
422
 
 
423
 
 
424
sub enter
 
425
{
 
426
  # Display a form for entering a new attachment.
 
427
 
 
428
  # Retrieve the attachments the user can edit from the database and write
 
429
  # them into an array of hashes where each hash represents one attachment.
 
430
  my $canEdit = "";
 
431
  if (!UserInGroup("editbugs")) {
 
432
      $canEdit = "AND submitter_id = $::userid";
 
433
  }
 
434
  SendSQL("SELECT attach_id, description 
 
435
           FROM attachments
 
436
           WHERE bug_id = $::FORM{'bugid'}
 
437
           AND isobsolete = 0 $canEdit
 
438
           ORDER BY attach_id");
 
439
  my @attachments; # the attachments array
 
440
  while ( MoreSQLData() ) {
 
441
    my %a; # the attachment hash
 
442
    ($a{'id'}, $a{'description'}) = FetchSQLData();
 
443
 
 
444
    # Add the hash representing the attachment to the array of attachments.
 
445
    push @attachments, \%a;
 
446
  }
 
447
 
 
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();
 
451
 
 
452
  # Define the variables and functions that will be passed to the UI template.
 
453
  $vars->{'bugid'} = $::FORM{'bugid'};
 
454
  $vars->{'bugsummary'} = $bugsummary;
 
455
  $vars->{'attachments'} = \@attachments;
 
456
 
 
457
  # Return the appropriate HTTP response headers.
 
458
  print "Content-Type: text/html\n\n";
 
459
 
 
460
  # Generate and return the UI (HTML page) from the appropriate template.
 
461
  $template->process("attachment/create.html.tmpl", $vars)
 
462
    || ThrowTemplateError($template->error());
 
463
}
 
464
 
 
465
 
 
466
sub insert
 
467
{
 
468
  # Insert a new attachment into the database.
 
469
 
 
470
  # Escape characters in strings that will be used in SQL statements.
 
471
  my $filename = SqlQuote($::FILE{'data'}->{'filename'});
 
472
  my $description = SqlQuote($::FORM{'description'});
 
473
  my $contenttype = SqlQuote($::FORM{'contenttype'});
 
474
  my $thedata = SqlQuote($::FORM{'data'});
 
475
 
 
476
  # 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)");
 
479
 
 
480
  # Retrieve the ID of the newly created attachment record.
 
481
  SendSQL("SELECT LAST_INSERT_ID()");
 
482
  my $attachid = FetchOneColumn();
 
483
 
 
484
  # Insert a comment about the new attachment into the database.
 
485
  my $comment = "Created an attachment (id=$attachid)\n$::FORM{'description'}\n";
 
486
  $comment .= ("\n" . $::FORM{'comment'}) if $::FORM{'comment'};
 
487
 
 
488
  use Text::Wrap;
 
489
  $Text::Wrap::columns = 80;
 
490
  $Text::Wrap::huge = 'overflow';
 
491
  $comment = Text::Wrap::wrap('', '', $comment);
 
492
 
 
493
  AppendComment($::FORM{'bugid'}, 
 
494
                $::COOKIE{"Bugzilla_login"},
 
495
                $comment);
 
496
 
 
497
  # Make existing attachments obsolete.
 
498
  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')");
 
503
  }
 
504
 
 
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('./processmail', $::FORM{'bugid'}, $::COOKIE{'Bugzilla_login'});
 
513
  $mailresults .= $_ while <PMAIL>;
 
514
  close(PMAIL);
 
515
 
 
516
  # Define the variables and functions that will be passed to the UI template.
 
517
  $vars->{'bugid'} = $::FORM{'bugid'};
 
518
  $vars->{'attachid'} = $attachid;
 
519
  $vars->{'description'} = $description;
 
520
  $vars->{'mailresults'} = $mailresults;
 
521
  $vars->{'contenttypemethod'} = $::FORM{'contenttypemethod'};
 
522
  $vars->{'contenttype'} = $::FORM{'contenttype'};
 
523
 
 
524
  # Return the appropriate HTTP response headers.
 
525
  print "Content-Type: text/html\n\n";
 
526
 
 
527
  # Generate and return the UI (HTML page) from the appropriate template.
 
528
  $template->process("attachment/created.html.tmpl", $vars)
 
529
    || ThrowTemplateError($template->error());
 
530
}
 
531
 
 
532
 
 
533
sub edit
 
534
{
 
535
  # Edit an attachment record.  Users with "editbugs" privileges, (or the 
 
536
  # original attachment's submitter) can edit the attachment's description,
 
537
  # content type, ispatch and isobsolete flags, and statuses, and they can
 
538
  # also submit a comment that appears in the bug.
 
539
  # Users cannot edit the content of the attachment itself.
 
540
 
 
541
  # Retrieve the attachment from the database.
 
542
  SendSQL("SELECT description, mimetype, bug_id, ispatch, isobsolete 
 
543
           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
  }
 
577
 
 
578
  # Retrieve a list of attachments for this bug as well as a summary of the bug
 
579
  # to use in a navigation bar across the top of the screen.
 
580
  SendSQL("SELECT attach_id FROM attachments WHERE bug_id = $bugid ORDER BY attach_id");
 
581
  my @bugattachments;
 
582
  push(@bugattachments, FetchSQLData()) while (MoreSQLData());
 
583
  SendSQL("SELECT short_desc FROM bugs WHERE bug_id = $bugid");
 
584
  my ($bugsummary) = FetchSQLData();
 
585
 
 
586
  # Define the variables and functions that will be passed to the UI template.
 
587
  $vars->{'attachid'} = $::FORM{'id'}; 
 
588
  $vars->{'description'} = $description; 
 
589
  $vars->{'contenttype'} = $contenttype; 
 
590
  $vars->{'bugid'} = $bugid; 
 
591
  $vars->{'bugsummary'} = $bugsummary; 
 
592
  $vars->{'ispatch'} = $ispatch; 
 
593
  $vars->{'isobsolete'} = $isobsolete; 
 
594
  $vars->{'isviewable'} = $isviewable; 
 
595
  $vars->{'statuses'} = \%statuses; 
 
596
  $vars->{'statusdefs'} = \@statusdefs; 
 
597
  $vars->{'attachments'} = \@bugattachments; 
 
598
 
 
599
  # Return the appropriate HTTP response headers.
 
600
  print "Content-Type: text/html\n\n";
 
601
 
 
602
  # Generate and return the UI (HTML page) from the appropriate template.
 
603
  $template->process("attachment/edit.html.tmpl", $vars)
 
604
    || ThrowTemplateError($template->error());
 
605
}
 
606
 
 
607
 
 
608
sub update
 
609
{
 
610
  # Update an attachment record.
 
611
 
 
612
  # Get the bug ID for the bug to which this attachment is attached.
 
613
  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
 
 
618
  # 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
 
 
622
  # Get a copy of the attachment record before we make changes
 
623
  # so we can record those changes in the activity table.
 
624
  SendSQL("SELECT description, mimetype, ispatch, isobsolete 
 
625
           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);
 
662
 
 
663
  # Quote the description and content type for use in the SQL UPDATE statement.
 
664
  my $quoteddescription = SqlQuote($::FORM{'description'});
 
665
  my $quotedcontenttype = SqlQuote($::FORM{'contenttype'});
 
666
 
 
667
  # Update the attachment record in the database.
 
668
  # Sets the creation timestamp to itself to avoid it being updated automatically.
 
669
  SendSQL("UPDATE  attachments 
 
670
           SET     description = $quoteddescription , 
 
671
                   mimetype = $quotedcontenttype , 
 
672
                   ispatch = $::FORM{'ispatch'} , 
 
673
                   isobsolete = $::FORM{'isobsolete'} , 
 
674
                   creation_ts = creation_ts
 
675
           WHERE   attach_id = $::FORM{'id'}
 
676
         ");
 
677
 
 
678
  # Record changes in the activity table.
 
679
  if ($olddescription ne $::FORM{'description'}) {
 
680
    my $quotedolddescription = SqlQuote($olddescription);
 
681
    my $fieldid = GetFieldID('attachments.description');
 
682
    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)");
 
684
  }
 
685
  if ($oldcontenttype ne $::FORM{'contenttype'}) {
 
686
    my $quotedoldcontenttype = SqlQuote($oldcontenttype);
 
687
    my $fieldid = GetFieldID('attachments.mimetype');
 
688
    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)");
 
690
  }
 
691
  if ($oldispatch ne $::FORM{'ispatch'}) {
 
692
    my $fieldid = GetFieldID('attachments.ispatch');
 
693
    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'})");
 
695
  }
 
696
  if ($oldisobsolete ne $::FORM{'isobsolete'}) {
 
697
    my $fieldid = GetFieldID('attachments.isobsolete');
 
698
    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'})");
 
700
  }
 
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');
 
706
    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)");
 
708
  }
 
709
 
 
710
  # Unlock all database tables now that we are finished updating the database.
 
711
  SendSQL("UNLOCK TABLES");
 
712
 
 
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
  # If the user submitted a comment while editing the attachment, 
 
732
  # add the comment to the bug.
 
733
  if ( $::FORM{'comment'} )
 
734
  {
 
735
    use Text::Wrap;
 
736
    $Text::Wrap::columns = 80;
 
737
    $Text::Wrap::huge = 'wrap';
 
738
 
 
739
    # Append a string to the comment to let users know that the comment came from
 
740
    # the "edit attachment" screen.
 
741
    my $comment = qq|(From update of attachment $::FORM{'id'})\n| . $::FORM{'comment'};
 
742
 
 
743
    my $wrappedcomment = "";
 
744
    foreach my $line (split(/\r\n|\r|\n/, $comment))
 
745
    {
 
746
      if ( $line =~ /^>/ )
 
747
      {
 
748
        $wrappedcomment .= $line . "\n";
 
749
      }
 
750
      else
 
751
      {
 
752
        $wrappedcomment .= wrap('', '', $line) . "\n";
 
753
      }
 
754
    }
 
755
 
 
756
    # Get the user's login name since the AppendComment function needs it.
 
757
    my $who = DBID_to_name($::userid);
 
758
    # Mention $::userid again so Perl doesn't give me a warning about it.
 
759
    my $neverused = $::userid;
 
760
 
 
761
    # Append the comment to the list of comments in the database.
 
762
    AppendComment($bugid, $who, $wrappedcomment);
 
763
 
 
764
  }
 
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('./processmail', $bugid, DBID_to_name($::userid));
 
774
  $mailresults .= $_ while <PMAIL>;
 
775
  close(PMAIL);
 
776
 
 
777
  # Define the variables and functions that will be passed to the UI template.
 
778
  $vars->{'attachid'} = $::FORM{'id'}; 
 
779
  $vars->{'bugid'} = $bugid; 
 
780
  $vars->{'mailresults'} = $mailresults; 
 
781
 
 
782
  # Return the appropriate HTTP response headers.
 
783
  print "Content-Type: text/html\n\n";
 
784
 
 
785
  # Generate and return the UI (HTML page) from the appropriate template.
 
786
  $template->process("attachment/updated.html.tmpl", $vars)
 
787
    || ThrowTemplateError($template->error());
 
788
}