| 72 | | for attachment, description in zip(attachments, descriptions): |
| 73 | | if hasattr(attachment, 'filename'): |
| 74 | | self._create_attachment(req, tid, attachment, description) |
| 75 | | |
| 76 | | def _create_attachment(self, req, tid, upload, description): |
| 77 | | attachment = Attachment(self.env, 'ticket', tid) |
| 78 | | |
| 79 | | if hasattr(upload.file, 'fileno'): |
| 80 | | size = os.fstat(upload.file.fileno())[6] |
| 81 | | else: |
| 82 | | upload.file.seek(0, 2) |
| 83 | | size = upload.file.tell() |
| 84 | | upload.file.seek(0) |
| 85 | | if size == 0: |
| 86 | | raise TracError(_("Can't upload empty file")) |
| 87 | | |
| 88 | | max_size = self.env.config.get('attachment', 'max_size') |
| 89 | | if 0 <= max_size < size: |
| 90 | | raise TracError(_('Maximum attachment size: %(num)s bytes', |
| 91 | | num=max_size), _("Upload failed")) |
| 92 | | |
| 93 | | filename = _normalized_filename(upload.filename) |
| 94 | | if not filename: |
| 95 | | raise TracError(_("No file uploaded")) |
| 96 | | |
| 97 | | attachment.description = description |
| 98 | | attachment.author = get_reporter_id(req, 'author') |
| 99 | | attachment.ipnr = req.remote_addr |
| 100 | | |
| 101 | | attachment.insert(filename, upload.file, size) |
| 102 | | |
| 103 | | |
| 104 | | _control_codes_re = re.compile( |
| 105 | | '[' + |
| 106 | | ''.join(filter(lambda c: unicodedata.category(c) == 'Cc', |
| 107 | | map(unichr, xrange(0x10000)))) + |
| 108 | | ']') |
| 109 | | |
| 110 | | |
| 111 | | def _normalized_filename(filepath): |
| 112 | | # We try to normalize the filename to unicode NFC if we can. |
| 113 | | # Files uploaded from OS X might be in NFD. |
| 114 | | if not isinstance(filepath, unicode): |
| 115 | | filepath = unicode(filepath, 'utf-8') |
| 116 | | filepath = unicodedata.normalize('NFC', filepath) |
| 117 | | # Replace control codes with spaces, e.g. NUL, LF, DEL, U+009F |
| 118 | | filepath = _control_codes_re.sub(' ', filepath) |
| 119 | | # Replace backslashes with slashes if filename is Windows full path |
| 120 | | if filepath.startswith('\\') or re.match(r'[A-Za-z]:\\', filepath): |
| 121 | | filepath = filepath.replace('\\', '/') |
| 122 | | # We want basename to be delimited by only slashes on all platforms |
| 123 | | filename = posixpath.basename(filepath) |
| 124 | | filename = stripws(filename) |
| 125 | | return filename |
| | 68 | max_size = self.env.config.getint('attachment', 'max_size') |
| | 69 | for filedata, description in zip(uploads, descriptions): |
| | 70 | filename, fileobj, filesize = filedata |
| | 71 | if 0 <= max_size < filesize: |
| | 72 | raise TracError(_('Maximum attachment size: %(num)s bytes', |
| | 73 | num=max_size), |
| | 74 | _("Upload failed for %(filename)s", |
| | 75 | filename=filename)) |
| | 76 | attachment = Attachment(self.env, 'ticket', tid) |
| | 77 | attachment.description = description |
| | 78 | attachment.author = get_reporter_id(req, 'author') |
| | 79 | attachment.insert(filename, fileobj, filesize) |