Improved external attachment support.

This commit is contained in:
Martin Edenhofer 2017-03-17 06:27:50 +01:00
parent b09ecbe23e
commit 14507180a3
9 changed files with 170 additions and 26 deletions

View file

@ -0,0 +1,19 @@
class App.TicketZoomArticleImageView extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: 'Download'
buttonClass: 'btn--success'
head: ''
large: true
events:
'submit form': 'submit'
'click .js-cancel': 'cancel'
'click .js-close': 'cancel'
content: ->
"<div class=\"centered\">#{@image}</div>"
onSubmit: =>
url = "#{$(@image).attr('src')}?disposition=attachment"
window.open(url, '_blank')

View file

@ -53,6 +53,7 @@ class ArticleViewItem extends App.ObserverController
'click .textBubble': 'toggleMetaWithDelay' 'click .textBubble': 'toggleMetaWithDelay'
'click .textBubble a': 'stopPropagation' 'click .textBubble a': 'stopPropagation'
'click .js-toggleFold': 'toggleFold' 'click .js-toggleFold': 'toggleFold'
'click .richtext-content img': 'imageView'
constructor: -> constructor: ->
super super
@ -367,3 +368,8 @@ class ArticleViewItem extends App.ObserverController
remove: => remove: =>
@el.remove() @el.remove()
imageView: (e) ->
e.preventDefault()
e.stopPropagation()
new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML)

View file

@ -7,7 +7,9 @@
<%- @Icon('diagonal-cross') %> <%- @Icon('diagonal-cross') %>
</div> </div>
<% end %> <% end %>
<% if @head: %>
<h1 class="modal-title"><% if @headPrefix: %><%- @T(@headPrefix) %>: <% end %><%- @T(@head) %></h1> <h1 class="modal-title"><% if @headPrefix: %><%- @T(@headPrefix) %>: <% end %><%- @T(@head) %></h1>
<% end %>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<%- @content %> <%- @content %>

View file

@ -48,22 +48,7 @@ module CreatesTicketArticles
# store dataurl images to store # store dataurl images to store
attachments_inline = [] attachments_inline = []
if article.body && article.content_type =~ %r{text/html}i if article.body && article.content_type =~ %r{text/html}i
article.body.gsub!( %r{(<img\s.?src=")(data:image/(jpeg|png);base64,.+?)"(|.+?)>}im ) { |_item| (article.body, attachments_inline) = HtmlSanitizer.replace_inline_images(article.body, ticket.id)
file_attributes = StaticAssets.data_url_attributes($2)
cid = "#{ticket.id}.#{rand(999_999_999)}@#{Setting.get('fqdn')}"
attachment = {
data: file_attributes[:content],
filename: cid,
preferences: {
'Content-Type' => file_attributes[:mime_type],
'Mime-Type' => file_attributes[:mime_type],
'Content-ID' => cid,
'Content-Disposition' => 'inline',
},
}
attachments_inline.push attachment
"#{$1}cid:#{cid}\">"
}
end end
# find attachments in upload cache # find attachments in upload cache

View file

@ -270,6 +270,9 @@ returns
} }
attributes['attachments'].push item attributes['attachments'].push item
} }
if articles['body'] && articles['content_type'] =~ %r{text/html}i
articles['body'] = HtmlSanitizer.dynamic_image_size(articles['body'])
end
Ticket::Article.insert_urls(attributes) Ticket::Article.insert_urls(attributes)
end end

View file

@ -352,6 +352,73 @@ cleanup html string:
false false
end end
=begin
reolace inline images with cid images
string = HtmlSanitizer.replace_inline_images(article.body)
=end
def self.replace_inline_images(string, prefix = rand(999_999_999))
attachments_inline = []
scrubber = Loofah::Scrubber.new do |node|
if node.name == 'img'
if node['src'] && node['src'] =~ %r{^(data:image/(jpeg|png);base64,.+?)$}i
file_attributes = StaticAssets.data_url_attributes($1)
cid = "#{prefix}.#{rand(999_999_999)}@#{Setting.get('fqdn')}"
attachment = {
data: file_attributes[:content],
filename: cid,
preferences: {
'Content-Type' => file_attributes[:mime_type],
'Mime-Type' => file_attributes[:mime_type],
'Content-ID' => cid,
'Content-Disposition' => 'inline',
},
}
attachments_inline.push attachment
node['src'] = "cid:#{cid}"
end
Loofah::Scrubber::STOP
end
end
[Loofah.fragment(string).scrub!(scrubber).to_s, attachments_inline]
end
=begin
satinize style of img tags
string = HtmlSanitizer.dynamic_image_size(article.body)
=end
def self.dynamic_image_size(string)
scrubber = Loofah::Scrubber.new do |node|
if node.name == 'img'
if node['src']
style = 'max-width:100%;'
if node['style']
pears = node['style'].downcase.gsub(/\t|\n|\r/, '').split(';')
pears.each { |local_pear|
prop = local_pear.split(':')
next if !prop[0]
key = prop[0].strip
if key == 'height'
key = 'max-height'
end
style += "#{key}:#{prop[1]};"
}
end
node['style'] = style
end
Loofah::Scrubber::STOP
end
end
Loofah.fragment(string).scrub!(scrubber).to_s
end
private_class_method :cleanup_target private_class_method :cleanup_target
private_class_method :add_link private_class_method :add_link
private_class_method :url_same? private_class_method :url_same?

View file

