blob: 26982d0b244411833aaa0a12e9d94616614ec375 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# 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
8Summary 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"""
13from __future__ import print_function
14from __future__ import division
15from __future__ import absolute_import
16
17import base64
18import logging
19import os
20import re
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020021from six.moves import urllib
Copybara854996b2021-09-07 19:36:02 +000022
23from google.appengine.api import app_identity
24from google.appengine.api import images
25
26from framework import exceptions
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020027from framework import flaskservlet
Copybara854996b2021-09-07 19:36:02 +000028from framework import framework_constants
29from framework import framework_helpers
30from framework import gcs_helpers
31from framework import permissions
32from framework import servlet
33from framework import urls
34from tracker import attachment_helpers
35from tracker import tracker_helpers
36from tracker import tracker_views
37
38
39# This will likely appear blank or as a broken image icon in the browser.
40NO_PREVIEW_ICON = ''
41NO_PREVIEW_MIME_TYPE = 'image/png'
42
43
44class 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ínezde942802022-07-15 14:06:55 +020056 self.abort(400, 'Please reload the issue page')
Copybara854996b2021-09-07 19:36:02 +000057
58 try:
59 attachment, _issue = tracker_helpers.GetAttachmentIfAllowed(
60 mr, self.services)
61 except exceptions.NoSuchIssueException:
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020062 self.abort(404, 'issue not found')
Copybara854996b2021-09-07 19:36:02 +000063 except exceptions.NoSuchAttachmentException:
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020064 self.abort(404, 'attachment not found')
Copybara854996b2021-09-07 19:36:02 +000065 except exceptions.NoSuchCommentException:
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020066 self.abort(404, 'comment not found')
Copybara854996b2021-09-07 19:36:02 +000067
68 if not attachment.gcs_object_id:
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020069 self.abort(404, 'attachment data not found')
Copybara854996b2021-09-07 19:36:02 +000070
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ínezde942802022-07-15 14:06:55 +020093
94 # def GetAttachmentPage(self, **kwargs):
95 # return self.handler(**kwargs)