Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style |
| 3 | # license that can be found in the LICENSE file or at |
| 4 | # https://developers.google.com/open-source/licenses/bsd |
| 5 | |
| 6 | """Issue Tracker code to serve out issue attachments. |
| 7 | |
| 8 | Summary of page classes: |
| 9 | AttachmentPage: Serve the content of an attachment w/ the appropriate |
| 10 | MIME type. |
| 11 | IssueAttachmentDeletion: Form handler for deleting attachments. |
| 12 | """ |
| 13 | from __future__ import print_function |
| 14 | from __future__ import division |
| 15 | from __future__ import absolute_import |
| 16 | |
| 17 | import base64 |
| 18 | import logging |
| 19 | import os |
| 20 | import re |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 21 | from six.moves import urllib |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 22 | |
| 23 | from google.appengine.api import app_identity |
| 24 | from google.appengine.api import images |
| 25 | |
| 26 | from framework import exceptions |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 27 | from framework import flaskservlet |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 28 | from framework import framework_constants |
| 29 | from framework import framework_helpers |
| 30 | from framework import gcs_helpers |
| 31 | from framework import permissions |
| 32 | from framework import servlet |
| 33 | from framework import urls |
| 34 | from tracker import attachment_helpers |
| 35 | from tracker import tracker_helpers |
| 36 | from tracker import tracker_views |
| 37 | |
| 38 | |
| 39 | # This will likely appear blank or as a broken image icon in the browser. |
| 40 | NO_PREVIEW_ICON = '' |
| 41 | NO_PREVIEW_MIME_TYPE = 'image/png' |
| 42 | |
| 43 | |
| 44 | class AttachmentPage(servlet.Servlet): |
| 45 | """AttachmentPage serves issue attachments.""" |
| 46 | |
| 47 | def GatherPageData(self, mr): |
| 48 | """Parse the attachment ID from the request and serve its content. |
| 49 | |
| 50 | Args: |
| 51 | mr: commonly used info parsed from the request. |
| 52 | |
| 53 | Returns: dict of values used by EZT for rendering the page. |
| 54 | """ |
| 55 | if mr.signed_aid != attachment_helpers.SignAttachmentID(mr.aid): |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 56 | self.abort(400, 'Please reload the issue page') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 57 | |
| 58 | try: |
| 59 | attachment, _issue = tracker_helpers.GetAttachmentIfAllowed( |
| 60 | mr, self.services) |
| 61 | except exceptions.NoSuchIssueException: |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 62 | self.abort(404, 'issue not found') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 63 | except exceptions.NoSuchAttachmentException: |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 64 | self.abort(404, 'attachment not found') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 65 | except exceptions.NoSuchCommentException: |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 66 | self.abort(404, 'comment not found') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 67 | |
| 68 | if not attachment.gcs_object_id: |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 69 | self.abort(404, 'attachment data not found') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 70 | |
| 71 | bucket_name = app_identity.get_default_gcs_bucket_name() |
| 72 | |
| 73 | gcs_object_id = attachment.gcs_object_id |
| 74 | |
| 75 | logging.info('attachment id %d is %s', mr.aid, gcs_object_id) |
| 76 | |
| 77 | # By default GCS will return images and attachments displayable inline. |
| 78 | if mr.thumb: |
| 79 | # Thumbnails are stored in a separate obj always displayed inline. |
| 80 | gcs_object_id = gcs_object_id + '-thumbnail' |
| 81 | elif not mr.inline: |
| 82 | # Downloads are stored in a separate obj with disposiiton set. |
| 83 | filename = attachment.filename |
| 84 | if not framework_constants.FILENAME_RE.match(filename): |
| 85 | logging.info('bad file name: %s' % attachment.attachment_id) |
| 86 | filename = 'attachment-%d.dat' % attachment.attachment_id |
| 87 | if gcs_helpers.MaybeCreateDownload( |
| 88 | bucket_name, gcs_object_id, filename): |
| 89 | gcs_object_id = gcs_object_id + '-download' |
| 90 | |
| 91 | url = gcs_helpers.SignUrl(bucket_name, gcs_object_id) |
| 92 | self.redirect(url, abort=True) |
Adrià Vilanova Martínez | de94280 | 2022-07-15 14:06:55 +0200 | [diff] [blame] | 93 | |
| 94 | # def GetAttachmentPage(self, **kwargs): |
| 95 | # return self.handler(**kwargs) |