##// END OF EJS Templates
Don't abort processing of emails when an email triggers a parsing exception (#16122)....
Jean-Philippe Lang -
r12720:ddcad64aa28e
parent child
Show More
@@ -1,541 +1,549
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MailHandler < ActionMailer::Base
19 19 include ActionView::Helpers::SanitizeHelper
20 20 include Redmine::I18n
21 21
22 22 class UnauthorizedAction < StandardError; end
23 23 class MissingInformation < StandardError; end
24 24
25 25 attr_reader :email, :user
26 26
27 27 def self.receive(email, options={})
28 28 @@handler_options = options.dup
29 29
30 30 @@handler_options[:issue] ||= {}
31 31
32 32 if @@handler_options[:allow_override].is_a?(String)
33 33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 34 end
35 35 @@handler_options[:allow_override] ||= []
36 36 # Project needs to be overridable if not specified
37 37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 38 # Status overridable by default
39 39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40 40
41 41 @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
42 42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
43 43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
44 44
45 45 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
46 46 super(email)
47 47 end
48 48
49 # Receives an email and rescues any exception
50 def self.safe_receive(*args)
51 receive(*args)
52 rescue => e
53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
54 return false
55 end
56
49 57 # Extracts MailHandler options from environment variables
50 58 # Use when receiving emails with rake tasks
51 59 def self.extract_options_from_env(env)
52 60 options = {:issue => {}}
53 61 %w(project status tracker category priority).each do |option|
54 62 options[:issue][option.to_sym] = env[option] if env[option]
55 63 end
56 64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
57 65 options[option.to_sym] = env[option] if env[option]
58 66 end
59 67 options
60 68 end
61 69
62 70 def logger
63 71 Rails.logger
64 72 end
65 73
66 74 cattr_accessor :ignored_emails_headers
67 75 @@ignored_emails_headers = {
68 76 'X-Auto-Response-Suppress' => 'oof',
69 77 'Auto-Submitted' => /^auto-/
70 78 }
71 79
72 80 # Processes incoming emails
73 81 # Returns the created object (eg. an issue, a message) or false
74 82 def receive(email)
75 83 @email = email
76 84 sender_email = email.from.to_a.first.to_s.strip
77 85 # Ignore emails received from the application emission address to avoid hell cycles
78 86 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
79 87 if logger
80 88 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
81 89 end
82 90 return false
83 91 end
84 92 # Ignore auto generated emails
85 93 self.class.ignored_emails_headers.each do |key, ignored_value|
86 94 value = email.header[key]
87 95 if value
88 96 value = value.to_s.downcase
89 97 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
90 98 if logger
91 99 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
92 100 end
93 101 return false
94 102 end
95 103 end
96 104 end
97 105 @user = User.find_by_mail(sender_email) if sender_email.present?
98 106 if @user && !@user.active?
99 107 if logger
100 108 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
101 109 end
102 110 return false
103 111 end
104 112 if @user.nil?
105 113 # Email was submitted by an unknown user
106 114 case @@handler_options[:unknown_user]
107 115 when 'accept'
108 116 @user = User.anonymous
109 117 when 'create'
110 118 @user = create_user_from_email
111 119 if @user
112 120 if logger
113 121 logger.info "MailHandler: [#{@user.login}] account created"
114 122 end
115 123 add_user_to_group(@@handler_options[:default_group])
116 124 unless @@handler_options[:no_account_notice]
117 125 Mailer.account_information(@user, @user.password).deliver
118 126 end
119 127 else
120 128 if logger
121 129 logger.error "MailHandler: could not create account for [#{sender_email}]"
122 130 end
123 131 return false
124 132 end
125 133 else
126 134 # Default behaviour, emails from unknown users are ignored
127 135 if logger
128 136 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
129 137 end
130 138 return false
131 139 end
132 140 end
133 141 User.current = @user
134 142 dispatch
135 143 end
136 144
137 145 private
138 146
139 147 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
140 148 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
141 149 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
142 150
143 151 def dispatch
144 152 headers = [email.in_reply_to, email.references].flatten.compact
145 153 subject = email.subject.to_s
146 154 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
147 155 klass, object_id = $1, $2.to_i
148 156 method_name = "receive_#{klass}_reply"
149 157 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
150 158 send method_name, object_id
151 159 else
152 160 # ignoring it
153 161 end
154 162 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
155 163 receive_issue_reply(m[1].to_i)
156 164 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
157 165 receive_message_reply(m[1].to_i)
158 166 else
159 167 dispatch_to_default
160 168 end
161 169 rescue ActiveRecord::RecordInvalid => e
162 170 # TODO: send a email to the user
163 171 logger.error e.message if logger
164 172 false
165 173 rescue MissingInformation => e
166 174 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
167 175 false
168 176 rescue UnauthorizedAction => e
169 177 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
170 178 false
171 179 end
172 180
173 181 def dispatch_to_default
174 182 receive_issue
175 183 end
176 184
177 185 # Creates a new issue
178 186 def receive_issue
179 187 project = target_project
180 188 # check permission
181 189 unless @@handler_options[:no_permission_check]
182 190 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
183 191 end
184 192
185 193 issue = Issue.new(:author => user, :project => project)
186 194 issue.safe_attributes = issue_attributes_from_keywords(issue)
187 195 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
188 196 issue.subject = cleaned_up_subject
189 197 if issue.subject.blank?
190 198 issue.subject = '(no subject)'
191 199 end
192 200 issue.description = cleaned_up_text_body
193 201
194 202 # add To and Cc as watchers before saving so the watchers can reply to Redmine
195 203 add_watchers(issue)
196 204 issue.save!
197 205 add_attachments(issue)
198 206 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
199 207 issue
200 208 end
201 209
202 210 # Adds a note to an existing issue
203 211 def receive_issue_reply(issue_id, from_journal=nil)
204 212 issue = Issue.find_by_id(issue_id)
205 213 return unless issue
206 214 # check permission
207 215 unless @@handler_options[:no_permission_check]
208 216 unless user.allowed_to?(:add_issue_notes, issue.project) ||
209 217 user.allowed_to?(:edit_issues, issue.project)
210 218 raise UnauthorizedAction
211 219 end
212 220 end
213 221
214 222 # ignore CLI-supplied defaults for new issues
215 223 @@handler_options[:issue].clear
216 224
217 225 journal = issue.init_journal(user)
218 226 if from_journal && from_journal.private_notes?
219 227 # If the received email was a reply to a private note, make the added note private
220 228 issue.private_notes = true
221 229 end
222 230 issue.safe_attributes = issue_attributes_from_keywords(issue)
223 231 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
224 232 journal.notes = cleaned_up_text_body
225 233 add_attachments(issue)
226 234 issue.save!
227 235 if logger
228 236 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
229 237 end
230 238 journal
231 239 end
232 240
233 241 # Reply will be added to the issue
234 242 def receive_journal_reply(journal_id)
235 243 journal = Journal.find_by_id(journal_id)
236 244 if journal && journal.journalized_type == 'Issue'
237 245 receive_issue_reply(journal.journalized_id, journal)
238 246 end
239 247 end
240 248
241 249 # Receives a reply to a forum message
242 250 def receive_message_reply(message_id)
243 251 message = Message.find_by_id(message_id)
244 252 if message
245 253 message = message.root
246 254
247 255 unless @@handler_options[:no_permission_check]
248 256 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
249 257 end
250 258
251 259 if !message.locked?
252 260 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
253 261 :content => cleaned_up_text_body)
254 262 reply.author = user
255 263 reply.board = message.board
256 264 message.children << reply
257 265 add_attachments(reply)
258 266 reply
259 267 else
260 268 if logger
261 269 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
262 270 end
263 271 end
264 272 end
265 273 end
266 274
267 275 def add_attachments(obj)
268 276 if email.attachments && email.attachments.any?
269 277 email.attachments.each do |attachment|
270 278 next unless accept_attachment?(attachment)
271 279 obj.attachments << Attachment.create(:container => obj,
272 280 :file => attachment.decoded,
273 281 :filename => attachment.filename,
274 282 :author => user,
275 283 :content_type => attachment.mime_type)
276 284 end
277 285 end
278 286 end
279 287
280 288 # Returns false if the +attachment+ of the incoming email should be ignored
281 289 def accept_attachment?(attachment)
282 290 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
283 291 @excluded.each do |pattern|
284 292 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
285 293 if attachment.filename.to_s =~ regexp
286 294 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
287 295 return false
288 296 end
289 297 end
290 298 true
291 299 end
292 300
293 301 # Adds To and Cc as watchers of the given object if the sender has the
294 302 # appropriate permission
295 303 def add_watchers(obj)
296 304 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
297 305 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
298 306 unless addresses.empty?
299 307 User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
300 308 obj.add_watcher(w)
301 309 end
302 310 end
303 311 end
304 312 end
305 313
306 314 def get_keyword(attr, options={})
307 315 @keywords ||= {}
308 316 if @keywords.has_key?(attr)
309 317 @keywords[attr]
310 318 else
311 319 @keywords[attr] = begin
312 320 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
313 321 (v = extract_keyword!(plain_text_body, attr, options[:format]))
314 322 v
315 323 elsif !@@handler_options[:issue][attr].blank?
316 324 @@handler_options[:issue][attr]
317 325 end
318 326 end
319 327 end
320 328 end
321 329
322 330 # Destructively extracts the value for +attr+ in +text+
323 331 # Returns nil if no matching keyword found
324 332 def extract_keyword!(text, attr, format=nil)
325 333 keys = [attr.to_s.humanize]
326 334 if attr.is_a?(Symbol)
327 335 if user && user.language.present?
328 336 keys << l("field_#{attr}", :default => '', :locale => user.language)
329 337 end
330 338 if Setting.default_language.present?
331 339 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
332 340 end
333 341 end
334 342 keys.reject! {|k| k.blank?}
335 343 keys.collect! {|k| Regexp.escape(k)}
336 344 format ||= '.+'
337 345 keyword = nil
338 346 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
339 347 if m = text.match(regexp)
340 348 keyword = m[2].strip
341 349 text.gsub!(regexp, '')
342 350 end
343 351 keyword
344 352 end
345 353
346 354 def target_project
347 355 # TODO: other ways to specify project:
348 356 # * parse the email To field
349 357 # * specific project (eg. Setting.mail_handler_target_project)
350 358 target = Project.find_by_identifier(get_keyword(:project))
351 359 if target.nil?
352 360 # Invalid project keyword, use the project specified as the default one
353 361 default_project = @@handler_options[:issue][:project]
354 362 if default_project.present?
355 363 target = Project.find_by_identifier(default_project)
356 364 end
357 365 end
358 366 raise MissingInformation.new('Unable to determine target project') if target.nil?
359 367 target
360 368 end
361 369
362 370 # Returns a Hash of issue attributes extracted from keywords in the email body
363 371 def issue_attributes_from_keywords(issue)
364 372 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
365 373
366 374 attrs = {
367 375 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
368 376 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
369 377 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
370 378 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
371 379 'assigned_to_id' => assigned_to.try(:id),
372 380 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
373 381 issue.project.shared_versions.named(k).first.try(:id),
374 382 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
375 383 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
376 384 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
377 385 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
378 386 }.delete_if {|k, v| v.blank? }
379 387
380 388 if issue.new_record? && attrs['tracker_id'].nil?
381 389 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
382 390 end
383 391
384 392 attrs
385 393 end
386 394
387 395 # Returns a Hash of issue custom field values extracted from keywords in the email body
388 396 def custom_field_values_from_keywords(customized)
389 397 customized.custom_field_values.inject({}) do |h, v|
390 398 if keyword = get_keyword(v.custom_field.name, :override => true)
391 399 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
392 400 end
393 401 h
394 402 end
395 403 end
396 404
397 405 # Returns the text/plain part of the email
398 406 # If not found (eg. HTML-only email), returns the body with tags removed
399 407 def plain_text_body
400 408 return @plain_text_body unless @plain_text_body.nil?
401 409
402 410 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
403 411 text_parts
404 412 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
405 413 html_parts
406 414 else
407 415 [email]
408 416 end
409 417
410 418 parts.reject! do |part|
411 419 part.header[:content_disposition].try(:disposition_type) == 'attachment'
412 420 end
413 421
414 422 @plain_text_body = parts.map do |p|
415 423 body_charset = p.charset.respond_to?(:force_encoding) ?
416 424 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
417 425 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
418 426 end.join("\r\n")
419 427
420 428 # strip html tags and remove doctype directive
421 429 if parts.any? {|p| p.mime_type == 'text/html'}
422 430 @plain_text_body = strip_tags(@plain_text_body.strip)
423 431 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
424 432 end
425 433
426 434 @plain_text_body
427 435 end
428 436
429 437 def cleaned_up_text_body
430 438 cleanup_body(plain_text_body)
431 439 end
432 440
433 441 def cleaned_up_subject
434 442 subject = email.subject.to_s
435 443 subject.strip[0,255]
436 444 end
437 445
438 446 def self.full_sanitizer
439 447 @full_sanitizer ||= HTML::FullSanitizer.new
440 448 end
441 449
442 450 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
443 451 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
444 452 value = value.to_s.slice(0, limit)
445 453 object.send("#{attribute}=", value)
446 454 end
447 455
448 456 # Returns a User from an email address and a full name
449 457 def self.new_user_from_attributes(email_address, fullname=nil)
450 458 user = User.new
451 459
452 460 # Truncating the email address would result in an invalid format
453 461 user.mail = email_address
454 462 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
455 463
456 464 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
457 465 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
458 466 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
459 467 user.lastname = '-' if user.lastname.blank?
460 468 user.language = Setting.default_language
461 469 user.generate_password = true
462 470 user.mail_notification = 'only_my_events'
463 471
464 472 unless user.valid?
465 473 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
466 474 user.firstname = "-" unless user.errors[:firstname].blank?
467 475 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
468 476 end
469 477
470 478 user
471 479 end
472 480
473 481 # Creates a User for the +email+ sender
474 482 # Returns the user or nil if it could not be created
475 483 def create_user_from_email
476 484 from = email.header['from'].to_s
477 485 addr, name = from, nil
478 486 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
479 487 addr, name = m[2], m[1]
480 488 end
481 489 if addr.present?
482 490 user = self.class.new_user_from_attributes(addr, name)
483 491 if @@handler_options[:no_notification]
484 492 user.mail_notification = 'none'
485 493 end
486 494 if user.save
487 495 user
488 496 else
489 497 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
490 498 nil
491 499 end
492 500 else
493 501 logger.error "MailHandler: failed to create User: no FROM address found" if logger
494 502 nil
495 503 end
496 504 end
497 505
498 506 # Adds the newly created user to default group
499 507 def add_user_to_group(default_group)
500 508 if default_group.present?
501 509 default_group.split(',').each do |group_name|
502 510 if group = Group.named(group_name).first
503 511 group.users << @user
504 512 elsif logger
505 513 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
506 514 end
507 515 end
508 516 end
509 517 end
510 518
511 519 # Removes the email body of text after the truncation configurations.
512 520 def cleanup_body(body)
513 521 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
514 522 unless delimiters.empty?
515 523 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
516 524 body = body.gsub(regex, '')
517 525 end
518 526 body.strip
519 527 end
520 528
521 529 def find_assignee_from_keyword(keyword, issue)
522 530 keyword = keyword.to_s.downcase
523 531 assignable = issue.assignable_users
524 532 assignee = nil
525 533 assignee ||= assignable.detect {|a|
526 534 a.mail.to_s.downcase == keyword ||
527 535 a.login.to_s.downcase == keyword
528 536 }
529 537 if assignee.nil? && keyword.match(/ /)
530 538 firstname, lastname = *(keyword.split) # "First Last Throwaway"
531 539 assignee ||= assignable.detect {|a|
532 540 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
533 541 a.lastname.to_s.downcase == lastname
534 542 }
535 543 end
536 544 if assignee.nil?
537 545 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
538 546 end
539 547 assignee
540 548 end
541 549 end
@@ -1,62 +1,62
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'net/imap'
19 19
20 20 module Redmine
21 21 module IMAP
22 22 class << self
23 23 def check(imap_options={}, options={})
24 24 host = imap_options[:host] || '127.0.0.1'
25 25 port = imap_options[:port] || '143'
26 26 ssl = !imap_options[:ssl].nil?
27 27 folder = imap_options[:folder] || 'INBOX'
28 28
29 29 imap = Net::IMAP.new(host, port, ssl)
30 30 imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil?
31 31 imap.select(folder)
32 32 imap.uid_search(['NOT', 'SEEN']).each do |uid|
33 33 msg = imap.uid_fetch(uid,'RFC822')[0].attr['RFC822']
34 34 logger.debug "Receiving message #{uid}" if logger && logger.debug?
35 if MailHandler.receive(msg, options)
35 if MailHandler.safe_receive(msg, options)
36 36 logger.debug "Message #{uid} successfully received" if logger && logger.debug?
37 37 if imap_options[:move_on_success]
38 38 imap.uid_copy(uid, imap_options[:move_on_success])
39 39 end
40 40 imap.uid_store(uid, "+FLAGS", [:Seen, :Deleted])
41 41 else
42 42 logger.debug "Message #{uid} can not be processed" if logger && logger.debug?
43 43 imap.uid_store(uid, "+FLAGS", [:Seen])
44 44 if imap_options[:move_on_failure]
45 45 imap.uid_copy(uid, imap_options[:move_on_failure])
46 46 imap.uid_store(uid, "+FLAGS", [:Deleted])
47 47 end
48 48 end
49 49 end
50 50 imap.expunge
51 51 imap.logout
52 52 imap.disconnect
53 53 end
54 54
55 55 private
56 56
57 57 def logger
58 58 ::Rails.logger
59 59 end
60 60 end
61 61 end
62 62 end
@@ -1,62 +1,62
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'net/pop'
19 19
20 20 module Redmine
21 21 module POP3
22 22 class << self
23 23 def check(pop_options={}, options={})
24 24 host = pop_options[:host] || '127.0.0.1'
25 25 port = pop_options[:port] || '110'
26 26 apop = (pop_options[:apop].to_s == '1')
27 27 delete_unprocessed = (pop_options[:delete_unprocessed].to_s == '1')
28 28
29 29 pop = Net::POP3.APOP(apop).new(host,port)
30 30 logger.debug "Connecting to #{host}..." if logger && logger.debug?
31 31 pop.start(pop_options[:username], pop_options[:password]) do |pop_session|
32 32 if pop_session.mails.empty?
33 33 logger.debug "No email to process" if logger && logger.debug?
34 34 else
35 35 logger.debug "#{pop_session.mails.size} email(s) to process..." if logger && logger.debug?
36 36 pop_session.each_mail do |msg|
37 37 message = msg.pop
38 38 message_id = (message =~ /^Message-I[dD]: (.*)/ ? $1 : '').strip
39 if MailHandler.receive(message, options)
39 if MailHandler.safe_receive(message, options)
40 40 msg.delete
41 41 logger.debug "--> Message #{message_id} processed and deleted from the server" if logger && logger.debug?
42 42 else
43 43 if delete_unprocessed
44 44 msg.delete
45 45 logger.debug "--> Message #{message_id} NOT processed and deleted from the server" if logger && logger.debug?
46 46 else
47 47 logger.debug "--> Message #{message_id} NOT processed and left on the server" if logger && logger.debug?
48 48 end
49 49 end
50 50 end
51 51 end
52 52 end
53 53 end
54 54
55 55 private
56 56
57 57 def logger
58 58 ::Rails.logger
59 59 end
60 60 end
61 61 end
62 62 end
General Comments 0
You need to be logged in to leave comments. Login now