@ -156,6 +156,11 @@ returns
result[:subject] = data[:main_object].subject_build(result[:subject]) result[:subject] = data[:main_object].subject_build(result[:subject])
end end
# prepare scaling of images
if result[:body]
result[:body] = HtmlSanitizer.dynamic_image_size(result[:body])
end
NotificationFactory::Mailer.send( NotificationFactory::Mailer.send(
recipient: data[:user], recipient: data[:user],
subject: result[:subject], subject: result[:subject],

View file

@ -99,8 +99,8 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal(nil, result['subject']) assert_equal(nil, result['subject'])
assert_no_match(/some body <img src="cid:/, result['body']) assert_no_match(/some body <img src="cid:.+?/, result['body'])
assert_match(%r{some body <img src="/api/v1/ticket_attachment/.}, result['body']) assert_match(%r{some body <img src="/api/v1/ticket_attachment/.+?" alt="Red dot"}, result['body'])
assert_equal('text/html', result['content_type']) assert_equal('text/html', result['content_type'])
assert_equal(@agent.id, result['updated_by_id']) assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id']) assert_equal(@agent.id, result['created_by_id'])

View file

@ -848,6 +848,18 @@ christian.schaefer@example.com'
result = 'john.smith2@example.com' result = 'john.smith2@example.com'
assert_equal(result, html.html2html_strict) assert_equal(result, html.html2html_strict)
html = '<img src="/some.png" style="color: blue; width: 30px; height: 50px">'
result = '<img src="/some.png" style=" width: 30px; height: 50px;">'
assert_equal(result, html.html2html_strict)
html = '<img src="/some.png" width="30px" height="50px">'
result = '<img src="/some.png" style="width:30px;height:50px;">'
assert_equal(result, html.html2html_strict)
html = '<img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...">'
result = '<img style="width: 181px; height: 125px;" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...">'
assert_equal(result, html.html2html_strict)
html = '<p class="MsoNormal"><a href="http://www.example.de/"><span style="color:blue;text-decoration:none"><img border="0" width="30" height="30" id="_x0000_i1030" src="cid:image001.png@01D172FC.F323CDB0"></span></a><o:p></o:p></p>' html = '<p class="MsoNormal"><a href="http://www.example.de/"><span style="color:blue;text-decoration:none"><img border="0" width="30" height="30" id="_x0000_i1030" src="cid:image001.png@01D172FC.F323CDB0"></span></a><o:p></o:p></p>'
#result = '<p>http://www.example.de/ <a href="http://www.example.de/" rel="nofollow" target="_blank"><img border="0" src="cid:image001.png@01D172FC.F323CDB0" style="width:30px;height:30px;"></a></p>' #result = '<p>http://www.example.de/ <a href="http://www.example.de/" rel="nofollow" target="_blank"><img border="0" src="cid:image001.png@01D172FC.F323CDB0" style="width:30px;height:30px;"></a></p>'
result = '<p><a href="http://www.example.de/" rel="nofollow" target="_blank">http://www.example.de/</a></p>' result = '<p><a href="http://www.example.de/" rel="nofollow" target="_blank">http://www.example.de/</a></p>'
@ -864,6 +876,51 @@ christian.schaefer@example.com'
assert_equal(result, html.html2html_strict) assert_equal(result, html.html2html_strict)
end end
test 'inline attachment replace' do
html = '<img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img style="width: 181px; height: 125px" src="cid:.+?">/, body)
assert(1, attachments_inline.count)
html = '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..." style="width: 181px; height: 125px" alt="abc">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="cid:.+?" style="width: 181px; height: 125px" alt="abc">/, body)
assert(1, attachments_inline.count)
html = '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..." style="width: 181px; height: 125px" alt="abc"><invalid what ever'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="cid:.+?" style="width: 181px; height: 125px" alt="abc">/, body)
assert(1, attachments_inline.count)
html = '<img src="/some_one.png" style="width: 181px; height: 125px" alt="abc">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="\/some_one.png" style="width: 181px; height: 125px" alt="abc">/, body)
assert(0, attachments_inline.count)
html = '<div><img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."><p>123</p><img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."></div>'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<div>\s+<img style="width: 181px; height: 125px" src="cid:.+?"><p>123<\/p>\s+<img style="width: 181px; height: 125px" src="cid:.+?">\s+<\/div>/, body)
assert(2, attachments_inline.count)
end
test 'set dynamic image size' do
html = '<img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...">'
body = HtmlSanitizer.dynamic_image_size(html)
assert_match(/<img style="max-width:100%;width: 181px;max-height: 125px;" src="data:image.+?">/, body)
html = '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..." style="width: 181px; height: 125px" alt="abc">'
body = HtmlSanitizer.dynamic_image_size(html)
assert_match(/<img src="data:image.+?" style="max-width:100%;width: 181px;max-height: 125px;" alt="abc">/, body)
html = '<img src="/some_one.png" style="width: 181px; height: 125px" alt="abc">'
body = HtmlSanitizer.dynamic_image_size(html)
assert_match(/<img src="\/some_one.png" style="max-width:100%;width: 181px;max-height: 125px;" alt="abc">/, body)
html = '<img src="/some_one.png" alt="abc">'
body = HtmlSanitizer.dynamic_image_size(html)
assert_match(/<img src="\/some_one.png" alt="abc" style="max-width:100%;">/, body)
end
test 'signature_identify function' do test 'signature_identify function' do
marker_template = '######SIGNATURE_MARKER######' marker_template = '######SIGNATURE_MARKER######'