Ejemplo n.º 1
0
    def _process_attachment(self, req, config, build):
        resource_id = req.args['member'] == 'config' \
                    and build.config or build.resource.id
        upload = req.args['file']
        if not upload.file:
            send_error(req, message="Attachment not received.")
        self.log.debug('Received attachment %s for attaching to build:%s',
                      upload.filename, resource_id)

        # Determine size of file
        upload.file.seek(0, 2) # to the end
        size = upload.file.tell()
        upload.file.seek(0)    # beginning again

        # Delete attachment if it already exists
        try:
            old_attach = Attachment(self.env, 'build',
                            parent_id=resource_id, filename=upload.filename)
            old_attach.delete()
        except ResourceNotFound:
            pass

        # Save new attachment
        attachment = Attachment(self.env, 'build', parent_id=resource_id)
        attachment.description = req.args.get('description', '')
        attachment.author = req.authname
        attachment.insert(upload.filename, upload.file, size)

        self._send_response(req, 201, 'Attachment created', headers={
                            'Content-Type': 'text/plain',
                            'Content-Length': str(len('Attachment created'))})
Ejemplo n.º 2
0
 def putAttachmentEx(self,
                     req,
                     pagename,
                     filename,
                     description,
                     data,
                     replace=True):
     """ Attach a file to a Wiki page. Returns the (possibly transformed)
     filename of the attachment.
     
     Use this method if you don't care about WikiRPC compatibility. """
     if not WikiPage(self.env, pagename).exists:
         raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
     if replace:
         try:
             attachment = Attachment(self.env, 'wiki', pagename, filename)
             req.perm(attachment.resource).require('ATTACHMENT_DELETE')
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, 'wiki', pagename)
     req.perm(attachment.resource).require('ATTACHMENT_CREATE')
     attachment.author = req.authname
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 3
0
 def deleteAttachment(self, req, ticket, filename):
     """ Delete an attachment. """
     if not model.Ticket(self.env, ticket).exists:
         raise TracError('Ticket "%s" does not exists' % ticket)
     attachment = Attachment(self.env, 'ticket', ticket, filename)
     attachment.delete()
     return True
Ejemplo n.º 4
0
 def remove(self, req, realm, objid, filename):
     """ Delete an attachment. """
     resource = Resource(realm, objid).child('attachment', filename)
     attachment = Attachment(self.env, resource)
     req.perm(attachment.resource).require('ATTACHMENT_DELETE')
     attachment.delete()
     return True
Ejemplo n.º 5
0
 def deleteAttachment(self, req, path):
     """ Delete an attachment. """
     pagename, filename = posixpath.split(path)
     if not WikiPage(self.env, pagename).exists:
         raise TracError, 'Wiki page "%s" does not exist' % pagename
     attachment = Attachment(self.env, 'wiki', pagename, filename)
     attachment.delete()
     return True
Ejemplo n.º 6
0
 def deleteAttachment(self, req, path):
     """ Delete an attachment. """
     pagename, filename = posixpath.split(path)
     if not WikiPage(self.env, pagename).exists:
         raise TracError, 'Wiki page "%s" does not exist' % pagename
     attachment = Attachment(self.env, 'wiki', pagename, filename)
     attachment.delete()
     return True
Ejemplo n.º 7
0
 def deleteAttachment(self, req, ticket, filename):
     """ Delete an attachment. """
     if not model.Ticket(self.env, ticket).exists:
         raise ResourceNotFound('Ticket "%s" does not exists' % ticket)
     attachment = Attachment(self.env, 'ticket', ticket, filename)
     req.perm(attachment.resource).require('ATTACHMENT_DELETE')
     attachment.delete()
     return True
Ejemplo n.º 8
0
 def deleteAttachment(self, req, ticket, filename):
     """ Delete an attachment. """
     if not model.Ticket(self.env, ticket).exists:
         raise ResourceNotFound('Ticket "%s" does not exists' % ticket)
     attachment = Attachment(self.env, 'ticket', ticket, filename)
     req.perm(attachment.resource).require('ATTACHMENT_DELETE')
     attachment.delete()
     return True
Ejemplo n.º 9
0
 def deleteAttachment(self, req, path):
     """ Delete an attachment. """
     pagename, filename = os.path.split(path)
     if not WikiPage(self.env, pagename).exists:
         raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
     attachment = Attachment(self.env, 'wiki', pagename, filename)
     req.perm(attachment.resource).require('ATTACHMENT_DELETE')
     attachment.delete()
     return True
