--- mail_handler.rb.orig 2023-02-15 23:25:49.023531264 +0100 +++ mail_handler.rb 2023-02-16 22:06:33.017071239 +0100 @@ -67,7 +67,7 @@ %w(project status tracker category priority assigned_to fixed_version).each do |option| options[:issue][option.to_sym] = env[option] if env[option] end - %w(allow_override unknown_user no_permission_check no_account_notice no_notification default_group project_from_subaddress).each do |option| + %w(allow_override unknown_user no_permission_check no_account_notice no_notification default_group project_from_subaddress ext_ref reprefix).each do |option| options[option.to_sym] = env[option] if env[option] end if env['private'] @@ -178,6 +178,40 @@ end def dispatch_to_default + extref_name = handler_options[:ext_ref] + extref = get_keyword(extref_name, {}, false) if extref_name + if extref_name && extref + Rails.logger.warn "MailHandlerExt: found ext ref #{extref_name} = #{extref}" + begin + query = IssueQuery.new(:name => '_', + :filters => { 'status_id' => {:operator => "o", :values => [""]}}) + cf = query.issue_custom_fields.visible.where(:is_filter => true).where(:name => extref_name).first + if cf + query.add_filter "cf_#{cf.id}", '=', [extref] + end + issues=query.issues + issue_id = nil + if issues.count > 10 + Rails.logger.error "MailHandler: found #{issues.count} open issues with #{extref_name}: #{extref}. Too much. Ignoring external reference." + elsif issues.count > 1 + Rails.logger.warn "MailHandler: found #{issues.count} open issues with #{extref_name}: #{extref}:" + Rails.logger.warn "MailHandler: issues: #{issues.map{ |i| i.id.to_s }.join(", ")}" + issue_id = issues.first.id + elsif issues.count == 1 + issue_id = issues.first.id + else + Rails.logger.warn "MailHandler: no open issues found with #{extref_name}: #{extref}:" + end + #Rails.logger.error "MailHandler: query #{query}" + #Rails.logger.error "MailHandler: query.statement #{query.statement}" + if !issue_id.nil? + Rails.logger.warn "MailHandler: ext ref maps mail to issue #{issue_id}" + return receive_issue_reply(issue_id) + end + rescue Exception => e + Rails.logger.error "MailHandlerExt: error quer ext ref #{extref}: #{e.message}" + end + end receive_issue end @@ -208,6 +242,7 @@ issue.subject = "(#{ll(Setting.default_language, :text_no_subject)})" end issue.description = cleaned_up_text_body + issue.description << "\n\n-- \n" << headers_as_text issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date? if handler_options[:issue][:is_private] == '1' issue.is_private = true @@ -221,6 +256,29 @@ issue end + def headers_as_text + # headers_txt must not be frozen + headers_txt = +'' + headers_txt << header_as_text('from') + headers_txt << header_as_text('to') + headers_txt << header_as_text('cc') + return headers_txt + end + + def header_as_text(h) + value = email.header[h] + # ret must not be frozen + ret = +'' + if value + ret << h << ': ' << value.to_s << "\n" + end + return ret + rescue Exception => e + ret = +'' + ret << h << ": error reading value\n" + return ret + end + # Adds a note to an existing issue def receive_issue_reply(issue_id, from_journal=nil) issue = Issue.find_by(:id => issue_id) @@ -251,6 +309,7 @@ issue.safe_attributes = issue_attributes_from_keywords(issue) issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} journal.notes = cleaned_up_text_body + journal.notes << "\n\n-- \n" << header_as_text('subject') << headers_as_text # add To and Cc as watchers before saving so the watchers can reply to Redmine add_watchers(issue) @@ -334,6 +393,12 @@ return false end end + + if attachment.filename.to_s =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i && attachment.body.decoded.size < 20000 + logger.info "MailHandler: ignoring attachment #{attachment.filename} based on size" + return false + end + true end @@ -352,30 +417,35 @@ end end - def get_keyword(attr, options={}) + def get_keyword(attr, options={}, remove_line=true) @keywords ||= {} if @keywords.has_key?(attr) @keywords[attr] else - @keywords[attr] = begin + value = begin override = if options.key?(:override) options[:override] else (handler_options[:allow_override] & [attr.to_s.downcase.gsub(/\s+/, '_'), 'all']).present? end - if override && (v = extract_keyword!(cleaned_up_text_body, attr, options[:format])) + if override && (v = extract_keyword!(cleaned_up_text_body, attr, options[:format], remove_line)) v elsif !handler_options[:issue][attr].blank? handler_options[:issue][attr] end end + if remove_line + #Rails.logger.warn "MailHandler: reading and removed attr #{attr.to_s}: '#{value}'" + @keywords[attr] = value + end + value end end # Destructively extracts the value for +attr+ in +text+ # Returns nil if no matching keyword found - def extract_keyword!(text, attr, format=nil) + def extract_keyword!(text, attr, format=nil, remove_line=true) keys = [attr.to_s.humanize] if attr.is_a?(Symbol) if user && user.language.present? @@ -389,10 +459,14 @@ keys.collect! {|k| Regexp.escape(k)} format ||= '.+' keyword = nil - regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i + prefix = handler_options[:reprefix] + prefix ||= '' + regexp = /^#{prefix}(#{keys.join('|')})[ \t]*:[ \t]*(?#{format})\s*$/i if m = text.match(regexp) - keyword = m[2].strip - text.sub!(regexp, '') + keyword = m[:kval].strip + if remove_line + text.sub!(regexp, '') + end end keyword end @@ -449,6 +523,7 @@ 'due_date' => get_keyword(:due_date, :format => '\d{4}-\d{2}-\d{2}'), 'estimated_hours' => get_keyword(:estimated_hours), 'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0'), + 'subject' => get_keyword(:subject), 'is_private' => get_keyword_bool(:is_private), 'parent_issue_id' => get_keyword(:parent_issue) }.delete_if {|k, v| v.blank?}