##// END OF EJS Templates
Adds a form to manually submit an email to the mail handler....
Jean-Philippe Lang -
r13932:95f7471e9c78
parent child
Show More
@@ -0,0 +1,43
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8" />
5 <style>
6 label {display:block;margin:0.5em;}
7 </style>
8 </head>
9 <body>
10 <h1>Redmine Mail Handler</h1>
11
12 <%= form_tag({}, :multipart => true, :action => 'post') do %>
13 <%= hidden_field_tag 'key', params[:key] %>
14
15 <fieldset>
16 <legend>Raw Email</legend>
17 <%= text_area_tag 'email', '', :style => 'width:95%; height:400px;' %></label>
18 </fieldset>
19
20 <fieldset>
21 <legend>Options</legend>
22 <label>unknown_user: <%= select_tag 'unknown_user', options_for_select(['', 'ignore', 'accept', 'create']) %></label>
23 <label>default_group: <%= text_field_tag 'default_group' %></label>
24 <label>no_account_notice: <%= check_box_tag 'no_account_notice', 1 %></label>
25 <label>no_notification: <%= check_box_tag 'no_notification', 1 %></label>
26 <label>no_permission_check: <%= check_box_tag 'no_permission_check', 1 %></label>
27 </fieldset>
28
29 <fieldset>
30 <legend>Issue attributes options</legend>
31 <label>project: <%= text_field_tag 'issue[project]' %></label>
32 <label>status: <%= text_field_tag 'issue[status]' %></label>
33 <label>tracker: <%= text_field_tag 'issue[tracker]' %></label>
34 <label>category: <%= text_field_tag 'issue[category]' %></label>
35 <label>priority: <%= text_field_tag 'issue[priority]' %></label>
36 <label>private: <%= check_box_tag 'issue[private]', 1 %></label>
37 <label>allow_override: <%= text_field_tag 'allow_override' %></label>
38 </fieldset>
39
40 <p><%= submit_tag 'Submit Email' %></p>
41 <% end %>
42 </body>
43 </html>
@@ -1,40 +1,44
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandlerController < ActionController::Base
18 class MailHandlerController < ActionController::Base
19 before_filter :check_credential
19 before_filter :check_credential
20
20
21 # Displays the email submission form
22 def new
23 end
24
21 # Submits an incoming email to MailHandler
25 # Submits an incoming email to MailHandler
22 def index
26 def index
23 options = params.dup
27 options = params.dup
24 email = options.delete(:email)
28 email = options.delete(:email)
25 if MailHandler.receive(email, options)
29 if MailHandler.receive(email, options)
26 render :nothing => true, :status => :created
30 render :nothing => true, :status => :created
27 else
31 else
28 render :nothing => true, :status => :unprocessable_entity
32 render :nothing => true, :status => :unprocessable_entity
29 end
33 end
30 end
34 end
31
35
32 private
36 private
33
37
34 def check_credential
38 def check_credential
35 User.current = nil
39 User.current = nil
36 unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
40 unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
37 render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
41 render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
38 end
42 end
39 end
43 end
40 end
44 end
@@ -1,363 +1,365
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 Rails.application.routes.draw do
18 Rails.application.routes.draw do
19 root :to => 'welcome#index', :as => 'home'
19 root :to => 'welcome#index', :as => 'home'
20
20
21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 match 'account/activate', :to => 'account#activate', :via => :get
25 match 'account/activate', :to => 'account#activate', :via => :get
26 get 'account/activation_email', :to => 'account#activation_email', :as => 'activation_email'
26 get 'account/activation_email', :to => 'account#activation_email', :as => 'activation_email'
27
27
28 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put, :patch]
28 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put, :patch]
29 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put, :patch]
29 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put, :patch]
30 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put, :patch]
30 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put, :patch]
31 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put, :patch]
31 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put, :patch]
32
32
33 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
34 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
35
35
36 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
36 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
37 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
38 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
39 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
40
40
41 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
41 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
42 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
43 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
44 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
45
45
46 # Misc issue routes. TODO: move into resources
46 # Misc issue routes. TODO: move into resources
47 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
48 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
48 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
49 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
49 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
50 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
51
51
52 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
53 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
54
54
55 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
55 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
56 get '/issues/gantt', :to => 'gantts#show'
56 get '/issues/gantt', :to => 'gantts#show'
57
57
58 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
58 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
59 get '/issues/calendar', :to => 'calendars#show'
59 get '/issues/calendar', :to => 'calendars#show'
60
60
61 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
61 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
62 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
62 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
63
63
64 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
65 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
66 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 match 'my/page', :controller => 'my', :action => 'page', :via => :get
67 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
68 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
69 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
70 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
71 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
72 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
73 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
74 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
75
75
76 resources :users do
76 resources :users do
77 resources :memberships, :controller => 'principal_memberships'
77 resources :memberships, :controller => 'principal_memberships'
78 resources :email_addresses, :only => [:index, :create, :update, :destroy]
78 resources :email_addresses, :only => [:index, :create, :update, :destroy]
79 end
79 end
80
80
81 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
81 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
82 delete 'watchers/watch', :to => 'watchers#unwatch'
82 delete 'watchers/watch', :to => 'watchers#unwatch'
83 get 'watchers/new', :to => 'watchers#new'
83 get 'watchers/new', :to => 'watchers#new'
84 post 'watchers', :to => 'watchers#create'
84 post 'watchers', :to => 'watchers#create'
85 post 'watchers/append', :to => 'watchers#append'
85 post 'watchers/append', :to => 'watchers#append'
86 delete 'watchers', :to => 'watchers#destroy'
86 delete 'watchers', :to => 'watchers#destroy'
87 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
87 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
88 # Specific routes for issue watchers API
88 # Specific routes for issue watchers API
89 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
89 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
90 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
90 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
91
91
92 resources :projects do
92 resources :projects do
93 member do
93 member do
94 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
94 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
95 post 'modules'
95 post 'modules'
96 post 'archive'
96 post 'archive'
97 post 'unarchive'
97 post 'unarchive'
98 post 'close'
98 post 'close'
99 post 'reopen'
99 post 'reopen'
100 match 'copy', :via => [:get, :post]
100 match 'copy', :via => [:get, :post]
101 end
101 end
102
102
103 shallow do
103 shallow do
104 resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
104 resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
105 collection do
105 collection do
106 get 'autocomplete'
106 get 'autocomplete'
107 end
107 end
108 end
108 end
109 end
109 end
110
110
111 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
111 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
112
112
113 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
113 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
114 resources :issues, :only => [:index, :new, :create]
114 resources :issues, :only => [:index, :new, :create]
115 # Used when updating the form of a new issue
115 # Used when updating the form of a new issue
116 post 'issues/new', :to => 'issues#new'
116 post 'issues/new', :to => 'issues#new'
117
117
118 resources :files, :only => [:index, :new, :create]
118 resources :files, :only => [:index, :new, :create]
119
119
120 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
120 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
121 collection do
121 collection do
122 put 'close_completed'
122 put 'close_completed'
123 end
123 end
124 end
124 end
125 get 'versions.:format', :to => 'versions#index'
125 get 'versions.:format', :to => 'versions#index'
126 get 'roadmap', :to => 'versions#index', :format => false
126 get 'roadmap', :to => 'versions#index', :format => false
127 get 'versions', :to => 'versions#index'
127 get 'versions', :to => 'versions#index'
128
128
129 resources :news, :except => [:show, :edit, :update, :destroy]
129 resources :news, :except => [:show, :edit, :update, :destroy]
130 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
130 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
131 get 'report', :on => :collection
131 get 'report', :on => :collection
132 end
132 end
133 resources :queries, :only => [:new, :create]
133 resources :queries, :only => [:new, :create]
134 shallow do
134 shallow do
135 resources :issue_categories
135 resources :issue_categories
136 end
136 end
137 resources :documents, :except => [:show, :edit, :update, :destroy]
137 resources :documents, :except => [:show, :edit, :update, :destroy]
138 resources :boards
138 resources :boards
139 shallow do
139 shallow do
140 resources :repositories, :except => [:index, :show] do
140 resources :repositories, :except => [:index, :show] do
141 member do
141 member do
142 match 'committers', :via => [:get, :post]
142 match 'committers', :via => [:get, :post]
143 end
143 end
144 end
144 end
145 end
145 end
146
146
147 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
147 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
148 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
148 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
149 member do
149 member do
150 get 'rename'
150 get 'rename'
151 post 'rename'
151 post 'rename'
152 get 'history'
152 get 'history'
153 get 'diff'
153 get 'diff'
154 match 'preview', :via => [:post, :put, :patch]
154 match 'preview', :via => [:post, :put, :patch]
155 post 'protect'
155 post 'protect'
156 post 'add_attachment'
156 post 'add_attachment'
157 end
157 end
158 collection do
158 collection do
159 get 'export'
159 get 'export'
160 get 'date_index'
160 get 'date_index'
161 end
161 end
162 end
162 end
163 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
163 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
164 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
164 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
165 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
165 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
166 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
166 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
167 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
167 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
168 end
168 end
169
169
170 resources :issues do
170 resources :issues do
171 member do
171 member do
172 # Used when updating the form of an existing issue
172 # Used when updating the form of an existing issue
173 patch 'edit', :to => 'issues#edit'
173 patch 'edit', :to => 'issues#edit'
174 end
174 end
175 collection do
175 collection do
176 match 'bulk_edit', :via => [:get, :post]
176 match 'bulk_edit', :via => [:get, :post]
177 post 'bulk_update'
177 post 'bulk_update'
178 end
178 end
179 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
179 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
180 collection do
180 collection do
181 get 'report'
181 get 'report'
182 end
182 end
183 end
183 end
184 shallow do
184 shallow do
185 resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
185 resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
186 end
186 end
187 end
187 end
188 # Used when updating the form of a new issue outside a project
188 # Used when updating the form of a new issue outside a project
189 post '/issues/new', :to => 'issues#new'
189 post '/issues/new', :to => 'issues#new'
190 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
190 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
191
191
192 resources :queries, :except => [:show]
192 resources :queries, :except => [:show]
193
193
194 resources :news, :only => [:index, :show, :edit, :update, :destroy]
194 resources :news, :only => [:index, :show, :edit, :update, :destroy]
195 match '/news/:id/comments', :to => 'comments#create', :via => :post
195 match '/news/:id/comments', :to => 'comments#create', :via => :post
196 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
196 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
197
197
198 resources :versions, :only => [:show, :edit, :update, :destroy] do
198 resources :versions, :only => [:show, :edit, :update, :destroy] do
199 post 'status_by', :on => :member
199 post 'status_by', :on => :member
200 end
200 end
201
201
202 resources :documents, :only => [:show, :edit, :update, :destroy] do
202 resources :documents, :only => [:show, :edit, :update, :destroy] do
203 post 'add_attachment', :on => :member
203 post 'add_attachment', :on => :member
204 end
204 end
205
205
206 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
206 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
207
207
208 resources :time_entries, :controller => 'timelog', :except => :destroy do
208 resources :time_entries, :controller => 'timelog', :except => :destroy do
209 collection do
209 collection do
210 get 'report'
210 get 'report'
211 get 'bulk_edit'
211 get 'bulk_edit'
212 post 'bulk_update'
212 post 'bulk_update'
213 end
213 end
214 end
214 end
215 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
215 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
216 # TODO: delete /time_entries for bulk deletion
216 # TODO: delete /time_entries for bulk deletion
217 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
217 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
218 # Used to update the new time entry form
218 # Used to update the new time entry form
219 post '/time_entries/new', :to => 'timelog#new'
219 post '/time_entries/new', :to => 'timelog#new'
220
220
221 get 'projects/:id/activity', :to => 'activities#index', :as => :project_activity
221 get 'projects/:id/activity', :to => 'activities#index', :as => :project_activity
222 get 'activity', :to => 'activities#index'
222 get 'activity', :to => 'activities#index'
223
223
224 # repositories routes
224 # repositories routes
225 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
225 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
226 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
226 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
227
227
228 get 'projects/:id/repository/:repository_id/changes(/*path)',
228 get 'projects/:id/repository/:repository_id/changes(/*path)',
229 :to => 'repositories#changes',
229 :to => 'repositories#changes',
230 :format => false
230 :format => false
231
231
232 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
232 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
233 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
233 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
234 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
234 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
235 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
235 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
236 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
236 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
237 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path)',
237 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path)',
238 :controller => 'repositories',
238 :controller => 'repositories',
239 :format => false,
239 :format => false,
240 :constraints => {
240 :constraints => {
241 :action => /(browse|show|entry|raw|annotate|diff)/,
241 :action => /(browse|show|entry|raw|annotate|diff)/,
242 :rev => /[a-z0-9\.\-_]+/
242 :rev => /[a-z0-9\.\-_]+/
243 }
243 }
244
244
245 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
245 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
246 get 'projects/:id/repository/graph', :to => 'repositories#graph'
246 get 'projects/:id/repository/graph', :to => 'repositories#graph'
247
247
248 get 'projects/:id/repository/changes(/*path)',
248 get 'projects/:id/repository/changes(/*path)',
249 :to => 'repositories#changes',
249 :to => 'repositories#changes',
250 :format => false
250 :format => false
251
251
252 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
252 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
253 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
253 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
254 get 'projects/:id/repository/revision', :to => 'repositories#revision'
254 get 'projects/:id/repository/revision', :to => 'repositories#revision'
255 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
255 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
256 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
256 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
257 get 'projects/:id/repository/revisions/:rev/:action(/*path)',
257 get 'projects/:id/repository/revisions/:rev/:action(/*path)',
258 :controller => 'repositories',
258 :controller => 'repositories',
259 :format => false,
259 :format => false,
260 :constraints => {
260 :constraints => {
261 :action => /(browse|show|entry|raw|annotate|diff)/,
261 :action => /(browse|show|entry|raw|annotate|diff)/,
262 :rev => /[a-z0-9\.\-_]+/
262 :rev => /[a-z0-9\.\-_]+/
263 }
263 }
264 get 'projects/:id/repository/:repository_id/:action(/*path)',
264 get 'projects/:id/repository/:repository_id/:action(/*path)',
265 :controller => 'repositories',
265 :controller => 'repositories',
266 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
266 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
267 :format => false
267 :format => false
268 get 'projects/:id/repository/:action(/*path)',
268 get 'projects/:id/repository/:action(/*path)',
269 :controller => 'repositories',
269 :controller => 'repositories',
270 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
270 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
271 :format => false
271 :format => false
272
272
273 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
273 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
274 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
274 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
275
275
276 # additional routes for having the file name at the end of url
276 # additional routes for having the file name at the end of url
277 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
277 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
278 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
278 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
279 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
279 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
280 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
280 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
281 resources :attachments, :only => [:show, :destroy]
281 resources :attachments, :only => [:show, :destroy]
282 get 'attachments/:object_type/:object_id/edit', :to => 'attachments#edit', :as => :object_attachments_edit
282 get 'attachments/:object_type/:object_id/edit', :to => 'attachments#edit', :as => :object_attachments_edit
283 patch 'attachments/:object_type/:object_id', :to => 'attachments#update', :as => :object_attachments
283 patch 'attachments/:object_type/:object_id', :to => 'attachments#update', :as => :object_attachments
284
284
285 resources :groups do
285 resources :groups do
286 resources :memberships, :controller => 'principal_memberships'
286 resources :memberships, :controller => 'principal_memberships'
287 member do
287 member do
288 get 'autocomplete_for_user'
288 get 'autocomplete_for_user'
289 end
289 end
290 end
290 end
291
291
292 get 'groups/:id/users/new', :to => 'groups#new_users', :id => /\d+/, :as => 'new_group_users'
292 get 'groups/:id/users/new', :to => 'groups#new_users', :id => /\d+/, :as => 'new_group_users'
293 post 'groups/:id/users', :to => 'groups#add_users', :id => /\d+/, :as => 'group_users'
293 post 'groups/:id/users', :to => 'groups#add_users', :id => /\d+/, :as => 'group_users'
294 delete 'groups/:id/users/:user_id', :to => 'groups#remove_user', :id => /\d+/, :as => 'group_user'
294 delete 'groups/:id/users/:user_id', :to => 'groups#remove_user', :id => /\d+/, :as => 'group_user'
295
295
296 resources :trackers, :except => :show do
296 resources :trackers, :except => :show do
297 collection do
297 collection do
298 match 'fields', :via => [:get, :post]
298 match 'fields', :via => [:get, :post]
299 end
299 end
300 end
300 end
301 resources :issue_statuses, :except => :show do
301 resources :issue_statuses, :except => :show do
302 collection do
302 collection do
303 post 'update_issue_done_ratio'
303 post 'update_issue_done_ratio'
304 end
304 end
305 end
305 end
306 resources :custom_fields, :except => :show
306 resources :custom_fields, :except => :show
307 resources :roles do
307 resources :roles do
308 collection do
308 collection do
309 match 'permissions', :via => [:get, :post]
309 match 'permissions', :via => [:get, :post]
310 end
310 end
311 end
311 end
312 resources :enumerations, :except => :show
312 resources :enumerations, :except => :show
313 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
313 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
314
314
315 get 'projects/:id/search', :controller => 'search', :action => 'index'
315 get 'projects/:id/search', :controller => 'search', :action => 'index'
316 get 'search', :controller => 'search', :action => 'index'
316 get 'search', :controller => 'search', :action => 'index'
317
317
318 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
318
319 get 'mail_handler', :to => 'mail_handler#new'
320 post 'mail_handler', :to => 'mail_handler#index'
319
321
320 match 'admin', :controller => 'admin', :action => 'index', :via => :get
322 match 'admin', :controller => 'admin', :action => 'index', :via => :get
321 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
323 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
322 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
324 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
323 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
325 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
324 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
326 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
325 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
327 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
326
328
327 resources :auth_sources do
329 resources :auth_sources do
328 member do
330 member do
329 get 'test_connection', :as => 'try_connection'
331 get 'test_connection', :as => 'try_connection'
330 end
332 end
331 collection do
333 collection do
332 get 'autocomplete_for_new_user'
334 get 'autocomplete_for_new_user'
333 end
335 end
334 end
336 end
335
337
336 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
338 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
337 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
339 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
338 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
340 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
339 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
341 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
340 match 'settings', :controller => 'settings', :action => 'index', :via => :get
342 match 'settings', :controller => 'settings', :action => 'index', :via => :get
341 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
343 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
342 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
344 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
343
345
344 match 'sys/projects', :to => 'sys#projects', :via => :get
346 match 'sys/projects', :to => 'sys#projects', :via => :get
345 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
347 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
346 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => [:get, :post]
348 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => [:get, :post]
347
349
348 match 'uploads', :to => 'attachments#upload', :via => :post
350 match 'uploads', :to => 'attachments#upload', :via => :post
349
351
350 get 'robots.txt', :to => 'welcome#robots'
352 get 'robots.txt', :to => 'welcome#robots'
351
353
352 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
354 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
353 file = File.join(plugin_dir, "config/routes.rb")
355 file = File.join(plugin_dir, "config/routes.rb")
354 if File.exists?(file)
356 if File.exists?(file)
355 begin
357 begin
356 instance_eval File.read(file)
358 instance_eval File.read(file)
357 rescue Exception => e
359 rescue Exception => e
358 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
360 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
359 exit 1
361 exit 1
360 end
362 end
361 end
363 end
362 end
364 end
363 end
365 end
@@ -1,89 +1,96
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MailHandlerControllerTest < ActionController::TestCase
20 class MailHandlerControllerTest < ActionController::TestCase
21 fixtures :users, :email_addresses, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses,
21 fixtures :users, :email_addresses, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses,
22 :trackers, :projects_trackers, :enumerations
22 :trackers, :projects_trackers, :enumerations
23
23
24 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
24 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
25
25
26 def setup
26 def setup
27 User.current = nil
27 User.current = nil
28 end
28 end
29
29
30 def test_should_create_issue
30 def test_should_create_issue
31 # Enable API and set a key
31 # Enable API and set a key
32 Setting.mail_handler_api_enabled = 1
32 Setting.mail_handler_api_enabled = 1
33 Setting.mail_handler_api_key = 'secret'
33 Setting.mail_handler_api_key = 'secret'
34
34
35 assert_difference 'Issue.count' do
35 assert_difference 'Issue.count' do
36 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
36 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
37 end
37 end
38 assert_response 201
38 assert_response 201
39 end
39 end
40
40
41 def test_should_create_issue_with_options
41 def test_should_create_issue_with_options
42 # Enable API and set a key
42 # Enable API and set a key
43 Setting.mail_handler_api_enabled = 1
43 Setting.mail_handler_api_enabled = 1
44 Setting.mail_handler_api_key = 'secret'
44 Setting.mail_handler_api_key = 'secret'
45
45
46 assert_difference 'Issue.count' do
46 assert_difference 'Issue.count' do
47 post :index, :key => 'secret',
47 post :index, :key => 'secret',
48 :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')),
48 :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')),
49 :issue => {:is_private => '1'}
49 :issue => {:is_private => '1'}
50 end
50 end
51 assert_response 201
51 assert_response 201
52 issue = Issue.order(:id => :desc).first
52 issue = Issue.order(:id => :desc).first
53 assert_equal true, issue.is_private
53 assert_equal true, issue.is_private
54 end
54 end
55
55
56 def test_should_respond_with_422_if_not_created
56 def test_should_respond_with_422_if_not_created
57 Project.find('onlinestore').destroy
57 Project.find('onlinestore').destroy
58
58
59 Setting.mail_handler_api_enabled = 1
59 Setting.mail_handler_api_enabled = 1
60 Setting.mail_handler_api_key = 'secret'
60 Setting.mail_handler_api_key = 'secret'
61
61
62 assert_no_difference 'Issue.count' do
62 assert_no_difference 'Issue.count' do
63 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
63 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
64 end
64 end
65 assert_response 422
65 assert_response 422
66 end
66 end
67
67
68 def test_should_not_allow_with_api_disabled
68 def test_should_not_allow_with_api_disabled
69 # Disable API
69 # Disable API
70 Setting.mail_handler_api_enabled = 0
70 Setting.mail_handler_api_enabled = 0
71 Setting.mail_handler_api_key = 'secret'
71 Setting.mail_handler_api_key = 'secret'
72
72
73 assert_no_difference 'Issue.count' do
73 assert_no_difference 'Issue.count' do
74 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
74 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
75 end
75 end
76 assert_response 403
76 assert_response 403
77 end
77 end
78
78
79 def test_should_not_allow_with_wrong_key
79 def test_should_not_allow_with_wrong_key
80 # Disable API
81 Setting.mail_handler_api_enabled = 1
80 Setting.mail_handler_api_enabled = 1
82 Setting.mail_handler_api_key = 'secret'
81 Setting.mail_handler_api_key = 'secret'
83
82
84 assert_no_difference 'Issue.count' do
83 assert_no_difference 'Issue.count' do
85 post :index, :key => 'wrong', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
84 post :index, :key => 'wrong', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
86 end
85 end
87 assert_response 403
86 assert_response 403
88 end
87 end
88
89 def test_new
90 Setting.mail_handler_api_enabled = 1
91 Setting.mail_handler_api_key = 'secret'
92
93 get :new, :key => 'secret'
94 assert_response :success
95 end
89 end
96 end
@@ -1,24 +1,25
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class RoutingMailHandlerTest < Redmine::RoutingTest
20 class RoutingMailHandlerTest < Redmine::RoutingTest
21 def test_mail_handler
21 def test_mail_handler
22 should_route 'GET /mail_handler' => 'mail_handler#new'
22 should_route 'POST /mail_handler' => 'mail_handler#index'
23 should_route 'POST /mail_handler' => 'mail_handler#index'
23 end
24 end
24 end
25 end
General Comments 0
You need to be logged in to leave comments. Login now