Ejemplo n.º 10
0
 def deleteAttachment(self, req, path):
     """ Delete an attachment. """
     pagename, filename = os.path.split(path)
     if not WikiPage(self.env, pagename).exists:
         raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
     attachment = Attachment(self.env, "wiki", pagename, filename)
     req.perm(attachment.resource).require("ATTACHMENT_DELETE")
     attachment.delete()
     return True
Ejemplo n.º 11
0
    def test_delete_file_gone(self):
        """
        Verify that deleting an attachment works even if the referenced file
        doesn't exist for some reason.
        """
        attachment = Attachment(self.env, 'wiki', 'SomePage')
        attachment.insert('foo.txt', StringIO(''), 0)
        os.unlink(attachment.path)

        attachment.delete()
Ejemplo n.º 12
0
    def test_delete_file_gone(self):
        """
        Verify that deleting an attachment works even if the referenced file
        doesn't exist for some reason.
        """
        attachment = Attachment(self.env, 'wiki', 'SomePage')
        attachment.insert('foo.txt', tempfile.TemporaryFile(), 0)
        os.unlink(attachment.path)

        attachment.delete()
Ejemplo n.º 13
0
 def putAttachment(self, req, ticket, filename, description, data, replace=True):
     """ Add an attachment, optionally (and defaulting to) overwriting an
     existing one. Returns filename."""
     if not model.Ticket(self.env, ticket).exists:
         raise TracError, 'Ticket "%s" does not exist' % ticket
     if replace:
         try:
             attachment = Attachment(self.env, 'ticket', ticket, filename)
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, 'ticket', ticket)
     attachment.author = req.authname or 'anonymous'
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 14
0
    def test_delete(self):
        attachment1 = Attachment(self.env, 'wiki', 'SomePage')
        attachment1.insert('foo.txt', StringIO(''), 0)
        attachment2 = Attachment(self.env, 'wiki', 'SomePage')
        attachment2.insert('bar.jpg', StringIO(''), 0)

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(2, len(list(attachments)))

        attachment1.delete()
        attachment2.delete()

        assert not os.path.exists(attachment1.path)
        assert not os.path.exists(attachment2.path)

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(0, len(list(attachments)))
Ejemplo n.º 15
0
    def test_attachment_change_listeners_called(self):
        """The move method calls attachment change listeners"""
        attachment = Attachment(self.env, 'wiki', 'SomePage')
        attachment.insert('foo.txt', io.BytesIO(), 0)
        attachment.move(new_realm='ticket', new_id=42)
        attachment.delete()

        modern_listener = self.attachment_change_listeners[0](self.env)
        self.assertEqual(1, modern_listener.added_call_count)
        self.assertEqual(1, modern_listener.deleted_call_count)
        self.assertEqual(1, modern_listener.moved_call_count)
        self.assertEqual('wiki', modern_listener.moved_old_parent_realm)
        self.assertEqual('SomePage', modern_listener.moved_old_parent_id)
        self.assertEqual('foo.txt', modern_listener.moved_old_filename)
        legacy_listener = self.attachment_change_listeners[0](self.env)
        self.assertEqual(1, legacy_listener.added_call_count)
        self.assertEqual(1, legacy_listener.deleted_call_count)
Ejemplo n.º 16
0
    def test_delete(self):
        attachment1 = Attachment(self.env, "wiki", "SomePage")
        attachment1.insert("foo.txt", StringIO(""), 0)
        attachment2 = Attachment(self.env, "wiki", "SomePage")
        attachment2.insert("bar.jpg", StringIO(""), 0)

        attachments = Attachment.select(self.env, "wiki", "SomePage")
        self.assertEqual(2, len(list(attachments)))

        attachment1.delete()
        attachment2.delete()

        assert not os.path.exists(attachment1.path)
        assert not os.path.exists(attachment2.path)

        attachments = Attachment.select(self.env, "wiki", "SomePage")
        self.assertEqual(0, len(list(attachments)))
