117 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| require_dependency 'gitlab/email/handler'
 | |
| 
 | |
| # Inspired in great part by Discourse's Email::Receiver
 | |
| module Gitlab
 | |
|   module Email
 | |
|     class Receiver
 | |
|       def initialize(raw)
 | |
|         @raw = raw
 | |
|       end
 | |
| 
 | |
|       def execute
 | |
|         raise EmptyEmailError if @raw.blank?
 | |
| 
 | |
|         mail = build_mail
 | |
| 
 | |
|         ignore_auto_reply!(mail)
 | |
| 
 | |
|         handler = find_handler(mail)
 | |
| 
 | |
|         raise UnknownIncomingEmail unless handler
 | |
| 
 | |
|         handler.execute.tap do
 | |
|           Gitlab::Metrics.add_event(handler.metrics_event, handler.metrics_params)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       private
 | |
| 
 | |
|       def find_handler(mail)
 | |
|         mail_key = extract_mail_key(mail)
 | |
|         Handler.for(mail, mail_key)
 | |
|       end
 | |
| 
 | |
|       def build_mail
 | |
|         Mail::Message.new(@raw)
 | |
|       rescue Encoding::UndefinedConversionError,
 | |
|              Encoding::InvalidByteSequenceError => e
 | |
|         raise EmailUnparsableError, e
 | |
|       end
 | |
| 
 | |
|       def extract_mail_key(mail)
 | |
|         key_from_to_header(mail) || key_from_additional_headers(mail)
 | |
|       end
 | |
| 
 | |
|       def key_from_to_header(mail)
 | |
|         mail.to.find do |address|
 | |
|           key = Gitlab::IncomingEmail.key_from_address(address)
 | |
|           break key if key
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def key_from_additional_headers(mail)
 | |
|         find_key_from_references(mail) ||
 | |
|           find_key_from_delivered_to_header(mail) ||
 | |
|           find_key_from_envelope_to_header(mail)
 | |
|       end
 | |
| 
 | |
|       def ensure_references_array(references)
 | |
|         case references
 | |
|         when Array
 | |
|           references
 | |
|         when String
 | |
|           # Handle emails from clients which append with commas,
 | |
|           # example clients are Microsoft exchange and iOS app
 | |
|           Gitlab::IncomingEmail.scan_fallback_references(references)
 | |
|         when nil
 | |
|           []
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def find_key_from_references(mail)
 | |
|         ensure_references_array(mail.references).find do |mail_id|
 | |
|           key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
 | |
|           break key if key
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def find_key_from_delivered_to_header(mail)
 | |
|         Array(mail[:delivered_to]).find do |header|
 | |
|           key = Gitlab::IncomingEmail.key_from_address(header.value)
 | |
|           break key if key
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def find_key_from_envelope_to_header(mail)
 | |
|         Array(mail[:envelope_to]).find do |header|
 | |
|           key = Gitlab::IncomingEmail.key_from_address(header.value)
 | |
|           break key if key
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def ignore_auto_reply!(mail)
 | |
|         if auto_submitted?(mail) || auto_replied?(mail)
 | |
|           raise AutoGeneratedEmailError
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def auto_submitted?(mail)
 | |
|         # Mail::Header#[] is case-insensitive
 | |
|         auto_submitted = mail.header['Auto-Submitted']&.value
 | |
| 
 | |
|         # Mail::Field#value would strip leading and trailing whitespace
 | |
|         # See also https://tools.ietf.org/html/rfc3834
 | |
|         auto_submitted && auto_submitted != 'no'
 | |
|       end
 | |
| 
 | |
|       def auto_replied?(mail)
 | |
|         autoreply = mail.header['X-Autoreply']&.value
 | |
| 
 | |
|         autoreply && autoreply == 'yes'
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |