Project

General

Profile

Feature #6326 ยป rdm-mailhandler.rb

An experimental attempt at parsing Git patches wboxes - Olivier Mehani, 2010-09-08 07:54

 
1
#!/usr/bin/ruby
2

    
3
# rdm-mailhandler
4
# Reads an email from standard input and forward it to a Redmine server
5
# Can be used from a remote mail server
6

    
7
require 'net/http'
8
require 'net/https'
9
require 'uri'
10
require 'getoptlong'
11
require 'tmail'
12

    
13
module Net
14
  class HTTPS < HTTP
15
    def self.post_form(url, params)
16
      request = Post.new(url.path)
17
      request.form_data = params
18
      request.basic_auth url.user, url.password if url.user
19
      http = new(url.host, url.port)
20
      http.use_ssl = (url.scheme == 'https')
21
      http.start {|h| h.request(request) }
22
    end
23
  end
24
end
25

    
26
class RedmineMailHandler
27
  VERSION = '0.1'
28
  
29
  attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key
30

    
31
  def initialize
32
    self.issue_attributes = {}
33
    
34
    opts = GetoptLong.new(
35
      [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
36
      [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
37
      [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
38
      [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
39
      [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
40
      [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
41
      [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT],
42
      [ '--category', GetoptLong::REQUIRED_ARGUMENT],
43
      [ '--priority', GetoptLong::REQUIRED_ARGUMENT],
44
      [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT]
45
    )
46

    
47
    opts.each do |opt, arg|
48
      case opt
49
      when '--url'
50
        self.url = arg.dup
51
      when '--key'
52
        self.key = arg.dup
53
      when '--help'
54
        usage
55
      when '--verbose'
56
        self.verbose = true
57
      when '--version'
58
        puts VERSION; exit
59
      when '--project', '--tracker', '--category', '--priority'
60
        self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
61
      when '--allow-override'
62
        self.allow_override = arg.dup
63
      end
64
    end
65
    
66
    usage if url.nil?
67
  end
68

    
69
  
70
  def submit(email)
71
    uri = url.gsub(%r{/*$}, '') + '/mail_handler'
72

    
73
    # Git marks the beginning of its patches with three dashes
74
    body, patch = email.split("\n---\n",5)
75
    if patch
76
      mpartmail = TMail::Mail.parse(email)
77
      mpartmail.body = ''
78

    
79
      content_type = mpartmail.content_type
80
      charset = mpartmail.charset
81
      transfer_encoding = mpartmail.transfer_encoding
82
      mpartmail.transfer_encoding = nil
83
      mpartmail.mime_version = "1.0"
84
      mpartmail.set_content_type 'multipart', 'mixed',
85
	{'boundary' => TMail.new_boundary}
86

    
87
      bodypart = TMail::Mail.new
88
      bodypart.mime_version = "1.0"
89
      bodypart.content_type = content_type
90
      bodypart.charset = charset
91
      bodypart.transfer_encoding = transfer_encoding
92
      # Remove the headers
93
      fromline,body = body.split("\n",2)
94
      bodypart.body = body.split("\n\n",2)[1]
95
      mpartmail.parts << bodypart
96

    
97
      patchpart = TMail::Mail.new
98
      patchpart.mime_version = "1.0"
99
      patchpart.content_type = "text/x-diff"
100
      begin
101
	      filename_bits = mpartmail.subject.split("]")
102
	      patchnum = "%04d" % (filename_bits[0].split(" ")[1].split("/")[0])
103
	      patchname = filename_bits[1].gsub(" ", "-")
104
	      filename = patchnum + patchname + ".patch"
105

    
106
      rescue
107
	      filename = "git-patch.patch"
108
      end
109
      patchpart.content_disposition = "attachment; filename=\"" + filename  +"\""
110
      patchpart.charset = charset
111
      patchpart.transfer_encoding = transfer_encoding
112
      # Include all Git's patch, with header
113
      patchpart.body = email.split("\n\n",2)[1]
114
      mpartmail.parts << patchpart
115

    
116
      email = fromline + "\n" + mpartmail.to_s
117
    end
118

    
119
    data = { 'key' => key, 'email' => email,
120
      'allow_override' => allow_override }
121
    issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
122

    
123
    debug "Posting to #{uri}..."
124
    response = Net::HTTPS.post_form(URI.parse(uri), data)
125
    debug "Response received: #{response.code}"
126
    response.code == 201 ? 0 : 1
127

    
128
  end
129
  
130
  private
131
  
132
  def usage
133
    puts  <<-USAGE
134
Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>
135
Reads an email from standard input and forward it to a Redmine server
136

    
137
Required:
138
  -u, --url                      URL of the Redmine server
139
  -k, --key                      Redmine API key
140
  
141
General options:
142
  -h, --help                     show this help
143
  -v, --verbose                  show extra information
144
  -V, --version                  show version information and exit
145

    
146
Issue attributes control options:
147
  -p, --project=PROJECT          identifier of the target project
148
  -t, --tracker=TRACKER          name of the target tracker
149
      --category=CATEGORY        name of the target category
150
      --priority=PRIORITY        name of the target priority
151
  -o, --allow-override=ATTRS     allow email content to override attributes
152
                                 specified by previous options
153
                                 ATTRS is a comma separated list of attributes
154
      
155
Examples:
156
  # No project specified. Emails MUST contain the 'Project' keyword:
157
  rdm-mailhandler --url http://redmine.domain.foo --key secret
158
  
159
  # Fixed project and default tracker specified, but emails can override
160
  # both tracker and priority attributes:
161
  rdm-mailhandler --url https://domain.foo/redmine --key secret \\
162
                  --project foo \\
163
                  --tracker bug \\
164
                  --allow-override tracker,priority
165
USAGE
166
    exit
167
  end
168
  
169
  def debug(msg)
170
    puts msg if verbose
171
  end
172
end
173

    
174
handler = RedmineMailHandler.new
175
handler.submit(STDIN.read)
    (1-1/1)