Ejemplo n.º 17
0
    def test_delete(self):
        attachment1 = Attachment(self.env, 'wiki', 'SomePage')
        attachment1.insert('foo.txt', tempfile.TemporaryFile(), 0)
        attachment2 = Attachment(self.env, 'wiki', 'SomePage')
        attachment2.insert('bar.jpg', tempfile.TemporaryFile(), 0)

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(2, len(list(attachments)))

        attachment1.delete()
        attachment2.delete()

        assert not os.path.exists(attachment1.path)
        assert not os.path.exists(attachment2.path)

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(0, len(list(attachments)))
Ejemplo n.º 18
0
    def test_delete(self):
        attachment1 = Attachment(self.env, 'wiki', 'SomePage')
        attachment1.insert('foo.txt', StringIO(''), 0)
        attachment2 = Attachment(self.env, 'wiki', 'SomePage')
        attachment2.insert('bar.jpg', StringIO(''), 0)

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(2, len(list(attachments)))

        attachment1.delete()
        attachment2.delete()

        self.assertFalse(os.path.exists(attachment1.path))
        self.assertFalse(os.path.exists(attachment2.path))

        attachments = Attachment.select(self.env, 'wiki', 'SomePage')
        self.assertEqual(0, len(list(attachments)))
Ejemplo n.º 19
0
 def putAttachmentEx(self, req, pagename, filename, description, data, replace=True):
     """ Attach a file to a Wiki page. Returns the (possibly transformed)
     filename of the attachment.
     
     Use this method if you don't care about WikiRPC compatibility. """
     if not WikiPage(self.env, pagename).exists:
         raise TracError, 'Wiki page "%s" does not exist' % pagename
     if replace:
         try:
             attachment = Attachment(self.env, 'wiki', pagename, filename)
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, 'wiki', pagename)
     attachment.author = req.authname or 'anonymous'
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 20
0
 def putAttachment(self, req, ticket, filename, description, data, replace=True):
     """ Add an attachment, optionally (and defaulting to) overwriting an
     existing one. Returns filename."""
     if not model.Ticket(self.env, ticket).exists:
         raise ResourceNotFound('Ticket "%s" does not exist' % ticket)
     if replace:
         try:
             attachment = Attachment(self.env, 'ticket', ticket, filename)
             req.perm(attachment.resource).require('ATTACHMENT_DELETE')
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, 'ticket', ticket)
     req.perm(attachment.resource).require('ATTACHMENT_CREATE')
     attachment.author = req.authname
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 21
0
 def putAttachmentEx(self, req, pagename, filename, description, data, replace=True):
     """ Attach a file to a Wiki page. Returns the (possibly transformed)
     filename of the attachment.
     
     Use this method if you don't care about WikiRPC compatibility. """
     if not WikiPage(self.env, pagename).exists:
         raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
     if replace:
         try:
             attachment = Attachment(self.env, "wiki", pagename, filename)
             req.perm(attachment.resource).require("ATTACHMENT_DELETE")
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, "wiki", pagename)
     req.perm(attachment.resource).require("ATTACHMENT_CREATE")
     attachment.author = req.authname
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 22
0
def copy_attachment(source_env, dest_env, parent_type, parent_id, filename, dest_db=None):
    # In case a string gets passed in
    if not isinstance(source_env, Environment):
        source_env = _open_environment(source_env)
    if not isinstance(dest_env, Environment):
        dest_env = _open_environment(dest_env)
        
    # Log message
    source_env.log.info('DatamoverPlugin: Moving attachment (%s,%s,%s) to the environment at %s', parent_type, parent_id, filename, dest_env.path)
    dest_env.log.info('DatamoverPlugin: Moving attachment (%s,%s,%s) from the environment at %s', parent_type, parent_id, filename, source_env.path)
    
    # Open databases
    source_db = source_env.get_db_cnx()
    source_cursor = source_db.cursor()
    handle_commit = True
    if not dest_db:
        dest_db, handle_commit = dest_env.get_db_cnx(), False
    dest_cursor = dest_db.cursor()
    
    # Remove the attachment from the destination
    try:
        dest_attachment = Attachment(dest_env, parent_type, parent_id, filename, db=dest_db)
        dest_attachment.delete(db=dest_db)
    except TracError:
        pass

    # Copy each entry in the attachments table
    source_cursor.execute('SELECT * FROM attachment WHERE type=%s AND id=%s AND filename=%s',(parent_type, parent_id, filename))
    for row in source_cursor:
        att_data = dict(zip([d[0] for d in source_cursor.description], row))
        q = make_query(att_data, 'attachment')
        dest_cursor.execute(*q)
        # now copy the file itself
        old_att = Attachment(source_env, parent_type, parent_id, filename, db=source_db)
        new_att = Attachment(dest_env, parent_type, parent_id, filename, db=dest_db)
        if not os.path.isdir(os.path.dirname(new_att.path)):
            os.makedirs(os.path.dirname(new_att.path))
        copyfile(old_att.path, new_att.path)
       
    if handle_commit:
        dest_db.commit()
