Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Tatuya Kamada
gitlab-ce
Commits
e9972efc
Commit
e9972efc
authored
Aug 20, 2015
by
Douwe Maan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Extract ReplyParser and AttachmentUploader from Receiver.
parent
3ff9d5c6
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
561 additions
and
333 deletions
+561
-333
app/workers/email_receiver_worker.rb
app/workers/email_receiver_worker.rb
+8
-8
lib/gitlab/email/attachment_uploader.rb
lib/gitlab/email/attachment_uploader.rb
+35
-0
lib/gitlab/email/html_cleaner.rb
lib/gitlab/email/html_cleaner.rb
+135
-0
lib/gitlab/email/receiver.rb
lib/gitlab/email/receiver.rb
+101
-0
lib/gitlab/email/reply_parser.rb
lib/gitlab/email/reply_parser.rb
+91
-0
lib/gitlab/email_html_cleaner.rb
lib/gitlab/email_html_cleaner.rb
+0
-133
lib/gitlab/email_receiver.rb
lib/gitlab/email_receiver.rb
+0
-192
spec/lib/gitlab/email/reply_parser_spec.rb
spec/lib/gitlab/email/reply_parser_spec.rb
+191
-0
No files found.
app/workers/email_receiver_worker.rb
View file @
e9972efc
...
@@ -7,7 +7,7 @@ class EmailReceiverWorker
...
@@ -7,7 +7,7 @@ class EmailReceiverWorker
return
unless
Gitlab
::
ReplyByEmail
.
enabled?
return
unless
Gitlab
::
ReplyByEmail
.
enabled?
begin
begin
Gitlab
::
EmailReceiver
.
new
(
raw
).
execute
Gitlab
::
Email
::
Receiver
.
new
(
raw
).
execute
rescue
=>
e
rescue
=>
e
handle_failure
(
raw
,
e
)
handle_failure
(
raw
,
e
)
end
end
...
@@ -22,20 +22,20 @@ class EmailReceiverWorker
...
@@ -22,20 +22,20 @@ class EmailReceiverWorker
reason
=
nil
reason
=
nil
case
e
case
e
when
Gitlab
::
EmailReceiver
::
SentNotificationNotFound
when
Gitlab
::
Email
::
Receiver
::
SentNotificationNotFound
reason
=
"We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
reason
=
"We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
when
Gitlab
::
EmailReceiver
::
EmptyEmailError
when
Gitlab
::
Email
::
Receiver
::
EmptyEmailError
can_retry
=
true
can_retry
=
true
reason
=
"It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
reason
=
"It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
when
Gitlab
::
EmailReceiver
::
AutoGeneratedEmailError
when
Gitlab
::
Email
::
Receiver
::
AutoGeneratedEmailError
reason
=
"The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
reason
=
"The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
when
Gitlab
::
EmailReceiver
::
UserNotFoundError
when
Gitlab
::
Email
::
Receiver
::
UserNotFoundError
reason
=
"We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
reason
=
"We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
when
Gitlab
::
EmailReceiver
::
UserNotAuthorizedError
when
Gitlab
::
Email
::
Receiver
::
UserNotAuthorizedError
reason
=
"You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
reason
=
"You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
when
Gitlab
::
EmailReceiver
::
NoteableNotFoundError
when
Gitlab
::
Email
::
Receiver
::
NoteableNotFoundError
reason
=
"The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
reason
=
"The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
when
Gitlab
::
EmailReceiver
::
InvalidNote
when
Gitlab
::
Email
::
Receiver
::
InvalidNote
can_retry
=
true
can_retry
=
true
reason
=
e
.
message
reason
=
e
.
message
else
else
...
...
lib/gitlab/email/attachment_uploader.rb
0 → 100644
View file @
e9972efc
module
Gitlab
module
Email
module
AttachmentUploader
attr_accessor
:message
def
initialize
(
message
)
@message
=
message
end
def
execute
(
project
)
attachments
=
[]
message
.
attachments
.
each
do
|
attachment
|
tmp
=
Tempfile
.
new
(
"gitlab-email-attachment"
)
begin
File
.
open
(
tmp
.
path
,
"w+b"
)
{
|
f
|
f
.
write
attachment
.
body
.
decoded
}
file
=
{
tempfile:
tmp
,
filename:
attachment
.
filename
,
content_type:
attachment
.
content_type
}
link
=
::
Projects
::
UploadService
.
new
(
project
,
file
).
execute
attachments
<<
link
if
link
ensure
tmp
.
close!
end
end
attachments
end
end
end
end
lib/gitlab/email/html_cleaner.rb
0 → 100644
View file @
e9972efc
# Taken mostly from Discourse's Email::HtmlCleaner
module
Gitlab
module
Email
# HtmlCleaner cleans up the extremely dirty HTML that many email clients
# generate by stripping out any excess divs or spans, removing styling in
# the process (which also makes the html more suitable to be parsed as
# Markdown).
class
HtmlCleaner
# Elements to hoist all children out of
HTML_HOIST_ELEMENTS
=
%w(div span font table tbody th tr td)
# Node types to always delete
HTML_DELETE_ELEMENT_TYPES
=
[
Nokogiri
::
XML
::
Node
::
DTD_NODE
,
Nokogiri
::
XML
::
Node
::
COMMENT_NODE
,
]
# Private variables:
# @doc - nokogiri document
# @out - same as @doc, but only if trimming has occured
def
initialize
(
html
)
if
html
.
is_a?
(
String
)
@doc
=
Nokogiri
::
HTML
(
html
)
else
@doc
=
html
end
end
class
<<
self
# HtmlCleaner.trim(inp, opts={})
#
# Arguments:
# inp - Either a HTML string or a Nokogiri document.
# Options:
# :return => :doc, :string
# Specify the desired return type.
# Defaults to the type of the input.
# A value of :string is equivalent to calling get_document_text()
# on the returned document.
def
trim
(
inp
,
opts
=
{})
cleaner
=
HtmlCleaner
.
new
(
inp
)
opts
[
:return
]
||=
(
inp
.
is_a?
(
String
)
?
:string
:
:doc
)
if
opts
[
:return
]
==
:string
cleaner
.
output_html
else
cleaner
.
output_document
end
end
# HtmlCleaner.get_document_text(doc)
#
# Get the body portion of the document, including html, as a string.
def
get_document_text
(
doc
)
body
=
doc
.
xpath
(
'//body'
)
if
body
body
.
inner_html
else
doc
.
inner_html
end
end
end
def
output_document
@out
||=
begin
doc
=
@doc
trim_process_node
doc
add_newlines
doc
doc
end
end
def
output_html
HtmlCleaner
.
get_document_text
(
output_document
)
end
private
def
add_newlines
(
doc
)
# Replace <br> tags with a markdown \n
doc
.
xpath
(
'//br'
).
each
do
|
br
|
br
.
replace
(
new_linebreak_node
doc
,
2
)
end
# Surround <p> tags with newlines, to help with line-wise postprocessing
# and ensure markdown paragraphs
doc
.
xpath
(
'//p'
).
each
do
|
p
|
p
.
before
(
new_linebreak_node
doc
)
p
.
after
(
new_linebreak_node
doc
,
2
)
end
end
def
new_linebreak_node
(
doc
,
count
=
1
)
Nokogiri
::
XML
::
Text
.
new
(
"
\n
"
*
count
,
doc
)
end
def
trim_process_node
(
node
)
if
should_hoist?
(
node
)
hoisted
=
trim_hoist_element
node
hoisted
.
each
{
|
child
|
trim_process_node
child
}
elsif
should_delete?
(
node
)
node
.
remove
else
if
children
=
node
.
children
children
.
each
{
|
child
|
trim_process_node
child
}
end
end
node
end
def
trim_hoist_element
(
element
)
hoisted
=
[]
element
.
children
.
each
do
|
child
|
element
.
before
(
child
)
hoisted
<<
child
end
element
.
remove
hoisted
end
def
should_hoist?
(
node
)
return
false
unless
node
.
element?
HTML_HOIST_ELEMENTS
.
include?
node
.
name
end
def
should_delete?
(
node
)
return
true
if
HTML_DELETE_ELEMENT_TYPES
.
include?
node
.
type
return
true
if
node
.
element?
&&
node
.
name
==
'head'
return
true
if
node
.
text?
&&
node
.
text
.
strip
.
blank?
false
end
end
end
end
lib/gitlab/email/receiver.rb
0 → 100644
View file @
e9972efc
# Inspired in great part by Discourse's Email::Receiver
module
Gitlab
module
Email
class
Receiver
class
ProcessingError
<
StandardError
;
end
class
EmailUnparsableError
<
ProcessingError
;
end
class
EmptyEmailError
<
ProcessingError
;
end
class
UserNotFoundError
<
ProcessingError
;
end
class
UserNotAuthorizedError
<
ProcessingError
;
end
class
NoteableNotFoundError
<
ProcessingError
;
end
class
AutoGeneratedEmailError
<
ProcessingError
;
end
class
SentNotificationNotFound
<
ProcessingError
;
end
class
InvalidNote
<
ProcessingError
;
end
def
initialize
(
raw
)
@raw
=
raw
end
def
message
@message
||=
Mail
::
Message
.
new
(
@raw
)
rescue
Encoding
::
UndefinedConversionError
,
Encoding
::
InvalidByteSequenceError
=>
e
raise
EmailUnparsableError
,
e
end
def
execute
raise
SentNotificationNotFound
unless
sent_notification
raise
EmptyEmailError
if
@raw
.
blank?
raise
AutoGeneratedEmailError
if
message
.
header
.
to_s
=~
/auto-(generated|replied)/
author
=
sent_notification
.
recipient
raise
UserNotFoundError
unless
author
project
=
sent_notification
.
project
raise
UserNotAuthorizedError
unless
author
.
can?
(
:create_note
,
project
)
raise
NoteableNotFoundError
unless
sent_notification
.
noteable
reply
=
ReplyParser
.
new
(
message
).
execute
.
strip
raise
EmptyEmailError
if
reply
.
blank?
reply
=
add_attachments
(
reply
)
note
=
create_note
(
reply
)
unless
note
.
persisted?
message
=
"The comment could not be created for the following reasons:"
note
.
errors
.
full_messages
.
each
do
|
error
|
message
<<
"
\n\n
-
#{
error
}
"
end
raise
InvalidNote
,
message
end
end
private
def
reply_key
reply_key
=
nil
message
.
to
.
each
do
|
address
|
reply_key
=
Gitlab
::
ReplyByEmail
.
reply_key_from_address
(
address
)
break
if
reply_key
end
reply_key
end
def
sent_notification
return
nil
unless
reply_key
SentNotification
.
for
(
reply_key
)
end
def
add_attachments
(
reply
)
attachments
=
AttachmentUploader
.
new
(
message
).
execute
(
project
)
attachments
.
each
do
|
link
|
text
=
"[
#{
link
[
:alt
]
}
](
#{
link
[
:url
]
}
)"
text
.
prepend
(
"!"
)
if
link
[
:is_image
]
reply
<<
"
\n\n
#{
text
}
"
end
end
def
create_note
(
reply
)
Notes
::
CreateService
.
new
(
sent_notification
.
project
,
sent_notification
.
recipient
,
note:
reply
,
noteable_type:
sent_notification
.
noteable_type
,
noteable_id:
sent_notification
.
noteable_id
,
commit_id:
sent_notification
.
commit_id
).
execute
end
end
end
end
lib/gitlab/email/reply_parser.rb
0 → 100644
View file @
e9972efc
# Inspired in great part by Discourse's Email::Receiver
module
Gitlab
module
Email
class
ReplyParser
attr_accessor
:message
def
initialize
(
message
)
@message
=
message
end
def
execute
body
=
select_body
(
message
)
encoding
=
body
.
encoding
body
=
discourse_email_trimmer
(
body
)
body
=
EmailReplyParser
.
parse_reply
(
body
)
body
.
force_encoding
(
encoding
).
encode
(
"UTF-8"
)
end
private
def
select_body
(
message
)
html
=
nil
text
=
nil
if
message
.
multipart?
html
=
fix_charset
(
message
.
html_part
)
text
=
fix_charset
(
message
.
text_part
)
elsif
message
.
content_type
=~
/text\/html/
html
=
fix_charset
(
message
)
end
# prefer plain text
return
text
if
text
if
html
body
=
HtmlCleaner
.
new
(
html
).
output_html
else
body
=
fix_charset
(
message
)
end
# Certain trigger phrases that means we didn't parse correctly
if
body
=~
/(Content\-Type\:|multipart\/alternative|text\/plain)/
return
""
end
body
end
# Force encoding to UTF-8 on a Mail::Message or Mail::Part
def
fix_charset
(
object
)
return
nil
if
object
.
nil?
if
object
.
charset
object
.
body
.
decoded
.
force_encoding
(
object
.
charset
.
gsub
(
/utf8/i
,
"UTF-8"
)).
encode
(
"UTF-8"
).
to_s
else
object
.
body
.
to_s
end
rescue
nil
end
REPLYING_HEADER_LABELS
=
%w(From Sent To Subject Reply To Cc Bcc Date)
REPLYING_HEADER_REGEX
=
Regexp
.
union
(
REPLYING_HEADER_LABELS
.
map
{
|
label
|
"
#{
label
}
:"
})
def
discourse_email_trimmer
(
body
)
lines
=
body
.
scrub
.
lines
.
to_a
range_end
=
0
lines
.
each_with_index
do
|
l
,
idx
|
# This one might be controversial but so many reply lines have years, times and end with a colon.
# Let's try it and see how well it works.
break
if
(
l
=~
/\d{4}/
&&
l
=~
/\d:\d\d/
&&
l
=~
/\:$/
)
||
(
l
=~
/On \w+ \d+,? \d+,?.*wrote:/
)
# Headers on subsequent lines
break
if
(
0
..
2
).
all?
{
|
off
|
lines
[
idx
+
off
]
=~
REPLYING_HEADER_REGEX
}
# Headers on the same line
break
if
REPLYING_HEADER_LABELS
.
count
{
|
label
|
l
.
include?
(
label
)
}
>=
3
range_end
=
idx
end
lines
[
0
..
range_end
].
join
.
strip
end
end
end
end
lib/gitlab/email_html_cleaner.rb
deleted
100644 → 0
View file @
3ff9d5c6
# Taken mostly from Discourse's Email::HtmlCleaner
module
Gitlab
# HtmlCleaner cleans up the extremely dirty HTML that many email clients
# generate by stripping out any excess divs or spans, removing styling in
# the process (which also makes the html more suitable to be parsed as
# Markdown).
class
EmailHtmlCleaner
# Elements to hoist all children out of
HTML_HOIST_ELEMENTS
=
%w(div span font table tbody th tr td)
# Node types to always delete
HTML_DELETE_ELEMENT_TYPES
=
[
Nokogiri
::
XML
::
Node
::
DTD_NODE
,
Nokogiri
::
XML
::
Node
::
COMMENT_NODE
,
]
# Private variables:
# @doc - nokogiri document
# @out - same as @doc, but only if trimming has occured
def
initialize
(
html
)
if
html
.
is_a?
(
String
)
@doc
=
Nokogiri
::
HTML
(
html
)
else
@doc
=
html
end
end
class
<<
self
# EmailHtmlCleaner.trim(inp, opts={})
#
# Arguments:
# inp - Either a HTML string or a Nokogiri document.
# Options:
# :return => :doc, :string
# Specify the desired return type.
# Defaults to the type of the input.
# A value of :string is equivalent to calling get_document_text()
# on the returned document.
def
trim
(
inp
,
opts
=
{})
cleaner
=
EmailHtmlCleaner
.
new
(
inp
)
opts
[
:return
]
||=
(
inp
.
is_a?
(
String
)
?
:string
:
:doc
)
if
opts
[
:return
]
==
:string
cleaner
.
output_html
else
cleaner
.
output_document
end
end
# EmailHtmlCleaner.get_document_text(doc)
#
# Get the body portion of the document, including html, as a string.
def
get_document_text
(
doc
)
body
=
doc
.
xpath
(
'//body'
)
if
body
body
.
inner_html
else
doc
.
inner_html
end
end
end
def
output_document
@out
||=
begin
doc
=
@doc
trim_process_node
doc
add_newlines
doc
doc
end
end
def
output_html
EmailHtmlCleaner
.
get_document_text
(
output_document
)
end
private
def
add_newlines
(
doc
)
# Replace <br> tags with a markdown \n
doc
.
xpath
(
'//br'
).
each
do
|
br
|
br
.
replace
(
new_linebreak_node
doc
,
2
)
end
# Surround <p> tags with newlines, to help with line-wise postprocessing
# and ensure markdown paragraphs
doc
.
xpath
(
'//p'
).
each
do
|
p
|
p
.
before
(
new_linebreak_node
doc
)
p
.
after
(
new_linebreak_node
doc
,
2
)
end
end
def
new_linebreak_node
(
doc
,
count
=
1
)
Nokogiri
::
XML
::
Text
.
new
(
"
\n
"
*
count
,
doc
)
end
def
trim_process_node
(
node
)
if
should_hoist?
(
node
)
hoisted
=
trim_hoist_element
node
hoisted
.
each
{
|
child
|
trim_process_node
child
}
elsif
should_delete?
(
node
)
node
.
remove
else
if
children
=
node
.
children
children
.
each
{
|
child
|
trim_process_node
child
}
end
end
node
end
def
trim_hoist_element
(
element
)
hoisted
=
[]
element
.
children
.
each
do
|
child
|
element
.
before
(
child
)
hoisted
<<
child
end
element
.
remove
hoisted
end
def
should_hoist?
(
node
)
return
false
unless
node
.
element?
HTML_HOIST_ELEMENTS
.
include?
node
.
name
end
def
should_delete?
(
node
)
return
true
if
HTML_DELETE_ELEMENT_TYPES
.
include?
node
.
type
return
true
if
node
.
element?
&&
node
.
name
==
'head'
return
true
if
node
.
text?
&&
node
.
text
.
strip
.
blank?
false
end
end
end
lib/gitlab/email_receiver.rb
deleted
100644 → 0
View file @
3ff9d5c6
# Inspired in great part by Discourse's Email::Receiver
module
Gitlab
class
EmailReceiver
class
ProcessingError
<
StandardError
;
end
class
EmailUnparsableError
<
ProcessingError
;
end
class
EmptyEmailError
<
ProcessingError
;
end
class
UserNotFoundError
<
ProcessingError
;
end
class
UserNotAuthorizedError
<
ProcessingError
;
end
class
NoteableNotFoundError
<
ProcessingError
;
end
class
AutoGeneratedEmailError
<
ProcessingError
;
end
class
SentNotificationNotFound
<
ProcessingError
;
end
class
InvalidNote
<
ProcessingError
;
end
def
initialize
(
raw
)
@raw
=
raw
end
def
message
@message
||=
Mail
::
Message
.
new
(
@raw
)
rescue
Encoding
::
UndefinedConversionError
,
Encoding
::
InvalidByteSequenceError
=>
e
raise
EmailUnparsableError
,
e
end
def
execute
raise
SentNotificationNotFound
unless
sent_notification
raise
EmptyEmailError
if
@raw
.
blank?
raise
AutoGeneratedEmailError
if
message
.
header
.
to_s
=~
/auto-(generated|replied)/
author
=
sent_notification
.
recipient
raise
UserNotFoundError
unless
author
project
=
sent_notification
.
project
raise
UserNotAuthorizedError
unless
author
.
can?
(
:create_note
,
project
)
raise
NoteableNotFoundError
unless
sent_notification
.
noteable
body
=
parse_body
(
message
)
upload_attachments
.
each
do
|
link
|
body
<<
"
\n\n
#{
link
}
"
end
note
=
Notes
::
CreateService
.
new
(
project
,
author
,
note:
body
,
noteable_type:
sent_notification
.
noteable_type
,
noteable_id:
sent_notification
.
noteable_id
,
commit_id:
sent_notification
.
commit_id
).
execute
unless
note
.
persisted?
message
=
"The comment could not be created for the following reasons:"
note
.
errors
.
full_messages
.
each
do
|
error
|
message
<<
"
\n\n
-
#{
error
}
"
end
raise
InvalidNote
,
message
end
end
def
parse_body
(
message
)
body
=
select_body
(
message
)
encoding
=
body
.
encoding
raise
EmptyEmailError
if
body
.
strip
.
blank?
body
=
discourse_email_trimmer
(
body
)
raise
EmptyEmailError
if
body
.
strip
.
blank?
body
=
EmailReplyParser
.
parse_reply
(
body
)
raise
EmptyEmailError
if
body
.
strip
.
blank?
body
.
force_encoding
(
encoding
).
encode
(
"UTF-8"
)
end
private
def
reply_key
reply_key
=
nil
message
.
to
.
each
do
|
address
|
reply_key
=
Gitlab
::
ReplyByEmail
.
reply_key_from_address
(
address
)
break
if
reply_key
end
reply_key
end
def
sent_notification
return
nil
unless
reply_key
SentNotification
.
for
(
reply_key
)
end
def
select_body
(
message
)
html
=
nil
text
=
nil
if
message
.
multipart?
html
=
fix_charset
(
message
.
html_part
)
text
=
fix_charset
(
message
.
text_part
)
elsif
message
.
content_type
=~
/text\/html/
html
=
fix_charset
(
message
)
end
# prefer plain text
return
text
if
text
if
html
body
=
EmailHtmlCleaner
.
new
(
html
).
output_html
else
body
=
fix_charset
(
message
)
end
# Certain trigger phrases that means we didn't parse correctly
if
body
=~
/(Content\-Type\:|multipart\/alternative|text\/plain)/
raise
EmptyEmailError
end
body
end
# Force encoding to UTF-8 on a Mail::Message or Mail::Part
def
fix_charset
(
object
)
return
nil
if
object
.
nil?
if
object
.
charset
object
.
body
.
decoded
.
force_encoding
(
object
.
charset
.
gsub
(
/utf8/i
,
"UTF-8"
)).
encode
(
"UTF-8"
).
to_s
else
object
.
body
.
to_s
end
rescue
nil
end
REPLYING_HEADER_LABELS
=
%w(From Sent To Subject Reply To Cc Bcc Date)
REPLYING_HEADER_REGEX
=
Regexp
.
union
(
REPLYING_HEADER_LABELS
.
map
{
|
label
|
"
#{
label
}
:"
})
def
discourse_email_trimmer
(
body
)
lines
=
body
.
scrub
.
lines
.
to_a
range_end
=
0
lines
.
each_with_index
do
|
l
,
idx
|
# This one might be controversial but so many reply lines have years, times and end with a colon.
# Let's try it and see how well it works.
break
if
(
l
=~
/\d{4}/
&&
l
=~
/\d:\d\d/
&&
l
=~
/\:$/
)
||
(
l
=~
/On \w+ \d+,? \d+,?.*wrote:/
)
# Headers on subsequent lines
break
if
(
0
..
2
).
all?
{
|
off
|
lines
[
idx
+
off
]
=~
REPLYING_HEADER_REGEX
}
# Headers on the same line
break
if
REPLYING_HEADER_LABELS
.
count
{
|
label
|
l
.
include?
(
label
)
}
>=
3
range_end
=
idx
end
lines
[
0
..
range_end
].
join
.
strip
end
def
upload_attachments
attachments
=
[]
message
.
attachments
.
each
do
|
attachment
|
tmp
=
Tempfile
.
new
(
"gitlab-email-attachment"
)
begin
File
.
open
(
tmp
.
path
,
"w+b"
)
{
|
f
|
f
.
write
attachment
.
body
.
decoded
}
file
=
{
tempfile:
tmp
,
filename:
attachment
.
filename
,
content_type:
attachment
.
content_type
}
link
=
::
Projects
::
UploadService
.
new
(
sent_notification
.
project
,
file
).
execute
if
link
text
=
"[
#{
link
[
:alt
]
}
](
#{
link
[
:url
]
}
)"
text
.
prepend
(
"!"
)
if
link
[
:is_image
]
attachments
<<
text
end
ensure
tmp
.
close!
end
end
attachments
end
end
end
spec/lib/gitlab/email
_receiv
er_spec.rb
→
spec/lib/gitlab/email
/reply_pars
er_spec.rb
View file @
e9972efc
require
"spec_helper"
require
"spec_helper"
# Inspired in great part by Discourse's Email::Receiver
# Inspired in great part by Discourse's Email::Receiver
describe
Gitlab
::
Email
Receiv
er
do
describe
Gitlab
::
Email
::
ReplyPars
er
do
def
fixture_file
(
filename
)
def
fixture_file
(
filename
)
return
''
if
filename
.
blank?
return
''
if
filename
.
blank?
file_path
=
File
.
expand_path
(
Rails
.
root
+
'spec/fixtures/'
+
filename
)
file_path
=
File
.
expand_path
(
Rails
.
root
+
'spec/fixtures/'
+
filename
)
File
.
read
(
file_path
)
File
.
read
(
file_path
)
end
end
before
do
describe
'self.parse_body'
do
allow
(
Gitlab
.
config
.
reply_by_email
).
to
receive
(
:enabled
).
and_return
(
true
)
allow
(
Gitlab
.
config
.
reply_by_email
).
to
receive
(
:address
).
and_return
(
"reply+%{reply_key}@appmail.adventuretime.ooo"
)
end
describe
'parse_body'
do
def
test_parse_body
(
mail_string
)
def
test_parse_body
(
mail_string
)
Gitlab
::
EmailReceiver
.
new
(
nil
).
parse_body
(
Mail
::
Message
.
new
(
mail_string
))
described_class
.
new
(
Mail
::
Message
.
new
(
mail_string
)).
execute
end
end
it
"r
aises EmptyEmailError
if the message is blank"
do
it
"r
eturns an empty string
if the message is blank"
do
expect
{
test_parse_body
(
""
)
}.
to
raise_error
(
Gitlab
::
EmailReceiver
::
EmptyEmailError
)
expect
(
test_parse_body
(
""
)).
to
eq
(
""
)
end
end
it
"r
aises EmptyEmailError
if the message is not an email"
do
it
"r
eturns an empty string
if the message is not an email"
do
expect
{
test_parse_body
(
"asdf"
*
30
)
}.
to
raise_error
(
Gitlab
::
EmailReceiver
::
EmptyEmailError
)
expect
(
test_parse_body
(
"asdf"
*
30
)).
to
eq
(
""
)
end
end
it
"r
aises EmptyEmailError
if there is no reply content"
do
it
"r
eturns an empty string
if there is no reply content"
do
expect
{
test_parse_body
(
fixture_file
(
"emails/no_content_reply.eml"
))
}.
to
raise_error
(
Gitlab
::
EmailReceiver
::
EmptyEmailError
)
expect
(
test_parse_body
(
fixture_file
(
"emails/no_content_reply.eml"
))).
to
eq
(
""
)
end
end
it
"can parse the html section"
do
it
"can parse the html section"
do
...
@@ -193,289 +188,4 @@ This is a link http://example.com"
...
@@ -193,289 +188,4 @@ This is a link http://example.com"
expect
(
test_parse_body
(
fixture_file
(
"emails/outlook.eml"
))).
to
eq
(
"Microsoft Outlook 2010"
)
expect
(
test_parse_body
(
fixture_file
(
"emails/outlook.eml"
))).
to
eq
(
"Microsoft Outlook 2010"
)
end
end
end
end
# describe "posting replies" do
# let(:reply_key) { raise "Override this in a lower describe block" }
# let(:email_raw) { raise "Override this in a lower describe block" }
# # ----
# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) }
# let(:post) { create_post }
# let(:topic) { post.topic }
# let(:posting_user) { post.user }
# let(:replying_user_email) { 'jake@adventuretime.ooo' }
# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2)}
# let(:email_log) { EmailLog.new(reply_key: reply_key,
# post: post,
# post_id: post.id,
# topic_id: post.topic_id,
# email_type: 'user_posted',
# user: replying_user,
# user_id: replying_user.id,
# to_address: replying_user_email
# ) }
# before do
# email_log.save
# end
# # === Success Posting ===
# describe "valid_reply.eml" do
# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
# let!(:email_raw) { fixture_file("emails/valid_reply.eml") }
# it "creates a post with the correct content" do
# start_count = topic.posts.count
# receiver.process
# expect(topic.posts.count).to eq(start_count + 1)
# created_post = topic.posts.last
# expect(created_post.via_email).to eq(true)
# expect(created_post.raw_email).to eq(fixture_file("emails/valid_reply.eml"))
# expect(created_post.cooked.strip).to eq(fixture_file("emails/valid_reply.cooked").strip)
# end
# end
# describe "paragraphs.eml" do
# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
# let!(:email_raw) { fixture_file("emails/paragraphs.eml") }
# it "cooks multiple paragraphs with traditional Markdown linebreaks" do
# start_count = topic.posts.count
# receiver.process
# expect(topic.posts.count).to eq(start_count + 1)
# expect(topic.posts.last.cooked.strip).to eq(fixture_file("emails/paragraphs.cooked").strip)
# expect(topic.posts.last.cooked).not_to match /<br/
# end
# end
# describe "attachment.eml" do
# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
# let!(:email_raw) {
# fixture_file("emails/attachment.eml")
# .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
# .gsub("FROM", replying_user_email)
# }
# let(:upload_sha) { '04df605be528d03876685c52166d4b063aabb78a' }
# it "creates a post with an attachment" do
# Upload.stubs(:fix_image_orientation)
# ImageOptim.any_instance.stubs(:optimize_image!)
# start_count = topic.posts.count
# Upload.find_by(sha1: upload_sha).try(:destroy)
# receiver.process
# expect(topic.posts.count).to eq(start_count + 1)
# expect(topic.posts.last.cooked).to match /<img src=['"](\/uploads\/default\/original\/.+\.png)['"] width=['"]289['"] height=['"]126['"]>/
# expect(Upload.find_by(sha1: upload_sha)).not_to eq(nil)
# end
# end
# # === Failure Conditions ===
# describe "too_short.eml" do
# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
# let!(:email_raw) {
# fixture_file("emails/too_short.eml")
# .gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
# .gsub("FROM", replying_user_email)
# .gsub("SUBJECT", "re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'")
# }
# it "raises an InvalidPost error" do
# SiteSetting.min_post_length = 5
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost)
# end
# end
# describe "too_many_mentions.eml" do
# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
# let!(:email_raw) { fixture_file("emails/too_many_mentions.eml") }
# it "raises an InvalidPost error" do
# SiteSetting.max_mentions_per_post = 10
# (1..11).each do |i|
# Fabricate(:user, username: "user#{i}").save
# end
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost)
# end
# end
# describe "auto response email replies should not be accepted" do
# let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
# let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
# it "raises a AutoGeneratedEmailError" do
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::AutoGeneratedEmailError)
# end
# end
# end
# describe "posting reply to a closed topic" do
# let(:reply_key) { raise "Override this in a lower describe block" }
# let(:email_raw) { raise "Override this in a lower describe block" }
# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) }
# let(:topic) { Fabricate(:topic, closed: true) }
# let(:post) { Fabricate(:post, topic: topic, post_number: 1) }
# let(:replying_user_email) { 'jake@adventuretime.ooo' }
# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) }
# let(:email_log) { EmailLog.new(reply_key: reply_key,
# post: post,
# post_id: post.id,
# topic_id: topic.id,
# email_type: 'user_posted',
# user: replying_user,
# user_id: replying_user.id,
# to_address: replying_user_email
# ) }
# before do
# email_log.save
# end
# describe "should not create post" do
# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
# let!(:email_raw) { fixture_file("emails/valid_reply.eml") }
# it "raises a TopicClosedError" do
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicClosedError)
# end
# end
# end
# describe "posting reply to a deleted topic" do
# let(:reply_key) { raise "Override this in a lower describe block" }
# let(:email_raw) { raise "Override this in a lower describe block" }
# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) }
# let(:deleted_topic) { Fabricate(:deleted_topic) }
# let(:post) { Fabricate(:post, topic: deleted_topic, post_number: 1) }
# let(:replying_user_email) { 'jake@adventuretime.ooo' }
# let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) }
# let(:email_log) { EmailLog.new(reply_key: reply_key,
# post: post,
# post_id: post.id,
# topic_id: deleted_topic.id,
# email_type: 'user_posted',
# user: replying_user,
# user_id: replying_user.id,
# to_address: replying_user_email
# ) }
# before do
# email_log.save
# end
# describe "should not create post" do
# let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
# let!(:email_raw) { fixture_file("emails/valid_reply.eml") }
# it "raises a TopicNotFoundError" do
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::TopicNotFoundError)
# end
# end
# end
# describe "posting a new topic" do
# let(:category_destination) { raise "Override this in a lower describe block" }
# let(:email_raw) { raise "Override this in a lower describe block" }
# let(:allow_strangers) { false }
# # ----
# let(:receiver) { Gitlab::EmailReceiver.new(email_raw) }
# let(:user_email) { 'jake@adventuretime.ooo' }
# let(:user) { Fabricate(:user, email: user_email, trust_level: 2)}
# let(:category) { Fabricate(:category, email_in: category_destination, email_in_allow_strangers: allow_strangers) }
# before do
# SiteSetting.email_in = true
# user.save
# category.save
# end
# describe "too_short.eml" do
# let!(:category_destination) { 'incoming+amazing@appmail.adventuretime.ooo' }
# let(:email_raw) {
# fixture_file("emails/too_short.eml")
# .gsub("TO", category_destination)
# .gsub("FROM", user_email)
# .gsub("SUBJECT", "A long subject that passes the checks")
# }
# it "does not create a topic if the post fails" do
# before_topic_count = Topic.count
# expect { receiver.process }.to raise_error(Gitlab::EmailReceiver::InvalidPost)
# expect(Topic.count).to eq(before_topic_count)
# end
# end
# end
# def fill_email(mail, from, to, body = nil, subject = nil)
# result = mail.gsub("FROM", from).gsub("TO", to)
# if body
# result.gsub!(/Hey.*/m, body)
# end
# if subject
# result.sub!(/We .*/, subject)
# end
# result
# end
# def process_email(opts)
# incoming_email = fixture_file("emails/valid_incoming.eml")
# email = fill_email(incoming_email, opts[:from], opts[:to], opts[:body], opts[:subject])
# Gitlab::EmailReceiver.new(email).process
# end
# describe "with a valid email" do
# let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
# let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
# let(:valid_reply) {
# reply = fixture_file("emails/valid_reply.eml")
# to = SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key)
# fill_email(reply, "test@test.com", to)
# }
# let(:receiver) { Gitlab::EmailReceiver.new(valid_reply) }
# let(:post) { create_post }
# let(:user) { post.user }
# let(:email_log) { EmailLog.new(reply_key: reply_key,
# post_id: post.id,
# topic_id: post.topic_id,
# user_id: post.user_id,
# post: post,
# user: user,
# email_type: 'test',
# to_address: 'test@test.com'
# ) }
# let(:reply_body) {
# "I could not disagree more. I am obviously biased but adventure time is the
# greatest show ever created. Everyone should watch it.
# - Jake out" }
# describe "with an email log" do
# it "extracts data" do
# expect{ receiver.process }.to raise_error(Gitlab::EmailReceiver::EmailLogNotFound)
# email_log.save!
# receiver.process
# expect(receiver.body).to eq(reply_body)
# expect(receiver.email_log).to eq(email_log)
# end
# end
# end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment