From f76eac56b9d7d4ae61010cddcca68682824b2239 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 15:46:36 -0700 Subject: Reply by email POC --- .gitignore | 1 + Gemfile | 2 + Gemfile.lock | 8 ++++ Procfile | 3 +- app/mailers/emails/issues.rb | 8 ++++ app/mailers/emails/merge_requests.rb | 11 ++++- app/mailers/emails/notes.rb | 6 +++ app/mailers/notify.rb | 54 ++++++++++++++-------- app/models/sent_notification.rb | 29 ++++++++++++ app/workers/email_receiver_worker.rb | 22 +++++++++ config/gitlab.yml.example | 5 ++ config/initializers/1_settings.rb | 6 +++ config/mail_room.yml.example | 14 ++++++ .../20150818213832_add_sent_notifications.rb | 13 ++++++ db/schema.rb | 13 +++++- lib/gitlab/email_receiver.rb | 50 ++++++++++++++++++++ 16 files changed, 224 insertions(+), 21 deletions(-) create mode 100644 app/models/sent_notification.rb create mode 100644 app/workers/email_receiver_worker.rb create mode 100644 config/mail_room.yml.example create mode 100644 db/migrate/20150818213832_add_sent_notifications.rb create mode 100644 lib/gitlab/email_receiver.rb diff --git a/.gitignore b/.gitignore index 3e30fb8cf77..8a68bb3e4f0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ config/initializers/rack_attack.rb config/initializers/smtp_settings.rb config/resque.yml config/unicorn.rb +config/mail_room.yml coverage/* db/*.sqlite3 db/*.sqlite3-journal diff --git a/Gemfile b/Gemfile index 8f65a274baa..e3f76671f5b 100644 --- a/Gemfile +++ b/Gemfile @@ -272,3 +272,5 @@ end gem "newrelic_rpm" gem 'octokit', '3.7.0' + +gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" diff --git a/Gemfile.lock b/Gemfile.lock index f0c661fa9c5..3aca2c8890b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: git://github.com/DouweM/mail_room.git + revision: e1795b807f492533ad40afcb80abf870f1baddb5 + branch: sidekiq + specs: + mail_room (0.3.1) + GEM remote: https://rubygems.org/ specs: @@ -805,6 +812,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 0.15.1) letter_opener + mail_room! minitest (~> 5.3.0) mousetrap-rails mysql2 diff --git a/Procfile b/Procfile index 799b92729fa..51154f150d1 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default +worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default +mail_room: bundle exec mail_room -c config/mail_room.yml diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 687bac3aa31..c8b7775c328 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -8,6 +8,8 @@ module Emails from: sender(@issue.author_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @@ -19,6 +21,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -30,6 +34,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -42,6 +48,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 512a8f7ea6b..7a685c04ebc 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -10,6 +10,8 @@ module Emails from: sender(@merge_request.author_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @@ -23,6 +25,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -36,6 +40,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -48,6 +54,8 @@ module Emails from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) @@ -58,11 +66,12 @@ module Emails @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request) - set_reference("merge_request_#{merge_request_id}") mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) + + sent_notification!(@merge_request, recipient_id) end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index ff251209e01..2612074a5ff 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -11,6 +11,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@commit.title} (#{@commit.short_id})")) + + sent_notification!(@commit, recipient_id) end def note_issue_email(recipient_id, note_id) @@ -24,6 +26,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) + + sent_notification!(@issue, recipient_id) end def note_merge_request_email(recipient_id, note_id) @@ -38,6 +42,8 @@ module Emails from: sender(@note.author_id), to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + + sent_notification!(@merge_request, recipient_id) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 79fb48b00d3..44df3d6407d 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -46,6 +46,17 @@ class Notify < ActionMailer::Base allowed_domains end + def sent_notification!(noteable, recipient_id) + return unless reply_key + + SentNotification.create( + project: noteable.project, + noteable: noteable, + recipient_id: recipient_id, + reply_key: reply_key + ) + end + private # The default email address to send emails from @@ -85,14 +96,6 @@ class Notify < ActionMailer::Base @current_user.notification_email end - # Set the References header field - # - # local_part - The local part of the referenced message ID - # - def set_reference(local_part) - headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>" - end - # Formats arguments into a String suitable for use as an email subject # # extra - Extra Strings to be inserted into the subject @@ -130,10 +133,23 @@ class Notify < ActionMailer::Base # with headers suitable for grouping by thread in email clients. # # See: mail_answer_thread - def mail_new_thread(model, headers = {}, &block) + def mail_new_thread(model, headers = {}) + if @project + headers['X-GitLab-Project'] = @project.name + headers['X-GitLab-Project-Id'] = @project.id + headers['X-GitLab-Project-Path'] = @project.path_with_namespace + end + + headers["X-GitLab-#{model.class.name}-ID"] = model.id + headers['Message-ID'] = message_id(model) - headers['X-GitLab-Project'] = "#{@project.name} | " if @project - mail(headers, &block) + + if reply_key + headers['X-GitLab-Reply-Key'] = reply_key + headers['Reply-To'] = Gitlab.config.reply_by_email.address.gsub('%{reply_key}', reply_key) + end + + mail(headers) end # Send an email that responds to an existing conversation thread, @@ -144,19 +160,21 @@ class Notify < ActionMailer::Base # * have a subject that begin by 'Re: ' # * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' # - def mail_answer_thread(model, headers = {}, &block) + def mail_answer_thread(model, headers = {}) + headers['Message-ID'] = SecureRandom.hex headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) - headers['X-GitLab-Project'] = "#{@project.name} | " if @project - if headers[:subject] - headers[:subject].prepend('Re: ') - end - - mail(headers, &block) + mail_new_thread(model, headers) end def can? Ability.abilities.allowed?(user, action, subject) end + + def reply_key + return nil unless Gitlab.config.reply_by_email.enabled + + @reply_key ||= SecureRandom.hex(16) + end end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb new file mode 100644 index 00000000000..a3d24669b52 --- /dev/null +++ b/app/models/sent_notification.rb @@ -0,0 +1,29 @@ +class SentNotification < ActiveRecord::Base + belongs_to :project + belongs_to :noteable, polymorphic: true + belongs_to :recipient, class_name: "User" + + validate :project, :recipient, :reply_key, presence: true + validate :reply_key, uniqueness: true + + validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } + validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + + def self.for(reply_key) + find_by(reply_key: reply_key) + end + + def for_commit? + noteable_type == "Commit" + end + + def noteable + if for_commit? + project.commit(commit_id) + else + super + end + rescue + nil + end +end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb new file mode 100644 index 00000000000..e44a430f6bc --- /dev/null +++ b/app/workers/email_receiver_worker.rb @@ -0,0 +1,22 @@ +class EmailReceiverWorker + include Sidekiq::Worker + + sidekiq_options queue: :incoming_email + + def perform(raw) + return unless Gitlab.config.reply_by_email.enabled + + # begin + Gitlab::EmailReceiver.new(raw).process + # rescue => e + # handle_failure(raw, e) + # end + end + + private + + def handle_failure(raw, e) + # TODO: Handle better. + Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 56770335ddc..e78429b29a3 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -94,6 +94,11 @@ production: &base # The default is 'tmp/repositories' relative to the root of the Rails app. # repository_downloads_path: tmp/repositories + ## Reply by email + reply_by_email: + enabled: false + address: "replies+%{reply_key}@gitlab.example.com" + ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 026c1a5792c..9e83660e103 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -149,6 +149,12 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.send Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root) Settings.gitlab['restricted_signup_domains'] ||= [] +# +# Reply by email +# +Settings['reply_by_email'] ||= Settingslogic.new({}) +Settings.reply_by_email['enabled'] = false if Settings.gravatar['enabled'].nil? + # # Gravatar # diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example new file mode 100644 index 00000000000..a7a6a25362e --- /dev/null +++ b/config/mail_room.yml.example @@ -0,0 +1,14 @@ +:mailboxes: + # - + # :host: "imap.gmail.com" + # :port: 993 + # :ssl: true + # :email: "replies@gitlab.example.com" + # :password: "password" + # :name: "inbox" + # :delivery_method: sidekiq + # :delivery_options: + # :redis_url: redis://localhost:6379 + # :namespace: resque:gitlab + # :queue: incoming_email + # :worker: EmailReceiverWorker diff --git a/db/migrate/20150818213832_add_sent_notifications.rb b/db/migrate/20150818213832_add_sent_notifications.rb new file mode 100644 index 00000000000..43e8d6a1a82 --- /dev/null +++ b/db/migrate/20150818213832_add_sent_notifications.rb @@ -0,0 +1,13 @@ +class AddSentNotifications < ActiveRecord::Migration + def change + create_table :sent_notifications do |t| + t.references :project + t.references :noteable, polymorphic: true + t.references :recipient + t.string :commit_id + t.string :reply_key, null: false + end + + add_index :sent_notifications, :reply_key, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e919f2883b..0827e810f18 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150806104937) do +ActiveRecord::Schema.define(version: 20150818213832) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -404,6 +404,17 @@ ActiveRecord::Schema.define(version: 20150806104937) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree + create_table "sent_notifications", force: true do |t| + t.integer "project_id" + t.integer "noteable_id" + t.string "noteable_type" + t.integer "recipient_id" + t.string "commit_id" + t.string "reply_key", null: false + end + + add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree + create_table "services", force: true do |t| t.string "type" t.string "title" diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb new file mode 100644 index 00000000000..a9f67bb5da0 --- /dev/null +++ b/lib/gitlab/email_receiver.rb @@ -0,0 +1,50 @@ +module Gitlab + class EmailReceiver + def initialize(raw) + @raw = raw + end + + def message + @message ||= Mail::Message.new(@raw) + end + + def process + return unless message && sent_notification + + Notes::CreateService.new( + sent_notification.project, + sent_notification.recipient, + note: message.text_part.to_s, + noteable_type: sent_notification.noteable_type, + noteable_id: sent_notification.noteable_id, + commit_id: sent_notification.commit_id + ).execute + end + + private + + def reply_key + address = Gitlab.config.reply_by_email.address + return nil unless address + + regex = Regexp.escape(address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.*)") + regex = Regexp.new(regex) + + address = message.to.find { |address| address =~ regex } + return nil unless address + + match = address.match(regex) + + return nil unless match && match[1].present? + + match[1] + end + + def sent_notification + return nil unless reply_key + + SentNotification.for(reply_key) + end + end +end -- cgit v1.2.1 From a428838dc00dd95f5dad0e433ad2eb73df2eff0f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 15:50:46 -0700 Subject: Update mail_room --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3aca2c8890b..597eb4cde05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/DouweM/mail_room.git - revision: e1795b807f492533ad40afcb80abf870f1baddb5 + revision: b9deb6430ca13b2d44676827c45476d76966922c branch: sidekiq specs: mail_room (0.3.1) -- cgit v1.2.1 From 8906cabae7a6be44cafcedcaf27352614fcc462b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 18 Aug 2015 17:02:26 -0700 Subject: Changes and stuff. --- Gemfile | 2 + Gemfile.lock | 2 + app/mailers/notify.rb | 10 ++- app/models/sent_notification.rb | 8 +- app/workers/email_receiver_worker.rb | 2 +- config/initializers/1_settings.rb | 2 +- lib/gitlab/email_html_cleaner.rb | 133 ++++++++++++++++++++++++++++++++ lib/gitlab/email_receiver.rb | 144 ++++++++++++++++++++++++++++++----- lib/gitlab/reply_by_email.rb | 47 ++++++++++++ 9 files changed, 320 insertions(+), 30 deletions(-) create mode 100644 lib/gitlab/email_html_cleaner.rb create mode 100644 lib/gitlab/reply_by_email.rb diff --git a/Gemfile b/Gemfile index e3f76671f5b..9879141f5cb 100644 --- a/Gemfile +++ b/Gemfile @@ -274,3 +274,5 @@ gem "newrelic_rpm" gem 'octokit', '3.7.0' gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" + +gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index 597eb4cde05..629d128b424 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -163,6 +163,7 @@ GEM dotenv (0.9.0) dropzonejs-rails (0.7.1) rails (> 3.1) + email_reply_parser (0.5.8) email_spec (1.6.0) launchy (~> 2.1) mail (~> 2.2) @@ -780,6 +781,7 @@ DEPENDENCIES diffy (~> 3.0.3) doorkeeper (= 2.1.3) dropzonejs-rails + email_reply_parser email_spec (~> 1.6.0) enumerize factory_girl_rails diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 44df3d6407d..c2ea99d9688 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -146,7 +146,7 @@ class Notify < ActionMailer::Base if reply_key headers['X-GitLab-Reply-Key'] = reply_key - headers['Reply-To'] = Gitlab.config.reply_by_email.address.gsub('%{reply_key}', reply_key) + headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) end mail(headers) @@ -165,6 +165,10 @@ class Notify < ActionMailer::Base headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) + if headers[:subject] + headers[:subject].prepend('Re: ') + end + mail_new_thread(model, headers) end @@ -173,8 +177,6 @@ class Notify < ActionMailer::Base end def reply_key - return nil unless Gitlab.config.reply_by_email.enabled - - @reply_key ||= SecureRandom.hex(16) + @reply_key ||= Gitlab::ReplyByEmail.reply_key end end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index a3d24669b52..23a1b19ea7c 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -6,8 +6,8 @@ class SentNotification < ActiveRecord::Base validate :project, :recipient, :reply_key, presence: true validate :reply_key, uniqueness: true - validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } - validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + validates :noteable_id, presence: true, unless: :for_commit? + validates :commit_id, presence: true, if: :for_commit? def self.for(reply_key) find_by(reply_key: reply_key) @@ -19,11 +19,9 @@ class SentNotification < ActiveRecord::Base def noteable if for_commit? - project.commit(commit_id) + project.commit(commit_id) rescue nil else super end - rescue - nil end end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index e44a430f6bc..94e346b5a51 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -4,7 +4,7 @@ class EmailReceiverWorker sidekiq_options queue: :incoming_email def perform(raw) - return unless Gitlab.config.reply_by_email.enabled + return unless Gitlab::ReplyByEmail.enabled? # begin Gitlab::EmailReceiver.new(raw).process diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 9e83660e103..654de6238d0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -153,7 +153,7 @@ Settings.gitlab['restricted_signup_domains'] ||= [] # Reply by email # Settings['reply_by_email'] ||= Settingslogic.new({}) -Settings.reply_by_email['enabled'] = false if Settings.gravatar['enabled'].nil? +Settings.reply_by_email['enabled'] = false if Settings.reply_by_email['enabled'].nil? # # Gravatar diff --git a/lib/gitlab/email_html_cleaner.rb b/lib/gitlab/email_html_cleaner.rb new file mode 100644 index 00000000000..6d7a17fe87c --- /dev/null +++ b/lib/gitlab/email_html_cleaner.rb @@ -0,0 +1,133 @@ +# 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
tags with a markdown \n + doc.xpath('//br').each do |br| + br.replace(new_linebreak_node doc, 2) + end + # Surround

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 diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index a9f67bb5da0..18a06d2ee8c 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -1,44 +1,69 @@ +# 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 UserNotAuthorizedLevelError < 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 process - return unless message && sent_notification + raise EmptyEmailError if @raw.blank? + + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ + + raise SentNotificationNotFound unless sent_notification + + author = sent_notification.recipient + + raise UserNotFoundError unless author + + project = sent_notification.project + + raise UserNotAuthorizedLevelError unless author.can?(:create_note, project) + + raise NoteableNotFoundError unless sent_notification.noteable + + body = parse_body(message) - Notes::CreateService.new( - sent_notification.project, - sent_notification.recipient, - note: message.text_part.to_s, + 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? + raise InvalidNote, note.errors.full_messages.join("\n") + end end private def reply_key - address = Gitlab.config.reply_by_email.address - return nil unless address - - regex = Regexp.escape(address) - regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.*)") - regex = Regexp.new(regex) - - address = message.to.find { |address| address =~ regex } - return nil unless address + reply_key = nil + message.to.each do |address| + reply_key = Gitlab::ReplyByEmail.reply_key_from_address(address) + break if reply_key + end - match = address.match(regex) - - return nil unless match && match[1].present? - - match[1] + reply_key end def sent_notification @@ -46,5 +71,86 @@ module Gitlab SentNotification.for(reply_key) 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 + + 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| + break if l =~ /\A\s*\-{3,80}\s*\z/ || + # 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. + (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 diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb new file mode 100644 index 00000000000..b6157de3610 --- /dev/null +++ b/lib/gitlab/reply_by_email.rb @@ -0,0 +1,47 @@ +module Gitlab + module ReplyByEmail + class << self + def enabled? + config.enabled && + config.address && + config.address.include?("%{reply_key}") + end + + def reply_key + return nil unless enabled? + + SecureRandom.hex(16) + end + + def reply_address(reply_key) + config.address.gsub('%{reply_key}', reply_key) + end + + def reply_key_from_address(address) + return unless address_regex + + match = address.match(address_regex) + return unless match + + match[1] + end + + private + + def config + Gitlab.config.reply_by_email + end + + def address_regex + @address_regex ||= begin + wildcard_address = config.address + return nil unless wildcard_address + + regex = Regexp.escape(wildcard_address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") + Regexp.new(regex).freeze + end + end + end + end +end -- cgit v1.2.1 From 73eef57d5e3dfc4a60e3e661c04a9172b94f1535 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:16:30 -0700 Subject: Make MR notification email subject more standard. --- app/mailers/emails/merge_requests.rb | 43 +----------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 7a685c04ebc..76902b58f32 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -69,50 +69,9 @@ module Emails mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) sent_notification!(@merge_request, recipient_id) end end - - # Over rides default behaviour to show source/target - # Formats arguments into a String suitable for use as an email subject - # - # extra - Extra Strings to be inserted into the subject - # - # Examples - # - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Lorem ipsum" - # - # # Automatically inserts Project name: - # Forked MR - # => source project => - # => target project => - # => source branch => source - # => target branch => target - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum " - # - # Non Forked MR - # => source project => - # => target project => - # => source branch => source - # => target branch => target - # >> subject('Lorem ipsum') - # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum " - # # Accepts multiple arguments - # >> subject('Lorem ipsum', 'Dolor sit amet') - # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet" - def subject(*extra) - subject = "Merge Request | " - if @merge_request.for_fork? - subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}" - else - subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}" - end - subject << " | " + extra.join(' | ') if extra.present? - subject - end - end -- cgit v1.2.1 From 4f34d363a532613df60f3e566644c082ed3fa395 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:20 -0700 Subject: Correctly set Message-ID for comment notifications. --- app/mailers/notify.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index c2ea99d9688..e893009c728 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -129,11 +129,7 @@ class Notify < ActionMailer::Base "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>" end - # Send an email that starts a new conversation thread, - # with headers suitable for grouping by thread in email clients. - # - # See: mail_answer_thread - def mail_new_thread(model, headers = {}) + def mail_thread(model, headers = {}) if @project headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id @@ -142,8 +138,6 @@ class Notify < ActionMailer::Base headers["X-GitLab-#{model.class.name}-ID"] = model.id - headers['Message-ID'] = message_id(model) - if reply_key headers['X-GitLab-Reply-Key'] = reply_key headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) @@ -152,6 +146,16 @@ class Notify < ActionMailer::Base mail(headers) end + # Send an email that starts a new conversation thread, + # with headers suitable for grouping by thread in email clients. + # + # See: mail_answer_thread + def mail_new_thread(model, headers = {}) + headers['Message-ID'] = message_id(model) + + mail_thread(model, headers) + end + # Send an email that responds to an existing conversation thread, # with headers suitable for grouping by thread in email clients. # @@ -165,11 +169,9 @@ class Notify < ActionMailer::Base headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) - if headers[:subject] - headers[:subject].prepend('Re: ') - end + headers[:subject].prepend('Re: ') if headers[:subject] - mail_new_thread(model, headers) + mail_thread(model, headers) end def can? -- cgit v1.2.1 From 6b31827a41b3a057569d8476f9e8d5a22980cc16 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:32 -0700 Subject: Include display name with reply to address. --- app/mailers/notify.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index e893009c728..8a386de9a39 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -17,7 +17,7 @@ class Notify < ActionMailer::Base helper_method :current_user, :can? default from: Proc.new { default_sender_address.format } - default reply_to: Gitlab.config.gitlab.email_reply_to + default reply_to: Proc.new { default_reply_to_address.format } # Just send email with 2 seconds delay def self.delay @@ -66,6 +66,13 @@ class Notify < ActionMailer::Base address end + # The default email address to send emails from + def default_reply_to_address + address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end + def can_send_from_user_email?(sender) sender_domain = sender.email.split("@").last self.class.allowed_email_domains.include?(sender_domain) @@ -140,7 +147,13 @@ class Notify < ActionMailer::Base if reply_key headers['X-GitLab-Reply-Key'] = reply_key - headers['Reply-To'] = Gitlab::ReplyByEmail.reply_address(reply_key) + + address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key)) + address.display_name = @project.name_with_namespace + + headers['Reply-To'] = address + + @reply_by_email = true end mail(headers) -- cgit v1.2.1 From 01c5ecedd405fed29dbae11039ecb1a90f170c28 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:17:51 -0700 Subject: Add "Reply to this email directly" to notification footer. --- app/views/layouts/notify.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index c8662a15adb..ec209c38eed 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -36,7 +36,11 @@ — %br - if @target_url - #{link_to "View it on GitLab", @target_url} + - if @reply_by_email + Reply to this email directly or + #{link_to "view it on GitLab", @target_url}. + - else + #{link_to "View it on GitLab", @target_url} = email_action @target_url - if @project && !@disable_footer You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. -- cgit v1.2.1 From ee1eb2948d30365be241bed6a68b29da57495af8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:18:05 -0700 Subject: Turn reply-by-email attachments into uploads. --- app/services/projects/upload_service.rb | 6 +++--- lib/gitlab/email_receiver.rb | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index 992a7a7a1dc..279550d6f4a 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -13,9 +13,9 @@ module Projects filename = uploader.image? ? uploader.file.basename : uploader.file.filename { - 'alt' => filename, - 'url' => uploader.secure_url, - 'is_image' => uploader.image? + alt: filename, + url: uploader.secure_url, + is_image: uploader.image? } end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index 18a06d2ee8c..3dd8942a262 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -40,6 +40,10 @@ module Gitlab body = parse_body(message) + upload_attachments.each do |link| + body << "\n\n#{link}" + end + note = Notes::CreateService.new( project, author, @@ -152,5 +156,34 @@ module Gitlab 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 -- cgit v1.2.1 From 170aa3b43b5186f73b149eae6b80b96a9b1171b2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 10:18:20 -0700 Subject: Update mail_room. --- Gemfile.lock | 2 +- Procfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 629d128b424..c4c327aa758 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/DouweM/mail_room.git - revision: b9deb6430ca13b2d44676827c45476d76966922c + revision: c17c74cdef55dcd962333545fdb4d6e496cd9050 branch: sidekiq specs: mail_room (0.3.1) diff --git a/Procfile b/Procfile index 51154f150d1..0b58566e2fe 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default -mail_room: bundle exec mail_room -c config/mail_room.yml +mail_room: bundle exec mail_room -q -c config/mail_room.yml -- cgit v1.2.1 From 76dbafba86dda96b7ba2f93fc7e07eea3ca48302 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 11:10:21 -0700 Subject: Send a rejection email when the incoming email couldn't be processed. --- app/mailers/email_rejection_mailer.rb | 35 ++++++++++++++++++++ .../email_rejection_mailer/rejection.html.haml | 5 +++ .../email_rejection_mailer/rejection.text.haml | 4 +++ app/workers/email_receiver_worker.rb | 37 ++++++++++++++++++---- lib/gitlab/email_receiver.rb | 16 ++++++---- 5 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 app/mailers/email_rejection_mailer.rb create mode 100644 app/views/email_rejection_mailer/rejection.html.haml create mode 100644 app/views/email_rejection_mailer/rejection.text.haml diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb new file mode 100644 index 00000000000..f29c4e052fc --- /dev/null +++ b/app/mailers/email_rejection_mailer.rb @@ -0,0 +1,35 @@ +class EmailRejectionMailer < ActionMailer::Base + add_template_helper ApplicationHelper + add_template_helper GitlabMarkdownHelper + + helper_method :current_user, :can? + + default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" + default reply_to: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_reply_to}>" + + def rejection(reason, original_raw, can_retry = false) + @reason = reason + @original_message = Mail::Message.new(original_raw) + + headers = { + to: @original_message.from, + subject: "[Rejected] #{@original_message.subject}" + } + + headers['Message-ID'] = SecureRandom.hex + headers['In-Reply-To'] = @original_message.message_id + headers['References'] = @original_message.message_id + + headers['Reply-To'] = @original_message.to.first if can_retry + + mail(headers) + end + + def current_user + nil + end + + def can? + false + end +end diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml new file mode 100644 index 00000000000..de29d399d32 --- /dev/null +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -0,0 +1,5 @@ +%p + Unfortunately, your email message to GitLab could not be processed. + +%p + = gfm @reason diff --git a/app/views/email_rejection_mailer/rejection.text.haml b/app/views/email_rejection_mailer/rejection.text.haml new file mode 100644 index 00000000000..6693e6f90e8 --- /dev/null +++ b/app/views/email_rejection_mailer/rejection.text.haml @@ -0,0 +1,4 @@ +Unfortunately, your email message to GitLab could not be processed. + + += @reason diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 94e346b5a51..2cfd64cefad 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -6,17 +6,42 @@ class EmailReceiverWorker def perform(raw) return unless Gitlab::ReplyByEmail.enabled? - # begin - Gitlab::EmailReceiver.new(raw).process - # rescue => e - # handle_failure(raw, e) - # end + begin + Gitlab::EmailReceiver.new(raw).execute + rescue => e + handle_failure(raw, e) + end end private def handle_failure(raw, e) - # TODO: Handle better. Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + + can_retry = false + reason = nil + + case e + when Gitlab::EmailReceiver::SentNotificationNotFound + 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 + 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." + when Gitlab::EmailReceiver::AutoGeneratedEmailError + 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 + reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." + when Gitlab::EmailReceiver::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." + when Gitlab::EmailReceiver::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." + when Gitlab::EmailReceiver::InvalidNote + can_retry = true + reason = e.message + else + return + end + + EmailRejectionMailer.delay.rejection(reason, raw, can_retry) end end diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index 3dd8942a262..a0b4ff87e02 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -5,7 +5,7 @@ module Gitlab class EmailUnparsableError < ProcessingError; end class EmptyEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end - class UserNotAuthorizedLevelError < ProcessingError; end + class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end class SentNotificationNotFound < ProcessingError; end @@ -21,20 +21,20 @@ module Gitlab raise EmailUnparsableError, e end - def process + def execute + raise SentNotificationNotFound unless sent_notification + raise EmptyEmailError if @raw.blank? raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ - raise SentNotificationNotFound unless sent_notification - author = sent_notification.recipient raise UserNotFoundError unless author project = sent_notification.project - raise UserNotAuthorizedLevelError unless author.can?(:create_note, project) + raise UserNotAuthorizedError unless author.can?(:create_note, project) raise NoteableNotFoundError unless sent_notification.noteable @@ -54,7 +54,11 @@ module Gitlab ).execute unless note.persisted? - raise InvalidNote, note.errors.full_messages.join("\n") + 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 -- cgit v1.2.1 From 03b54f1f0324c4d424d66e4bf36e42c2a88fc26d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 11:35:13 -0700 Subject: Fix responding to commit notes --- app/mailers/notify.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 8a386de9a39..b4c83f8c5fc 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -49,11 +49,21 @@ class Notify < ActionMailer::Base def sent_notification!(noteable, recipient_id) return unless reply_key + noteable_id = nil + commit_id = nil + if noteable.is_a?(Commit) + commit_id = noteable.id + else + noteable_id = noteable.id + end + SentNotification.create( - project: noteable.project, - noteable: noteable, - recipient_id: recipient_id, - reply_key: reply_key + project: noteable.project, + noteable_type: noteable.class.name, + noteable_id: noteable_id, + commit_id: commit_id, + recipient_id: recipient_id, + reply_key: reply_key ) end -- cgit v1.2.1 From a8a861ae2a122b310d1aca6f9f9b1d0601b8c49f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:35:08 -0700 Subject: Add BaseMailer to house shared mailer functionality. --- app/mailers/base_mailer.rb | 50 +++++++++++++++++++++++++++++++++++ app/mailers/email_rejection_mailer.rb | 18 +------------ app/mailers/notify.rb | 33 +---------------------- 3 files changed, 52 insertions(+), 49 deletions(-) create mode 100644 app/mailers/base_mailer.rb diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb new file mode 100644 index 00000000000..38b8a296842 --- /dev/null +++ b/app/mailers/base_mailer.rb @@ -0,0 +1,50 @@ +class EmailRejectionMailer < ActionMailer::Base + add_template_helper ApplicationHelper + add_template_helper GitlabMarkdownHelper + + attr_accessor :current_user + helper_method :current_user, :can? + + default from: Proc.new { default_sender_address.format } + default reply_to: Proc.new { default_reply_to_address.format } + + def self.delay + delay_for(2.seconds) + end + + def rejection(reason, original_raw, can_retry = false) + @reason = reason + @original_message = Mail::Message.new(original_raw) + + headers = { + to: @original_message.from, + subject: "[Rejected] #{@original_message.subject}" + } + + headers['Message-ID'] = SecureRandom.hex + headers['In-Reply-To'] = @original_message.message_id + headers['References'] = @original_message.message_id + + headers['Reply-To'] = @original_message.to.first if can_retry + + mail(headers) + end + + def can? + Ability.abilities.allowed?(user, action, subject) + end + + private + + def default_sender_address + address = Mail::Address.new(Gitlab.config.gitlab.email_from) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end + + def default_reply_to_address + address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) + address.display_name = Gitlab.config.gitlab.email_display_name + address + end +end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index f29c4e052fc..89aceda82d1 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -1,12 +1,4 @@ -class EmailRejectionMailer < ActionMailer::Base - add_template_helper ApplicationHelper - add_template_helper GitlabMarkdownHelper - - helper_method :current_user, :can? - - default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" - default reply_to: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_reply_to}>" - +class EmailRejectionMailer < BaseMailer def rejection(reason, original_raw, can_retry = false) @reason = reason @original_message = Mail::Message.new(original_raw) @@ -24,12 +16,4 @@ class EmailRejectionMailer < ActionMailer::Base mail(headers) end - - def current_user - nil - end - - def can? - false - end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index b4c83f8c5fc..1a2286f9d47 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,4 +1,4 @@ -class Notify < ActionMailer::Base +class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include Emails::Issues @@ -8,22 +8,9 @@ class Notify < ActionMailer::Base include Emails::Profile include Emails::Groups - add_template_helper ApplicationHelper - add_template_helper GitlabMarkdownHelper add_template_helper MergeRequestsHelper add_template_helper EmailsHelper - attr_accessor :current_user - helper_method :current_user, :can? - - default from: Proc.new { default_sender_address.format } - default reply_to: Proc.new { default_reply_to_address.format } - - # Just send email with 2 seconds delay - def self.delay - delay_for(2.seconds) - end - def test_email(recipient_email, subject, body) mail(to: recipient_email, subject: subject, @@ -69,20 +56,6 @@ class Notify < ActionMailer::Base private - # The default email address to send emails from - def default_sender_address - address = Mail::Address.new(Gitlab.config.gitlab.email_from) - address.display_name = Gitlab.config.gitlab.email_display_name - address - end - - # The default email address to send emails from - def default_reply_to_address - address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to) - address.display_name = Gitlab.config.gitlab.email_display_name - address - end - def can_send_from_user_email?(sender) sender_domain = sender.email.split("@").last self.class.allowed_email_domains.include?(sender_domain) @@ -197,10 +170,6 @@ class Notify < ActionMailer::Base mail_thread(model, headers) end - def can? - Ability.abilities.allowed?(user, action, subject) - end - def reply_key @reply_key ||= Gitlab::ReplyByEmail.reply_key end -- cgit v1.2.1 From 992dbbd9fe619ab3002f7c0c552657da8269df49 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:35:23 -0700 Subject: Move `sent_notification!` out of Notify. --- app/mailers/emails/issues.rb | 8 ++++---- app/mailers/emails/merge_requests.rb | 10 +++++----- app/mailers/emails/notes.rb | 6 +++--- app/mailers/notify.rb | 21 --------------------- app/models/sent_notification.rb | 27 +++++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index c8b7775c328..2c035fbb70b 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -9,7 +9,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @@ -22,7 +22,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -35,7 +35,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -49,7 +49,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 76902b58f32..7923fb770d0 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -11,7 +11,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @@ -26,7 +26,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -41,7 +41,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -55,7 +55,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) @@ -71,7 +71,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 2612074a5ff..63d4aca61af 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -12,7 +12,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@commit.title} (#{@commit.short_id})")) - sent_notification!(@commit, recipient_id) + SentNotification.record(@commit, recipient_id, reply_key) end def note_issue_email(recipient_id, note_id) @@ -27,7 +27,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - sent_notification!(@issue, recipient_id) + SentNotification.record(@issue, recipient_id, reply_key) end def note_merge_request_email(recipient_id, note_id) @@ -43,7 +43,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - sent_notification!(@merge_request, recipient_id) + SentNotification.record(@merge_request, recipient_id, reply_key) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 1a2286f9d47..5717c89e61d 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -33,27 +33,6 @@ class Notify < BaseMailer allowed_domains end - def sent_notification!(noteable, recipient_id) - return unless reply_key - - noteable_id = nil - commit_id = nil - if noteable.is_a?(Commit) - commit_id = noteable.id - else - noteable_id = noteable.id - end - - SentNotification.create( - project: noteable.project, - noteable_type: noteable.class.name, - noteable_id: noteable_id, - commit_id: commit_id, - recipient_id: recipient_id, - reply_key: reply_key - ) - end - private def can_send_from_user_email?(sender) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 23a1b19ea7c..460ca40be3f 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -9,8 +9,31 @@ class SentNotification < ActiveRecord::Base validates :noteable_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - def self.for(reply_key) - find_by(reply_key: reply_key) + class << self + def for(reply_key) + find_by(reply_key: reply_key) + end + + def record(noteable, recipient_id, reply_key) + return unless reply_key + + noteable_id = nil + commit_id = nil + if noteable.is_a?(Commit) + commit_id = noteable.id + else + noteable_id = noteable.id + end + + create( + project: noteable.project, + noteable_type: noteable.class.name, + noteable_id: noteable_id, + commit_id: commit_id, + recipient_id: recipient_id, + reply_key: reply_key + ) + end end def for_commit? -- cgit v1.2.1 From ed6122d7ea1d7ffa1e89078cedf416c6ae2e0383 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:37:34 -0700 Subject: Markdown, not GFM. --- app/views/email_rejection_mailer/rejection.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml index de29d399d32..84b040d8cbc 100644 --- a/app/views/email_rejection_mailer/rejection.html.haml +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -2,4 +2,4 @@ Unfortunately, your email message to GitLab could not be processed. %p - = gfm @reason + = markdown @reason -- cgit v1.2.1 From 4e33127b3a72d507e093fd5cc520089c88640892 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:38:08 -0700 Subject: We'll get the

for free. --- app/views/email_rejection_mailer/rejection.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml index 84b040d8cbc..7f7d841fe21 100644 --- a/app/views/email_rejection_mailer/rejection.html.haml +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -1,5 +1,4 @@ %p Unfortunately, your email message to GitLab could not be processed. -%p - = markdown @reason += markdown @reason -- cgit v1.2.1 From 0bc94198fc64f7a0f9f7ee7d33dcae9aa50d1e3f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 12:51:42 -0700 Subject: Fix BaseMailer. --- app/mailers/base_mailer.rb | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 38b8a296842..8299a4140dc 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -1,4 +1,4 @@ -class EmailRejectionMailer < ActionMailer::Base +class BaseMailer < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper @@ -12,24 +12,6 @@ class EmailRejectionMailer < ActionMailer::Base delay_for(2.seconds) end - def rejection(reason, original_raw, can_retry = false) - @reason = reason - @original_message = Mail::Message.new(original_raw) - - headers = { - to: @original_message.from, - subject: "[Rejected] #{@original_message.subject}" - } - - headers['Message-ID'] = SecureRandom.hex - headers['In-Reply-To'] = @original_message.message_id - headers['References'] = @original_message.message_id - - headers['Reply-To'] = @original_message.to.first if can_retry - - mail(headers) - end - def can? Ability.abilities.allowed?(user, action, subject) end -- cgit v1.2.1 From 089aa2de2badcb4d6caf2d2001ad17db42d08de8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:57:27 -0700 Subject: Add Reply by email feature to admin dashboard. --- app/views/admin/dashboard/index.html.haml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3732ff847b9..54191aadda6 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -55,6 +55,10 @@ OmniAuth %span.light.pull-right = boolean_to_icon Gitlab.config.omniauth.enabled + %p + Reply by email + %span.light.pull-right + = boolean_to_icon Gitlab::ReplyByEmail.enabled? .col-md-4 %h4 Components -- cgit v1.2.1 From f3b57ce677f016fbbbcc6426e33b2b61c34c068f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:58:07 -0700 Subject: Update init scripts. --- Procfile | 2 +- bin/background_jobs | 2 +- bin/mail_room | 51 +++++++++++++++++ lib/support/init.d/gitlab | 95 ++++++++++++++++++++++++------- lib/support/init.d/gitlab.default.example | 9 +++ 5 files changed, 137 insertions(+), 22 deletions(-) create mode 100755 bin/mail_room diff --git a/Procfile b/Procfile index 0b58566e2fe..18fd9eb3d92 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default -mail_room: bundle exec mail_room -q -c config/mail_room.yml +# mail_room: bundle exec mail_room -q -c config/mail_room.yml diff --git a/bin/background_jobs b/bin/background_jobs index a041a4b0433..a4895cf6586 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -37,7 +37,7 @@ start_no_deamonize() start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 + bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 } load_ok() diff --git a/bin/mail_room b/bin/mail_room new file mode 100755 index 00000000000..0fabfa778b7 --- /dev/null +++ b/bin/mail_room @@ -0,0 +1,51 @@ +#!/bin/sh + +cd $(dirname $0)/.. +app_root=$(pwd) + +mail_room_pidfile="$app_root/tmp/pids/mail_room.pid" +mail_room_config="$app_root/config/mail_room.yml" + +get_mail_room_pid() +{ + local pid=$(cat $mail_room_pidfile) + if [ -z "$pid" ] ; then + echo "Could not find a PID in $mail_room_pidfile" + exit 1 + fi + mail_room_pid=$pid +} + +start() +{ + bundle exec mail_room -q -c $mail_room_config + PID=$! + echo $PID > $mail_room_pidfile +} + +stop() +{ + get_mail_room_pid + kill -QUIT $mail_room_pid +} + +reload() +{ + get_mail_room_pid + kill -USR2 $mail_room_pid +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + reload) + reload + ;; + *) + echo "Usage: $0 {start|stop|reload}" + ;; +esac diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index a3455728a94..57478434e09 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids" socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +mail_room_enabled=false +mail_room_pid_path="$pid_path/mail_room.pid" shell_path="/bin/bash" # Read configuration variable file if it is present @@ -70,13 +72,20 @@ check_pids(){ else spid=0 fi + if [ "$mail_room_enabled" = true ]; then + if [ -f "$mail_room_pid_path" ]; then + mpid=$(cat "$mail_room_pid_path") + else + mpid=0 + fi + fi } ## Called when we have started the two processes and are waiting for their pid files. wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path ]; do + while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path -o [ "$mail_room_enabled" = true && ! -f $mail_room_pid_path ] ]; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -111,7 +120,13 @@ check_status(){ else sidekiq_status="-1" fi - if [ $web_status = 0 -a $sidekiq_status = 0 ]; then + if [ "$mail_room_enabled" = true && $mpid -ne 0 ]; then + kill -0 "$mpid" 2>/dev/null + mail_room_status="$?" + else + mail_room_status="-1" + fi + if [ $web_status = 0 -a $sidekiq_status = 0 -a [ "$mail_room_enabled" != true || $mail_room_status = 0 ] ]; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -139,12 +154,19 @@ check_stale_pids(){ exit 1 fi fi + if [ "$mail_room_enabled" = true && "$mpid" != "0" -a "$mail_room_status" != "0" ]; then + echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran." + if ! rm "$mail_room_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi } ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ] ]; then echo "GitLab is not running." exit fi @@ -154,13 +176,15 @@ exit_if_not_running(){ start_gitlab() { check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then - echo -n "Starting both the GitLab Unicorn and Sidekiq" - elif [ "$web_status" != "0" ]; then + if [ "$web_status" != "0" ]; then echo -n "Starting GitLab Unicorn" - elif [ "$sidekiq_status" != "0" ]; then + fi + if [ "$sidekiq_status" != "0" ]; then echo -n "Starting GitLab Sidekiq" fi + if [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ]; then + echo -n "Starting GitLab MailRoom" + fi # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then @@ -179,22 +203,33 @@ start_gitlab() { RAILS_ENV=$RAILS_ENV bin/background_jobs start & fi + if [ "$mail_room_enabled" = true ]; then + # If MailRoom is already running, don't start it again. + if [ "$mail_room_status" = "0" ]; then + echo "The MailRoom email processor is already running with pid $spid, not restarting" + else + RAILS_ENV=$RAILS_ENV bin/mail_room start & + fi + fi + # Wait for the pids to be planted wait_for_pids # Finally check the status to tell wether or not GitLab is running print_status } -## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. +## Asks Unicorn, Sidekiq and MailRoom if they would be so kind as to stop, if not kills them. stop_gitlab() { exit_if_not_running - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then - echo -n "Shutting down both Unicorn and Sidekiq" - elif [ "$web_status" = "0" ]; then - echo -n "Shutting down Unicorn" - elif [ "$sidekiq_status" = "0" ]; then - echo -n "Shutting down Sidekiq" + if [ "$web_status" = "0" ]; then + echo -n "Shutting down GitLab Unicorn" + fi + if [ "$sidekiq_status" = "0" ]; then + echo -n "Shutting down GitLab Sidekiq" + fi + if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + echo -n "Shutting down GitLab MailRoom" fi # If the Unicorn web server is running, tell it to stop; @@ -205,13 +240,17 @@ stop_gitlab() { if [ "$sidekiq_status" = "0" ]; then RAILS_ENV=$RAILS_ENV bin/background_jobs stop fi + # And do the same thing for the MailRoom. + if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + RAILS_ENV=$RAILS_ENV bin/mail_room stop + fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; do + while [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; do sleep 1 check_status printf "." - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then printf "\n" break fi @@ -220,7 +259,10 @@ stop_gitlab() { sleep 1 # Cleaning up unused pids rm "$web_server_pid_path" 2>/dev/null - # rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid. + # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid. + if [ "$mail_room_enabled" = true ]; then + rm "$mail_room_pid_path" 2>/dev/null + fi print_status } @@ -228,7 +270,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then echo "GitLab is not running." return fi @@ -242,7 +284,14 @@ print_status() { else printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" fi - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then + if [ "$mail_room_enabled" = true ]; then + if [ "$mail_room_status" = "0" ]; then + echo "The GitLab MailRoom email processor with pid $spid is running." + else + printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" + fi + end + if [ "$web_status" = "0" -a "$sidekiq_status" = "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" = "0" ] ]; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -257,9 +306,15 @@ reload_gitlab(){ printf "Reloading GitLab Unicorn configuration... " RAILS_ENV=$RAILS_ENV bin/web reload echo "Done." + echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." RAILS_ENV=$RAILS_ENV bin/background_jobs restart + if [ "$mail_room_enabled" != true ]; then + echo "Restarting GitLab MailRoom since it isn't capable of reloading its config..." + RAILS_ENV=$RAILS_ENV bin/mail_room restart + fi + wait_for_pids print_status } @@ -267,7 +322,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then + if [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index cf7f4198cbf..fd70cb7cc74 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid" # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +# mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. +# This is required for the Reply by email feature. +# The default is "false" +mail_room_enabled=false + +# mail_room_pid_path defines the path in which to create the pid file for mail_room +# The default is "$pid_path/mail_room.pid" +mail_room_pid_path="$pid_path/mail_room.pid" + # shell_path defines the path of shell for "$app_user" in case you are using # shell other than "bash" # The default is "/bin/bash" -- cgit v1.2.1 From 34026c97b4d55026c414e839af926af3ffeb31cc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 13:58:14 -0700 Subject: Add documentation. --- config/gitlab.yml.example | 2 ++ config/mail_room.yml.example | 37 ++++++++++++------- doc/README.md | 1 + doc/reply_by_email/README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 doc/reply_by_email/README.md diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e78429b29a3..c7b60a1d4b1 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -95,6 +95,8 @@ production: &base # repository_downloads_path: tmp/repositories ## Reply by email + # Allow users to comment on issues and merge requests by replying to notification emails. + # For documentation on how to set this up, see http://doc.gitlab.com/ce/reply_by_email/README.md reply_by_email: enabled: false address: "replies+%{reply_key}@gitlab.example.com" diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example index a7a6a25362e..28366eb7394 100644 --- a/config/mail_room.yml.example +++ b/config/mail_room.yml.example @@ -1,14 +1,25 @@ :mailboxes: - # - - # :host: "imap.gmail.com" - # :port: 993 - # :ssl: true - # :email: "replies@gitlab.example.com" - # :password: "password" - # :name: "inbox" - # :delivery_method: sidekiq - # :delivery_options: - # :redis_url: redis://localhost:6379 - # :namespace: resque:gitlab - # :queue: incoming_email - # :worker: EmailReceiverWorker + - + # # IMAP server host + # :host: "imap.gmail.com" + # # IMAP server port + # :port: 993 + # # Whether the IMAP server uses SSL + # :ssl: true + # # Email account username. Usually the full email address. + # :email: "replies@gitlab.example.com" + # # Email account password + # :password: "password" + # # The name of the mailbox where incoming mail will end up. Usually "inbox". + # :name: "inbox" + # # Always "sidekiq". + # :delivery_method: sidekiq + # :delivery_options: + # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + # :redis_url: redis://localhost:6379 + # # Always "resque:gitlab". + # :namespace: resque:gitlab + # # Always "incoming_email". + # :queue: incoming_email + # # Always "EmailReceiverWorker" + # :worker: EmailReceiverWorker diff --git a/doc/README.md b/doc/README.md index 0524fda3ed6..337c4e6a62d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -29,6 +29,7 @@ - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. +- [Reply by email](reply_by_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. ## Contributor documentation diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md new file mode 100644 index 00000000000..fffc120d2bb --- /dev/null +++ b/doc/reply_by_email/README.md @@ -0,0 +1,85 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails. + +In order to do this, you need access to an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the [Postfix](http://www.postfix.org/) mail server which you can run on-premises. + +## Set it up + +In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you're actually using Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +### Installations from source + + +1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```yaml + reply_by_email: + enabled: true + address: "gitlab-replies+%{reply_key}@gmail.com" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`. + +2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: + + ```sh + cp config/mail_room.yml.example config/mail_room.yml + ``` + +3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + + ```yaml + :mailboxes: + - + # IMAP server host + :host: "imap.gmail.com" + # IMAP server port + :port: 993 + # Whether the IMAP server uses SSL + :ssl: true + # Email account username. Usually the full email address. + :email: "gitlab-replies@gmail.com" + # Email account password + :password: "[REDACTED]" + # The name of the mailbox where incoming mail will end up. Usually "inbox". + :name: "inbox" + # Always "sidekiq". + :delivery_method: sidekiq + :delivery_options: + # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + :redis_url: redis://localhost:6379 + # Always "resque:gitlab". + :namespace: resque:gitlab + # Always "incoming_email". + :queue: incoming_email + # Always "EmailReceiverWorker" + :worker: EmailReceiverWorker + ``` + + +4. Find `lib/support/init.d/gitlab.default.example` and copy it to `/etc/default/gitlab`: + + ```sh + sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab + ``` + +5. Edit `/etc/default/gitlab` to enable `mail_room`: + + ```sh + mail_room_enabled=true + ``` + +6. Restart GitLab + + ```sh + sudo service gitlab restart + ``` + +8. Reply by email should now be working. + +Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. + +### Omnibus package installations + +TODO -- cgit v1.2.1 From c50e5e68c7d4863c26700ced7ea31a18ddfc1c66 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:27:47 -0700 Subject: Fix bin/mail_room. --- bin/mail_room | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/mail_room b/bin/mail_room index 0fabfa778b7..f4f1a170c04 100755 --- a/bin/mail_room +++ b/bin/mail_room @@ -4,6 +4,7 @@ cd $(dirname $0)/.. app_root=$(pwd) mail_room_pidfile="$app_root/tmp/pids/mail_room.pid" +mail_room_logfile="$app_root/log/mail_room.log" mail_room_config="$app_root/config/mail_room.yml" get_mail_room_pid() @@ -18,7 +19,7 @@ get_mail_room_pid() start() { - bundle exec mail_room -q -c $mail_room_config + bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 & PID=$! echo $PID > $mail_room_pidfile } @@ -26,13 +27,13 @@ start() stop() { get_mail_room_pid - kill -QUIT $mail_room_pid + kill -TERM $mail_room_pid } -reload() +restart() { - get_mail_room_pid - kill -USR2 $mail_room_pid + stop + start } case "$1" in @@ -42,10 +43,10 @@ case "$1" in stop) stop ;; - reload) - reload + restart) + restart ;; *) - echo "Usage: $0 {start|stop|reload}" + echo "Usage: $0 {start|stop|restart}" ;; esac -- cgit v1.2.1 From 1202875dff5a120c27842b80994b19a682d080f5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:54:30 -0700 Subject: Fix lib/support/init.d/gitlab. --- lib/support/init.d/gitlab | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 57478434e09..3f13dfbfdfc 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -85,7 +85,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path -o [ "$mail_room_enabled" = true && ! -f $mail_room_pid_path ] ]; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -120,13 +120,15 @@ check_status(){ else sidekiq_status="-1" fi - if [ "$mail_room_enabled" = true && $mpid -ne 0 ]; then - kill -0 "$mpid" 2>/dev/null - mail_room_status="$?" - else - mail_room_status="-1" + if [ "$mail_room_enabled" = true ]; then + if [ $mpid -ne 0 ]; then + kill -0 "$mpid" 2>/dev/null + mail_room_status="$?" + else + mail_room_status="-1" + fi fi - if [ $web_status = 0 -a $sidekiq_status = 0 -a [ "$mail_room_enabled" != true || $mail_room_status = 0 ] ]; then + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ] }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -140,21 +142,21 @@ check_stale_pids(){ check_status # If there is a pid it is something else than 0, the service is running if # *_status is == 0. - if [ "$wpid" != "0" -a "$web_status" != "0" ]; then + if [ "$wpid" != "0" ] && [ "$web_status" != "0" ]; then echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran." if ! rm "$web_server_pid_path"; then echo "Unable to remove stale pid, exiting." exit 1 fi fi - if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then + if [ "$spid" != "0" ] && [ "$sidekiq_status" != "0" ]; then echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran." if ! rm "$sidekiq_pid_path"; then echo "Unable to remove stale pid, exiting" exit 1 fi fi - if [ "$mail_room_enabled" = true && "$mpid" != "0" -a "$mail_room_status" != "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mpid" != "0" ] && [ "$mail_room_status" != "0" ]; then echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran." if ! rm "$mail_room_pid_path"; then echo "Unable to remove stale pid, exiting" @@ -166,7 +168,7 @@ check_stale_pids(){ ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then echo "GitLab is not running." exit fi @@ -182,7 +184,7 @@ start_gitlab() { if [ "$sidekiq_status" != "0" ]; then echo -n "Starting GitLab Sidekiq" fi - if [ "$mail_room_enabled" = true && "$mail_room_status" != "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo -n "Starting GitLab MailRoom" fi @@ -206,7 +208,7 @@ start_gitlab() { if [ "$mail_room_enabled" = true ]; then # If MailRoom is already running, don't start it again. if [ "$mail_room_status" = "0" ]; then - echo "The MailRoom email processor is already running with pid $spid, not restarting" + echo "The MailRoom email processor is already running with pid $mpid, not restarting" else RAILS_ENV=$RAILS_ENV bin/mail_room start & fi @@ -228,7 +230,7 @@ stop_gitlab() { if [ "$sidekiq_status" = "0" ]; then echo -n "Shutting down GitLab Sidekiq" fi - if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then echo -n "Shutting down GitLab MailRoom" fi @@ -241,16 +243,16 @@ stop_gitlab() { RAILS_ENV=$RAILS_ENV bin/background_jobs stop fi # And do the same thing for the MailRoom. - if [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ]; then + if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then RAILS_ENV=$RAILS_ENV bin/mail_room stop fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then printf "\n" break fi @@ -270,7 +272,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" -a "$sidekiq_status" != "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" != "0" ] ]; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then echo "GitLab is not running." return fi @@ -291,7 +293,7 @@ print_status() { printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi end - if [ "$web_status" = "0" -a "$sidekiq_status" = "0" -a [ "$mail_room_enabled" != true || "$mail_room_status" = "0" ] ]; then + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ] }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -322,7 +324,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" -o "$sidekiq_status" = "0" -o [ "$mail_room_enabled" = true && "$mail_room_status" = "0" ] ]; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; then stop_gitlab fi start_gitlab -- cgit v1.2.1 From eacdd5080a4f62442e7ee885d4053562a4bb8acc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 14:58:41 -0700 Subject: Fix Basemailer#can? --- app/mailers/base_mailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 8299a4140dc..aedb0889185 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -13,7 +13,7 @@ class BaseMailer < ActionMailer::Base end def can? - Ability.abilities.allowed?(user, action, subject) + Ability.abilities.allowed?(current_user, action, subject) end private -- cgit v1.2.1 From 41fdd20c746f34a02d670dc2784cc279b2478cb7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 16:05:58 -0700 Subject: Test Gitlab::ReplyByEmail. --- spec/lib/gitlab/reply_by_email_spec.rb | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 spec/lib/gitlab/reply_by_email_spec.rb diff --git a/spec/lib/gitlab/reply_by_email_spec.rb b/spec/lib/gitlab/reply_by_email_spec.rb new file mode 100644 index 00000000000..a52af51e97c --- /dev/null +++ b/spec/lib/gitlab/reply_by_email_spec.rb @@ -0,0 +1,86 @@ +require "spec_helper" + +describe Gitlab::ReplyByEmail do + describe "self.enabled?" do + context "when reply by email is enabled" do + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + end + + context "when the address is valid" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns true" do + expect(described_class.enabled?).to be_truthy + end + end + + context "when the address is invalid" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies@example.com") + end + + it "returns false" do + expect(described_class.enabled?).to be_falsey + end + end + end + + context "when reply by email is disabled" do + before do + allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(false) + end + + it "returns false" do + expect(described_class.enabled?).to be_falsey + end + end + end + + describe "self.reply_key" do + context "when enabled" do + before do + allow(described_class).to receive(:enabled?).and_return(true) + end + + it "returns a random hex" do + key = described_class.reply_key + key2 = described_class.reply_key + + expect(key).not_to eq(key2) + end + end + + context "when disabled" do + before do + allow(described_class).to receive(:enabled?).and_return(false) + end + + it "returns nil" do + expect(described_class.reply_key).to be_nil + end + end + end + + context "self.reply_address" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns the address with an interpolated reply key" do + expect(described_class.reply_address("key")).to eq("replies+key@example.com") + end + end + + context "self.reply_key_from_address" do + before do + allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + end + + it "returns reply key" do + expect(described_class.reply_key_from_address("replies+key@example.com")).to eq("key") + end + end +end -- cgit v1.2.1 From 9501495aeb512202dc4ce1a88afb39d66f3d39f1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 17:43:24 -0700 Subject: Fix init.d script. --- lib/support/init.d/gitlab | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 3f13dfbfdfc..41a2f254db6 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -85,7 +85,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -128,7 +128,7 @@ check_status(){ mail_room_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ] }; then + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -168,7 +168,7 @@ check_stale_pids(){ ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -179,13 +179,13 @@ start_gitlab() { check_stale_pids if [ "$web_status" != "0" ]; then - echo -n "Starting GitLab Unicorn" + echo "Starting GitLab Unicorn" fi if [ "$sidekiq_status" != "0" ]; then - echo -n "Starting GitLab Sidekiq" + echo "Starting GitLab Sidekiq" fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then - echo -n "Starting GitLab MailRoom" + echo "Starting GitLab MailRoom" fi # Then check if the service is running. If it is: don't start again. @@ -225,13 +225,13 @@ stop_gitlab() { exit_if_not_running if [ "$web_status" = "0" ]; then - echo -n "Shutting down GitLab Unicorn" + echo "Shutting down GitLab Unicorn" fi if [ "$sidekiq_status" = "0" ]; then - echo -n "Shutting down GitLab Sidekiq" + echo "Shutting down GitLab Sidekiq" fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then - echo -n "Shutting down GitLab MailRoom" + echo "Shutting down GitLab MailRoom" fi # If the Unicorn web server is running, tell it to stop; @@ -248,11 +248,11 @@ stop_gitlab() { fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then printf "\n" break fi @@ -272,7 +272,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ] }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -292,8 +292,8 @@ print_status() { else printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi - end - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ] }; then + fi + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -324,7 +324,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ] }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then stop_gitlab fi start_gitlab -- cgit v1.2.1 From 19a049ece9e350623e3adb141298c00344cab02d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 17:43:36 -0700 Subject: Make documentation clearer. --- doc/install/installation.md | 2 +- doc/reply_by_email/README.md | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 202704088a6..c75e1d87862 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -370,7 +370,7 @@ Make sure to edit the config file to match your setup: # domain name of your host serving GitLab. # If using Ubuntu default nginx install: # either remove the default_server from the listen line - # or else rm -f /etc/sites-enabled/default + # or else sudo rm -f /etc/nginx/sites-enabled/default sudo editor /etc/nginx/sites-available/gitlab **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index fffc120d2bb..e168e9a17e4 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -10,8 +10,17 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' ### Installations from source +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` 1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```sh + sudo editor config/gitlab.yml + ``` ```yaml reply_by_email: @@ -24,11 +33,15 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: ```sh - cp config/mail_room.yml.example config/mail_room.yml + sudo cp config/mail_room.yml.example config/mail_room.yml ``` 3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + ```sh + sudo editor config/mail_room.yml + ``` + ```yaml :mailboxes: - @@ -66,6 +79,10 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 5. Edit `/etc/default/gitlab` to enable `mail_room`: + ```sh + sudo editor /etc/default/gitlab + ``` + ```sh mail_room_enabled=true ``` -- cgit v1.2.1 From 83081f167397c6c325a4f8c604191e766b2c3d3b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Aug 2015 18:00:13 -0700 Subject: Start on tests. --- lib/gitlab/email_receiver.rb | 37 +- spec/fixtures/emails/android_gmail.eml | 177 +++++++++ spec/fixtures/emails/attachment.eml | 351 ++++++++++++++++++ spec/fixtures/emails/auto_reply.eml | 21 ++ spec/fixtures/emails/big5.eml | 26 ++ spec/fixtures/emails/bottom_reply.eml | 160 ++++++++ spec/fixtures/emails/boundary.eml | 61 ++++ spec/fixtures/emails/dutch.eml | 20 + spec/fixtures/emails/empty.eml | 21 ++ spec/fixtures/emails/gmail_web.eml | 181 ++++++++++ spec/fixtures/emails/hebrew.eml | 17 + spec/fixtures/emails/html_only.eml | 93 +++++ spec/fixtures/emails/html_paragraphs.eml | 205 +++++++++++ spec/fixtures/emails/inline_reply.eml | 60 +++ spec/fixtures/emails/ios_default.eml | 136 +++++++ spec/fixtures/emails/iphone_signature.eml | 29 ++ spec/fixtures/emails/multiple_destinations.eml | 40 ++ spec/fixtures/emails/newlines.eml | 84 +++++ spec/fixtures/emails/no_content_reply.eml | 34 ++ spec/fixtures/emails/on_wrote.eml | 277 ++++++++++++++ spec/fixtures/emails/outlook.eml | 188 ++++++++++ spec/fixtures/emails/paragraphs.cooked | 7 + spec/fixtures/emails/paragraphs.eml | 42 +++ spec/fixtures/emails/previous.eml | 38 ++ spec/fixtures/emails/previous_replies.eml | 180 +++++++++ spec/fixtures/emails/too_many_mentions.eml | 31 ++ spec/fixtures/emails/too_short.eml | 21 ++ spec/fixtures/emails/valid_incoming.cooked | 5 + spec/fixtures/emails/valid_incoming.eml | 25 ++ spec/fixtures/emails/valid_reply.cooked | 4 + spec/fixtures/emails/valid_reply.eml | 40 ++ spec/fixtures/emails/via_line.eml | 25 ++ spec/fixtures/emails/windows_8_metro.eml | 173 +++++++++ spec/fixtures/emails/wrong_reply_key.eml | 40 ++ spec/lib/gitlab/email_receiver_spec.rb | 481 +++++++++++++++++++++++++ 35 files changed, 3311 insertions(+), 19 deletions(-) create mode 100644 spec/fixtures/emails/android_gmail.eml create mode 100644 spec/fixtures/emails/attachment.eml create mode 100644 spec/fixtures/emails/auto_reply.eml create mode 100644 spec/fixtures/emails/big5.eml create mode 100644 spec/fixtures/emails/bottom_reply.eml create mode 100644 spec/fixtures/emails/boundary.eml create mode 100644 spec/fixtures/emails/dutch.eml create mode 100644 spec/fixtures/emails/empty.eml create mode 100644 spec/fixtures/emails/gmail_web.eml create mode 100644 spec/fixtures/emails/hebrew.eml create mode 100644 spec/fixtures/emails/html_only.eml create mode 100644 spec/fixtures/emails/html_paragraphs.eml create mode 100644 spec/fixtures/emails/inline_reply.eml create mode 100644 spec/fixtures/emails/ios_default.eml create mode 100644 spec/fixtures/emails/iphone_signature.eml create mode 100644 spec/fixtures/emails/multiple_destinations.eml create mode 100644 spec/fixtures/emails/newlines.eml create mode 100644 spec/fixtures/emails/no_content_reply.eml create mode 100644 spec/fixtures/emails/on_wrote.eml create mode 100644 spec/fixtures/emails/outlook.eml create mode 100644 spec/fixtures/emails/paragraphs.cooked create mode 100644 spec/fixtures/emails/paragraphs.eml create mode 100644 spec/fixtures/emails/previous.eml create mode 100644 spec/fixtures/emails/previous_replies.eml create mode 100644 spec/fixtures/emails/too_many_mentions.eml create mode 100644 spec/fixtures/emails/too_short.eml create mode 100644 spec/fixtures/emails/valid_incoming.cooked create mode 100644 spec/fixtures/emails/valid_incoming.eml create mode 100644 spec/fixtures/emails/valid_reply.cooked create mode 100644 spec/fixtures/emails/valid_reply.eml create mode 100644 spec/fixtures/emails/via_line.eml create mode 100644 spec/fixtures/emails/windows_8_metro.eml create mode 100644 spec/fixtures/emails/wrong_reply_key.eml create mode 100644 spec/lib/gitlab/email_receiver_spec.rb diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb index a0b4ff87e02..3c1f346c0cf 100644 --- a/lib/gitlab/email_receiver.rb +++ b/lib/gitlab/email_receiver.rb @@ -62,6 +62,21 @@ module Gitlab 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 @@ -80,21 +95,6 @@ module Gitlab SentNotification.for(reply_key) 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 - def select_body(message) html = nil text = nil @@ -144,10 +144,9 @@ module Gitlab range_end = 0 lines.each_with_index do |l, idx| - break if l =~ /\A\s*\-{3,80}\s*\z/ || - # 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. - (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || + # 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 diff --git a/spec/fixtures/emails/android_gmail.eml b/spec/fixtures/emails/android_gmail.eml new file mode 100644 index 00000000000..21c5dde2346 --- /dev/null +++ b/spec/fixtures/emails/android_gmail.eml @@ -0,0 +1,177 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:53:21 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=089e0149cfa485c6630508f173df + +--089e0149cfa485c6630508f173df +Content-Type: text/plain; charset=UTF-8 + +### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com +On Nov 28, 2014 12:36 PM, "Arpit Jalan" wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--089e0149cfa485c6630508f173df +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +

### this is a reply from Android 5 gmail

+

The quick brown fox jumps over the lazy dog. The quick brown= + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog. The quick brown fox jumps over the lazy dog.

+

This is **bold** in Markdown.

+

This is a link to http://exam= +ple.com

+
On Nov 28, 2014 12:36 PM, "Arpit Jalan"= +; <info@discourse.org> wrot= +e:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+ +--089e0149cfa485c6630508f173df-- diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml new file mode 100644 index 00000000000..f25c3d1a449 --- /dev/null +++ b/spec/fixtures/emails/attachment.eml @@ -0,0 +1,351 @@ +Message-ID: <51C22E52.1030509@darthvader.ca> +Date: Wed, 19 Jun 2013 18:18:58 -0400 +From: Anakin Skywalker +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 +MIME-Version: 1.0 +To: Han Solo via Death Star +Subject: Re: [Death Star] [PM] re: Regarding your post in "Site Customization + not working" +References: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> +In-Reply-To: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> +Content-Type: multipart/mixed; boundary=047d7b45041e19c68004eb9f3de8 + +--047d7b45041e19c68004eb9f3de8 +Content-Type: multipart/alternative; boundary=047d7b45041e19c67b04eb9f3de6 + +--047d7b45041e19c67b04eb9f3de6 +Content-Type: text/plain; charset=ISO-8859-1 + +here is an image attachment + + +On Tue, Nov 19, 2013 at 5:11 PM, Neil wrote: + +> Neil +> November 19 +> +> Actually, deleting a spammer does what it's supposed to. It does mark the +> topic as deleted. +> +> That topic has id 11002, and you're right that the user was deleted. +> +> @eviltrout Any idea why it showed up in +> suggested topics? +> +> To respond, reply to this email or visit +> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. +> ------------------------------ +> Previous Replies Neil +> November 19 +> +> Looks like a bug when deleting a spammer. I'll look at it. +> riking +> November 19 +> +> codinghorror: +> +> I can't even find that topic by name. +> +> In that case, I'm fairly certain someone used the 'Delete Spammer' +> function on the user, which would explain your inability to find it - it's +> gone. +> +> I'm raising this because, well, it's gone and shouldn't be showing up. And +> even if it was hanging around, it should be invisible to me, and not +> showing up in Suggested Topics. +> codinghorror +> November 19 +> +> Hmm, that's interesting -- can you have a look @eviltrout? +> I can't even find that topic by name. +> riking +> November 19 +> +> I'm one of the users who flagged this particular spam post, and it was +> promptly deleted/hidden, but it just popped up in the Suggested Topics box: +> +> Pasted image1125x220 27.7 KB +> +> +> We may want to recheck the suppression on these. +> ------------------------------ +> +> To respond, reply to this email or visit +> http://meta.discourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--047d7b45041e19c67b04eb9f3de6 +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + +
here is an image attachment


On Tue, Nov 19, 2013 at 5:11 PM, Neil = +<info@discourse.org> wrote:
+
+ + + + + + + + +
+ + + Neil<= +/a>
+No= +vember 19 +
+

Actually, deleting a spammer does what it's s= +upposed to. It does mark the topic as deleted.

+ +

That topic has id 11002, and you're right tha= +t the user was deleted.

+ +

@eviltrout Any idea why it showed up in suggested topics?

+
+
+

To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back= +-up-in-suggested-topics/11005/5 in your browser.

+ +
+
+

Previous Replies

+ + + + + + + + + +
+ + + Neil<= +/a>
+No= +vember 19 +

Looks= + like a bug when deleting a spammer. I'll look at it.

+ + + + + + + + +
+ + + rik= +ing
+No= +vember 19 +
+

+
+codinghorror:
+

I can't even find that topic by n= +ame.

+ +

In that case, I'm fairly certain someone used= + the 'Delete Spammer' function on the user, which would explain you= +r inability to find it - it's gone.

+ +

I'm raising this because, well, it's gone= + and shouldn't be showing up. And even if it was hanging around, it sho= +uld be invisible to me, and not showing up in Suggested Topics.

+
+ + + + + + + + +
+ + + codinghorror
+No= +vember 19 +

Hmm, = +that's interesting -- can you have a look @eviltrout? I can't even find that topic by= + name.

+
+ + + + + + + + +
+ + + rik= +ing
+No= +vember 19 +
+

I'm one of the users who flagged this particu= +lar spam post, and it was promptly deleted/hidden, but it just popped up in= + the Suggested Topics box:

+ +

+ + +

We may want to recheck the suppression on these.<= +/p> +

+
+
+

To respond, reply to this email or visit http://meta.discourse.org/t/spam-post-pops-back-up-= +in-suggested-topics/11005/5 in your browser.

+ +
+
+

To unsubscribe from these emails, visit your user pre= +ferences.

+
+

+ +--047d7b45041e19c67b04eb9f3de6-- +--047d7b45041e19c68004eb9f3de8 +Content-Type: image/png; name="bricks.png" +Content-Disposition: attachment; filename="bricks.png" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_ho8uteve0 + +iVBORw0KGgoAAAANSUhEUgAAASEAAAB+CAIAAADk0DDaAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ +bWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp +bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6 +eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEz +NDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo +dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw +dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv +IiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS +ZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD +cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNl +SUQ9InhtcC5paWQ6MDYxQjcyOUUzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiIHhtcE1NOkRvY3Vt +ZW50SUQ9InhtcC5kaWQ6MDYxQjcyOUYzMDM1MTFFM0JFRTFBOTQ1RUY4QUU4MDIiPiA8eG1wTU06 +RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNjFCNzI5QzMwMzUxMUUzQkVF +MUE5NDVFRjhBRTgwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNjFCNzI5RDMwMzUxMUUz +QkVFMUE5NDVFRjhBRTgwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1w +bWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pm2fyz0AAAyISURBVHja7F2/i11FFL6rL12aBdlGRDCF +EQmEbVJtChfSJJDGRkgZBBsVUhgQ7NSkCKiFVUr/AUGbhW1MlWaJBAkWVsFmG0HshMT7duJk9szc +uefOjzPn3vd9xfL2/bh35rtnznfOuXNnth7c/6ID2Lh261vO13669wm4SsZ7H3396gmePXu2OkH/ +Yr4Mv4IrCgAYY8Am4vnz51sn8EVsXth68P7eYq7Kj4cP3H+v79fq2tWDX/u/d25/7n/08/3PzIvb +u3vLs3sxhh/vXrOvb9/50v1o77W/X340B5IXMsbsta931eN24I6uRQ4wd3SJkUwYnqkLQ6wIAHWx +gn/Nx3ff3Ov/njvbWFcXFibESdZw3aFjAKBDx46Ofk/42e7u2/3f4G8jH5XF07+O7es3tnfSThps +beRNA/PRmd1rxrlGkMNDf8a2DLskJzOcRrJ5/7czb/Z/fzk8qESyjBlDxwBAZT4WGd/1/CtxLcaz +ZiLYWvOmezpXxMQwxKQYwzIkK2S4LMnQMQCorGMm4C7irhp6nUzPHfSs7un6176jffT4cULSuGkM ++1mWq5b2jDlqRpJGdWNsFqNLxqrstfejxEzjA8l+LBpkm+DihQucmodyhhErAoCOmkcvx4t3xsG4 +RaZEbgOeZZNMwu9u+P7EkkiGjgGADh2LDH21Ehd0Wvz82E/VqiLOsE6JizM8iWSZ2n0TM4aOAYAO +HUvzDW0RbNhoa8ld0Ui2cPHCBU7JCwz7DDPzMc7dEf0krzqAESsCIBmxIgAsN1YUSKMlU/9N8KxD ++b02hvn3oDWbMXQMADZMxyIOtUnqn1lTVluuWAzD+kmGjgGAeh2rcfMu7YDCd8PFKss10qRkhiV1 +Q7J2X8+Mpe+PuRcpOCEgp59lOWry1GCRfgVJdg+STFRxK4yTLFnzSCCZacaIFQGgcqworP5FvKlM +YFBwvuIGkszscny+Ij9WlJ/SyY+8oGMAUFnHZIa+tpnjRVrCn68o0PFFMqztdGkkQ8cAQCQfI87A +X0lGlZtJW4gmx9Mnr5lDGuyenawko82RJ5OczLCflfHriprNGDoGAOL5WD/63QX7tU1USV7oq2FH +yKmNf7Ukq2V4RiRrNuOVf+3LLsSrYXTlI7l2TwLUSgvxahhdNRhmRuMkQNVmxogVAUBEx9yh7zoz +STc2quwFHVKTdX7sc/WtGB4NUMsynH/AqXOpdJoxdAwAKuuYGwc3SXj0TL2NIFi7n+pfWyU8c2E4 +p6mazRg6BgAi+ZgbRIpF2yRDKIhRuRhdMJTTu8v7VyY9dpFAcr4nJhlCDZKTGS4uNTrNOLBXLeeU +beuhVefm8Q8bma/4ZLt756+XRyMkM0+xVJL5x4zU7nuGe1iSNZsxYkUAqBwrBoXbf1Os2F3E/cg0 +NeJle//qPyRLGkZiLcmJ83MhOVK7d8OEIZKDZizTcjwHDQCCOubGtfHbdpNSVc6+UuYL1/f33JRx +RttwRfKxvv2mI4Ze63pHb4zySWZuj9Z/gTDczWc3uUik4OqSJZljxt2UslYRM4aOAUBlHTNDPxJN +EmEx/wbfPBUcHy2fu4iXPeiOu22aPAyR7Eu3JTl4ITaH4QjWDPfYZjHc1oxXoxHL0DtumyIJJWl3 +8CHF0QZkJqxFbsj4ExE4aw0Er32wj3GG48Unsg4Zh2T/dHb05iy9mBnnE5KZ8xWHSK5nxt3Ak6DB +IyNWBADBmkca+P6YfPr08JS8vFD/kGc69au8+dTJP89xz5kkT2J4iGTTfkuy35jgNZJkOIdkYTM+ +RdeYGUPHAKCyjpV1BqXwZHs8nxGG8VsHR+u/r1+6sX7rdM3jj3/WPvjc2eNgR9QyrJPkqzfvBqtK +PcmEYf0kQ8cAoC62rr4FEibAKJipd333zb2hr/m+FphKskWwrjgjhrfO7+zgonLw8ae3bPRirrp5 +Jz7YgEm4vH/F/df4srmTjFgRAKBjOkQsqGAG7kdAvoL18jU0h2aOJEPHAKCyjn34wY2hz9xomIC4 +GfPNtJ1FyW8jJ423Ie7/cnpnvmzyAZIw1OtdPsnkXO4P7Uf1Llm9CxE5sqtywdSulJlN6iB0DAAq +69j3X92ND8rgqHXdwNBvR4e7+4W4L0xug+/5gv5s9Mi9g/QVLO5TM3vHVJtI++OdCrJX8JKNXohS +ZjYaZYiZ2dChoGMAUFnHvn1LS13xzM1bHH/z7kOU79Lx26XxLOXf+7jdl8uwa8Ar5sqsZPk482R1 +WRyZS3vSxKAo//nwh/Xfrru9u7e8a+Mv0FeD5O7EQ5GRZvHz/c/s600guR7Dj1DzAIDGsaIrbmlb +0dnFRsh+oaOyaX5lHa3RXNe/Xul2hprK34+UNM9/TY5vWz70acdexMZvedpWdP6pO/aq8f3X/Mjc +kkwY7pK21Q0yk8Yh+UICwzkkB814lGTXjKFjANBOx0aH/qjX4bwZdADGy3b/zwR1J1nb54KC25O6 +p+AIy1TxKQjOhmCZDEdIdlMyc+vWkuw+eRXcZdeehcleK5KVmDF0DABa6FiRhZzS3K3rAOzjDEwd +S0gXJ31UFkUWckpzt1bH3MlHHB3LbJiwrNUz4yE7CZrxKnigIovIBkkcqjQII3KB6117clXESN4o +hmXM2C/hRPaMR6wIAHWxqudaguVO88I9XbKaNdn3tZJrzyfZDxDs6XLihSb7vupk2Cd51IxNKA4d +AwARHavtVIJ3ISO5L//hnFn4VwGSh4gdKuEwl7kGyUN3g4LTGAjMcwDQMQCYrY4FnUHatKNMzGhP +syLulkNy2hPQINl9zTTjVUFC+UUIX3+rItIYzZYxtEULM34jYSRB8cVn5kiyjBkjVgQAlbFicHzz +d/4cFVmxJb40xzYJJPOfAzDL18ksDKqW5GQznhQrQscAYBE1j0ggG4QpemJV0KokAzIMQ8cAQETH +ZCo/m+BZI0wG64StGC5eu1fCsCozNjf6Vw2z0syqveZyRXA4geTaI00bw5h3DwAS4I6xzH24p6IX +2UlLw+e4wxpdS3ColVqiRHOKd61neC4kQ8cAoC64tfuykW6TJ3OL9MtNA4LTmpKJKp5LzJpkwrB7 +kByimpgxdAwAdOhYmqcfQsE5wcLzTYtIlgDD3dic4EnPjwncdQiS3LCqWcSM8Rw0AMxTx4r4Hm3P +QQs7coEuT5oNLNB3bc+/FGmJmRK4GurtpNPkEBRcbIQfyQTT4rRF8MWMLG21n2SSgwxPmncfNImE +RfAlh7EeM0asCACCsWLyQl8NJT64IHvaJh1imfTCSNY230qPGWMuFQAI6ljD9UAjixhPjZ5rLHat +wb+2YtgnucZi10rChBokG0DHAEBExwoO+iJF2KlPQFv/2mRaLTMJLEVykzK3q2AaSK7KcCmSTz0/ +1hCZlWX3h/LBmJ45gVMZnjTPw/62STA2X5IRKwKAYKxYMK0cXcuS4wKnPgnvxmnMXuS74d5pTT1v +keoIh+FRkgUYztc6PwgXq44UNGPoGACI61i9uXlFDvtk+8VmquZoZCIP8xRti871ihalGO66XJKb +l/U1mDHmUgFAIx2LD9Pm/qn3r/5DsqRtJNqWLHYX8fFtSXYVjJDsNoykJWIkFwlVZBg2+dhK59CP +VJbtmDEhjWsW8fs2/HoAcx3z/gvX9/dIUj6XLYLiDNuOWJI5DE+qB3BINp8Skme3CRNiRQAQjxWV +46A77jwFi0QCJPc1XjD45kv/fbT8Cx+p3a8Z7sEmmQiLZXjoQrzQug0gGTUPABDUsZwYt8gMJrub +06iXjTtXP/UayiLcLkeydvtmcJo/swH2+JkM55BMJvsw51KVJTnOcDcwzX8Sw6rMGPkYAIjomOsP +MudT5/ycOA/+jFX3hmmRNkf8Mfn06eEpz/cijQm5/+DPhUkmDE+aS2Xv+xdpc5zhU3QdUgG3JA8x +rMqMMZcKAATzseboncfB0dp/XL151//0j3/W7uHc2WNfwQq624Igt5WUMLzWgf9Jvnjyphsp9CQT +hn2SM6OGGgxrI9kw/PqlGy/HmG+prRAcXaMjjTDepDtPumOOKeghuY9hgtvicBgGyXGY0WXoRawI +ABLYOr+jYk6KWVGV1Dy6icvZAqMMu/7VAnvbFzdjN0yAjgHA0mseZukO4lnNv70zMI4BjrZgjOA7 +WqhZcZJde4aOAUDlfOz7r+6SYdd7OPJv51Si3AQp6CD9Hw65TytW/tCPwz9y/FyRb7r/Tu3pEFHx +/g7pCbOR8SP7Le/DBNI7v+Uckl2VC2YdkQMmXAi/zfGm+t8hJ2U2tdQldr/5nwADACLM1IGrPYuL +AAAAAElFTkSuQmCC +--047d7b45041e19c68004eb9f3de8-- diff --git a/spec/fixtures/emails/auto_reply.eml b/spec/fixtures/emails/auto_reply.eml new file mode 100644 index 00000000000..7999c8d78b7 --- /dev/null +++ b/spec/fixtures/emails/auto_reply.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +Auto-Submitted: auto-generated +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Test reply to Discourse email digest diff --git a/spec/fixtures/emails/big5.eml b/spec/fixtures/emails/big5.eml new file mode 100644 index 00000000000..4a7b2082486 --- /dev/null +++ b/spec/fixtures/emails/big5.eml @@ -0,0 +1,26 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=20cf301cc47ada510404f040b262 + +--20cf301cc47ada510404f040b262 +Content-Type: text/plain; charset=Big5 +Content-Transfer-Encoding: base64 + +tv2hSafapFe5cbX4pEahSQ0K +--20cf301cc47ada510404f040b262 +Content-Type: text/html; charset=Big5 +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9Imx0ciI+tv2hSafapFe5cbX4pEahSTxicj48L2Rpdj4NCg== +--20cf301cc47ada510404f040b262-- diff --git a/spec/fixtures/emails/bottom_reply.eml b/spec/fixtures/emails/bottom_reply.eml new file mode 100644 index 00000000000..5fc992971fc --- /dev/null +++ b/spec/fixtures/emails/bottom_reply.eml @@ -0,0 +1,160 @@ +Received: by 10.107.19.29 with SMTP id b29csp111716ioj; + Wed, 30 Jul 2014 17:52:05 -0700 (PDT) +X-Received: by 10.194.238.6 with SMTP id vg6mr11340975wjc.24.1406767925330; + Wed, 30 Jul 2014 17:52:05 -0700 (PDT) +Received: from localhost (localhost [127.0.0.1]) + by bendel.debian.org (Postfix) with QMQP + id 18F5C417; Thu, 31 Jul 2014 00:52:04 +0000 (UTC) +Old-Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on bendel.debian.org +X-Spam-Level: +X-Spam-Status: No, score=-25.9 required=4.0 tests=FOURLA,LDOSUBSCRIBER, + LDO_WHITELIST,MURPHY_DEBIAN_MESSAGE,PGPSIGNATURE autolearn=unavailable + version=3.3.2 +X-Original-To: lists-debian-ctte@bendel.debian.org +Delivered-To: lists-debian-ctte@bendel.debian.org +Received: from localhost (localhost [127.0.0.1]) + by bendel.debian.org (Postfix) with ESMTP id CE6CDEE + for ; Thu, 31 Jul 2014 00:51:52 +0000 (UTC) +X-Virus-Scanned: at lists.debian.org with policy bank en-lt +X-Amavis-Spam-Status: No, score=-11.9 tagged_above=-10000 required=5.3 + tests=[BAYES_00=-2, FOURLA=0.1, LDO_WHITELIST=-5, PGPSIGNATURE=-5] + autolearn=ham +Received: from bendel.debian.org ([127.0.0.1]) + by localhost (lists.debian.org [127.0.0.1]) (amavisd-new, port 2525) + with ESMTP id SB451DwGZCOe for ; + Thu, 31 Jul 2014 00:51:47 +0000 (UTC) +X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_BL_NJABL=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .debian. - helo: .becquer.dodds. - helo-domain: .dodds.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=0; rate: -5 +Received: from becquer.dodds.net (becquer.dodds.net [207.224.24.209]) + by bendel.debian.org (Postfix) with ESMTP id 8E89A2B4 + for ; Thu, 31 Jul 2014 00:51:47 +0000 (UTC) +Received: from virgil.dodds.net (unknown [192.168.15.59]) + by becquer.dodds.net (Postfix) with ESMTPA id 9B0A9256EB + for ; Wed, 30 Jul 2014 17:51:19 -0700 (PDT) +Received: by virgil.dodds.net (Postfix, from userid 1000) + id 942FB60199; Wed, 30 Jul 2014 17:51:15 -0700 (PDT) +Date: Wed, 30 Jul 2014 17:51:15 -0700 +From: Jake +To: incoming+amazing@appmail.adventuretime.ooo +Subject: Re: Next Debian CTTE IRC Meeting at date -d'Thu Jul 31 17:00:00 UTC + 2014' +Message-ID: <20140731005115.GA19044@virgil.dodds.net> +Mail-Followup-To: debian-ctte@lists.debian.org +References: <20140730213924.GA12356@teltox.donarmstrong.com> +MIME-Version: 1.0 +Content-Type: multipart/signed; micalg=pgp-sha256; + protocol="application/pgp-signature"; boundary="qMm9M+Fa2AknHoGS" +Content-Disposition: inline +In-Reply-To: <20140730213924.GA12356@teltox.donarmstrong.com> +User-Agent: Mutt/1.5.23 (2014-03-12) +X-Debian-Message: Signature check passed for Debian member +X-Rc-Virus: 2007-09-13_01 +X-Rc-Spam: 2008-11-04_01 +Resent-Message-ID: +Resent-From: debian-ctte@lists.debian.org +X-Mailing-List: archive/latest/4791 +X-Loop: debian-ctte@lists.debian.org +List-Id: +List-Post: +List-Help: +List-Subscribe: +List-Unsubscribe: +Precedence: list +Resent-Sender: debian-ctte-request@lists.debian.org +Resent-Date: Thu, 31 Jul 2014 00:52:04 +0000 (UTC) + + +--qMm9M+Fa2AknHoGS +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable + +On Wed, Jul 30, 2014 at 02:39:24PM -0700, Don Armstrong wrote: +> The next Debian CTTE IRC meeting is at=20 + +> date -d 'Thu Jul 31 17:00:00 UTC 2014' on irc.debian.org in +> #debian-ctte. + +> The current meeting agenda is here, and more up-to-date ones may be +> found in the git repository. + +> #startmeeting + +> #topic Who is here? + +> #topic Next Meeting? + +> #topic #717076 Decide between libjpeg-turbo and libjpeg8 et al. + +This has been voted on; should probably be removed from the agenda, someone +just needs to confirm the vote results and get it on the website. (AIUI the +archive has already begun moving on accordingly.) + +> #topic #636783 constitution: super-majority bug + +> #topic #636783 constitution: casting vote + +> #topic #636783 constitution: minimum discussion period + +> #topic #636783 constitution: TC member retirement/rollover + +> #topic #636783 constitution: TC chair retirement/rollover + +> #topic #681419 Depends: foo | foo-nonfree + +> #topic #741573 menu systems and mime-support + +> #topic #746715 init system fallout + +Also voted and just needs to be confirmed. + +> #topic #750135 Maintainer of aptitude package +>=20 +> #topic #752400 Advice on util-linux + +This has been closed by mutual agreement of the people involved and doesn't +require any action from the TC. Removed from the agenda. + +There's also bug #744246, which was assigned to the TC at my request but +without any preamble so it may have escaped notice. However, that situation +has been evolving constructively among the related parties (apparently, an +informal comment from a member of the release team was mistaken for a +release team position, so that's now being revisited), so I don't believe +this is anything we need to put on the agenda for tomorrow despite being on +the open bug list. + +--=20 +Steve Langasek Give me a lever long enough and a Free OS +Debian Developer to set it on, and I can move the world. +Ubuntu Developer http://www.debian.org/ +slangasek@ubuntu.com vorlon@debian.org + +--qMm9M+Fa2AknHoGS +Content-Type: application/pgp-signature; name="signature.asc" +Content-Description: Digital signature + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1 + +iQIcBAEBCAAGBQJT2ZMDAAoJEFaNMPMhshM9GLsP/244S3wtYZEeVgJWIdB5PE0A +sZVezEA692y++/0oVZVecwV67yBOyfSjPPetdAph2UDMRtfurwxfj2BkbOFA2+Y6 +++MErbmC3V7IGpd/L/fFGdXgvMQT2MNBpw0fnMA7bLpNjCvoj+Hr1HXRUcWoJSlj +WmHWwWSTVRcHg8a3iWYJzY6XfLyEEgHlahrlKvJExsTx/9mc1qg7g8KGdnhzHFBl +ttdH2fxpAk/624dReCcw5RKmOLfZ1HsEl9XcVe1cb4K+MDaQiXmoEK5v3xaNz1tS +NK5v2D5gDs229zoxKzQnnzOPLHxqI5E0L9PpI/mu4T9z7H2bHR3U5BvhnT99t5uw +ydf2cZNGY0uFCV3Rvn07BfAIW5WSXhOfN/5IymRKmdhjsTiwZ/wFjFrK8tVjtERu +yeyA7RIYiblGCEKYIYLWSxhoXeEdmAdfp6EA2/IA1CpgMB+ZdSfaeMgFY7xosgmG +ax3NTnaKyhr1QEUJ2gjAwHnKjuGbRVDAinYrSvP0o8Bh9sAs2BN2negWBCZVwwkN +S9hWTjVqsBmpaPOt5SEDwDo9O9dfzkmaamDsxOuUEz9F7v5jYg0mxA/WbogGty9R +vOMKxdxRkzflL/CferVbkzL/EkZRDfWDp9SleZggrpz7miiNDbS7jdRzJ4EttmJ8 +gHBAVrOzcnbIPOIkk9pw +=KXIu +-----END PGP SIGNATURE----- + +--qMm9M+Fa2AknHoGS-- + + +-- +To UNSUBSCRIBE, email to debian-ctte-REQUEST@lists.debian.org +with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org +Archive: https://lists.debian.org/20140731005115.GA19044@virgil.dodds.net diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml new file mode 100644 index 00000000000..1250fe498b0 --- /dev/null +++ b/spec/fixtures/emails/boundary.eml @@ -0,0 +1,61 @@ + +MIME-Version: 1.0 +Received: by 10.64.14.41 with HTTP; Wed, 19 Jun 2013 06:29:41 -0700 (PDT) +In-Reply-To: <51c19490e928a_13442dd8ae892548@tree.mail> +References: <51c19490e928a_13442dd8ae892548@tree.mail> +Date: Wed, 19 Jun 2013 09:29:41 -0400 +Delivered-To: finn@adventuretime.ooo +Message-ID: +Subject: Re: [Adventure Time] jake mentioned you in 'peppermint butler is + missing' +From: Finn the Human +To: jake via Adventure Time +Content-Type: multipart/alternative; boundary=001a11c206a073876a04df81d2a9 + +--001a11c206a073876a04df81d2a9 +Content-Type: text/plain; charset=ISO-8859-1 + +I'll look into it, thanks! + + +On Wednesday, June 19, 2013, jake via Discourse wrote: + +> jake mentioned you in 'peppermint butler is missing' on Adventure +> Time: +> ------------------------------ +> +> yeah, just noticed this cc @jake +> ------------------------------ +> +> Please visit this link to respond: +> http://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c206a073876a04df81d2a9 +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + +I'll look into it, thanks!

On Wednesday, June 19, 2= +013, jake via Adventure Time wrote:

sa= +m mentioned you in 'Duplicate message are shown in profile' on Adve= +nture Time

+ + +

yeah, just noticed this cc @eviltrout

+ +

Please visit this link to respond: http= +://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 + + +

To unsubscribe from these emails, visit your user preferences.

+
+ +--001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/dutch.eml b/spec/fixtures/emails/dutch.eml new file mode 100644 index 00000000000..7be08dc4938 --- /dev/null +++ b/spec/fixtures/emails/dutch.eml @@ -0,0 +1,20 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +Dit is een antwoord in het Nederlands. + +Op 18 juli 2013 10:23 schreef Sander Datema het volgende: + +Dit is de originele post. \ No newline at end of file diff --git a/spec/fixtures/emails/empty.eml b/spec/fixtures/emails/empty.eml new file mode 100644 index 00000000000..85bebc66245 --- /dev/null +++ b/spec/fixtures/emails/empty.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + + diff --git a/spec/fixtures/emails/gmail_web.eml b/spec/fixtures/emails/gmail_web.eml new file mode 100644 index 00000000000..8bb83835711 --- /dev/null +++ b/spec/fixtures/emails/gmail_web.eml @@ -0,0 +1,181 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:36:49 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a11c2e04e6544f30508f138ba + +--001a11c2e04e6544f30508f138ba +Content-Type: text/plain; charset=UTF-8 + +### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com + +On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c2e04e6544f30508f138ba +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
### This is a reply from standard GMail in Google Chr= +ome.

The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown= + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. = +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over= + the lazy dog.=C2=A0

Here's some **bold** text= + in Markdown.

Here's a link http://example.com
<= +br>
On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan= + <info@discourse.org> wrote:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+

+ +--001a11c2e04e6544f30508f138ba-- diff --git a/spec/fixtures/emails/hebrew.eml b/spec/fixtures/emails/hebrew.eml new file mode 100644 index 00000000000..f4c8f01adc3 --- /dev/null +++ b/spec/fixtures/emails/hebrew.eml @@ -0,0 +1,17 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 + +16nXnNeV150= \ No newline at end of file diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml new file mode 100644 index 00000000000..561b8db2c79 --- /dev/null +++ b/spec/fixtures/emails/html_only.eml @@ -0,0 +1,93 @@ + +Delivered-To: walter@breakingbad.com +Received: by 10.64.13.41 with SMTP id m9csp29769iec; + Thu, 20 Jun 2013 08:53:22 -0700 (PDT) +X-Received: by 10.252.23.9 with SMTP id p9mr4055675lag.4.1371743601980; + Thu, 20 Jun 2013 08:53:21 -0700 (PDT) +Received: from mail-la0-x229.google.com (mail-la0-x229.google.com [2a00:1450:4010:c03::229]) + by mx.google.com with ESMTPS id u4si430203lae.48.2013.06.20.08.53.20 + for + (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); + Thu, 20 Jun 2013 08:53:21 -0700 (PDT) +X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=google.com; s=20120113; + h=x-forwarded-to:x-forwarded-for:delivered-to:x-return-path + :content-type:mime-version:content-transfer-encoding:x-mailer + :message-id:date:subject:from:in-reply-to:to:resent-date:resent-from + :resent-to:resent-subject:resent-message-id:resent-user-agent + :x-scanned-by:x-gm-message-state; + bh=9O67r74ofh9WkEaKTRB/frQ3MKOtQlbCac2mz0/MiyY=; + b=YVAo2/JDMP53RxDmqDEKNcEMtggtfaVyq2DoseZ6vBAfB7G6NtHC9ZEkRs4oGhk6LU + fnyAPe0wnz5d9WINoMAuuTRIhplLxzcqysduSnAJAQ2qqR7mFBnlj9wJeVEKltNwmUME + nPwxsf8go20VBzrZCtECPedcLi60wbl32NCXVn0qwt2LvKiy6ktSS5Xgb4zY8i4dfXAP + 6Y5gu32boooWIb9DkH1TJkn3C0RrEugNlw/DUnXrnkFefgxWF3pt/zcoW/wYRyikOdx+ + smBClgR9my6QmsS2KsQrMvWJZUva7fddTiZ6FC22e4hW+8Wha0RaZOZu5O7hjg6G4/1g + IEyg== +X-Received: by 10.112.55.9 with SMTP id n9mr5916187lbp.5.1371743600857; + Thu, 20 Jun 2013 08:53:20 -0700 (PDT) +X-Forwarded-To: walter@breakingbad.com +X-Forwarded-For: walter@breakingbad.com +Delivered-To: walter@breakingbad.com +Content-Type: text/html; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +X-Mailer: BlackBerry Email (10.1.0.1720) +Message-ID: <20130619231548.6307981.74194.2379@breakingbad.com> +Date: Wed, 19 Jun 2013 19:15:48 -0400 +Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site + Customization not working" +From: aaron@breakingbad.com +In-Reply-To: <51c238655a394_5f4e3ce6690667bd@tiefighter2.mail> +To: reply+20c1b0a8bd1a63c0163cc7e7641ca06b@appmail.adventuretime.ooo +ReSent-Date: Thu, 20 Jun 2013 11:53:08 -0400 (EDT) +ReSent-From: Aaron +ReSent-Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site + Customization not working" +X-Gm-Message-State: ALoCoQl1BtN83rAX7At808XAPv1yCqUK3Du2IvK7eCyY3jsI77u4e5cak28307pYYHAo1JlO/Eu9 + +
The EC2 instance - I've seen that th= +ere tends to be odd and unrecommended settings on the Bitnami installs that= + I've checked out.
= + = +

= + = +
= + = + = +
= +From: Grizzly B via Discourse Meta
Sent: Wednesday, J= +une 19, 2013 19:02
To: aaron@breakingbad.com
= +Reply To: Grizzly B via Discourse Meta
Subject: [Disc= +ourse Meta] [PM] re: Regarding your post in "Site Customization
not wor= +king"

Grizzly B just sent you a private message

+ +

Log in to our EC2 instance -or- log into a new Digital Ocean instanc= +e?

+ +

Please visit this link to respond: http://= +meta.discourse.org/t/regarding-your-post-in-site-customization-not-working/= +7641/5

+ +

To unsubscribe from these emails, visit your user preferences.

+
diff --git a/spec/fixtures/emails/html_paragraphs.eml b/spec/fixtures/emails/html_paragraphs.eml new file mode 100644 index 00000000000..3fe37fb8b17 --- /dev/null +++ b/spec/fixtures/emails/html_paragraphs.eml @@ -0,0 +1,205 @@ + +MIME-Version: 1.0 +Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) +X-Originating-IP: [117.207.85.84] +In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +References: + <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +Date: Wed, 8 Oct 2014 10:47:17 +0530 +Delivered-To: arpit@techapj.com +Message-ID: +Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! +From: Arpit Jalan +To: Discourse +Content-Type: multipart/alternative; boundary=001a114119d8f4e46e0504e26d5b + +--001a114119d8f4e46e0504e26d5b +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Awesome! + +Pleasure to have you here! + +:boom: + +On Wed, Oct 8, 2014 at 10:46 AM, ajalan +wrote: + +> ajalan +> +> October 8 +> +> Nice to be here! Thanks! [image: smile] +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 +> +> in your browser. +> ------------------------------ +> Previous Replies techAPJ +> +> October 8 +> +> Welcome to techAPJ's Discourse! +> ------------------------------ +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2 +> +> in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> +> . +> + +--001a114119d8f4e46e0504e26d5b +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
Awesome!

Pleasure to have you here!

:boom:

On Wed, Oct 8, 2014 at 10:46 AM, ajalan <info@unconfigured.discourse.org> wrote:
+ + + + + + + + + + + +
+ + + ajalan
+ October 8 +

Nice to be here! Thanks! 3D"smile"

+ + +
+

To respond, reply to this email or visit http://discourse= +.techapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + techAPJ
+ October 8 +

Welcome to techAPJ's Discourse!

+ + +
+ +
+

To respond, reply to this email or visit http://discourse.tec= +hapj.com/t/welcome-to-techapjs-discourse/35/2 in your browser.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+ +
= +
+ +--001a114119d8f4e46e0504e26d5b-- diff --git a/spec/fixtures/emails/inline_reply.eml b/spec/fixtures/emails/inline_reply.eml new file mode 100644 index 00000000000..39625a225da --- /dev/null +++ b/spec/fixtures/emails/inline_reply.eml @@ -0,0 +1,60 @@ + +MIME-Version: 1.0 +In-Reply-To: +References: + <5434ced4ee0f9_663fb0b5f76070593b@discourse-app.mail> +Date: Mon, 1 Dec 2014 20:48:40 +0530 +Delivered-To: someone@googlemail.com +Subject: Re: [Discourse] [Meta] Testing reply via email +From: Walter White +To: Discourse +Content-Type: multipart/alternative; boundary=20cf30363f8522466905092920a6 + +--20cf30363f8522466905092920a6 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +On Wed, Oct 8, 2014 at 11:12 AM, techAPJ +wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +--20cf30363f8522466905092920a6-- diff --git a/spec/fixtures/emails/ios_default.eml b/spec/fixtures/emails/ios_default.eml new file mode 100644 index 00000000000..8d4d58feb16 --- /dev/null +++ b/spec/fixtures/emails/ios_default.eml @@ -0,0 +1,136 @@ +Delivered-To: reply@discourse.org +Return-Path: +From: Walter White +Content-Type: multipart/alternative; + boundary=Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Transfer-Encoding: 7bit +Mime-Version: 1.0 (1.0) +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +Date: Fri, 28 Nov 2014 12:41:41 -0800 +References: +In-Reply-To: +To: Discourse Meta +X-Mailer: iPhone Mail (12B436) + + +--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Type: text/plain; + charset=us-ascii +Content-Transfer-Encoding: quoted-printable + +### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over t= +he lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fo= +x jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The q= +uick brown fox jumps over the lazy dog. The quick brown fox jumps over the l= +azy dog.=20 + +Here's some **bold** markdown text. + +Here's a link http://example.com + + +> On Nov 28, 2014, at 12:35 PM, Arpit Jalan wrote: +>=20 +>=20 +> techAPJ +> November 28 +> Test reply. +>=20 +> First paragraph. +>=20 +> Second paragraph. +>=20 +> To respond, reply to this email or visit https://meta.discourse.org/t/test= +ing-default-email-replies/22638/3 in your browser. +>=20 +> Previous Replies +>=20 +> codinghorror +> November 28 +> We're testing the latest GitHub email processing library which we are inte= +grating now. +>=20 +> https://github.com/github/email_reply_parser +>=20 +> Go ahead and reply to this topic and I'll reply from various email clients= + for testing. +>=20 +> To respond, reply to this email or visit https://meta.discourse.org/t/test= +ing-default-email-replies/22638/3 in your browser. +>=20 +> To unsubscribe from these emails, visit your user preferences. + +--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105 +Content-Type: text/html; + charset=utf-8 +Content-Transfer-Encoding: 7bit + +
### this is a reply from iOS default mail

The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. 

Here's some **bold** markdown text.

Here's a link http://example.com


On Nov 28, 2014, at 12:35 PM, Arpit Jalan <info@discourse.org> wrote:

+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror
+ November 28 +
+

We're testing the latest GitHub email processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I'll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://meta.discourse.org/t/testing-default-email-replies/22638/3 in your browser.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105-- diff --git a/spec/fixtures/emails/iphone_signature.eml b/spec/fixtures/emails/iphone_signature.eml new file mode 100644 index 00000000000..d314ad1f1ea --- /dev/null +++ b/spec/fixtures/emails/iphone_signature.eml @@ -0,0 +1,29 @@ +Delivered-To: test@mail.com +Return-Path: +From: Walter White +Content-Type: multipart/alternative; + boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +Content-Transfer-Encoding: 7bit +Mime-Version: 1.0 (1.0) +Subject: Re: Signature in email replies! +Date: Thu, 23 Oct 2014 14:43:49 +0530 +References: <1234@mail.gmail.com> +In-Reply-To: <1234@mail.gmail.com> +To: Arpit Jalan +X-Mailer: iPhone Mail (12A405) + + +--Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 +Content-Type: text/plain; + charset=us-ascii +Content-Transfer-Encoding: 7bit + +This post should not include signature. + +Sent from my iPhone + +> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: +> +> Signature in email replies! + +--Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 diff --git a/spec/fixtures/emails/multiple_destinations.eml b/spec/fixtures/emails/multiple_destinations.eml new file mode 100644 index 00000000000..6d31bbf1974 --- /dev/null +++ b/spec/fixtures/emails/multiple_destinations.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: finn@adventuretime.ooo, reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/newlines.eml b/spec/fixtures/emails/newlines.eml new file mode 100644 index 00000000000..cf03b9d18bc --- /dev/null +++ b/spec/fixtures/emails/newlines.eml @@ -0,0 +1,84 @@ +In-Reply-To: +Date: Wed, 8 Oct 2014 10:36:19 +0530 +Delivered-To: walter.white@googlemail.com +Subject: Re: [Discourse] Welcome to Discourse +From: Walter White +To: Discourse +Content-Type: multipart/alternative; boundary=bcaec554078cc3d0c10504e24661 + +--bcaec554078cc3d0c10504e24661 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +This is my reply. +It is my best reply. +It will also be my *only* reply. + +On Wed, Oct 8, 2014 at 10:33 AM, ajalan +wrote: + +> ajalan +> +> October 8 +> +> Awesome! Thank You! [image: +1] +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-discourse/8/2 +> +> in your browser. +> ------------------------------ +> Previous Replies system +> +> October 8 +> +> The first paragraph of this pinned topic will be visible as a welcome +> message to all new visitors on your homepage. It's important! +> +> *Edit this* into a brief description of your community: +> +> - Who is it for? +> - What can they find here? +> - Why should they come here? +> - Where can they read more (links, resources, etc)? +> +> You may want to close this topic via the wrench icon at the upper right, +> so that replies don't pile up on an announcement. +> ------------------------------ +> +> To respond, reply to this email or visit +> http://discourse.techapj.com/t/welcome-to-discourse/8/2 +> +> in your browser. +> +> To unsubscribe from these emails, visit your user preferences +> +> . +> + +--bcaec554078cc3d0c10504e24661 diff --git a/spec/fixtures/emails/no_content_reply.eml b/spec/fixtures/emails/no_content_reply.eml new file mode 100644 index 00000000000..95eb2055ce6 --- /dev/null +++ b/spec/fixtures/emails/no_content_reply.eml @@ -0,0 +1,34 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/on_wrote.eml b/spec/fixtures/emails/on_wrote.eml new file mode 100644 index 00000000000..feb59bd27bb --- /dev/null +++ b/spec/fixtures/emails/on_wrote.eml @@ -0,0 +1,277 @@ + +MIME-Version: 1.0 +Received: by 10.107.9.17 with HTTP; Tue, 9 Sep 2014 16:18:19 -0700 (PDT) +In-Reply-To: <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> +References: + <540f16d4c08d9_4a3f9ff6d61890391c@tiefighter4-meta.mail> +Date: Tue, 9 Sep 2014 16:18:19 -0700 +Delivered-To: kanepyork@gmail.com +Message-ID: +Subject: Re: [Discourse Meta] Badge icons - where to find them? +From: Kane York +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a11c34c389e728f0502aa26a0 + +--001a11c34c389e728f0502aa26a0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Sure, all you need to do is frobnicate the foobar and you'll be all set! + +On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan wrote: + +> gordon_ryan +> September 9 +> +> @riking - willing to step by +> step of the custom icon method for an admittedly ignorant admin? Seriousl= +y +> confused. +> +> Or anyone else who knows how to do this [image: smiley] +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in +> your browser. +> ------------------------------ +> Previous Replies riking +> July 25 +> +> Check out the "HTML Head" section in the "Content" tab of the admin panel= +. +> meglio +> July 25 +> +> How will it load the related custom font? +> riking +> July 25 +> +> Here's an example of the styles that FA applies. I'll use fa-heart"> as the example. +> +> .fa { +> display: inline-block; +> font-family: FontAwesome; +> font-style: normal; +> font-weight: normal; +> line-height: 1; +> -webkit-font-smoothing: antialiased; +> -moz-osx-font-smoothing: grayscale; +> } +> .fa-heart:before { +> content: "\f004"; +> } +> +> So you could do this in your site stylesheet: +> +> .fa-custom-burger:before { +> content: "\01f354"; +> font-family: inherit; +> } +> +> And get =F0=9F=8D=94 as your badge icon when you enter custom-burger. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a11c34c389e728f0502aa26a0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
Sure, all you need to do is frobnicate the foobar and you'll be all s= +et!


= +
On Tue, Sep 9, 2014 at 8:03 AM, gordon_ryan = +<info@discourse.org> wrote:
+ + + + + + + + + + + + +
+ + + gordon_ryan
+ September 9 +
+

@riking- willing to step by step of the custom icon me= +thod for an admittedly ignorant admin? Seriously confused.

+ +

Or anyone else who knows how to do this = +3D"smiley"

+
+ + +
+

To respond, reply to this email or visit https:= +//meta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your b= +rowser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + riking
+ July 25 +

Check out the "HTML Head" section in the "Content&= +quot; tab of the admin panel.

+ + + + + + + + + + + +
+ + + meglio
+ July 25 +

How will it load the related custom font?

+ + + + + + + + + + + +
+ + + riking
+ July 25 +
+

Here's an example of the styles that= + FA applies. I'll use <i class=3D"fa fa-heart"></i> as the e= +xample.

+ +

+
.fa {
+  display: inline-block;
+  font-family: FontAwesome;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.fa-heart:before {
+  content: "\f004";
+}
+ +

So you could do this in your site styles= +heet:

+ +

+
.fa-custom-burger:before {
+  content: "\01f354";
+  font-family: inherit;
+}
+ +

And get =F0=9F=8D=94 as your badge icon = +when you enter cus= +tom-burger.

+
+ + +
+ +
+

To respond, reply to this email or visit https://me= +ta.discourse.org/t/badge-icons-where-to-find-them/18058/9 in your brows= +er.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+

+ +--001a11c34c389e728f0502aa26a0-- \ No newline at end of file diff --git a/spec/fixtures/emails/outlook.eml b/spec/fixtures/emails/outlook.eml new file mode 100644 index 00000000000..fb1f590a30e --- /dev/null +++ b/spec/fixtures/emails/outlook.eml @@ -0,0 +1,188 @@ + +MIME-Version: 1.0 +Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT) +X-Originating-IP: [117.207.85.84] +In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +References: + <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail> +Date: Wed, 8 Oct 2014 10:47:17 +0530 +Delivered-To: arpit@techapj.com +Message-ID: +Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse! +From: Arpit Jalan +To: Discourse Accept-Language: en-US +Content-Language: en-US +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +x-originating-ip: [134.68.31.227] +Content-Type: multipart/alternative; + boundary="_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_" +MIME-Version: 1.0 + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 + +TWljcm9zb2Z0IE91dGxvb2sgMjAxMA0KDQpGcm9tOiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVu +bXJzLm9yZ10NClNlbnQ6IE1vbmRheSwgT2N0b2JlciAxMywgMjAxNCA5OjM4IEFNDQpUbzogUG93 +ZXIsIENocmlzDQpTdWJqZWN0OiBbUE1dIFlvdXIgcG9zdCBpbiAiQnVyZ2VyaGF1czogTmV3IHJl +c3RhdXJhbnQgLyBsdW5jaCB2ZW51ZSINCg0KDQptaWNoYWVsPGh0dHA6Ly9jbC5vcGVubXJzLm9y +Zy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2liR2xaYTFW +MGVYaENZMDFNUlRGc1VESm1ZelZRTTBabGVqRTRJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96 +TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhi +R3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkWE5sY25OY1hGd3ZiV2xqYUdGbGJGd2lMRndpYVdSY0lq +cGNJbVExWW1Nd04yTmtORFJqWkRRNE1HTTRZVGcyTXpsalpXSTFOemd6WW1ZMlhDSXNYQ0oxY214 +ZmFXUnpYQ0k2VzF3aVlqaGtPRGcxTWprNU56ZG1aalkxWldZeU5URTNPV1JpTkdZeU1XSTNOekZq +TnpoalpqaGtPRndpWFgwaWZRPg0KT2N0b2JlciAxMw0KDQpodHRwczovL3RhbGsub3Blbm1ycy5v +cmcvdC9idXJnZXJoYXVzLW5ldy1yZXN0YXVyYW50LWx1bmNoLXZlbnVlLzY3Mi8zPGh0dHA6Ly9j +bC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkwNS90YWxrLm9wZW5tcnMub3JnP3A9ZXlK +eklqb2lVRVJJU1VOeVIzbFZNRGRCVlZocFduUjNXV3g0TVdOc1RXNVpJaXdpZGlJNk1Td2ljQ0k2 +SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hMRndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNY +Rnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hGd3ZkRnhjWEM5aWRYSm5aWEpvWVhWekxX +NWxkeTF5WlhOMFlYVnlZVzUwTFd4MWJtTm9MWFpsYm5WbFhGeGNMelkzTWx4Y1hDOHpYQ0lzWENK +cFpGd2lPbHdpWkRWaVl6QTNZMlEwTkdOa05EZ3dZemhoT0RZek9XTmxZalUzT0ROaVpqWmNJaXhj +SW5WeWJGOXBaSE5jSWpwYlhDSmlOelppWWprMFpURmlOekk1WlRrMlpUUmxaV000TkdSbU1qUTRN +RE13WWpZeVlXWXlNR00wWENKZGZTSjk+DQoNCkxvb2tzIGxpa2UgeW91ciByZXBseS1ieS1lbWFp +bCB3YXNuJ3QgcHJvY2Vzc2VkIGNvcnJlY3RseSBieSBvdXIgc29mdHdhcmUuIENhbiB5b3UgbGV0 +IG1lIGtub3cgd2hhdCB2ZXJzaW9uL09TIG9mIHdoYXQgZW1haWwgcHJvZ3JhbSB5b3UncmUgdXNp +bmc/IFdlIHdpbGwgd2FudCB0byB0cnkgdG8gZml4IHRoZSBidWcuIDpzbWlsZToNCg0KVGhhbmtz +IQ0KDQoNCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fDQoNClRvIHJlc3BvbmQsIHJl +cGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91 +ci1wb3N0LWluLWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8aHR0 +cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/ +cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5aRWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3 +aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3 +Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZj +M1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpW +eGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJanBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRn +Mk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNteGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5 +T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJMk1tRm1NakJqTkZ3aVhYMGlmUT4gaW4geW91ciBi +cm93c2VyLg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzPGh0dHA6Ly9jbC5vcGVubXJzLm9yZy90cmFjay9jbGljay8zMDAzOTkw +NS90YWxrLm9wZW5tcnMub3JnP3A9ZXlKeklqb2lkVXh1V2xnNVZGYzBPV1pXUzBZNGJGZExkbWx5 +V0dzeFRWOXpJaXdpZGlJNk1Td2ljQ0k2SW50Y0luVmNJam96TURBek9Ua3dOU3hjSW5aY0lqb3hM +RndpZFhKc1hDSTZYQ0pvZEhSd2N6cGNYRnd2WEZ4Y0wzUmhiR3N1YjNCbGJtMXljeTV2Y21kY1hG +d3ZiWGxjWEZ3dmNISmxabVZ5Wlc1alpYTmNJaXhjSW1sa1hDSTZYQ0prTldKak1EZGpaRFEwWTJR +ME9EQmpPR0U0TmpNNVkyVmlOVGM0TTJKbU5sd2lMRndpZFhKc1gybGtjMXdpT2x0Y0ltSTRNV1V3 +WmpBMU5EWTVORE0wTnpneU0yRm1NakEyTmpGalpqYzNaR05pTjJOaFl6ZG1NakpjSWwxOUluMD4u +DQoNCg== + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_ +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: base64 + +PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVy +bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVt +YXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWlj +cm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv +VFIvUkVDLWh0bWw0MCI+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIg +Y29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRv +ciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTQgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPCEtLVtp +ZiAhbXNvXT48c3R5bGU+dlw6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kb1w6KiB7 +YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kd1w6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0 +I1ZNTCk7fQ0KLnNoYXBlIHtiZWhhdmlvcjp1cmwoI2RlZmF1bHQjVk1MKTt9DQo8L3N0eWxlPjwh +W2VuZGlmXS0tPjxzdHlsZT48IS0tDQovKiBGb250IERlZmluaXRpb25zICovDQpAZm9udC1mYWNl +DQoJe2ZvbnQtZmFtaWx5OkNhbGlicmk7DQoJcGFub3NlLTE6MiAxNSA1IDIgMiAyIDQgMyAyIDQ7 +fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpUYWhvbWE7DQoJcGFub3NlLTE6MiAxMSA2IDQg +MyA1IDQgNCAyIDQ7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMgKi8NCnAuTXNvTm9ybWFsLCBsaS5N +c29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBpbjsNCgltYXJnaW4tYm90dG9tOi4w +MDAxcHQ7DQoJZm9udC1zaXplOjEyLjBwdDsNCglmb250LWZhbWlseToiVGltZXMgTmV3IFJvbWFu +Iiwic2VyaWYiO30NCmE6bGluaywgc3Bhbi5Nc29IeXBlcmxpbmsNCgl7bXNvLXN0eWxlLXByaW9y +aXR5Ojk5Ow0KCWNvbG9yOmJsdWU7DQoJdGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTt9DQphOnZp +c2l0ZWQsIHNwYW4uTXNvSHlwZXJsaW5rRm9sbG93ZWQNCgl7bXNvLXN0eWxlLXByaW9yaXR5Ojk5 +Ow0KCWNvbG9yOnB1cnBsZTsNCgl0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO30NCnANCgl7bXNv +LXN0eWxlLXByaW9yaXR5Ojk5Ow0KCW1zby1tYXJnaW4tdG9wLWFsdDphdXRvOw0KCW1hcmdpbi1y +aWdodDowaW47DQoJbXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG87DQoJbWFyZ2luLWxlZnQ6MGlu +Ow0KCWZvbnQtc2l6ZToxMi4wcHQ7DQoJZm9udC1mYW1pbHk6IlRpbWVzIE5ldyBSb21hbiIsInNl +cmlmIjt9DQpzcGFuLkVtYWlsU3R5bGUxOA0KCXttc28tc3R5bGUtdHlwZTpwZXJzb25hbC1yZXBs +eTsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsInNhbnMtc2VyaWYiOw0KCWNvbG9yOiMxRjQ5N0Q7 +fQ0KLk1zb0NocERlZmF1bHQNCgl7bXNvLXN0eWxlLXR5cGU6ZXhwb3J0LW9ubHk7DQoJZm9udC1m +YW1pbHk6IkNhbGlicmkiLCJzYW5zLXNlcmlmIjt9DQpAcGFnZSBXb3JkU2VjdGlvbjENCgl7c2l6 +ZTo4LjVpbiAxMS4waW47DQoJbWFyZ2luOjEuMGluIDEuMGluIDEuMGluIDEuMGluO30NCmRpdi5X +b3JkU2VjdGlvbjENCgl7cGFnZTpXb3JkU2VjdGlvbjE7fQ0KLS0+PC9zdHlsZT48IS0tW2lmIGd0 +ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlZGVmYXVsdHMgdjpleHQ9ImVkaXQiIHNwaWRtYXg9IjEw +MjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0tW2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNo +YXBlbGF5b3V0IHY6ZXh0PSJlZGl0Ij4NCjxvOmlkbWFwIHY6ZXh0PSJlZGl0IiBkYXRhPSIxIiAv +Pg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPg0KPC9oZWFkPg0KPGJvZHkgbGFu +Zz0iRU4tVVMiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPg0KPGRpdiBjbGFzcz0iV29yZFNl +Y3Rpb24xIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTEu +MHB0O2ZvbnQtZmFtaWx5OiZxdW90O0NhbGlicmkmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90 +Oztjb2xvcjojMUY0OTdEIj5NaWNyb3NvZnQgT3V0bG9vayAyMDEwPG86cD48L286cD48L3NwYW4+ +PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7 +Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7O2Nv +bG9yOiMxRjQ5N0QiPjxvOnA+Jm5ic3A7PC9vOnA+PC9zcGFuPjwvcD4NCjxwIGNsYXNzPSJNc29O +b3JtYWwiPjxiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTAuMHB0O2ZvbnQtZmFtaWx5OiZxdW90 +O1RhaG9tYSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7Ij5Gcm9tOjwvc3Bhbj48L2I+PHNw +YW4gc3R5bGU9ImZvbnQtc2l6ZToxMC4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7VGFob21hJnF1b3Q7 +LCZxdW90O3NhbnMtc2VyaWYmcXVvdDsiPiBtaWNoYWVsIFttYWlsdG86dGFsa0BvcGVubXJzLm9y +Z10NCjxicj4NCjxiPlNlbnQ6PC9iPiBNb25kYXksIE9jdG9iZXIgMTMsIDIwMTQgOTozOCBBTTxi +cj4NCjxiPlRvOjwvYj4gUG93ZXIsIENocmlzPGJyPg0KPGI+U3ViamVjdDo8L2I+IFtQTV0gWW91 +ciBwb3N0IGluICZxdW90O0J1cmdlcmhhdXM6IE5ldyByZXN0YXVyYW50IC8gbHVuY2ggdmVudWUm +cXVvdDs8bzpwPjwvbzpwPjwvc3Bhbj48L3A+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48bzpwPiZu +YnNwOzwvbzpwPjwvcD4NCjxkaXY+DQo8dGFibGUgY2xhc3M9Ik1zb05vcm1hbFRhYmxlIiBib3Jk +ZXI9IjAiIGNlbGxzcGFjaW5nPSIwIiBjZWxscGFkZGluZz0iMCI+DQo8dGJvZHk+DQo8dHI+DQo8 +dGQgdmFsaWduPSJ0b3AiIHN0eWxlPSJwYWRkaW5nOjBpbiAwaW4gMGluIDBpbiI+PC90ZD4NCjx0 +ZCBzdHlsZT0icGFkZGluZzowaW4gMGluIDBpbiAwaW4iPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIg +c3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMu +b3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWJHbFph +MVYwZVhoQ1kwMU1SVEZzVURKbVl6VlFNMFpsZWpFNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lq +b3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNS +aGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRYTmxjbk5jWEZ3dmJXbGphR0ZsYkZ3aUxGd2lhV1Jj +SWpwY0ltUTFZbU13TjJOa05EUmpaRFE0TUdNNFlUZzJNemxqWldJMU56Z3pZbVkyWENJc1hDSjFj +bXhmYVdSelhDSTZXMXdpWWpoa09EZzFNams1TnpkbVpqWTFaV1l5TlRFM09XUmlOR1l5TVdJM056 +RmpOemhqWmpoa09Gd2lYWDBpZlEiIHRhcmdldD0iX2JsYW5rIj48Yj48c3BhbiBzdHlsZT0iZm9u +dC1zaXplOjEwLjBwdDtmb250LWZhbWlseTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1z +ZXJpZiZxdW90Oztjb2xvcjojMDA2Njk5O3RleHQtZGVjb3JhdGlvbjpub25lIj5taWNoYWVsPC9z +cGFuPjwvYj48L2E+PGJyPg0KPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTo4LjVwdDtmb250LWZhbWls +eTomcXVvdDtUYWhvbWEmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90Oztjb2xvcjojOTk5OTk5 +Ij5PY3RvYmVyIDEzPC9zcGFuPg0KPG86cD48L286cD48L3A+DQo8L3RkPg0KPC90cj4NCjx0cj4N +Cjx0ZCBjb2xzcGFuPSIyIiBzdHlsZT0icGFkZGluZzozLjc1cHQgMGluIDBpbiAwaW4iPg0KPHAg +Y2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbi1ib3R0b206MTguNzVwdCI+PGEgaHJlZj0i +aHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2NsaWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5v +cmc/cD1leUp6SWpvaVVFUklTVU55UjNsVk1EZEJWVmhwV25SM1dXeDRNV05zVFc1Wklpd2lkaUk2 +TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05TeGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9k +SFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0xeWN5NXZjbWRjWEZ3dmRGeGNYQzlpZFhKblpY +Sm9ZWFZ6TFc1bGR5MXlaWE4wWVhWeVlXNTBMV3gxYm1Ob0xYWmxiblZsWEZ4Y0x6WTNNbHhjWEM4 +elhDSXNYQ0pwWkZ3aU9sd2laRFZpWXpBM1kyUTBOR05rTkRnd1l6aGhPRFl6T1dObFlqVTNPRE5p +WmpaY0lpeGNJblZ5YkY5cFpITmNJanBiWENKaU56WmlZamswWlRGaU56STVaVGsyWlRSbFpXTTRO +R1JtTWpRNE1ETXdZall5WVdZeU1HTTBYQ0pkZlNKOSI+PGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMw +MDY2OTk7dGV4dC1kZWNvcmF0aW9uOm5vbmUiPmh0dHBzOi8vdGFsay5vcGVubXJzLm9yZy90L2J1 +cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjcyLzM8L3NwYW4+PC9iPjwvYT4N +CjxvOnA+PC9vOnA+PC9wPg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6MGluIj5Mb29rcyBsaWtlIHlv +dXIgcmVwbHktYnktZW1haWwgd2Fzbid0IHByb2Nlc3NlZCBjb3JyZWN0bHkgYnkgb3VyIHNvZnR3 +YXJlLiBDYW4geW91IGxldCBtZSBrbm93IHdoYXQgdmVyc2lvbi9PUyBvZiB3aGF0IGVtYWlsIHBy +b2dyYW0geW91J3JlIHVzaW5nPyBXZSB3aWxsIHdhbnQgdG8gdHJ5IHRvIGZpeCB0aGUgYnVnLiA6 +c21pbGU6PG86cD48L286cD48L3A+DQo8cCBzdHlsZT0ibWFyZ2luLXRvcDowaW4iPlRoYW5rcyE8 +bzpwPjwvbzpwPjwvcD4NCjwvdGQ+DQo8L3RyPg0KPC90Ym9keT4NCjwvdGFibGU+DQo8ZGl2IGNs +YXNzPSJNc29Ob3JtYWwiIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciI+ +DQo8aHIgc2l6ZT0iMSIgd2lkdGg9IjEwMCUiIGFsaWduPSJjZW50ZXIiPg0KPC9kaXY+DQo8ZGl2 +Pg0KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiM2NjY2NjYiPlRvIHJlc3BvbmQsIHJlcGx5IHRvIHRo +aXMgZW1haWwgb3IgdmlzaXQgPGEgaHJlZj0iaHR0cDovL2NsLm9wZW5tcnMub3JnL3RyYWNrL2Ns +aWNrLzMwMDM5OTA1L3RhbGsub3Blbm1ycy5vcmc/cD1leUp6SWpvaWVYaDJWbnBGTUhSMU1uRm5a +RWR1TlhFd01GcFFPVlp0VFZvNElpd2lkaUk2TVN3aWNDSTZJbnRjSW5WY0lqb3pNREF6T1Rrd05T +eGNJblpjSWpveExGd2lkWEpzWENJNlhDSm9kSFJ3Y3pwY1hGd3ZYRnhjTDNSaGJHc3ViM0JsYm0x +eWN5NXZjbWRjWEZ3dmRGeGNYQzk1YjNWeUxYQnZjM1F0YVc0dFluVnlaMlZ5YUdGMWN5MXVaWGN0 +Y21WemRHRjFjbUZ1ZEMxc2RXNWphQzEyWlc1MVpWeGNYQzgyTnpSY1hGd3ZNVndpTEZ3aWFXUmNJ +anBjSW1RMVltTXdOMk5rTkRSalpEUTRNR000WVRnMk16bGpaV0kxTnpnelltWTJYQ0lzWENKMWNt +eGZhV1J6WENJNlcxd2lZamMyWW1JNU5HVXhZamN5T1dVNU5tVTBaV1ZqT0RSa1pqSTBPREF6TUdJ +Mk1tRm1NakJqTkZ3aVhYMGlmUSI+DQo8Yj48c3BhbiBzdHlsZT0iY29sb3I6IzAwNjY5OTt0ZXh0 +LWRlY29yYXRpb246bm9uZSI+aHR0cHM6Ly90YWxrLm9wZW5tcnMub3JnL3QveW91ci1wb3N0LWlu +LWJ1cmdlcmhhdXMtbmV3LXJlc3RhdXJhbnQtbHVuY2gtdmVudWUvNjc0LzE8L3NwYW4+PC9iPjwv +YT4gaW4geW91ciBicm93c2VyLjxvOnA+PC9vOnA+PC9zcGFuPjwvcD4NCjwvZGl2Pg0KPGRpdj4N +CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjojNjY2NjY2Ij5UbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNl +IGVtYWlscywgdmlzaXQgeW91ciA8YSBocmVmPSJodHRwOi8vY2wub3Blbm1ycy5vcmcvdHJhY2sv +Y2xpY2svMzAwMzk5MDUvdGFsay5vcGVubXJzLm9yZz9wPWV5SnpJam9pZFV4dVdsZzVWRmMwT1da +V1MwWTRiRmRMZG1seVdHc3hUVjl6SWl3aWRpSTZNU3dpY0NJNkludGNJblZjSWpvek1EQXpPVGt3 +TlN4Y0luWmNJam94TEZ3aWRYSnNYQ0k2WENKb2RIUndjenBjWEZ3dlhGeGNMM1JoYkdzdWIzQmxi +bTF5Y3k1dmNtZGNYRnd2YlhsY1hGd3ZjSEpsWm1WeVpXNWpaWE5jSWl4Y0ltbGtYQ0k2WENKa05X +SmpNRGRqWkRRMFkyUTBPREJqT0dFNE5qTTVZMlZpTlRjNE0ySm1ObHdpTEZ3aWRYSnNYMmxrYzF3 +aU9sdGNJbUk0TVdVd1pqQTFORFk1TkRNME56Z3lNMkZtTWpBMk5qRmpaamMzWkdOaU4yTmhZemRt +TWpKY0lsMTlJbjAiPg0KPGI+PHNwYW4gc3R5bGU9ImNvbG9yOiMwMDY2OTk7dGV4dC1kZWNvcmF0 +aW9uOm5vbmUiPnVzZXIgcHJlZmVyZW5jZXM8L3NwYW4+PC9iPjwvYT4uPG86cD48L286cD48L3Nw +YW4+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxpbWcgYm9yZGVy +PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBpZD0iX3gwMDAwX2kxMDI2IiBzcmM9Imh0dHA6Ly9j +bC5vcGVubXJzLm9yZy90cmFjay9vcGVuLnBocD91PTMwMDM5OTA1JmFtcDtpZD1kNWJjMDdjZDQ0 +Y2Q0ODBjOGE4NjM5Y2ViNTc4M2JmNiI+PG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwvYm9keT4N +CjwvaHRtbD4NCg== + +--_000_B0DFE1BEB3739743BC9B639D0E6BC8FF217A6341IUMSSGMBX104ads_-- diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked new file mode 100644 index 00000000000..da83260e09c --- /dev/null +++ b/spec/fixtures/emails/paragraphs.cooked @@ -0,0 +1,7 @@ +

Is there any reason the old candy can't be be kept in silos while the new candy +is imported into new silos?

+ +

The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo.

+ +

Thanks for listening.

\ No newline at end of file diff --git a/spec/fixtures/emails/paragraphs.eml b/spec/fixtures/emails/paragraphs.eml new file mode 100644 index 00000000000..2d5b5283f7e --- /dev/null +++ b/spec/fixtures/emails/paragraphs.eml @@ -0,0 +1,42 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening. + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> diff --git a/spec/fixtures/emails/previous.eml b/spec/fixtures/emails/previous.eml new file mode 100644 index 00000000000..24ac5a63deb --- /dev/null +++ b/spec/fixtures/emails/previous.eml @@ -0,0 +1,38 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +This will not include the previous discussion that is present in this email. + ------------------------------ +Previous discussion +skylerwhite July 24 + +This is a reply. + fring July 24 + +This is an older reply. + hank_schrader July 24 + +Of course another reply here. + walterwhite July 24 + + ------------------------------ + +To respond, reply to this email or visit +http://discourse.org/t/crystal-blue/5043/10in +your browser. + +To unsubscribe from these emails, visit your user +preferences +. diff --git a/spec/fixtures/emails/previous_replies.eml b/spec/fixtures/emails/previous_replies.eml new file mode 100644 index 00000000000..3fd74482c07 --- /dev/null +++ b/spec/fixtures/emails/previous_replies.eml @@ -0,0 +1,180 @@ +Delivered-To: reply@discourse.org +MIME-Version: 1.0 +In-Reply-To: +References: + +Date: Fri, 28 Nov 2014 12:55:32 -0800 +Subject: Re: [Discourse Meta] [Lounge] Testing default email replies +From: Walter White +To: Discourse Meta +Content-Type: multipart/alternative; boundary=001a1137c9285318bb0508f17bc5 + +--001a1137c9285318bb0508f17bc5 +Content-Type: text/plain; charset=UTF-8 + +### this is a reply from iOS Gmail app + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy +dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps +over the lazy dog. + +This is **bold** text in Markdown. + +This is a link to http://example.com + +On Friday, November 28, 2014, Arpit Jalan wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +--001a1137c9285318bb0508f17bc5 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +### this is a reply from iOS Gmail app

The quick brown f= +ox jumps over the lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= +k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= +e lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= +k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= +e lazy dog.=C2=A0

This is **bold** text in Markdown.

This is a link to http://example.com

On Friday, November 28, 2014, Arpit Jalan <
info@discourse.org> wrote:
+ + + + + + + + + + + +
+ + + techAPJ
+ November 28 +
+

Test reply.

+ +

First paragraph.

+ +

Second paragraph.

+
+ + +
+

To respond, reply to this email or visit https:/= +/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= +wser.

+
+
+

Previous Replies

+ + + + + + + + + + + +
+ + + codinghorror + November 28 +
+

We're testing the latest GitHub emai= +l processing library which we are integrating now.

+ +

https://github.com/github/email_reply_parser

+ +

Go ahead and reply to this topic and I&#= +39;ll reply from various email clients for testing.

+
+ + +
+ +
+

To respond, reply to this email or visit https://met= +a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= +.

+
+
+

To unsubscribe from these emails, visit your user preferences.

+
+
+
+ +--001a1137c9285318bb0508f17bc5-- diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml new file mode 100644 index 00000000000..9cc7b75c94f --- /dev/null +++ b/spec/fixtures/emails/too_many_mentions.eml @@ -0,0 +1,31 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + +@user1 +@user2 +@user3 +@user4 +@user5 +@user6 +@user7 +@user8 +@user9 +@user10 +@user11 \ No newline at end of file diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml new file mode 100644 index 00000000000..54fed0f98c5 --- /dev/null +++ b/spec/fixtures/emails/too_short.eml @@ -0,0 +1,21 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: TO +Message-ID: +Subject: SUBJECT +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + + ++1 \ No newline at end of file diff --git a/spec/fixtures/emails/valid_incoming.cooked b/spec/fixtures/emails/valid_incoming.cooked new file mode 100644 index 00000000000..2bf35825373 --- /dev/null +++ b/spec/fixtures/emails/valid_incoming.cooked @@ -0,0 +1,5 @@ +

Hey folks,

+ +

I was thinking. Wouldn't it be great if we could post topics via email? Yes it would!

+ +

Jakie

diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml new file mode 100644 index 00000000000..5e8db53319e --- /dev/null +++ b/spec/fixtures/emails/valid_incoming.eml @@ -0,0 +1,25 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: +Message-ID: +Subject: We should have a post-by-email-feature. +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Hey folks, + +I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! + +Jakie + diff --git a/spec/fixtures/emails/valid_reply.cooked b/spec/fixtures/emails/valid_reply.cooked new file mode 100644 index 00000000000..4bce79ad12a --- /dev/null +++ b/spec/fixtures/emails/valid_reply.cooked @@ -0,0 +1,4 @@ +

I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it.

+ +
  • Jake out
diff --git a/spec/fixtures/emails/valid_reply.eml b/spec/fixtures/emails/valid_reply.eml new file mode 100644 index 00000000000..1e696389954 --- /dev/null +++ b/spec/fixtures/emails/valid_reply.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/via_line.eml b/spec/fixtures/emails/via_line.eml new file mode 100644 index 00000000000..0b2947ff954 --- /dev/null +++ b/spec/fixtures/emails/via_line.eml @@ -0,0 +1,25 @@ + +Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org +Received: by 10.194.216.104 with SMTP id op8csp80593wjc; + Wed, 24 Jul 2013 07:59:14 -0700 (PDT) +Return-Path: +References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +From: Walter White +In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> +Mime-Version: 1.0 (1.0) +Date: Wed, 24 Jul 2013 15:59:10 +0100 +Message-ID: <4597127794206131679@unknownmsgid> +Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' +To: walter via Discourse +Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 + +Hello this email has content! + +codinghorror via Discourse wrote: +> [codinghorror] codinghorror +> +> August 7 +> +> It wouldn't be great at the moment for 100% email, since there's no +> way to be notified of new topics via email. (you can get notified of +> new replies to your posts and topics via email, and reply to them.) diff --git a/spec/fixtures/emails/windows_8_metro.eml b/spec/fixtures/emails/windows_8_metro.eml new file mode 100644 index 00000000000..67d204af562 --- /dev/null +++ b/spec/fixtures/emails/windows_8_metro.eml @@ -0,0 +1,173 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +From: +To: + =?utf-8?Q?Discourse_Meta?= + +Subject: + =?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?= +Importance: Normal +Date: Fri, 28 Nov 2014 21:29:10 +0000 +In-Reply-To: +References: + , +Content-Type: multipart/alternative; + boundary="_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_" + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ +Content-Transfer-Encoding: base64 +Content-Type: text/plain; charset="utf-8" + +IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K +DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj +ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg +anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0 +aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu +IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU +aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp +dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO +MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K +DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw +aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg +ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1 +bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz +IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg +dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp +bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf +cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5 +IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl +c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz +ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv +d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzLg== + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_ +Content-Transfer-Encoding: base64 +Content-Type: text/html; charset="utf-8" + +CjxodG1sPgo8aGVhZD4KPG1ldGEgbmFtZT0iZ2VuZXJhdG9yIiBjb250ZW50PSJXaW5kb3dzIE1h +aWwgMTcuNS45NjAwLjIwNjA1Ij4KPHN0eWxlIGRhdGEtZXh0ZXJuYWxzdHlsZT0idHJ1ZSI+PCEt +LQpwLk1zb0xpc3RQYXJhZ3JhcGgsIGxpLk1zb0xpc3RQYXJhZ3JhcGgsIGRpdi5Nc29MaXN0UGFy +YWdyYXBoIHsKbWFyZ2luLXRvcDowaW47Cm1hcmdpbi1yaWdodDowaW47Cm1hcmdpbi1ib3R0b206 +MGluOwptYXJnaW4tbGVmdDouNWluOwptYXJnaW4tYm90dG9tOi4wMDAxcHQ7Cn0KcC5Nc29Ob3Jt +YWwsIGxpLk1zb05vcm1hbCwgZGl2Lk1zb05vcm1hbCB7Cm1hcmdpbjowaW47Cm1hcmdpbi1ib3R0 +b206LjAwMDFwdDsKfQpwLk1zb0xpc3RQYXJhZ3JhcGhDeFNwRmlyc3QsIGxpLk1zb0xpc3RQYXJh +Z3JhcGhDeFNwRmlyc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcEZpcnN0LCAKcC5Nc29MaXN0 +UGFyYWdyYXBoQ3hTcE1pZGRsZSwgbGkuTXNvTGlzdFBhcmFncmFwaEN4U3BNaWRkbGUsIGRpdi5N +c29MaXN0UGFyYWdyYXBoQ3hTcE1pZGRsZSwgCnAuTXNvTGlzdFBhcmFncmFwaEN4U3BMYXN0LCBs +aS5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3QsIGRpdi5Nc29MaXN0UGFyYWdyYXBoQ3hTcExhc3Qg +ewptYXJnaW4tdG9wOjBpbjsKbWFyZ2luLXJpZ2h0OjBpbjsKbWFyZ2luLWJvdHRvbTowaW47Cm1h +cmdpbi1sZWZ0Oi41aW47Cm1hcmdpbi1ib3R0b206LjAwMDFwdDsKbGluZS1oZWlnaHQ6MTE1JTsK +fQotLT48L3N0eWxlPjwvaGVhZD4KPGJvZHkgZGlyPSJsdHIiPgo8ZGl2IGRhdGEtZXh0ZXJuYWxz +dHlsZT0iZmFsc2UiIGRpcj0ibHRyIiBzdHlsZT0iZm9udC1mYW1pbHk6ICdDYWxpYnJpJywgJ1Nl +Z29lIFVJJywgJ01laXJ5bycsICdNaWNyb3NvZnQgWWFIZWkgVUknLCAnTWljcm9zb2Z0IEpoZW5n +SGVpIFVJJywgJ01hbGd1biBHb3RoaWMnLCAnc2Fucy1zZXJpZic7Zm9udC1zaXplOjEycHQ7Ij48 +ZGl2IHN0eWxlPSJmb250LXNpemU6IDE0cHQ7Ij4jIyMgcmVwbHkgZnJvbSBkZWZhdWx0IG1haWwg +Y2xpZW50IGluIFdpbmRvd3MgOC4xIE1ldHJvPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1zaXplOiAx +NHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZTogMTRwdDsiPlRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRo +ZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93 +biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMg +b3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6 +eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuPC9kaXY+ +PGRpdiBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQt +c2l6ZTogMTRwdDsiPlRoaXMgaXMgYSAqKmJvbGQqKiB3b3JkIGluIE1hcmtkb3duPC9kaXY+PGRp +diBzdHlsZT0iZm9udC1zaXplOiAxNHB0OyI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6 +ZTogMTRwdDsiPlRoaXMgaXMgYSBsaW5rIDxhIGhyZWY9Imh0dHA6Ly9leGFtcGxlLmNvbSI+aHR0 +cDovL2V4YW1wbGUuY29tPC9hPjxicj4mbmJzcDs8L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6 +IDE0cHQ7Ij48YnI+PC9kaXY+PGRpdiBzdHlsZT0icGFkZGluZy10b3A6IDVweDsgYm9yZGVyLXRv +cC1jb2xvcjogcmdiKDIyOSwgMjI5LCAyMjkpOyBib3JkZXItdG9wLXdpZHRoOiAxcHg7IGJvcmRl +ci10b3Atc3R5bGU6IHNvbGlkOyI+PGRpdj48Zm9udCBmYWNlPSIgJ0NhbGlicmknLCAnU2Vnb2Ug +VUknLCAnTWVpcnlvJywgJ01pY3Jvc29mdCBZYUhlaSBVSScsICdNaWNyb3NvZnQgSmhlbmdIZWkg +VUknLCAnTWFsZ3VuIEdvdGhpYycsICdzYW5zLXNlcmlmJyIgc3R5bGU9J2xpbmUtaGVpZ2h0OiAx +NXB0OyBsZXR0ZXItc3BhY2luZzogMC4wMmVtOyBmb250LWZhbWlseTogIkNhbGlicmkiLCAiU2Vn +b2UgVUkiLCAiTWVpcnlvIiwgIk1pY3Jvc29mdCBZYUhlaSBVSSIsICJNaWNyb3NvZnQgSmhlbmdI +ZWkgVUkiLCAiTWFsZ3VuIEdvdGhpYyIsICJzYW5zLXNlcmlmIjsgZm9udC1zaXplOiAxMnB0Oyc+ +PGI+RnJvbTo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmluZm9AZGlzY291cnNlLm9yZyIgdGFy +Z2V0PSJfcGFyZW50Ij5BcnBpdCBKYWxhbjwvYT48YnI+PGI+U2VudDo8L2I+Jm5ic3A74oCORnJp +ZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCOMjAxNCDigI4xMuKAjjrigI4zNeKA +jiDigI5QTTxicj48Yj5Ubzo8L2I+Jm5ic3A7PGEgaHJlZj0ibWFpbHRvOmphdHdvb2RAY29kaW5n +aG9ycm9yLmNvbSIgdGFyZ2V0PSJfcGFyZW50Ij5qZWZmIGF0d29vZDwvYT48L2ZvbnQ+PC9kaXY+ +PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdiBkaXI9IiI+PGRpdj4KCjx0YWJsZSB0YWJpbmRleD0i +LTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIgYm9yZGVyPSIwIiBjZWxsc3BhY2luZz0i +MCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAgIDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3 +aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsiPgogICAgICAgIDxpbWcgd2lkdGg9IjQ1 +IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9 +Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNzbC5mYXN0bHkubmV0L3VzZXJfYXZhdGFy +L21ldGEuZGlzY291cnNlLm9yZy90ZWNoYXBqLzQ1LzMyODEucG5nIiBkYXRhLW1zLWltZ3NyYz0i +aHR0cHM6Ly9tZXRhLWRpc2NvdXJzZS5nbG9iYWwuc3NsLmZhc3RseS5uZXQvdXNlcl9hdmF0YXIv +bWV0YS5kaXNjb3Vyc2Uub3JnL3RlY2hhcGovNDUvMzI4MS5wbmciPgogICAgICA8L3RkPgogICAg +ICA8dGQ+CiAgICAgICAgPGEgc3R5bGU9J2NvbG9yOiByZ2IoNTksIDg5LCAxNTIpOyBmb250LWZh +bWlseTogImx1Y2lkYSBncmFuZGUiLHRhaG9tYSx2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7IGZv +bnQtc2l6ZTogMTNweDsgZm9udC13ZWlnaHQ6IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsn +IGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3VzZXJzL3RlY2hhcGoiIHRhcmdldD0i +X3BhcmVudCI+dGVjaEFQSjwvYT48YnI+CiAgICAgICAgPHNwYW4gc3R5bGU9J3RleHQtYWxpZ246 +IHJpZ2h0OyBjb2xvcjogcmdiKDE1MywgMTUzLCAxNTMpOyBwYWRkaW5nLXJpZ2h0OiA1cHg7IGZv +bnQtZmFtaWx5OiAibHVjaWRhIGdyYW5kZSIsdGFob21hLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJp +ZjsgZm9udC1zaXplOiAxMXB4Oyc+Tm92ZW1iZXIgMjg8L3NwYW4+CiAgICAgIDwvdGQ+CiAgICA8 +L3RyPgogICAgPHRyPgogICAgICA8dGQgc3R5bGU9InBhZGRpbmctdG9wOiA1cHg7IiBjb2xzcGFu +PSIyIj4KPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1h +cmdpbi10b3A6IDBweDsiPlRlc3QgcmVwbHkuPC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJs +YWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkZpcnN0IHBhcmFncmFw +aC48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsg +bWFyZ2luLXRvcDogMHB4OyI+U2Vjb25kIHBhcmFncmFwaC48L3A+CjwvdGQ+CiAgICA8L3RyPgog +IDwvdGJvZHk+CjwvdGFibGU+CgoKICA8ZGl2IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx +MDIpOyI+CiAgICA8cD5UbyByZXNwb25kLCByZXBseSB0byB0aGlzIGVtYWlsIG9yIHZpc2l0IDxh +IHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4 +dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90 +ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIiB0YXJnZXQ9Il9wYXJlbnQiPmh0 +dHBzOi8vbWV0YS5kaXNjb3Vyc2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMv +MjI2MzgvMzwvYT4gaW4geW91ciBicm93c2VyLjwvcD4KICA8L2Rpdj4KICA8aHIgc3R5bGU9ImJv +cmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNrZ3Jv +dW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KICA8aDQ+UHJldmlvdXMgUmVwbGllczwv +aDQ+CgogIDx0YWJsZSB0YWJpbmRleD0iLTEiIHN0eWxlPSJtYXJnaW4tYm90dG9tOiAyNXB4OyIg +Ym9yZGVyPSIwIiBjZWxsc3BhY2luZz0iMCIgY2VsbHBhZGRpbmc9IjAiPgogIDx0Ym9keT4KICAg +IDx0cj4KICAgICAgPHRkIHN0eWxlPSJ3aWR0aDogNTVweDsgdmVydGljYWwtYWxpZ246IHRvcDsi +PgogICAgICAgIDxpbWcgd2lkdGg9IjQ1IiBoZWlnaHQ9IjQ1IiB0YWJpbmRleD0iLTEiIHN0eWxl +PSJtYXgtd2lkdGg6IDEwMCU7IiBzcmM9Imh0dHBzOi8vbWV0YS1kaXNjb3Vyc2UuZ2xvYmFsLnNz +bC5mYXN0bHkubmV0L3VzZXJfYXZhdGFyL21ldGEuZGlzY291cnNlLm9yZy9jb2Rpbmdob3Jyb3Iv +NDUvNTI5Ny5wbmciIGRhdGEtbXMtaW1nc3JjPSJodHRwczovL21ldGEtZGlzY291cnNlLmdsb2Jh +bC5zc2wuZmFzdGx5Lm5ldC91c2VyX2F2YXRhci9tZXRhLmRpc2NvdXJzZS5vcmcvY29kaW5naG9y +cm9yLzQ1LzUyOTcucG5nIj4KICAgICAgPC90ZD4KICAgICAgPHRkPgogICAgICAgIDxhIHN0eWxl +PSdjb2xvcjogcmdiKDU5LCA4OSwgMTUyKTsgZm9udC1mYW1pbHk6ICJsdWNpZGEgZ3JhbmRlIix0 +YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6IDEzcHg7IGZvbnQtd2Vp +Z2h0OiBib2xkOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7JyBocmVmPSJodHRwczovL21ldGEuZGlz +Y291cnNlLm9yZy91c2Vycy9jb2Rpbmdob3Jyb3IiIHRhcmdldD0iX3BhcmVudCI+Y29kaW5naG9y +cm9yPC9hPjxicj4KICAgICAgICA8c3BhbiBzdHlsZT0ndGV4dC1hbGlnbjogcmlnaHQ7IGNvbG9y +OiByZ2IoMTUzLCAxNTMsIDE1Myk7IHBhZGRpbmctcmlnaHQ6IDVweDsgZm9udC1mYW1pbHk6ICJs +dWNpZGEgZ3JhbmRlIix0YWhvbWEsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmOyBmb250LXNpemU6 +IDExcHg7Jz5Ob3ZlbWJlciAyODwvc3Bhbj4KICAgICAgPC90ZD4KICAgIDwvdHI+CiAgICA8dHI+ +CiAgICAgIDx0ZCBzdHlsZT0icGFkZGluZy10b3A6IDVweDsiIGNvbHNwYW49IjIiPgo8cCBzdHls +ZT0iYm9yZGVyOiAwcHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4 +OyI+V2UncmUgdGVzdGluZyB0aGUgbGF0ZXN0IEdpdEh1YiBlbWFpbCBwcm9jZXNzaW5nIGxpYnJh +cnkgd2hpY2ggd2UgYXJlIGludGVncmF0aW5nIG5vdy48L3A+Cgo8cCBzdHlsZT0iYm9yZGVyOiAw +cHggYmxhY2s7IGJvcmRlci1pbWFnZTogbm9uZTsgbWFyZ2luLXRvcDogMHB4OyI+PGEgc3R5bGU9 +ImNvbG9yOiByZ2IoMCwgMTAyLCAxNTMpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0 +aW9uOiBub25lOyIgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9yZXBseV9w +YXJzZXIiIHRhcmdldD0iX3BhcmVudCI+aHR0cHM6Ly9naXRodWIuY29tL2dpdGh1Yi9lbWFpbF9y +ZXBseV9wYXJzZXI8L2E+PC9wPgoKPHAgc3R5bGU9ImJvcmRlcjogMHB4IGJsYWNrOyBib3JkZXIt +aW1hZ2U6IG5vbmU7IG1hcmdpbi10b3A6IDBweDsiPkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlz +IHRvcGljIGFuZCBJJ2xsIHJlcGx5IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0 +aW5nLjwvcD4KPC90ZD4KICAgIDwvdHI+CiAgPC90Ym9keT4KPC90YWJsZT4KCgo8aHIgc3R5bGU9 +ImJvcmRlcjogMXB4IGJsYWNrOyBib3JkZXItaW1hZ2U6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNr +Z3JvdW5kLWNvbG9yOiByZ2IoMjIxLCAyMjEsIDIyMSk7Ij4KCjxkaXYgc3R5bGU9ImNvbG9yOiBy +Z2IoMTAyLCAxMDIsIDEwMik7Ij4KPHA+VG8gcmVzcG9uZCwgcmVwbHkgdG8gdGhpcyBlbWFpbCBv +ciB2aXNpdCA8YSBzdHlsZT0iY29sb3I6IHJnYigxMDIsIDEwMiwgMTAyKTsgZm9udC13ZWlnaHQ6 +IGJvbGQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsiIGhyZWY9Imh0dHBzOi8vbWV0YS5kaXNjb3Vy +c2Uub3JnL3QvdGVzdGluZy1kZWZhdWx0LWVtYWlsLXJlcGxpZXMvMjI2MzgvMyIgdGFyZ2V0PSJf +cGFyZW50Ij5odHRwczovL21ldGEuZGlzY291cnNlLm9yZy90L3Rlc3RpbmctZGVmYXVsdC1lbWFp +bC1yZXBsaWVzLzIyNjM4LzM8L2E+IGluIHlvdXIgYnJvd3Nlci48L3A+CjwvZGl2Pgo8ZGl2IHN0 +eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyI+CjxwPlRvIHVuc3Vic2NyaWJlIGZyb20g +dGhlc2UgZW1haWxzLCB2aXNpdCB5b3VyIDxhIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAx +MDIpOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyIgaHJlZj0iaHR0 +cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvbXkvcHJlZmVyZW5jZXMiIHRhcmdldD0iX3BhcmVudCI+ +dXNlciBwcmVmZXJlbmNlczwvYT4uPC9wPgo8L2Rpdj4KPC9kaXY+CjwvZGl2PjxkaXYgc3R5bGU9 +ImZvbnQtc2l6ZTogMTRwdDsiPjxicj48L2Rpdj48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cg== + +--_866E2678-BB4F-4DD8-BE18-81B04AD8D1BC_-- diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_reply_key.eml new file mode 100644 index 00000000000..491e078fb5b --- /dev/null +++ b/spec/fixtures/emails/wrong_reply_key.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/lib/gitlab/email_receiver_spec.rb b/spec/lib/gitlab/email_receiver_spec.rb new file mode 100644 index 00000000000..d6302eac13a --- /dev/null +++ b/spec/lib/gitlab/email_receiver_spec.rb @@ -0,0 +1,481 @@ +require "spec_helper" + +# Inspired in great part by Discourse's Email::Receiver +describe Gitlab::EmailReceiver do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + before 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) + Gitlab::EmailReceiver.new(nil).parse_body(Mail::Message.new(mail_string)) + end + + it "raises EmptyEmailError if the message is blank" do + expect { test_parse_body("") }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "raises EmptyEmailError if the message is not an email" do + expect { test_parse_body("asdf" * 30) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "raises EmptyEmailError if there is no reply content" do + expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) + end + + it "can parse the html section" do + expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + + "unrecommended settings on the Bitnami installs that I've checked out.") + end + + it "supports a Dutch reply" do + expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") + end + + it "removes an 'on date wrote' quoting line" do + expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") + end + + it "handles multiple paragraphs" do + expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). + to eq( +"Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening." + ) + end + + it "handles multiple paragraphs when parsing html" do + expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). + to eq( +"Awesome! + +Pleasure to have you here! + +:boom:" + ) + end + + it "handles newlines" do + expect(test_parse_body(fixture_file("emails/newlines.eml"))). + to eq( +"This is my reply. +It is my best reply. +It will also be my *only* reply." + ) + end + + it "handles inline reply" do + expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). + to eq( +"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog." + ) + end + + it "strips iPhone signature" do + expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + end + + it "properly renders email reply from gmail web client" do + expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). + to eq( +"### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from iOS default mail client" do + expect(test_parse_body(fixture_file("emails/ios_default.eml"))). + to eq( +"### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** markdown text. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from Android 5 gmail client" do + expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). + to eq( +"### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com" + ) + end + + it "properly renders email reply from Windows 8.1 Metro default mail client" do + expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). + to eq( +"### reply from default mail client in Windows 8.1 Metro + + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + +This is a **bold** word in Markdown + + +This is a link http://example.com" + ) + end + + it "properly renders email reply from MS Outlook client" do + expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") + 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 /
/ +# 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 -- cgit v1.2.1 From 3ff9d5c64cef8bf8daed5e253e388545987fb945 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 10:58:04 -0700 Subject: Use canonical version of mail_room. --- Gemfile | 2 +- Gemfile.lock | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 9879141f5cb..3aa3c72e088 100644 --- a/Gemfile +++ b/Gemfile @@ -273,6 +273,6 @@ gem "newrelic_rpm" gem 'octokit', '3.7.0' -gem "mail_room", github: "DouweM/mail_room", branch: "sidekiq" +gem "mail_room", "~> 0.4.0" gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index c4c327aa758..5278fe243a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: git://github.com/DouweM/mail_room.git - revision: c17c74cdef55dcd962333545fdb4d6e496cd9050 - branch: sidekiq - specs: - mail_room (0.3.1) - GEM remote: https://rubygems.org/ specs: @@ -379,6 +372,7 @@ GEM systemu (~> 2.6.2) mail (2.6.3) mime-types (>= 1.16, < 3) + mail_room (0.4.0) method_source (0.8.2) mime-types (1.25.1) mimemagic (0.3.0) @@ -814,7 +808,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 0.15.1) letter_opener - mail_room! + mail_room (~> 0.4.0) minitest (~> 5.3.0) mousetrap-rails mysql2 -- cgit v1.2.1 From e9972efc2f3d730e989907585dd1438c517a0bba Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:05:06 -0700 Subject: Extract ReplyParser and AttachmentUploader from Receiver. --- app/workers/email_receiver_worker.rb | 16 +- lib/gitlab/email/attachment_uploader.rb | 35 +++ lib/gitlab/email/html_cleaner.rb | 135 ++++++++ lib/gitlab/email/receiver.rb | 101 ++++++ lib/gitlab/email/reply_parser.rb | 91 ++++++ lib/gitlab/email_html_cleaner.rb | 133 -------- lib/gitlab/email_receiver.rb | 192 ------------ spec/lib/gitlab/email/reply_parser_spec.rb | 191 ++++++++++++ spec/lib/gitlab/email_receiver_spec.rb | 481 ----------------------------- 9 files changed, 561 insertions(+), 814 deletions(-) create mode 100644 lib/gitlab/email/attachment_uploader.rb create mode 100644 lib/gitlab/email/html_cleaner.rb create mode 100644 lib/gitlab/email/receiver.rb create mode 100644 lib/gitlab/email/reply_parser.rb delete mode 100644 lib/gitlab/email_html_cleaner.rb delete mode 100644 lib/gitlab/email_receiver.rb create mode 100644 spec/lib/gitlab/email/reply_parser_spec.rb delete mode 100644 spec/lib/gitlab/email_receiver_spec.rb diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 2cfd64cefad..4f3556bfc03 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -7,7 +7,7 @@ class EmailReceiverWorker return unless Gitlab::ReplyByEmail.enabled? begin - Gitlab::EmailReceiver.new(raw).execute + Gitlab::Email::Receiver.new(raw).execute rescue => e handle_failure(raw, e) end @@ -22,20 +22,20 @@ class EmailReceiverWorker reason = nil 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." - when Gitlab::EmailReceiver::EmptyEmailError + when Gitlab::Email::Receiver::EmptyEmailError 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." - 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." - 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." - 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." - 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." - when Gitlab::EmailReceiver::InvalidNote + when Gitlab::Email::Receiver::InvalidNote can_retry = true reason = e.message else diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb new file mode 100644 index 00000000000..0c0f50f2751 --- /dev/null +++ b/lib/gitlab/email/attachment_uploader.rb @@ -0,0 +1,35 @@ +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 diff --git a/lib/gitlab/email/html_cleaner.rb b/lib/gitlab/email/html_cleaner.rb new file mode 100644 index 00000000000..e1ae9eee56c --- /dev/null +++ b/lib/gitlab/email/html_cleaner.rb @@ -0,0 +1,135 @@ +# 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
tags with a markdown \n + doc.xpath('//br').each do |br| + br.replace(new_linebreak_node doc, 2) + end + # Surround

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 diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb new file mode 100644 index 00000000000..c46fce6afe2 --- /dev/null +++ b/lib/gitlab/email/receiver.rb @@ -0,0 +1,101 @@ +# 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 diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb new file mode 100644 index 00000000000..6ceb755968c --- /dev/null +++ b/lib/gitlab/email/reply_parser.rb @@ -0,0 +1,91 @@ +# 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 diff --git a/lib/gitlab/email_html_cleaner.rb b/lib/gitlab/email_html_cleaner.rb deleted file mode 100644 index 6d7a17fe87c..00000000000 --- a/lib/gitlab/email_html_cleaner.rb +++ /dev/null @@ -1,133 +0,0 @@ -# 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
tags with a markdown \n - doc.xpath('//br').each do |br| - br.replace(new_linebreak_node doc, 2) - end - # Surround

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 diff --git a/lib/gitlab/email_receiver.rb b/lib/gitlab/email_receiver.rb deleted file mode 100644 index 3c1f346c0cf..00000000000 --- a/lib/gitlab/email_receiver.rb +++ /dev/null @@ -1,192 +0,0 @@ -# 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 diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb new file mode 100644 index 00000000000..999515beb1c --- /dev/null +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -0,0 +1,191 @@ +require "spec_helper" + +# Inspired in great part by Discourse's Email::Receiver +describe Gitlab::Email::ReplyParser do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + describe 'self.parse_body' do + def test_parse_body(mail_string) + described_class.new(Mail::Message.new(mail_string)).execute + end + + it "returns an empty string if the message is blank" do + expect(test_parse_body("")).to eq("") + end + + it "returns an empty string if the message is not an email" do + expect(test_parse_body("asdf" * 30)).to eq("") + end + + it "returns an empty string if there is no reply content" do + expect(test_parse_body(fixture_file("emails/no_content_reply.eml"))).to eq("") + end + + it "can parse the html section" do + expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + + "unrecommended settings on the Bitnami installs that I've checked out.") + end + + it "supports a Dutch reply" do + expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") + end + + it "removes an 'on date wrote' quoting line" do + expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") + end + + it "handles multiple paragraphs" do + expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). + to eq( +"Is there any reason the *old* candy can't be be kept in silos while the new candy +is imported into *new* silos? + +The thing about candy is it stays delicious for a long time -- we can just keep +it there without worrying about it too much, imo. + +Thanks for listening." + ) + end + + it "handles multiple paragraphs when parsing html" do + expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). + to eq( +"Awesome! + +Pleasure to have you here! + +:boom:" + ) + end + + it "handles newlines" do + expect(test_parse_body(fixture_file("emails/newlines.eml"))). + to eq( +"This is my reply. +It is my best reply. +It will also be my *only* reply." + ) + end + + it "handles inline reply" do + expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). + to eq( +"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + +> techAPJ +> November 28 +> +> Test reply. +> +> First paragraph. +> +> Second paragraph. +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> ------------------------------ +> Previous Replies codinghorror +> +> November 28 +> +> We're testing the latest GitHub email processing library which we are +> integrating now. +> +> https://github.com/github/email_reply_parser +> +> Go ahead and reply to this topic and I'll reply from various email clients +> for testing. +> ------------------------------ +> +> To respond, reply to this email or visit +> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in +> your browser. +> +> To unsubscribe from these emails, visit your user preferences +> . +> + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog." + ) + end + + it "strips iPhone signature" do + expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + end + + it "properly renders email reply from gmail web client" do + expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). + to eq( +"### This is a reply from standard GMail in Google Chrome. + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** text in Markdown. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from iOS default mail client" do + expect(test_parse_body(fixture_file("emails/ios_default.eml"))). + to eq( +"### this is a reply from iOS default mail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + +Here's some **bold** markdown text. + +Here's a link http://example.com" + ) + end + + it "properly renders email reply from Android 5 gmail client" do + expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). + to eq( +"### this is a reply from Android 5 gmail + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over +the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown +fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. +The quick brown fox jumps over the lazy dog. + +This is **bold** in Markdown. + +This is a link to http://example.com" + ) + end + + it "properly renders email reply from Windows 8.1 Metro default mail client" do + expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). + to eq( +"### reply from default mail client in Windows 8.1 Metro + + +The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + +This is a **bold** word in Markdown + + +This is a link http://example.com" + ) + end + + it "properly renders email reply from MS Outlook client" do + expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") + end + end +end diff --git a/spec/lib/gitlab/email_receiver_spec.rb b/spec/lib/gitlab/email_receiver_spec.rb deleted file mode 100644 index d6302eac13a..00000000000 --- a/spec/lib/gitlab/email_receiver_spec.rb +++ /dev/null @@ -1,481 +0,0 @@ -require "spec_helper" - -# Inspired in great part by Discourse's Email::Receiver -describe Gitlab::EmailReceiver do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - - before 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) - Gitlab::EmailReceiver.new(nil).parse_body(Mail::Message.new(mail_string)) - end - - it "raises EmptyEmailError if the message is blank" do - expect { test_parse_body("") }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "raises EmptyEmailError if the message is not an email" do - expect { test_parse_body("asdf" * 30) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "raises EmptyEmailError if there is no reply content" do - expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Gitlab::EmailReceiver::EmptyEmailError) - end - - it "can parse the html section" do - expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + - "unrecommended settings on the Bitnami installs that I've checked out.") - end - - it "supports a Dutch reply" do - expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.") - end - - it "removes an 'on date wrote' quoting line" do - expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!") - end - - it "handles multiple paragraphs" do - expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). - to eq( -"Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? - -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. - -Thanks for listening." - ) - end - - it "handles multiple paragraphs when parsing html" do - expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). - to eq( -"Awesome! - -Pleasure to have you here! - -:boom:" - ) - end - - it "handles newlines" do - expect(test_parse_body(fixture_file("emails/newlines.eml"))). - to eq( -"This is my reply. -It is my best reply. -It will also be my *only* reply." - ) - end - - it "handles inline reply" do - expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). - to eq( -"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog." - ) - end - - it "strips iPhone signature" do - expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ - end - - it "properly renders email reply from gmail web client" do - expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). - to eq( -"### This is a reply from standard GMail in Google Chrome. - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. - -Here's some **bold** text in Markdown. - -Here's a link http://example.com" - ) - end - - it "properly renders email reply from iOS default mail client" do - expect(test_parse_body(fixture_file("emails/ios_default.eml"))). - to eq( -"### this is a reply from iOS default mail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. - -Here's some **bold** markdown text. - -Here's a link http://example.com" - ) - end - - it "properly renders email reply from Android 5 gmail client" do - expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). - to eq( -"### this is a reply from Android 5 gmail - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. - -This is **bold** in Markdown. - -This is a link to http://example.com" - ) - end - - it "properly renders email reply from Windows 8.1 Metro default mail client" do - expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). - to eq( -"### reply from default mail client in Windows 8.1 Metro - - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. - - -This is a **bold** word in Markdown - - -This is a link http://example.com" - ) - end - - it "properly renders email reply from MS Outlook client" do - expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010") - 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 /
/ -# 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 -- cgit v1.2.1 From 0b401f2e94c5062d26887cdeda73341c1aae82a5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:17:14 -0700 Subject: Fix a couple of whoopsy daisies. --- lib/gitlab/email/attachment_uploader.rb | 2 +- lib/gitlab/email/receiver.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb index 0c0f50f2751..32cece8316b 100644 --- a/lib/gitlab/email/attachment_uploader.rb +++ b/lib/gitlab/email/attachment_uploader.rb @@ -1,6 +1,6 @@ module Gitlab module Email - module AttachmentUploader + class AttachmentUploader attr_accessor :message def initialize(message) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index c46fce6afe2..7160af29089 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -76,7 +76,7 @@ module Gitlab end def add_attachments(reply) - attachments = AttachmentUploader.new(message).execute(project) + attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project) attachments.each do |link| text = "[#{link[:alt]}](#{link[:url]})" @@ -84,6 +84,8 @@ module Gitlab reply << "\n\n#{text}" end + + reply end def create_note(reply) -- cgit v1.2.1 From 2f78b5e8afbf0024211bbd5bfe3a6c6f53b2421e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:33:18 -0700 Subject: Make error class names more consistent. --- app/workers/email_receiver_worker.rb | 4 ++-- lib/gitlab/email/receiver.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 4f3556bfc03..8f6c27ce4af 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -22,7 +22,7 @@ class EmailReceiverWorker reason = nil case e - when Gitlab::Email::Receiver::SentNotificationNotFound + when Gitlab::Email::Receiver::SentNotificationNotFoundError reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." when Gitlab::Email::Receiver::EmptyEmailError can_retry = true @@ -35,7 +35,7 @@ class EmailReceiverWorker 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::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." - when Gitlab::Email::Receiver::InvalidNote + when Gitlab::Email::Receiver::InvalidNoteError can_retry = true reason = e.message else diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 7160af29089..466e48b22e1 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -4,13 +4,13 @@ module Gitlab class Receiver class ProcessingError < StandardError; end class EmailUnparsableError < ProcessingError; end + class SentNotificationNotFoundError < ProcessingError; end class EmptyEmailError < ProcessingError; end + class AutoGeneratedEmailError < 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 + class InvalidNoteError < ProcessingError; end def initialize(raw) @raw = raw @@ -23,7 +23,7 @@ module Gitlab end def execute - raise SentNotificationNotFound unless sent_notification + raise SentNotificationNotFoundError unless sent_notification raise EmptyEmailError if @raw.blank? @@ -53,7 +53,7 @@ module Gitlab message << "\n\n- #{error}" end - raise InvalidNote, message + raise InvalidNoteError, message end end -- cgit v1.2.1 From 991c9f6fdab46909fbbe652b1c88a6521a969c02 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:33:35 -0700 Subject: Test Email::AttachmentUploader. --- spec/lib/gitlab/email/attachment_uploader_spec.rb | 26 ++++++++++++ spec/lib/gitlab/email/reply_parser_spec.rb | 2 +- spec/services/projects/upload_service_spec.rb | 48 +++++++++++------------ 3 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 spec/lib/gitlab/email/attachment_uploader_spec.rb diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb new file mode 100644 index 00000000000..b54ee8aa72a --- /dev/null +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" + +describe Gitlab::Email::AttachmentUploader do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + describe "#execute" do + let(:project) { build(:project) } + let(:message_raw) { fixture_file("emails/attachment.eml") } + let(:message) { Mail::Message.new(message_raw) } + + it "creates a post with an attachment" do + links = described_class.new(message).execute(project) + link = links.first + + expect(link).not_to be_nil + expect(link[:is_image]).to be_truthy + expect(link[:alt]).to eq("bricks") + expect(link[:url]).to include("/#{project.path_with_namespace}") + expect(link[:url]).to include("bricks.png") + end + end +end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 999515beb1c..32120d56cbc 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Email::ReplyParser do File.read(file_path) end - describe 'self.parse_body' do + describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 7aa26857649..fa4ff6b01ad 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -13,13 +13,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, gif) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('banana_sample') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('banana_sample.gif') } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('banana_sample.gif') } end context 'for valid png file' do @@ -29,13 +29,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, png) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } it { expect(@link_to_file).to have_value('dk') } - it { expect(@link_to_file).to have_key('is_image') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('dk.png') } + it { expect(@link_to_file).to have_key(:is_image) } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('dk.png') } end context 'for valid jpg file' do @@ -44,13 +44,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, jpg) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('rails_sample') } - it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('rails_sample.jpg') } + it { expect(@link_to_file[:is_image]).to equal(true) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('rails_sample.jpg') } end context 'for txt file' do @@ -59,13 +59,13 @@ describe Projects::UploadService do @link_to_file = upload_file(@project.repository, txt) end - it { expect(@link_to_file).to have_key('alt') } - it { expect(@link_to_file).to have_key('url') } - it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_key(:alt) } + it { expect(@link_to_file).to have_key(:url) } + it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('doc_sample.txt') } - it { expect(@link_to_file['is_image']).to equal(false) } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } - it { expect(@link_to_file['url']).to match('doc_sample.txt') } + it { expect(@link_to_file[:is_image]).to equal(false) } + it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file[:url]).to match('doc_sample.txt') } end context 'for too large a file' do -- cgit v1.2.1 From e44936f3ed4799714fc36a342c1dbab1d50f0ffc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 11:47:09 -0700 Subject: Test EmailReceiverWorker. --- spec/fixtures/emails/valid_incoming.eml | 10 +++--- spec/workers/email_receiver_worker_spec.rb | 51 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 spec/workers/email_receiver_worker_spec.rb diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml index 5e8db53319e..409711ab417 100644 --- a/spec/fixtures/emails/valid_incoming.eml +++ b/spec/fixtures/emails/valid_incoming.eml @@ -1,11 +1,11 @@ -Return-Path: +Return-Path: Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: +From: Jake the Dog +To: Message-ID: Subject: We should have a post-by-email-feature. Mime-Version: 1.0 diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb new file mode 100644 index 00000000000..109a7b3eb1d --- /dev/null +++ b/spec/workers/email_receiver_worker_spec.rb @@ -0,0 +1,51 @@ +require "spec_helper" + +describe EmailReceiverWorker do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + let(:raw_message) { fixture_file('emails/valid_incoming.eml') } + + context "when reply by email is enabled" do + before do + allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(true) + end + + it "calls the email receiver" do + expect(Gitlab::Email::Receiver).to receive(:new).with(raw_message).and_call_original + expect_any_instance_of(Gitlab::Email::Receiver).to receive(:execute) + + described_class.new.perform(raw_message) + end + + context "when an error occurs" do + before do + allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError) + end + + it "sends out a rejection email" do + described_class.new.perform(raw_message) + + email = ActionMailer::Base.deliveries.last + expect(email).not_to be_nil + expect(email.to).to eq(["from@example.com"]) + expect(email.subject).to include("Rejected") + end + end + end + + context "when reply by email is disabled" do + before do + allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(false) + end + + it "doesn't call the email receiver" do + expect(Gitlab::Email::Receiver).not_to receive(:new) + + described_class.new.perform(raw_message) + end + end +end -- cgit v1.2.1 From 8ec5fb138dde9937814ac138352177399d3e776d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:17:59 -0700 Subject: Test Gitlab::Email::Receiver. --- lib/gitlab/email/receiver.rb | 18 ++--- spec/lib/gitlab/email/receiver_spec.rb | 135 +++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 spec/lib/gitlab/email/receiver_spec.rb diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 466e48b22e1..17b8339edcd 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -16,17 +16,11 @@ module Gitlab @raw = raw end - def message - @message ||= Mail::Message.new(@raw) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e - raise EmailUnparsableError, e - end - def execute - raise SentNotificationNotFoundError unless sent_notification - raise EmptyEmailError if @raw.blank? + raise SentNotificationNotFoundError unless sent_notification + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ author = sent_notification.recipient @@ -35,7 +29,7 @@ module Gitlab project = sent_notification.project - raise UserNotAuthorizedError unless author.can?(:create_note, project) + raise UserNotAuthorizedError unless project && author.can?(:create_note, project) raise NoteableNotFoundError unless sent_notification.noteable @@ -59,6 +53,12 @@ module Gitlab private + def message + @message ||= Mail::Message.new(@raw) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + raise EmailUnparsableError, e + end + def reply_key reply_key = nil message.to.each do |address| diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb new file mode 100644 index 00000000000..65f3ceb5def --- /dev/null +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -0,0 +1,135 @@ +require "spec_helper" + +describe Gitlab::Email::Receiver do + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) + File.read(file_path) + end + + before 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 + + let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } + let(:email_raw) { fixture_file('emails/valid_reply.eml') } + + let(:project) { create(:project, :public) } + let(:noteable) { create(:issue, project: project) } + let(:user) { create(:user) } + let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) } + + let(:receiver) { described_class.new(email_raw) } + + context "when the recipient address doesn't include a reply key" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + end + end + + context "when no sent notificiation for the reply key could be found" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "nope") } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + end + end + + context "when the email is blank" do + let(:email_raw) { "" } + + it "raises an EmptyEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + end + end + + context "when the email was auto generated" do + let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } + let!(:email_raw) { fixture_file("emails/auto_reply.eml") } + + it "raises an AutoGeneratedEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError) + end + end + + context "when the user could not be found" do + before do + user.destroy + end + + it "raises a UserNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError) + end + end + + context "when the user is not authorized to create a note" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + it "raises a UserNotAuthorizedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + end + end + + context "when the noteable could not be found" do + before do + noteable.destroy + end + + it "raises a NoteableNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError) + end + end + + context "when the reply is blank" do + let!(:email_raw) { fixture_file("emails/no_content_reply.eml") } + + it "raises an EmptyEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + end + end + + context "when the note could not be saved" do + before do + allow_any_instance_of(Note).to receive(:persisted?).and_return(false) + end + + it "raises an InvalidNoteError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError) + end + end + + context "when everything is fine" do + before do + allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( + [ + { + url: "uploads/image.png", + is_image: true, + alt: "image" + } + ] + ) + end + + it "creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + note = noteable.notes.last + + expect(note.author).to eq(sent_notification.recipient) + expect(note.note).to include("I could not disagree more.") + end + + it "adds all attachments" do + receiver.execute + + note = noteable.notes.last + + expect(note.note).to include("![image](uploads/image.png)") + end + end +end -- cgit v1.2.1 From 2b5a2b8f398aa6af0a24c40cd6586eae76c85a3f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:29:03 -0700 Subject: Removed unused fixtures. --- spec/fixtures/emails/boundary.eml | 61 --------- spec/fixtures/emails/multiple_destinations.eml | 40 ------ spec/fixtures/emails/paragraphs.cooked | 7 - spec/fixtures/emails/previous.eml | 38 ------ spec/fixtures/emails/previous_replies.eml | 180 ------------------------- spec/fixtures/emails/too_many_mentions.eml | 31 ----- spec/fixtures/emails/valid_incoming.cooked | 5 - spec/fixtures/emails/valid_incoming.eml | 25 ---- spec/fixtures/emails/valid_reply.cooked | 4 - spec/fixtures/emails/via_line.eml | 25 ---- spec/lib/gitlab/email/receiver_spec.rb | 2 +- 11 files changed, 1 insertion(+), 417 deletions(-) delete mode 100644 spec/fixtures/emails/boundary.eml delete mode 100644 spec/fixtures/emails/multiple_destinations.eml delete mode 100644 spec/fixtures/emails/paragraphs.cooked delete mode 100644 spec/fixtures/emails/previous.eml delete mode 100644 spec/fixtures/emails/previous_replies.eml delete mode 100644 spec/fixtures/emails/too_many_mentions.eml delete mode 100644 spec/fixtures/emails/valid_incoming.cooked delete mode 100644 spec/fixtures/emails/valid_incoming.eml delete mode 100644 spec/fixtures/emails/valid_reply.cooked delete mode 100644 spec/fixtures/emails/via_line.eml diff --git a/spec/fixtures/emails/boundary.eml b/spec/fixtures/emails/boundary.eml deleted file mode 100644 index 1250fe498b0..00000000000 --- a/spec/fixtures/emails/boundary.eml +++ /dev/null @@ -1,61 +0,0 @@ - -MIME-Version: 1.0 -Received: by 10.64.14.41 with HTTP; Wed, 19 Jun 2013 06:29:41 -0700 (PDT) -In-Reply-To: <51c19490e928a_13442dd8ae892548@tree.mail> -References: <51c19490e928a_13442dd8ae892548@tree.mail> -Date: Wed, 19 Jun 2013 09:29:41 -0400 -Delivered-To: finn@adventuretime.ooo -Message-ID: -Subject: Re: [Adventure Time] jake mentioned you in 'peppermint butler is - missing' -From: Finn the Human -To: jake via Adventure Time -Content-Type: multipart/alternative; boundary=001a11c206a073876a04df81d2a9 - ---001a11c206a073876a04df81d2a9 -Content-Type: text/plain; charset=ISO-8859-1 - -I'll look into it, thanks! - - -On Wednesday, June 19, 2013, jake via Discourse wrote: - -> jake mentioned you in 'peppermint butler is missing' on Adventure -> Time: -> ------------------------------ -> -> yeah, just noticed this cc @jake -> ------------------------------ -> -> Please visit this link to respond: -> http://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a11c206a073876a04df81d2a9 -Content-Type: text/html; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable - -I'll look into it, thanks!

On Wednesday, June 19, 2= -013, jake via Adventure Time wrote:

sa= -m mentioned you in 'Duplicate message are shown in profile' on Adve= -nture Time

- - -

yeah, just noticed this cc @eviltrout

- -

Please visit this link to respond: http= -://adventuretime.ooo/t/peppermint-butler-is-missing/7628/2 - - -

To unsubscribe from these emails, visit your user preferences.

-
- ---001a11c206a073876a04df81d2a9-- diff --git a/spec/fixtures/emails/multiple_destinations.eml b/spec/fixtures/emails/multiple_destinations.eml deleted file mode 100644 index 6d31bbf1974..00000000000 --- a/spec/fixtures/emails/multiple_destinations.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: finn@adventuretime.ooo, reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> \ No newline at end of file diff --git a/spec/fixtures/emails/paragraphs.cooked b/spec/fixtures/emails/paragraphs.cooked deleted file mode 100644 index da83260e09c..00000000000 --- a/spec/fixtures/emails/paragraphs.cooked +++ /dev/null @@ -1,7 +0,0 @@ -

Is there any reason the old candy can't be be kept in silos while the new candy -is imported into new silos?

- -

The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo.

- -

Thanks for listening.

\ No newline at end of file diff --git a/spec/fixtures/emails/previous.eml b/spec/fixtures/emails/previous.eml deleted file mode 100644 index 24ac5a63deb..00000000000 --- a/spec/fixtures/emails/previous.eml +++ /dev/null @@ -1,38 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -This will not include the previous discussion that is present in this email. - ------------------------------ -Previous discussion -skylerwhite July 24 - -This is a reply. - fring July 24 - -This is an older reply. - hank_schrader July 24 - -Of course another reply here. - walterwhite July 24 - - ------------------------------ - -To respond, reply to this email or visit -http://discourse.org/t/crystal-blue/5043/10in -your browser. - -To unsubscribe from these emails, visit your user -preferences -. diff --git a/spec/fixtures/emails/previous_replies.eml b/spec/fixtures/emails/previous_replies.eml deleted file mode 100644 index 3fd74482c07..00000000000 --- a/spec/fixtures/emails/previous_replies.eml +++ /dev/null @@ -1,180 +0,0 @@ -Delivered-To: reply@discourse.org -MIME-Version: 1.0 -In-Reply-To: -References: - -Date: Fri, 28 Nov 2014 12:55:32 -0800 -Subject: Re: [Discourse Meta] [Lounge] Testing default email replies -From: Walter White -To: Discourse Meta -Content-Type: multipart/alternative; boundary=001a1137c9285318bb0508f17bc5 - ---001a1137c9285318bb0508f17bc5 -Content-Type: text/plain; charset=UTF-8 - -### this is a reply from iOS Gmail app - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy -dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps -over the lazy dog. - -This is **bold** text in Markdown. - -This is a link to http://example.com - -On Friday, November 28, 2014, Arpit Jalan wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - ---001a1137c9285318bb0508f17bc5 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -### this is a reply from iOS Gmail app

The quick brown f= -ox jumps over the lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0The quick brown fox jumps over the lazy dog.=C2=A0The quic= -k brown fox jumps over the lazy dog.=C2=A0The quick brown fox jumps over th= -e lazy dog.=C2=A0

This is **bold** text in Markdown.

This is a link to http://example.com

On Friday, November 28, 2014, Arpit Jalan <
info@discourse.org> wrote:
- - - - - - - - - - - -
- - - techAPJ
- November 28 -
-

Test reply.

- -

First paragraph.

- -

Second paragraph.

-
- - -
-

To respond, reply to this email or visit https:/= -/meta.discourse.org/t/testing-default-email-replies/22638/3 in your bro= -wser.

-
-
-

Previous Replies

- - - - - - - - - - - -
- - - codinghorror - November 28 -
-

We're testing the latest GitHub emai= -l processing library which we are integrating now.

- -

https://github.com/github/email_reply_parser

- -

Go ahead and reply to this topic and I&#= -39;ll reply from various email clients for testing.

-
- - -
- -
-

To respond, reply to this email or visit https://met= -a.discourse.org/t/testing-default-email-replies/22638/3 in your browser= -.

-
-
-

To unsubscribe from these emails, visit your user preferences.

-
-
-
- ---001a1137c9285318bb0508f17bc5-- diff --git a/spec/fixtures/emails/too_many_mentions.eml b/spec/fixtures/emails/too_many_mentions.eml deleted file mode 100644 index 9cc7b75c94f..00000000000 --- a/spec/fixtures/emails/too_many_mentions.eml +++ /dev/null @@ -1,31 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - -@user1 -@user2 -@user3 -@user4 -@user5 -@user6 -@user7 -@user8 -@user9 -@user10 -@user11 \ No newline at end of file diff --git a/spec/fixtures/emails/valid_incoming.cooked b/spec/fixtures/emails/valid_incoming.cooked deleted file mode 100644 index 2bf35825373..00000000000 --- a/spec/fixtures/emails/valid_incoming.cooked +++ /dev/null @@ -1,5 +0,0 @@ -

Hey folks,

- -

I was thinking. Wouldn't it be great if we could post topics via email? Yes it would!

- -

Jakie

diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml deleted file mode 100644 index 409711ab417..00000000000 --- a/spec/fixtures/emails/valid_incoming.eml +++ /dev/null @@ -1,25 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: -Message-ID: -Subject: We should have a post-by-email-feature. -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -Hey folks, - -I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! - -Jakie - diff --git a/spec/fixtures/emails/valid_reply.cooked b/spec/fixtures/emails/valid_reply.cooked deleted file mode 100644 index 4bce79ad12a..00000000000 --- a/spec/fixtures/emails/valid_reply.cooked +++ /dev/null @@ -1,4 +0,0 @@ -

I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it.

- -
  • Jake out
diff --git a/spec/fixtures/emails/via_line.eml b/spec/fixtures/emails/via_line.eml deleted file mode 100644 index 0b2947ff954..00000000000 --- a/spec/fixtures/emails/via_line.eml +++ /dev/null @@ -1,25 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790 - -Hello this email has content! - -codinghorror via Discourse wrote: -> [codinghorror] codinghorror -> -> August 7 -> -> It wouldn't be great at the moment for 100% email, since there's no -> way to be notified of new topics via email. (you can get notified of -> new replies to your posts and topics via email, and reply to them.) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 65f3ceb5def..3d434aeaf91 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Email::Receiver do end context "when no sent notificiation for the reply key could be found" do - let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "nope") } + let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } it "raises a SentNotificationNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) -- cgit v1.2.1 From e1eb09dd0e376768eb73d660b64343d7b3ad8fad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:33:33 -0700 Subject: Remove more unused fixtures. --- spec/fixtures/emails/big5.eml | 26 ------ spec/fixtures/emails/bottom_reply.eml | 160 ---------------------------------- spec/fixtures/emails/empty.eml | 21 ----- spec/fixtures/emails/hebrew.eml | 17 ---- spec/fixtures/emails/too_short.eml | 21 ----- 5 files changed, 245 deletions(-) delete mode 100644 spec/fixtures/emails/big5.eml delete mode 100644 spec/fixtures/emails/bottom_reply.eml delete mode 100644 spec/fixtures/emails/empty.eml delete mode 100644 spec/fixtures/emails/hebrew.eml delete mode 100644 spec/fixtures/emails/too_short.eml diff --git a/spec/fixtures/emails/big5.eml b/spec/fixtures/emails/big5.eml deleted file mode 100644 index 4a7b2082486..00000000000 --- a/spec/fixtures/emails/big5.eml +++ /dev/null @@ -1,26 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: multipart/alternative; boundary=20cf301cc47ada510404f040b262 - ---20cf301cc47ada510404f040b262 -Content-Type: text/plain; charset=Big5 -Content-Transfer-Encoding: base64 - -tv2hSafapFe5cbX4pEahSQ0K ---20cf301cc47ada510404f040b262 -Content-Type: text/html; charset=Big5 -Content-Transfer-Encoding: base64 - -PGRpdiBkaXI9Imx0ciI+tv2hSafapFe5cbX4pEahSTxicj48L2Rpdj4NCg== ---20cf301cc47ada510404f040b262-- diff --git a/spec/fixtures/emails/bottom_reply.eml b/spec/fixtures/emails/bottom_reply.eml deleted file mode 100644 index 5fc992971fc..00000000000 --- a/spec/fixtures/emails/bottom_reply.eml +++ /dev/null @@ -1,160 +0,0 @@ -Received: by 10.107.19.29 with SMTP id b29csp111716ioj; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -X-Received: by 10.194.238.6 with SMTP id vg6mr11340975wjc.24.1406767925330; - Wed, 30 Jul 2014 17:52:05 -0700 (PDT) -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with QMQP - id 18F5C417; Thu, 31 Jul 2014 00:52:04 +0000 (UTC) -Old-Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on bendel.debian.org -X-Spam-Level: -X-Spam-Status: No, score=-25.9 required=4.0 tests=FOURLA,LDOSUBSCRIBER, - LDO_WHITELIST,MURPHY_DEBIAN_MESSAGE,PGPSIGNATURE autolearn=unavailable - version=3.3.2 -X-Original-To: lists-debian-ctte@bendel.debian.org -Delivered-To: lists-debian-ctte@bendel.debian.org -Received: from localhost (localhost [127.0.0.1]) - by bendel.debian.org (Postfix) with ESMTP id CE6CDEE - for ; Thu, 31 Jul 2014 00:51:52 +0000 (UTC) -X-Virus-Scanned: at lists.debian.org with policy bank en-lt -X-Amavis-Spam-Status: No, score=-11.9 tagged_above=-10000 required=5.3 - tests=[BAYES_00=-2, FOURLA=0.1, LDO_WHITELIST=-5, PGPSIGNATURE=-5] - autolearn=ham -Received: from bendel.debian.org ([127.0.0.1]) - by localhost (lists.debian.org [127.0.0.1]) (amavisd-new, port 2525) - with ESMTP id SB451DwGZCOe for ; - Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_BL_NJABL=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .debian. - helo: .becquer.dodds. - helo-domain: .dodds.) FROM/MX_MATCHES_NOT_HELO(DOMAIN)=0; rate: -5 -Received: from becquer.dodds.net (becquer.dodds.net [207.224.24.209]) - by bendel.debian.org (Postfix) with ESMTP id 8E89A2B4 - for ; Thu, 31 Jul 2014 00:51:47 +0000 (UTC) -Received: from virgil.dodds.net (unknown [192.168.15.59]) - by becquer.dodds.net (Postfix) with ESMTPA id 9B0A9256EB - for ; Wed, 30 Jul 2014 17:51:19 -0700 (PDT) -Received: by virgil.dodds.net (Postfix, from userid 1000) - id 942FB60199; Wed, 30 Jul 2014 17:51:15 -0700 (PDT) -Date: Wed, 30 Jul 2014 17:51:15 -0700 -From: Jake -To: incoming+amazing@appmail.adventuretime.ooo -Subject: Re: Next Debian CTTE IRC Meeting at date -d'Thu Jul 31 17:00:00 UTC - 2014' -Message-ID: <20140731005115.GA19044@virgil.dodds.net> -Mail-Followup-To: debian-ctte@lists.debian.org -References: <20140730213924.GA12356@teltox.donarmstrong.com> -MIME-Version: 1.0 -Content-Type: multipart/signed; micalg=pgp-sha256; - protocol="application/pgp-signature"; boundary="qMm9M+Fa2AknHoGS" -Content-Disposition: inline -In-Reply-To: <20140730213924.GA12356@teltox.donarmstrong.com> -User-Agent: Mutt/1.5.23 (2014-03-12) -X-Debian-Message: Signature check passed for Debian member -X-Rc-Virus: 2007-09-13_01 -X-Rc-Spam: 2008-11-04_01 -Resent-Message-ID: -Resent-From: debian-ctte@lists.debian.org -X-Mailing-List: archive/latest/4791 -X-Loop: debian-ctte@lists.debian.org -List-Id: -List-Post: -List-Help: -List-Subscribe: -List-Unsubscribe: -Precedence: list -Resent-Sender: debian-ctte-request@lists.debian.org -Resent-Date: Thu, 31 Jul 2014 00:52:04 +0000 (UTC) - - ---qMm9M+Fa2AknHoGS -Content-Type: text/plain; charset=us-ascii -Content-Disposition: inline -Content-Transfer-Encoding: quoted-printable - -On Wed, Jul 30, 2014 at 02:39:24PM -0700, Don Armstrong wrote: -> The next Debian CTTE IRC meeting is at=20 - -> date -d 'Thu Jul 31 17:00:00 UTC 2014' on irc.debian.org in -> #debian-ctte. - -> The current meeting agenda is here, and more up-to-date ones may be -> found in the git repository. - -> #startmeeting - -> #topic Who is here? - -> #topic Next Meeting? - -> #topic #717076 Decide between libjpeg-turbo and libjpeg8 et al. - -This has been voted on; should probably be removed from the agenda, someone -just needs to confirm the vote results and get it on the website. (AIUI the -archive has already begun moving on accordingly.) - -> #topic #636783 constitution: super-majority bug - -> #topic #636783 constitution: casting vote - -> #topic #636783 constitution: minimum discussion period - -> #topic #636783 constitution: TC member retirement/rollover - -> #topic #636783 constitution: TC chair retirement/rollover - -> #topic #681419 Depends: foo | foo-nonfree - -> #topic #741573 menu systems and mime-support - -> #topic #746715 init system fallout - -Also voted and just needs to be confirmed. - -> #topic #750135 Maintainer of aptitude package ->=20 -> #topic #752400 Advice on util-linux - -This has been closed by mutual agreement of the people involved and doesn't -require any action from the TC. Removed from the agenda. - -There's also bug #744246, which was assigned to the TC at my request but -without any preamble so it may have escaped notice. However, that situation -has been evolving constructively among the related parties (apparently, an -informal comment from a member of the release team was mistaken for a -release team position, so that's now being revisited), so I don't believe -this is anything we need to put on the agenda for tomorrow despite being on -the open bug list. - ---=20 -Steve Langasek Give me a lever long enough and a Free OS -Debian Developer to set it on, and I can move the world. -Ubuntu Developer http://www.debian.org/ -slangasek@ubuntu.com vorlon@debian.org - ---qMm9M+Fa2AknHoGS -Content-Type: application/pgp-signature; name="signature.asc" -Content-Description: Digital signature - ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1 - -iQIcBAEBCAAGBQJT2ZMDAAoJEFaNMPMhshM9GLsP/244S3wtYZEeVgJWIdB5PE0A -sZVezEA692y++/0oVZVecwV67yBOyfSjPPetdAph2UDMRtfurwxfj2BkbOFA2+Y6 -++MErbmC3V7IGpd/L/fFGdXgvMQT2MNBpw0fnMA7bLpNjCvoj+Hr1HXRUcWoJSlj -WmHWwWSTVRcHg8a3iWYJzY6XfLyEEgHlahrlKvJExsTx/9mc1qg7g8KGdnhzHFBl -ttdH2fxpAk/624dReCcw5RKmOLfZ1HsEl9XcVe1cb4K+MDaQiXmoEK5v3xaNz1tS -NK5v2D5gDs229zoxKzQnnzOPLHxqI5E0L9PpI/mu4T9z7H2bHR3U5BvhnT99t5uw -ydf2cZNGY0uFCV3Rvn07BfAIW5WSXhOfN/5IymRKmdhjsTiwZ/wFjFrK8tVjtERu -yeyA7RIYiblGCEKYIYLWSxhoXeEdmAdfp6EA2/IA1CpgMB+ZdSfaeMgFY7xosgmG -ax3NTnaKyhr1QEUJ2gjAwHnKjuGbRVDAinYrSvP0o8Bh9sAs2BN2negWBCZVwwkN -S9hWTjVqsBmpaPOt5SEDwDo9O9dfzkmaamDsxOuUEz9F7v5jYg0mxA/WbogGty9R -vOMKxdxRkzflL/CferVbkzL/EkZRDfWDp9SleZggrpz7miiNDbS7jdRzJ4EttmJ8 -gHBAVrOzcnbIPOIkk9pw -=KXIu ------END PGP SIGNATURE----- - ---qMm9M+Fa2AknHoGS-- - - --- -To UNSUBSCRIBE, email to debian-ctte-REQUEST@lists.debian.org -with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org -Archive: https://lists.debian.org/20140731005115.GA19044@virgil.dodds.net diff --git a/spec/fixtures/emails/empty.eml b/spec/fixtures/emails/empty.eml deleted file mode 100644 index 85bebc66245..00000000000 --- a/spec/fixtures/emails/empty.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - - diff --git a/spec/fixtures/emails/hebrew.eml b/spec/fixtures/emails/hebrew.eml deleted file mode 100644 index f4c8f01adc3..00000000000 --- a/spec/fixtures/emails/hebrew.eml +++ /dev/null @@ -1,17 +0,0 @@ - -Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org -Received: by 10.194.216.104 with SMTP id op8csp80593wjc; - Wed, 24 Jul 2013 07:59:14 -0700 (PDT) -Return-Path: -References: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -From: Walter White -In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail> -Mime-Version: 1.0 (1.0) -Date: Wed, 24 Jul 2013 15:59:10 +0100 -Message-ID: <4597127794206131679@unknownmsgid> -Subject: Re: [Discourse] new reply to your post in 'Crystal Blue' -To: walter via Discourse -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: base64 - -16nXnNeV150= \ No newline at end of file diff --git a/spec/fixtures/emails/too_short.eml b/spec/fixtures/emails/too_short.eml deleted file mode 100644 index 54fed0f98c5..00000000000 --- a/spec/fixtures/emails/too_short.eml +++ /dev/null @@ -1,21 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: TO -Message-ID: -Subject: SUBJECT -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - - -+1 \ No newline at end of file -- cgit v1.2.1 From ac268674edfdaa5b8232f9875481410fc5112f60 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:34:45 -0700 Subject: Update spec. --- spec/workers/email_receiver_worker_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 109a7b3eb1d..e0081904db2 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -7,7 +7,7 @@ describe EmailReceiverWorker do File.read(file_path) end - let(:raw_message) { fixture_file('emails/valid_incoming.eml') } + let(:raw_message) { fixture_file('emails/valid_reply.eml') } context "when reply by email is enabled" do before do @@ -31,7 +31,7 @@ describe EmailReceiverWorker do email = ActionMailer::Base.deliveries.last expect(email).not_to be_nil - expect(email.to).to eq(["from@example.com"]) + expect(email.to).to eq(["jake@adventuretime.ooo"]) expect(email.subject).to include("Rejected") end end -- cgit v1.2.1 From 54b04f1c5b6c486a67c654ee9987e5f5ec4501fd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 12:41:47 -0700 Subject: Add fixture_file helper. --- spec/lib/gitlab/email/attachment_uploader_spec.rb | 8 +------- spec/lib/gitlab/email/receiver_spec.rb | 6 ------ spec/lib/gitlab/email/reply_parser_spec.rb | 6 ------ spec/lib/gitlab/google_code_import/client_spec.rb | 2 +- spec/lib/gitlab/google_code_import/importer_spec.rb | 2 +- spec/support/fixture_helpers.rb | 11 +++++++++++ spec/support/markdown_feature.rb | 4 ++-- spec/workers/email_receiver_worker_spec.rb | 6 ------ 8 files changed, 16 insertions(+), 29 deletions(-) create mode 100644 spec/support/fixture_helpers.rb diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index b54ee8aa72a..e8208e15e29 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -1,18 +1,12 @@ require "spec_helper" describe Gitlab::Email::AttachmentUploader do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - describe "#execute" do let(:project) { build(:project) } let(:message_raw) { fixture_file("emails/attachment.eml") } let(:message) { Mail::Message.new(message_raw) } - it "creates a post with an attachment" do + it "uploads all attachments and returns their links" do links = described_class.new(message).execute(project) link = links.first diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 3d434aeaf91..a6f682a2711 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -1,12 +1,6 @@ require "spec_helper" describe Gitlab::Email::Receiver do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - before 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") diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 32120d56cbc..c552e632ce2 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -2,12 +2,6 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver describe Gitlab::Email::ReplyParser do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb index 6aa4428f367..37985c062b4 100644 --- a/spec/lib/gitlab/google_code_import/client_spec.rb +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::GoogleCodeImport::Client do - let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } subject { described_class.new(raw_data) } describe "#valid?" do diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index f49cbb7f532..65ad7524cc2 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::GoogleCodeImport::Importer do let(:mapped_user) { create(:user, username: "thilo123") } - let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } let(:import_data) do { diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb new file mode 100644 index 00000000000..a05c9d18002 --- /dev/null +++ b/spec/support/fixture_helpers.rb @@ -0,0 +1,11 @@ +module FixtureHelpers + def fixture_file(filename) + return '' if filename.blank? + file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename)) + File.read(file_path) + end +end + +RSpec.configure do |config| + config.include FixtureHelpers +end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index c59df4e84d6..f8b2ce03e6c 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -100,7 +100,7 @@ class MarkdownFeature end def raw_markdown - fixture = Rails.root.join('spec/fixtures/markdown.md.erb') - ERB.new(File.read(fixture)).result(binding) + markdown = fixture_file('markdown.md.erb') + ERB.new(markdown).result(binding) end end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index e0081904db2..e8f1bd2fa2f 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -1,12 +1,6 @@ require "spec_helper" describe EmailReceiverWorker do - def fixture_file(filename) - return '' if filename.blank? - file_path = File.expand_path(Rails.root + 'spec/fixtures/' + filename) - File.read(file_path) - end - let(:raw_message) { fixture_file('emails/valid_reply.eml') } context "when reply by email is enabled" do -- cgit v1.2.1 From 6110e175dc3bcf6d9ef416c7b95696efe509d02b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 13:10:55 -0700 Subject: Use heredocs. --- spec/fixtures/emails/iphone_signature.eml | 29 ----- spec/lib/gitlab/email/reply_parser_spec.rb | 202 +++++++++++++++-------------- 2 files changed, 107 insertions(+), 124 deletions(-) delete mode 100644 spec/fixtures/emails/iphone_signature.eml diff --git a/spec/fixtures/emails/iphone_signature.eml b/spec/fixtures/emails/iphone_signature.eml deleted file mode 100644 index d314ad1f1ea..00000000000 --- a/spec/fixtures/emails/iphone_signature.eml +++ /dev/null @@ -1,29 +0,0 @@ -Delivered-To: test@mail.com -Return-Path: -From: Walter White -Content-Type: multipart/alternative; - boundary=Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Transfer-Encoding: 7bit -Mime-Version: 1.0 (1.0) -Subject: Re: Signature in email replies! -Date: Thu, 23 Oct 2014 14:43:49 +0530 -References: <1234@mail.gmail.com> -In-Reply-To: <1234@mail.gmail.com> -To: Arpit Jalan -X-Mailer: iPhone Mail (12A405) - - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 -Content-Type: text/plain; - charset=us-ascii -Content-Transfer-Encoding: 7bit - -This post should not include signature. - -Sent from my iPhone - -> On 23-Oct-2014, at 9:45 am, Arpit Jalan wrote: -> -> Signature in email replies! - ---Apple-Mail-8E182EEF-9DBC-41DE-A593-DF2E5EBD3975 diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index c552e632ce2..a94c92ad53c 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -34,148 +34,160 @@ describe Gitlab::Email::ReplyParser do it "handles multiple paragraphs" do expect(test_parse_body(fixture_file("emails/paragraphs.eml"))). - to eq( -"Is there any reason the *old* candy can't be be kept in silos while the new candy -is imported into *new* silos? + to eq( + <<-BODY.strip_heredoc.chomp + Is there any reason the *old* candy can't be be kept in silos while the new candy + is imported into *new* silos? -The thing about candy is it stays delicious for a long time -- we can just keep -it there without worrying about it too much, imo. + The thing about candy is it stays delicious for a long time -- we can just keep + it there without worrying about it too much, imo. -Thanks for listening." - ) + Thanks for listening. + BODY + ) end it "handles multiple paragraphs when parsing html" do expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))). - to eq( -"Awesome! + to eq( + <<-BODY.strip_heredoc.chomp + Awesome! -Pleasure to have you here! + Pleasure to have you here! -:boom:" - ) + :boom: + BODY + ) end it "handles newlines" do expect(test_parse_body(fixture_file("emails/newlines.eml"))). - to eq( -"This is my reply. -It is my best reply. -It will also be my *only* reply." - ) + to eq( + <<-BODY.strip_heredoc.chomp + This is my reply. + It is my best reply. + It will also be my *only* reply. + BODY + ) end it "handles inline reply" do expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). - to eq( -"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: - -> techAPJ -> November 28 -> -> Test reply. -> -> First paragraph. -> -> Second paragraph. -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> ------------------------------ -> Previous Replies codinghorror -> -> November 28 -> -> We're testing the latest GitHub email processing library which we are -> integrating now. -> -> https://github.com/github/email_reply_parser -> -> Go ahead and reply to this topic and I'll reply from various email clients -> for testing. -> ------------------------------ -> -> To respond, reply to this email or visit -> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in -> your browser. -> -> To unsubscribe from these emails, visit your user preferences -> . -> - -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog." - ) - end - - it "strips iPhone signature" do - expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match /Sent from my iPhone/ + to eq( + <<-BODY.strip_heredoc.chomp + On Wed, Oct 8, 2014 at 11:12 AM, techAPJ wrote: + + > techAPJ + > November 28 + > + > Test reply. + > + > First paragraph. + > + > Second paragraph. + > + > To respond, reply to this email or visit + > https://meta.discourse.org/t/testing-default-email-replies/22638/3 in + > your browser. + > ------------------------------ + > Previous Replies codinghorror + > + > November 28 + > + > We're testing the latest GitHub email processing library which we are + > integrating now. + > + > https://github.com/github/email_reply_parser + > + > Go ahead and reply to this topic and I'll reply from various email clients + > for testing. + > ------------------------------ + > + > To respond, reply to this email or visit + > https://meta.discourse.org/t/testing-default-email-replies/22638/3 in + > your browser. + > + > To unsubscribe from these emails, visit your user preferences + > . + > + + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. + BODY + ) end it "properly renders email reply from gmail web client" do expect(test_parse_body(fixture_file("emails/gmail_web.eml"))). - to eq( -"### This is a reply from standard GMail in Google Chrome. + to eq( + <<-BODY.strip_heredoc.chomp + ### This is a reply from standard GMail in Google Chrome. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. -Here's some **bold** text in Markdown. + Here's some **bold** text in Markdown. -Here's a link http://example.com" - ) + Here's a link http://example.com + BODY + ) end it "properly renders email reply from iOS default mail client" do expect(test_parse_body(fixture_file("emails/ios_default.eml"))). - to eq( -"### this is a reply from iOS default mail + to eq( + <<-BODY.strip_heredoc.chomp + ### this is a reply from iOS default mail -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -Here's some **bold** markdown text. + Here's some **bold** markdown text. -Here's a link http://example.com" - ) + Here's a link http://example.com + BODY + ) end it "properly renders email reply from Android 5 gmail client" do expect(test_parse_body(fixture_file("emails/android_gmail.eml"))). - to eq( -"### this is a reply from Android 5 gmail + to eq( + <<-BODY.strip_heredoc.chomp + ### this is a reply from Android 5 gmail -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over -the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown -fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over + the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. -This is **bold** in Markdown. + This is **bold** in Markdown. -This is a link to http://example.com" - ) + This is a link to http://example.com + BODY + ) end it "properly renders email reply from Windows 8.1 Metro default mail client" do expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))). - to eq( -"### reply from default mail client in Windows 8.1 Metro + to eq( + <<-BODY.strip_heredoc.chomp + ### reply from default mail client in Windows 8.1 Metro -The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. -This is a **bold** word in Markdown + This is a **bold** word in Markdown -This is a link http://example.com" - ) + This is a link http://example.com + BODY + ) end it "properly renders email reply from MS Outlook client" do -- cgit v1.2.1 From 48e25a019a7dacbadeb3a49ed96e41d2c2241feb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 13:21:22 -0700 Subject: Add stub_reply_by_email_setting helper. --- spec/lib/gitlab/email/receiver_spec.rb | 3 +-- spec/lib/gitlab/reply_by_email_spec.rb | 12 ++++++------ spec/support/stub_configuration.rb | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index a6f682a2711..af44c1242ed 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -2,8 +2,7 @@ require "spec_helper" describe Gitlab::Email::Receiver do before 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") + stub_reply_by_email_setting(enabled: true, address: "reply+%{reply_key}@appmail.adventuretime.ooo") end let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } diff --git a/spec/lib/gitlab/reply_by_email_spec.rb b/spec/lib/gitlab/reply_by_email_spec.rb index a52af51e97c..a678c7e1a76 100644 --- a/spec/lib/gitlab/reply_by_email_spec.rb +++ b/spec/lib/gitlab/reply_by_email_spec.rb @@ -4,12 +4,12 @@ describe Gitlab::ReplyByEmail do describe "self.enabled?" do context "when reply by email is enabled" do before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(true) + stub_reply_by_email_setting(enabled: true) end context "when the address is valid" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns true" do @@ -19,7 +19,7 @@ describe Gitlab::ReplyByEmail do context "when the address is invalid" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies@example.com") + stub_reply_by_email_setting(address: "replies@example.com") end it "returns false" do @@ -30,7 +30,7 @@ describe Gitlab::ReplyByEmail do context "when reply by email is disabled" do before do - allow(Gitlab.config.reply_by_email).to receive(:enabled).and_return(false) + stub_reply_by_email_setting(enabled: false) end it "returns false" do @@ -66,7 +66,7 @@ describe Gitlab::ReplyByEmail do context "self.reply_address" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns the address with an interpolated reply key" do @@ -76,7 +76,7 @@ describe Gitlab::ReplyByEmail do context "self.reply_key_from_address" do before do - allow(Gitlab.config.reply_by_email).to receive(:address).and_return("replies+%{reply_key}@example.com") + stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com") end it "returns reply key" do diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index e4004ec8f79..ef3a120d44a 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -17,6 +17,10 @@ module StubConfiguration allow(Gitlab.config.gravatar).to receive_messages(messages) end + def stub_reply_by_email_setting(messages) + allow(Gitlab.config.reply_by_email).to receive_messages(messages) + end + private # Modifies stubbed messages to also stub possible predicate versions -- cgit v1.2.1 From 123af7856167f01ac0a7873bca1ef2cbb835e3b5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:03:04 -0700 Subject: Add gitlab:reply_by_email:check rake task. --- doc/reply_by_email/README.md | 6 ++ lib/gitlab/reply_by_email.rb | 9 ++- lib/tasks/gitlab/check.rake | 145 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index e168e9a17e4..1a0ce79bc5b 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -93,6 +93,12 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' sudo service gitlab restart ``` +7. Check if everything is configured correctly + + ```sh + sudo bundle exec rake gitlab:reply_by_email:check RAILS_ENV=production + ``` + 8. Reply by email should now be working. Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index b6157de3610..61d048fd200 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -2,9 +2,12 @@ module Gitlab module ReplyByEmail class << self def enabled? - config.enabled && - config.address && - config.address.include?("%{reply_key}") + config.enabled && address_formatted_correctly? + end + + def address_formatted_correctly? + config.address && + config.address.include?("%{reply_key}") end def reply_key diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 80ee572938d..608253d916c 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -2,6 +2,7 @@ namespace :gitlab do desc "GitLab | Check the configuration of GitLab and its environment" task check: %w{gitlab:gitlab_shell:check gitlab:sidekiq:check + gitlab:reply_by_email:check gitlab:ldap:check gitlab:app:check} @@ -577,6 +578,150 @@ namespace :gitlab do end end + + namespace :reply_by_email do + desc "GitLab | Check the configuration of Reply by email" + task check: :environment do + warn_user_is_not_gitlab + start_checking "Reply by email" + + if Gitlab.config.reply_by_email.enabled + check_address_formatted_correctly + check_mail_room_config_exists + check_imap_authentication + check_initd_configured_correctly + check_mail_room_running + else + puts 'Reply by email is disabled in config/gitlab.yml' + end + + finished_checking "Reply by email" + end + + + # Checks + ######################## + + def check_address_formatted_correctly + print "Address formatted correctly? ... " + + if Gitlab::ReplyByEmail.address_formatted_correctly? + puts "yes".green + else + puts "no".red + try_fixing_it( + "Make sure that the address in config/gitlab.yml includes the '%{reply_key}' placeholder." + ) + fix_and_rerun + end + end + + def check_initd_configured_correctly + print "Init.d configured correctly? ... " + + path = "/etc/default/gitlab" + + if File.exist?(path) && File.read(path).include?("mail_room_enabled=true") + puts "yes".green + else + puts "no".red + try_fixing_it( + "Enable mail_room in the init.d configuration." + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def check_mail_room_running + print "MailRoom running? ... " + + path = "/etc/default/gitlab" + + unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true") + puts "can't check because of previous errors".magenta + return + end + + if mail_room_process_count > 0 + puts "yes".green + else + puts "no".red + try_fixing_it( + sudo_gitlab("RAILS_ENV=production bin/mail_room start") + ) + for_more_information( + see_installation_guide_section("Install Init Script"), + "see log/mail_room.log for possible errors" + ) + fix_and_rerun + end + end + + def check_mail_room_config_exists + print "MailRoom config exists? ... " + + mail_room_config_file = Rails.root.join("config", "mail_room.yml") + + if File.exists?(mail_room_config_file) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Copy config/mail_room.yml.example to config/mail_room.yml", + "Check that the information in config/mail_room.yml is correct" + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def check_imap_authentication + print "IMAP server credentials are correct? ... " + + mail_room_config_file = Rails.root.join("config", "mail_room.yml") + + unless File.exists?(mail_room_config_file) + puts "can't check because of previous errors".magenta + return + end + + config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil + + if config + begin + imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) + imap.login(config[:email], config[:password]) + connected = true + rescue + connected = false + end + end + + if connected + puts "yes".green + else + puts "no".red + try_fixing_it( + "Check that the information in config/mail_room.yml is correct" + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + + def mail_room_process_count + ps_ux, _ = Gitlab::Popen.popen(%W(ps ux)) + ps_ux.scan(/mail_room \d+\.\d+\.\d+/).count + end + end + namespace :ldap do task :check, [:limit] => :environment do |t, args| # Only show up to 100 results because LDAP directories can be very big. -- cgit v1.2.1 From 02b7be4a6847c0f79c6088edcc84600008ba0c1e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:03:09 -0700 Subject: Remove weirdness. --- app/helpers/icons_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 30b17a736a7..1cf5b96481a 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -20,7 +20,7 @@ module IconsHelper end def boolean_to_icon(value) - if value.to_s == "true" + if value icon('circle', class: 'cgreen') else icon('power-off', class: 'clgray') -- cgit v1.2.1 From 41ed60c21a34f5a413c45d2d303e28b438a09ef2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:09:37 -0700 Subject: Fix MailRoom running check. --- lib/tasks/gitlab/check.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 608253d916c..3fbd429ef8d 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -645,7 +645,7 @@ namespace :gitlab do return end - if mail_room_process_count > 0 + if mail_room_running? puts "yes".green else puts "no".red @@ -716,9 +716,9 @@ namespace :gitlab do end end - def mail_room_process_count + def mail_room_running? ps_ux, _ = Gitlab::Popen.popen(%W(ps ux)) - ps_ux.scan(/mail_room \d+\.\d+\.\d+/).count + ps_ux.include?("mail_room") end end -- cgit v1.2.1 From f26c2905d198436ea4c4b7f26c6971edd4ff8049 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 14:25:56 -0700 Subject: Fix indentation --- lib/gitlab/email/reply_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 6ceb755968c..6e768e46a71 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -72,8 +72,8 @@ module Gitlab 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 =~ /\:$/) || + # 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 -- cgit v1.2.1 From 99ef8c81598ad31922dfbe28c0c56130e01bd13a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 15:37:43 -0700 Subject: Fix markdown specs. --- lib/gitlab/reply_by_email.rb | 2 +- spec/support/markdown_feature.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index 61d048fd200..4fdeb6b8a74 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -7,7 +7,7 @@ module Gitlab def address_formatted_correctly? config.address && - config.address.include?("%{reply_key}") + config.address.include?("%{reply_key}") end def reply_key diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index f8b2ce03e6c..cb47d290822 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -9,6 +9,7 @@ # reference to the factory-created objects. class MarkdownFeature include FactoryGirl::Syntax::Methods + include FixtureHelpers def user @user ||= create(:user) -- cgit v1.2.1 From afb765ad9ef6fd394a9709f6ab519267a761e6ad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 15:41:34 -0700 Subject: Fix markdown specs again. Apparently development and test behave differently. --- spec/support/markdown_feature.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index cb47d290822..39a64391460 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -9,7 +9,6 @@ # reference to the factory-created objects. class MarkdownFeature include FactoryGirl::Syntax::Methods - include FixtureHelpers def user @user ||= create(:user) @@ -101,7 +100,7 @@ class MarkdownFeature end def raw_markdown - markdown = fixture_file('markdown.md.erb') + markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) ERB.new(markdown).result(binding) end end -- cgit v1.2.1 From 3d141d1512a39b61159026457b31c6f0aecf5b6c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 16:21:31 -0700 Subject: Fix spec by removing global state. --- lib/gitlab/reply_by_email.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index 4fdeb6b8a74..f93fda4302c 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -36,14 +36,12 @@ module Gitlab end def address_regex - @address_regex ||= begin - wildcard_address = config.address - return nil unless wildcard_address - - regex = Regexp.escape(wildcard_address) - regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") - Regexp.new(regex).freeze - end + wildcard_address = config.address + return nil unless wildcard_address + + regex = Regexp.escape(wildcard_address) + regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)") + Regexp.new(regex).freeze end end end -- cgit v1.2.1 From 35224d5e7f3e0c978640b7a6dd64e9778c4d1c60 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Aug 2015 16:44:44 -0700 Subject: Memoize address_regex locally. --- lib/gitlab/reply_by_email.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/reply_by_email.rb b/lib/gitlab/reply_by_email.rb index f93fda4302c..c3fe6778f06 100644 --- a/lib/gitlab/reply_by_email.rb +++ b/lib/gitlab/reply_by_email.rb @@ -21,9 +21,10 @@ module Gitlab end def reply_key_from_address(address) - return unless address_regex + regex = address_regex + return unless regex - match = address.match(address_regex) + match = address.match(regex) return unless match match[1] -- cgit v1.2.1 From 69708dab9f6e1c265dd2bf80eafc39bf68c356e0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 10:14:45 -0700 Subject: Block blocked users from replying to threads by email. --- app/workers/email_receiver_worker.rb | 2 ++ lib/gitlab/email/receiver.rb | 3 +++ spec/lib/gitlab/email/receiver_spec.rb | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 8f6c27ce4af..a588a1f45ee 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -31,6 +31,8 @@ class EmailReceiverWorker reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." 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." + when Gitlab::Email::Receiver::UserBlockedError + reason = "Your account has been blocked. If you believe this is in error, contact a staff member." 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." when Gitlab::Email::Receiver::NoteableNotFoundError diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 17b8339edcd..355fbd27898 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -8,6 +8,7 @@ module Gitlab class EmptyEmailError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end + class UserBlockedError < ProcessingError; end class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end class InvalidNoteError < ProcessingError; end @@ -27,6 +28,8 @@ module Gitlab raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + project = sent_notification.project raise UserNotAuthorizedError unless project && author.can?(:create_note, project) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index af44c1242ed..1cc80f35f98 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -58,6 +58,16 @@ describe Gitlab::Email::Receiver do end end + context "when the user has been blocked" do + before do + user.block + end + + it "raises a UserBlockedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError) + end + end + context "when the user is not authorized to create a note" do before do project.update_attribute(:visibility_level, Project::PRIVATE) -- cgit v1.2.1 From 50baa1fdd1be1090c0d8ae6f7346d82f07369657 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:03:16 -0700 Subject: Add development section to doc. --- doc/reply_by_email/README.md | 4 ++-- lib/tasks/gitlab/check.rake | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index 1a0ce79bc5b..358e48abccc 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -87,13 +87,13 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' mail_room_enabled=true ``` -6. Restart GitLab +6. Restart GitLab: ```sh sudo service gitlab restart ``` -7. Check if everything is configured correctly +7. Check if everything is configured correctly: ```sh sudo bundle exec rake gitlab:reply_by_email:check RAILS_ENV=production diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 3fbd429ef8d..a48e23e63a8 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -589,8 +589,13 @@ namespace :gitlab do check_address_formatted_correctly check_mail_room_config_exists check_imap_authentication - check_initd_configured_correctly - check_mail_room_running + + if Rails.env.production? + check_initd_configured_correctly + check_mail_room_running + else + check_foreman_configured_correctly + end else puts 'Reply by email is disabled in config/gitlab.yml' end @@ -635,6 +640,25 @@ namespace :gitlab do end end + def check_foreman_configured_correctly + print "Foreman configured correctly? ... " + + path = Rails.root.join("Procfile") + + if File.exist?(path) && File.read(path) =~ /mail_room:/ + puts "yes".green + else + puts "no".red + try_fixing_it( + "Enable mail_room in your Procfile." + ) + for_more_information( + "doc/reply_by_email/README.md" + ) + fix_and_rerun + end + end + def check_mail_room_running print "MailRoom running? ... " -- cgit v1.2.1 From 0366655311f3de0806eb1ad84abfffb881b5c609 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:06:04 -0700 Subject: Fix check task for development. --- lib/tasks/gitlab/check.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index a48e23e63a8..6f7a13159fc 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -589,7 +589,7 @@ namespace :gitlab do check_address_formatted_correctly check_mail_room_config_exists check_imap_authentication - + if Rails.env.production? check_initd_configured_correctly check_mail_room_running @@ -645,7 +645,7 @@ namespace :gitlab do path = Rails.root.join("Procfile") - if File.exist?(path) && File.read(path) =~ /mail_room:/ + if File.exist?(path) && File.read(path) =~ /^mail_room:/ puts "yes".green else puts "no".red -- cgit v1.2.1 From 3abb356dd215235765f89c78c98655dc62688b77 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 15:13:22 -0700 Subject: Add development documentation for real. --- doc/reply_by_email/README.md | 72 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index 358e48abccc..91eea956e52 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -101,8 +101,76 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you' 8. Reply by email should now be working. -Note: If you're running GitLab in development mode and using `foreman`, make sure to also uncomment the `mail_room` line in your `Procfile`. - ### Omnibus package installations TODO + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`: + + ```yaml + reply_by_email: + enabled: true + address: "gitlab-replies+%{reply_key}@gmail.com" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`. + +2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`: + + ```sh + sudo cp config/mail_room.yml.example config/mail_room.yml + ``` + +3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + + ```yaml + :mailboxes: + - + # IMAP server host + :host: "imap.gmail.com" + # IMAP server port + :port: 993 + # Whether the IMAP server uses SSL + :ssl: true + # Email account username. Usually the full email address. + :email: "gitlab-replies@gmail.com" + # Email account password + :password: "[REDACTED]" + # The name of the mailbox where incoming mail will end up. Usually "inbox". + :name: "inbox" + # Always "sidekiq". + :delivery_method: sidekiq + :delivery_options: + # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. + :redis_url: redis://localhost:6379 + # Always "resque:gitlab". + :namespace: resque:gitlab + # Always "incoming_email". + :queue: incoming_email + # Always "EmailReceiverWorker" + :worker: EmailReceiverWorker + ``` + +4. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +6. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +7. Check if everything is configured correctly: + + ```sh + bundle exec rake gitlab:reply_by_email:check RAILS_ENV=development + ``` + +8. Reply by email should now be working. -- cgit v1.2.1 From 15fc7bd6139f0b429c05c055b4cfab561c926e08 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 21 Aug 2015 16:09:55 -0700 Subject: No HTML-only email please --- lib/gitlab/email/html_cleaner.rb | 135 ----------------------------- lib/gitlab/email/reply_parser.rb | 24 ++--- spec/fixtures/emails/dutch.eml | 2 +- spec/fixtures/emails/html_only.eml | 93 -------------------- spec/fixtures/emails/plaintext_only.eml | 42 +++++++++ spec/lib/gitlab/email/reply_parser_spec.rb | 19 +++- 6 files changed, 65 insertions(+), 250 deletions(-) delete mode 100644 lib/gitlab/email/html_cleaner.rb delete mode 100644 spec/fixtures/emails/html_only.eml create mode 100644 spec/fixtures/emails/plaintext_only.eml diff --git a/lib/gitlab/email/html_cleaner.rb b/lib/gitlab/email/html_cleaner.rb deleted file mode 100644 index e1ae9eee56c..00000000000 --- a/lib/gitlab/email/html_cleaner.rb +++ /dev/null @@ -1,135 +0,0 @@ -# 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
tags with a markdown \n - doc.xpath('//br').each do |br| - br.replace(new_linebreak_node doc, 2) - end - # Surround

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 diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 6e768e46a71..6ed36b51f12 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -23,31 +23,19 @@ module Gitlab 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 + text = message.text_part if message.multipart? + text ||= message if message.content_type !~ /text\/html/ - # prefer plain text - return text if text + return "" unless text - if html - body = HtmlCleaner.new(html).output_html - else - body = fix_charset(message) - end + text = fix_charset(text) # Certain trigger phrases that means we didn't parse correctly - if body =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + if text =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ return "" end - body + text end # Force encoding to UTF-8 on a Mail::Message or Mail::Part diff --git a/spec/fixtures/emails/dutch.eml b/spec/fixtures/emails/dutch.eml index 7be08dc4938..3142bf30c3b 100644 --- a/spec/fixtures/emails/dutch.eml +++ b/spec/fixtures/emails/dutch.eml @@ -17,4 +17,4 @@ Dit is een antwoord in het Nederlands. Op 18 juli 2013 10:23 schreef Sander Datema het volgende: -Dit is de originele post. \ No newline at end of file +Dit is de originele post. diff --git a/spec/fixtures/emails/html_only.eml b/spec/fixtures/emails/html_only.eml deleted file mode 100644 index 561b8db2c79..00000000000 --- a/spec/fixtures/emails/html_only.eml +++ /dev/null @@ -1,93 +0,0 @@ - -Delivered-To: walter@breakingbad.com -Received: by 10.64.13.41 with SMTP id m9csp29769iec; - Thu, 20 Jun 2013 08:53:22 -0700 (PDT) -X-Received: by 10.252.23.9 with SMTP id p9mr4055675lag.4.1371743601980; - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -Received: from mail-la0-x229.google.com (mail-la0-x229.google.com [2a00:1450:4010:c03::229]) - by mx.google.com with ESMTPS id u4si430203lae.48.2013.06.20.08.53.20 - for - (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); - Thu, 20 Jun 2013 08:53:21 -0700 (PDT) -X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; - d=google.com; s=20120113; - h=x-forwarded-to:x-forwarded-for:delivered-to:x-return-path - :content-type:mime-version:content-transfer-encoding:x-mailer - :message-id:date:subject:from:in-reply-to:to:resent-date:resent-from - :resent-to:resent-subject:resent-message-id:resent-user-agent - :x-scanned-by:x-gm-message-state; - bh=9O67r74ofh9WkEaKTRB/frQ3MKOtQlbCac2mz0/MiyY=; - b=YVAo2/JDMP53RxDmqDEKNcEMtggtfaVyq2DoseZ6vBAfB7G6NtHC9ZEkRs4oGhk6LU - fnyAPe0wnz5d9WINoMAuuTRIhplLxzcqysduSnAJAQ2qqR7mFBnlj9wJeVEKltNwmUME - nPwxsf8go20VBzrZCtECPedcLi60wbl32NCXVn0qwt2LvKiy6ktSS5Xgb4zY8i4dfXAP - 6Y5gu32boooWIb9DkH1TJkn3C0RrEugNlw/DUnXrnkFefgxWF3pt/zcoW/wYRyikOdx+ - smBClgR9my6QmsS2KsQrMvWJZUva7fddTiZ6FC22e4hW+8Wha0RaZOZu5O7hjg6G4/1g - IEyg== -X-Received: by 10.112.55.9 with SMTP id n9mr5916187lbp.5.1371743600857; - Thu, 20 Jun 2013 08:53:20 -0700 (PDT) -X-Forwarded-To: walter@breakingbad.com -X-Forwarded-For: walter@breakingbad.com -Delivered-To: walter@breakingbad.com -Content-Type: text/html; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -X-Mailer: BlackBerry Email (10.1.0.1720) -Message-ID: <20130619231548.6307981.74194.2379@breakingbad.com> -Date: Wed, 19 Jun 2013 19:15:48 -0400 -Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -From: aaron@breakingbad.com -In-Reply-To: <51c238655a394_5f4e3ce6690667bd@tiefighter2.mail> -To: reply+20c1b0a8bd1a63c0163cc7e7641ca06b@appmail.adventuretime.ooo -ReSent-Date: Thu, 20 Jun 2013 11:53:08 -0400 (EDT) -ReSent-From: Aaron -ReSent-Subject: Re: [Discourse Meta] [PM] re: Regarding your post in "Site - Customization not working" -X-Gm-Message-State: ALoCoQl1BtN83rAX7At808XAPv1yCqUK3Du2IvK7eCyY3jsI77u4e5cak28307pYYHAo1JlO/Eu9 - -

The EC2 instance - I've seen that th= -ere tends to be odd and unrecommended settings on the Bitnami installs that= - I've checked out.
= - = -

= - = -
= - = - = -
= -From: Grizzly B via Discourse Meta
Sent: Wednesday, J= -une 19, 2013 19:02
To: aaron@breakingbad.com
= -Reply To: Grizzly B via Discourse Meta
Subject: [Disc= -ourse Meta] [PM] re: Regarding your post in "Site Customization
not wor= -king"

Grizzly B just sent you a private message

- -

Log in to our EC2 instance -or- log into a new Digital Ocean instanc= -e?

- -

Please visit this link to respond: http://= -meta.discourse.org/t/regarding-your-post-in-site-customization-not-working/= -7641/5

- -

To unsubscribe from these emails, visit your user preferences.

-
diff --git a/spec/fixtures/emails/plaintext_only.eml b/spec/fixtures/emails/plaintext_only.eml new file mode 100644 index 00000000000..1bfaec771dc --- /dev/null +++ b/spec/fixtures/emails/plaintext_only.eml @@ -0,0 +1,42 @@ +Delivered-To: reply@discourse.org +Return-Path: +MIME-Version: 1.0 +From: +To: + =?utf-8?Q?Discourse_Meta?= + +Subject: + =?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?= +Importance: Normal +Date: Fri, 28 Nov 2014 21:29:10 +0000 +In-Reply-To: +References: + , +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 + +IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K +DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj +ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg +anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0 +aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu +IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi +cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt +cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg +bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU +aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp +dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO +MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K +DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw +aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg +ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1 +bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz +IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg +dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp +bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf +cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5 +IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl +c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz +ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv +d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1 +c2VyIHByZWZlcmVuY2VzLg== diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index a94c92ad53c..7cae1da8050 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -19,9 +19,22 @@ describe Gitlab::Email::ReplyParser do expect(test_parse_body(fixture_file("emails/no_content_reply.eml"))).to eq("") end - it "can parse the html section" do - expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " + - "unrecommended settings on the Bitnami installs that I've checked out.") + it "properly renders plaintext-only email" do + expect(test_parse_body(fixture_file("emails/plaintext_only.eml"))). + to eq( + <<-BODY.strip_heredoc.chomp + ### reply from default mail client in Windows 8.1 Metro + + + The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + + This is a **bold** word in Markdown + + + This is a link http://example.com + BODY + ) end it "supports a Dutch reply" do -- cgit v1.2.1