Ejemplo n.º 23
0
    def _process_attachment(self, req, config, build):
        resource_id = req.args['member'] == 'config' \
                    and build.config or build.resource.id
        upload = req.args['file']
        if not upload.file:
            send_error(req, message="Attachment not received.")
        self.log.debug('Received attachment %s for attaching to build:%s',
                       upload.filename, resource_id)

        # Determine size of file
        upload.file.seek(0, 2)  # to the end
        size = upload.file.tell()
        upload.file.seek(0)  # beginning again

        # Delete attachment if it already exists
        try:
            old_attach = Attachment(self.env,
                                    'build',
                                    parent_id=resource_id,
                                    filename=upload.filename)
            old_attach.delete()
        except ResourceNotFound:
            pass

        # Save new attachment
        attachment = Attachment(self.env, 'build', parent_id=resource_id)
        attachment.description = req.args.get('description', '')
        attachment.author = req.authname
        attachment.insert(upload.filename, upload.file, size)

        self._send_response(req,
                            201,
                            'Attachment created',
                            headers={
                                'Content-Type': 'text/plain',
                                'Content-Length':
                                str(len('Attachment created'))
                            })
Ejemplo n.º 24
0
 def putAttachment(self,
                   req,
                   ticket,
                   filename,
                   description,
                   data,
                   replace=True):
     """ Add an attachment, optionally (and defaulting to) overwriting an
     existing one. Returns filename."""
     if not model.Ticket(self.env, ticket).exists:
         raise ResourceNotFound('Ticket "%s" does not exist' % ticket)
     if replace:
         try:
             attachment = Attachment(self.env, 'ticket', ticket, filename)
             req.perm(attachment.resource).require('ATTACHMENT_DELETE')
             attachment.delete()
         except TracError:
             pass
     attachment = Attachment(self.env, 'ticket', ticket)
     req.perm(attachment.resource).require('ATTACHMENT_CREATE')
     attachment.author = req.authname
     attachment.description = description
     attachment.insert(filename, StringIO(data.data), len(data.data))
     return attachment.filename
Ejemplo n.º 25
0
def copy_attachment(source_env,
                    dest_env,
                    parent_type,
                    parent_id,
                    filename,
                    dest_db=None):
    # In case a string gets passed in
    if not isinstance(source_env, Environment):
        source_env = _open_environment(source_env)
    if not isinstance(dest_env, Environment):
        dest_env = _open_environment(dest_env)

    # Log message
    source_env.log.info(
        'DatamoverPlugin: Moving attachment (%s,%s,%s) to the environment at %s',
        parent_type, parent_id, filename, dest_env.path)
    dest_env.log.info(
        'DatamoverPlugin: Moving attachment (%s,%s,%s) from the environment at %s',
        parent_type, parent_id, filename, source_env.path)

    # Open databases
    source_db = source_env.get_db_cnx()
    source_cursor = source_db.cursor()
    handle_commit = True
    if not dest_db:
        dest_db, handle_commit = dest_env.get_db_cnx(), False
    dest_cursor = dest_db.cursor()

    # Remove the attachment from the destination
    try:
        dest_attachment = Attachment(dest_env,
                                     parent_type,
                                     parent_id,
                                     filename,
                                     db=dest_db)
        dest_attachment.delete(db=dest_db)
    except TracError:
        pass

    # Copy each entry in the attachments table
    source_cursor.execute(
        'SELECT * FROM attachment WHERE type=%s AND id=%s AND filename=%s',
        (parent_type, parent_id, filename))
    for row in source_cursor:
        att_data = dict(zip([d[0] for d in source_cursor.description], row))
        q = make_query(att_data, 'attachment')
        dest_cursor.execute(*q)
        # now copy the file itself
        old_att = Attachment(source_env,
                             parent_type,
                             parent_id,
                             filename,
                             db=source_db)
        new_att = Attachment(dest_env,
                             parent_type,
                             parent_id,
                             filename,
                             db=dest_db)
        if not os.path.isdir(os.path.dirname(new_att.path)):
            os.makedirs(os.path.dirname(new_att.path))
        copyfile(old_att.path, new_att.path)

    if handle_commit:
        dest_db.commit()
