| Index: appengine/monorail/tracker/issueattachment.py
|
| diff --git a/appengine/monorail/tracker/issueattachment.py b/appengine/monorail/tracker/issueattachment.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..27f84a1633ed8fa9a95a56f587d5b8ed290b46ad
|
| --- /dev/null
|
| +++ b/appengine/monorail/tracker/issueattachment.py
|
| @@ -0,0 +1,126 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is govered by a BSD-style
|
| +# license that can be found in the LICENSE file or at
|
| +# https://developers.google.com/open-source/licenses/bsd
|
| +
|
| +"""Issue Tracker code to serve out issue attachments.
|
| +
|
| +Summary of page classes:
|
| + AttachmentPage: Serve the content of an attachment w/ the appropriate
|
| + MIME type.
|
| + IssueAttachmentDeletion: Form handler for deleting attachments.
|
| +"""
|
| +
|
| +import base64
|
| +import logging
|
| +import os
|
| +import re
|
| +import urllib
|
| +
|
| +import webapp2
|
| +
|
| +from google.appengine.api import app_identity
|
| +from google.appengine.api import images
|
| +
|
| +from framework import framework_helpers
|
| +from framework import gcs_helpers
|
| +from framework import permissions
|
| +from framework import servlet
|
| +from framework import urls
|
| +from services import issue_svc
|
| +from tracker import tracker_helpers
|
| +from tracker import tracker_views
|
| +
|
| +
|
| +# This will likely appear blank or as a broken image icon in the browser.
|
| +NO_PREVIEW_ICON = ''
|
| +NO_PREVIEW_MIME_TYPE = 'image/png'
|
| +
|
| +FILE_RE = re.compile('^[-_.a-zA-Z0-9 #+()]+$')
|
| +
|
| +
|
| +class AttachmentPage(servlet.Servlet):
|
| + """AttachmentPage serves issue attachments."""
|
| +
|
| + def GatherPageData(self, mr):
|
| + """Parse the attachment ID from the request and serve its content.
|
| +
|
| + Args:
|
| + mr: commonly used info parsed from the request.
|
| +
|
| + Returns: dict of values used by EZT for rendering the page.
|
| + """
|
| + try:
|
| + attachment, _issue = tracker_helpers.GetAttachmentIfAllowed(
|
| + mr, self.services)
|
| + except issue_svc.NoSuchIssueException:
|
| + webapp2.abort(404, 'issue not found')
|
| + except issue_svc.NoSuchAttachmentException:
|
| + webapp2.abort(404, 'attachment not found')
|
| + except issue_svc.NoSuchCommentException:
|
| + webapp2.abort(404, 'comment not found')
|
| +
|
| + if not attachment.gcs_object_id:
|
| + webapp2.abort(404, 'attachment data not found')
|
| +
|
| + bucket_name = app_identity.get_default_gcs_bucket_name()
|
| + object_path = '/' + bucket_name + attachment.gcs_object_id
|
| +
|
| + if mr.thumb:
|
| + url = gcs_helpers.SignUrl(object_path + '-thumbnail')
|
| + self.redirect(url, abort=True)
|
| +
|
| + # By default GCS will return images and attachments displayable inline.
|
| + url = gcs_helpers.SignUrl(object_path)
|
| + if not mr.inline:
|
| + filename = attachment.filename
|
| + if not FILE_RE.match(filename):
|
| + print "bad file name: %s" % attachment.attachment_id
|
| + filename = 'attachment-%d.dat' % attachment.attachment_id
|
| +
|
| + url = url + '&' + urllib.urlencode(
|
| + {'response-content-disposition':
|
| + ('attachment; filename=%s' % filename)})
|
| +
|
| + self.redirect(url, abort=True)
|
| +
|
| +
|
| +class IssueAttachmentDeletion(servlet.Servlet):
|
| + """Form handler that allows user to hard-delete attachments."""
|
| +
|
| + def ProcessFormData(self, mr, post_data):
|
| + """Process the form that soft-deletes an issue attachment.
|
| +
|
| + Args:
|
| + mr: commonly used info parsed from the request.
|
| + post_data: HTML form data from the request.
|
| +
|
| + Returns:
|
| + String URL to redirect the user to after processing.
|
| + """
|
| + local_id = int(post_data['id'])
|
| + sequence_num = int(post_data['sequence_num'])
|
| + attachment_id = int(post_data['aid'])
|
| + delete = 'delete' in post_data
|
| +
|
| + issue = self.services.issue.GetIssueByLocalID(
|
| + mr.cnxn, mr.project_id, local_id)
|
| +
|
| + all_comments = self.services.issue.GetCommentsForIssue(
|
| + mr.cnxn, issue.issue_id)
|
| + logging.info('comments on %s are: %s', local_id, all_comments)
|
| + comment = all_comments[sequence_num]
|
| +
|
| + if not permissions.CanDelete(
|
| + mr.auth.user_id, mr.auth.effective_ids, mr.perms,
|
| + comment.deleted_by, comment.user_id, mr.project,
|
| + permissions.GetRestrictions(issue)):
|
| + raise permissions.PermissionException(
|
| + 'Cannot un/delete attachment')
|
| +
|
| + self.services.issue.SoftDeleteAttachment(
|
| + mr.cnxn, mr.project_id, local_id, sequence_num,
|
| + attachment_id, self.services.user, delete=delete)
|
| +
|
| + return framework_helpers.FormatAbsoluteURL(
|
| + mr, urls.ISSUE_DETAIL, id=local_id)
|
|
|