Ejemplo n.º 26
0
                item.update(item_elem.attr)
                for child_elem in item_elem.children():
                    item[child_elem.name] = child_elem.gettext()
                report.items.append(item)
            report.insert(db=db)

        # Collect attachments from the request body
        for attach_elem in elem.children(Recipe.ATTACH):
            attach_elem = list(attach_elem.children('file'))[0] # One file only
            filename = attach_elem.attr.get('filename')
            resource_id = attach_elem.attr.get('resource') == 'config' \
                                    and build.config or build.resource.id
            try: # Delete attachment if it already exists
                old_attach = Attachment(self.env, 'build',
                                    parent_id=resource_id, filename=filename)
                old_attach.delete()
            except ResourceNotFound:
                pass
            attachment = Attachment(self.env, 'build', parent_id=resource_id)
            attachment.description = attach_elem.attr.get('description')
            attachment.author = req.authname
            fileobj = StringIO(attach_elem.gettext().decode('base64'))
            attachment.insert(filename, fileobj, fileobj.len, db=db)

        # If this was the last step in the recipe we mark the build as
        # completed
        if last_step:
            self.log.info('Slave %s completed build %d ("%s" as of [%s])',
                          build.slave, build.id, build.config, build.rev)
            build.stopped = step.stopped
Ejemplo n.º 27
0
                report.items.append(item)
            report.insert(db=db)

        # Collect attachments from the request body
        for attach_elem in elem.children(Recipe.ATTACH):
            attach_elem = list(
                attach_elem.children('file'))[0]  # One file only
            filename = attach_elem.attr.get('filename')
            resource_id = attach_elem.attr.get('resource') == 'config' \
                                    and build.config or build.resource.id
            try:  # Delete attachment if it already exists
                old_attach = Attachment(self.env,
                                        'build',
                                        parent_id=resource_id,
                                        filename=filename)
                old_attach.delete()
            except ResourceNotFound:
                pass
            attachment = Attachment(self.env, 'build', parent_id=resource_id)
            attachment.description = attach_elem.attr.get('description')
            attachment.author = req.authname
            fileobj = StringIO(attach_elem.gettext().decode('base64'))
            attachment.insert(filename, fileobj, fileobj.len, db=db)

        # If this was the last step in the recipe we mark the build as
        # completed
        if last_step:
            self.log.info('Slave %s completed build %d ("%s" as of [%s])',
                          build.slave, build.id, build.config, build.rev)
            build.stopped = step.stopped
Ejemplo n.º 28
0
    def _save_attachement(self, req, attachment):
        from trac.web import RequestDone
        from trac.attachment import AttachmentModule, InvalidAttachment
        from trac.resource import get_resource_url
        from trac.timeline.web_ui import TimelineModule
        import os
        import unicodedata
        from trac.util.datefmt import pretty_timedelta

        response = None
        try:
            upload = req.args["attachment"]
            if not hasattr(upload, "filename") or not upload.filename:
                raise TracError(_("No file uploaded"))
            if hasattr(upload.file, "fileno"):
                size = os.fstat(upload.file.fileno())[6]
            else:
                upload.file.seek(0, 2)  # seek to end of file
                size = upload.file.tell()
                upload.file.seek(0)
            if size == 0:
                raise TracError(_("Can't upload empty file"))

            # Maximum attachment size (in bytes)
            max_size = AttachmentModule(self.env).max_size
            if max_size >= 0 and size > max_size:
                raise TracError(_("Maximum attachment size: %(num)s bytes", num=max_size), _("Upload failed"))

            # We try to normalize the filename to unicode NFC if we can.
            # Files uploaded from OS X might be in NFD.
            filename = unicodedata.normalize("NFC", unicode(upload.filename, "utf-8"))
            filename = filename.replace("\\", "/").replace(":", "/")
            filename = os.path.basename(filename)
            if not filename:
                raise TracError(_("No file uploaded"))
            # Now the filename is known, update the attachment resource
            # attachment.filename = filename
            attachment.description = req.args.get("description", "")
            attachment.author = get_reporter_id(req, "author")
            attachment.ipnr = req.remote_addr

            # Validate attachment
            for manipulator in AttachmentModule(self.env).manipulators:
                for field, message in manipulator.validate_attachment(req, attachment):
                    if field:
                        raise InvalidAttachment(
                            _("Attachment field %(field)s is " "invalid: %(message)s", field=field, message=message)
                        )
                    else:
                        raise InvalidAttachment(_("Invalid attachment: %(message)s", message=message))

            if req.args.get("replace"):
                try:
                    old_attachment = Attachment(self.env, attachment.resource(id=filename))
                    if not (old_attachment.author and req.authname and old_attachment.author == req.authname):
                        req.perm(attachment.resource).require("ATTACHMENT_DELETE")
                    if not attachment.description.strip() and old_attachment.description:
                        attachment.description = old_attachment.description
                    old_attachment.delete()
                except TracError:
                    pass  # don't worry if there's nothing to replace
                attachment.filename = None
            attachment.insert(filename, upload.file, size)
            timeline = TimelineModule(self.env).get_timeline_link(
                req, attachment.date, pretty_timedelta(attachment.date), precision="second"
            )
            response = {
                "attachment": {
                    "href": get_resource_url(self.env, attachment.resource, req.href),
                    "realm": attachment.resource.parent.realm,
                    "objid": attachment.resource.parent.id,
                    "filename": filename,
                    "size": size,
                    "author": attachment.author,
                    "description": attachment.description,
                    "timeline": timeline.generate().render().replace("<", "&lt;").replace(">", "&gt;"),
                }
            }
        except (TracError, InvalidAttachment), e:
            response = {"error": e.message}
Ejemplo n.º 29
0
    def _save_attachement(self, req, attachment):
        from trac.web import RequestDone
        from trac.attachment import AttachmentModule, InvalidAttachment
        from trac.resource import get_resource_url
        from trac.timeline.web_ui import TimelineModule
        import os
        import unicodedata
        from trac.util.datefmt import pretty_timedelta

        response = None
        try:
            upload = req.args['attachment']
            if not hasattr(upload, 'filename') or not upload.filename:
                raise TracError(_('No file uploaded'))
            if hasattr(upload.file, 'fileno'):
                size = os.fstat(upload.file.fileno())[6]
            else:
                upload.file.seek(0, 2)  # seek to end of file
                size = upload.file.tell()
                upload.file.seek(0)
            if size == 0:
                raise TracError(_("Can't upload empty file"))

            # Maximum attachment size (in bytes)
            max_size = AttachmentModule(self.env).max_size
            if max_size >= 0 and size > max_size:
                raise TracError(
                    _('Maximum attachment size: %(num)s bytes', num=max_size),
                    _('Upload failed'))

            # We try to normalize the filename to unicode NFC if we can.
            # Files uploaded from OS X might be in NFD.
            filename = unicodedata.normalize('NFC',
                                             unicode(upload.filename, 'utf-8'))
            filename = filename.replace('\\', '/').replace(':', '/')
            filename = os.path.basename(filename)
            if not filename:
                raise TracError(_('No file uploaded'))
            # Now the filename is known, update the attachment resource
            # attachment.filename = filename
            attachment.description = req.args.get('description', '')
            attachment.author = get_reporter_id(req, 'author')
            attachment.ipnr = req.remote_addr

            # Validate attachment
            for manipulator in AttachmentModule(self.env).manipulators:
                for field, message in manipulator.validate_attachment(
                        req, attachment):
                    if field:
                        raise InvalidAttachment(
                            _(
                                'Attachment field %(field)s is '
                                'invalid: %(message)s',
                                field=field,
                                message=message))
                    else:
                        raise InvalidAttachment(
                            _('Invalid attachment: %(message)s',
                              message=message))

            if req.args.get('replace'):
                try:
                    old_attachment = Attachment(
                        self.env, attachment.resource(id=filename))
                    if not (old_attachment.author and req.authname \
                            and old_attachment.author == req.authname):
                        req.perm(
                            attachment.resource).require('ATTACHMENT_DELETE')
                    if (not attachment.description.strip()
                            and old_attachment.description):
                        attachment.description = old_attachment.description
                    old_attachment.delete()
                except TracError:
                    pass  # don't worry if there's nothing to replace
                attachment.filename = None
            attachment.insert(filename, upload.file, size)
            timeline = TimelineModule(self.env).get_timeline_link(
                req,
                attachment.date,
                pretty_timedelta(attachment.date),
                precision='second')
            response = {
                'attachment': {
                    'href':
                    get_resource_url(self.env, attachment.resource, req.href),
                    'realm':
                    attachment.resource.parent.realm,
                    'objid':
                    attachment.resource.parent.id,
                    'filename':
                    filename,
                    'size':
                    size,
                    'author':
                    attachment.author,
                    'description':
                    attachment.description,
                    'timeline':
                    timeline.generate().render().replace('<', '&lt;').replace(
                        '>', '&gt;')
                }
            }
        except (TracError, InvalidAttachment), e:
            response = {'error': e.message}