##// END OF EJS Templates
fixes #481 rhodecode emails are sent without Date header...
marcink -
r2452:d95ef658 beta
parent child Browse files
Show More
@@ -1,699 +1,700
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.4.0 (**2012-XX-XX**)
7 1.4.0 (**2012-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 - new codereview system
16 - new codereview system
17 - email map, allowing users to have multiple email addresses mapped into
17 - email map, allowing users to have multiple email addresses mapped into
18 their accounts
18 their accounts
19 - improved git-hook system. Now all actions for git are logged into journal
19 - improved git-hook system. Now all actions for git are logged into journal
20 including pushed revisions, user and IP address
20 including pushed revisions, user and IP address
21 - changed setup-app into setup-rhodecode and added default options to it.
21 - changed setup-app into setup-rhodecode and added default options to it.
22 - new git repos are created as bare now by default
22 - new git repos are created as bare now by default
23 - #464 added links to groups in permission box
23 - #464 added links to groups in permission box
24 - #465 mentions autocomplete inside comments boxes
24 - #465 mentions autocomplete inside comments boxes
25 - #469 added --update-only option to whoosh to re-index only given list
25 - #469 added --update-only option to whoosh to re-index only given list
26 of repos in index
26 of repos in index
27 - rhodecode-api CLI client
27 - rhodecode-api CLI client
28 - new git http protocol replaced buggy dulwich implementation.
28 - new git http protocol replaced buggy dulwich implementation.
29 Now based on pygrack & gitweb
29 Now based on pygrack & gitweb
30 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
30 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
31 reformated based on user suggestions. Additional rss/atom feeds for user
31 reformated based on user suggestions. Additional rss/atom feeds for user
32 journal
32 journal
33 - various i18n improvements
33 - various i18n improvements
34 - #478 permissions overview for admin in user edit view
34 - #478 permissions overview for admin in user edit view
35
35
36 fixes
36 fixes
37 +++++
37 +++++
38
38
39 - improved translations
39 - improved translations
40 - fixes issue #455 Creating an archive generates an exception on Windows
40 - fixes issue #455 Creating an archive generates an exception on Windows
41 - fixes #448 Download ZIP archive keeps file in /tmp open and results
41 - fixes #448 Download ZIP archive keeps file in /tmp open and results
42 in out of disk space
42 in out of disk space
43 - fixes issue #454 Search results under Windows include proceeding
43 - fixes issue #454 Search results under Windows include proceeding
44 backslash
44 backslash
45 - fixed issue #450. Rhodecode no longer will crash when bad revision is
45 - fixed issue #450. Rhodecode no longer will crash when bad revision is
46 present in journal data.
46 present in journal data.
47 - fix for issue #417, git execution was broken on windows for certain
47 - fix for issue #417, git execution was broken on windows for certain
48 commands.
48 commands.
49 - fixed #413. Don't disable .git directory for bare repos on deleting
49 - fixed #413. Don't disable .git directory for bare repos on deleting
50 - fixed issue #459. Changed the way of obtaining logger in reindex task.
50 - fixed issue #459. Changed the way of obtaining logger in reindex task.
51 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
51 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
52 reindexing modified files
52 reindexing modified files
53 - fixes #481 rhodecode emails are sent without Date header
53
54
54 1.3.6 (**2012-05-17**)
55 1.3.6 (**2012-05-17**)
55 ----------------------
56 ----------------------
56
57
57 news
58 news
58 ++++
59 ++++
59
60
60 - chinese traditional translation
61 - chinese traditional translation
61 - changed setup-app into setup-rhodecode and added arguments for auto-setup
62 - changed setup-app into setup-rhodecode and added arguments for auto-setup
62 mode that doesn't need user interaction
63 mode that doesn't need user interaction
63
64
64 fixes
65 fixes
65 +++++
66 +++++
66
67
67 - fixed no scm found warning
68 - fixed no scm found warning
68 - fixed __future__ import error on rcextensions
69 - fixed __future__ import error on rcextensions
69 - made simplejson required lib for speedup on JSON encoding
70 - made simplejson required lib for speedup on JSON encoding
70 - fixes #449 bad regex could get more than revisions from parsing history
71 - fixes #449 bad regex could get more than revisions from parsing history
71 - don't clear DB session when CELERY_EAGER is turned ON
72 - don't clear DB session when CELERY_EAGER is turned ON
72
73
73 1.3.5 (**2012-05-10**)
74 1.3.5 (**2012-05-10**)
74 ----------------------
75 ----------------------
75
76
76 news
77 news
77 ++++
78 ++++
78
79
79 - use ext_json for json module
80 - use ext_json for json module
80 - unified annotation view with file source view
81 - unified annotation view with file source view
81 - notification improvements, better inbox + css
82 - notification improvements, better inbox + css
82 - #419 don't strip passwords for login forms, make rhodecode
83 - #419 don't strip passwords for login forms, make rhodecode
83 more compatible with LDAP servers
84 more compatible with LDAP servers
84 - Added HTTP_X_FORWARDED_FOR as another method of extracting
85 - Added HTTP_X_FORWARDED_FOR as another method of extracting
85 IP for pull/push logs. - moved all to base controller
86 IP for pull/push logs. - moved all to base controller
86 - #415: Adding comment to changeset causes reload.
87 - #415: Adding comment to changeset causes reload.
87 Comments are now added via ajax and doesn't reload the page
88 Comments are now added via ajax and doesn't reload the page
88 - #374 LDAP config is discarded when LDAP can't be activated
89 - #374 LDAP config is discarded when LDAP can't be activated
89 - limited push/pull operations are now logged for git in the journal
90 - limited push/pull operations are now logged for git in the journal
90 - bumped mercurial to 2.2.X series
91 - bumped mercurial to 2.2.X series
91 - added support for displaying submodules in file-browser
92 - added support for displaying submodules in file-browser
92 - #421 added bookmarks in changelog view
93 - #421 added bookmarks in changelog view
93
94
94 fixes
95 fixes
95 +++++
96 +++++
96
97
97 - fixed dev-version marker for stable when served from source codes
98 - fixed dev-version marker for stable when served from source codes
98 - fixed missing permission checks on show forks page
99 - fixed missing permission checks on show forks page
99 - #418 cast to unicode fixes in notification objects
100 - #418 cast to unicode fixes in notification objects
100 - #426 fixed mention extracting regex
101 - #426 fixed mention extracting regex
101 - fixed remote-pulling for git remotes remopositories
102 - fixed remote-pulling for git remotes remopositories
102 - fixed #434: Error when accessing files or changesets of a git repository
103 - fixed #434: Error when accessing files or changesets of a git repository
103 with submodules
104 with submodules
104 - fixed issue with empty APIKEYS for users after registration ref. #438
105 - fixed issue with empty APIKEYS for users after registration ref. #438
105 - fixed issue with getting README files from git repositories
106 - fixed issue with getting README files from git repositories
106
107
107 1.3.4 (**2012-03-28**)
108 1.3.4 (**2012-03-28**)
108 ----------------------
109 ----------------------
109
110
110 news
111 news
111 ++++
112 ++++
112
113
113 - Whoosh logging is now controlled by the .ini files logging setup
114 - Whoosh logging is now controlled by the .ini files logging setup
114 - added clone-url into edit form on /settings page
115 - added clone-url into edit form on /settings page
115 - added help text into repo add/edit forms
116 - added help text into repo add/edit forms
116 - created rcextensions module with additional mappings (ref #322) and
117 - created rcextensions module with additional mappings (ref #322) and
117 post push/pull/create repo hooks callbacks
118 post push/pull/create repo hooks callbacks
118 - implemented #377 Users view for his own permissions on account page
119 - implemented #377 Users view for his own permissions on account page
119 - #399 added inheritance of permissions for users group on repos groups
120 - #399 added inheritance of permissions for users group on repos groups
120 - #401 repository group is automatically pre-selected when adding repos
121 - #401 repository group is automatically pre-selected when adding repos
121 inside a repository group
122 inside a repository group
122 - added alternative HTTP 403 response when client failed to authenticate. Helps
123 - added alternative HTTP 403 response when client failed to authenticate. Helps
123 solving issues with Mercurial and LDAP
124 solving issues with Mercurial and LDAP
124 - #402 removed group prefix from repository name when listing repositories
125 - #402 removed group prefix from repository name when listing repositories
125 inside a group
126 inside a group
126 - added gravatars into permission view and permissions autocomplete
127 - added gravatars into permission view and permissions autocomplete
127 - #347 when running multiple RhodeCode instances, properly invalidates cache
128 - #347 when running multiple RhodeCode instances, properly invalidates cache
128 for all registered servers
129 for all registered servers
129
130
130 fixes
131 fixes
131 +++++
132 +++++
132
133
133 - fixed #390 cache invalidation problems on repos inside group
134 - fixed #390 cache invalidation problems on repos inside group
134 - fixed #385 clone by ID url was loosing proxy prefix in URL
135 - fixed #385 clone by ID url was loosing proxy prefix in URL
135 - fixed some unicode problems with waitress
136 - fixed some unicode problems with waitress
136 - fixed issue with escaping < and > in changeset commits
137 - fixed issue with escaping < and > in changeset commits
137 - fixed error occurring during recursive group creation in API
138 - fixed error occurring during recursive group creation in API
138 create_repo function
139 create_repo function
139 - fixed #393 py2.5 fixes for routes url generator
140 - fixed #393 py2.5 fixes for routes url generator
140 - fixed #397 Private repository groups shows up before login
141 - fixed #397 Private repository groups shows up before login
141 - fixed #396 fixed problems with revoking users in nested groups
142 - fixed #396 fixed problems with revoking users in nested groups
142 - fixed mysql unicode issues + specified InnoDB as default engine with
143 - fixed mysql unicode issues + specified InnoDB as default engine with
143 utf8 charset
144 utf8 charset
144 - #406 trim long branch/tag names in changelog to not break UI
145 - #406 trim long branch/tag names in changelog to not break UI
145
146
146 1.3.3 (**2012-03-02**)
147 1.3.3 (**2012-03-02**)
147 ----------------------
148 ----------------------
148
149
149 news
150 news
150 ++++
151 ++++
151
152
152
153
153 fixes
154 fixes
154 +++++
155 +++++
155
156
156 - fixed some python2.5 compatibility issues
157 - fixed some python2.5 compatibility issues
157 - fixed issues with removed repos was accidentally added as groups, after
158 - fixed issues with removed repos was accidentally added as groups, after
158 full rescan of paths
159 full rescan of paths
159 - fixes #376 Cannot edit user (using container auth)
160 - fixes #376 Cannot edit user (using container auth)
160 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
161 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
161 configuration
162 configuration
162 - fixed initial sorting of repos inside repo group
163 - fixed initial sorting of repos inside repo group
163 - fixes issue when user tried to resubmit same permission into user/user_groups
164 - fixes issue when user tried to resubmit same permission into user/user_groups
164 - bumped beaker version that fixes #375 leap error bug
165 - bumped beaker version that fixes #375 leap error bug
165 - fixed raw_changeset for git. It was generated with hg patch headers
166 - fixed raw_changeset for git. It was generated with hg patch headers
166 - fixed vcs issue with last_changeset for filenodes
167 - fixed vcs issue with last_changeset for filenodes
167 - fixed missing commit after hook delete
168 - fixed missing commit after hook delete
168 - fixed #372 issues with git operation detection that caused a security issue
169 - fixed #372 issues with git operation detection that caused a security issue
169 for git repos
170 for git repos
170
171
171 1.3.2 (**2012-02-28**)
172 1.3.2 (**2012-02-28**)
172 ----------------------
173 ----------------------
173
174
174 news
175 news
175 ++++
176 ++++
176
177
177
178
178 fixes
179 fixes
179 +++++
180 +++++
180
181
181 - fixed git protocol issues with repos-groups
182 - fixed git protocol issues with repos-groups
182 - fixed git remote repos validator that prevented from cloning remote git repos
183 - fixed git remote repos validator that prevented from cloning remote git repos
183 - fixes #370 ending slashes fixes for repo and groups
184 - fixes #370 ending slashes fixes for repo and groups
184 - fixes #368 improved git-protocol detection to handle other clients
185 - fixes #368 improved git-protocol detection to handle other clients
185 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
186 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
186 Moved To Root
187 Moved To Root
187 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
188 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
188 - fixed #373 missing cascade drop on user_group_to_perm table
189 - fixed #373 missing cascade drop on user_group_to_perm table
189
190
190 1.3.1 (**2012-02-27**)
191 1.3.1 (**2012-02-27**)
191 ----------------------
192 ----------------------
192
193
193 news
194 news
194 ++++
195 ++++
195
196
196
197
197 fixes
198 fixes
198 +++++
199 +++++
199
200
200 - redirection loop occurs when remember-me wasn't checked during login
201 - redirection loop occurs when remember-me wasn't checked during login
201 - fixes issues with git blob history generation
202 - fixes issues with git blob history generation
202 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
203 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
203
204
204 1.3.0 (**2012-02-26**)
205 1.3.0 (**2012-02-26**)
205 ----------------------
206 ----------------------
206
207
207 news
208 news
208 ++++
209 ++++
209
210
210 - code review, inspired by github code-comments
211 - code review, inspired by github code-comments
211 - #215 rst and markdown README files support
212 - #215 rst and markdown README files support
212 - #252 Container-based and proxy pass-through authentication support
213 - #252 Container-based and proxy pass-through authentication support
213 - #44 branch browser. Filtering of changelog by branches
214 - #44 branch browser. Filtering of changelog by branches
214 - mercurial bookmarks support
215 - mercurial bookmarks support
215 - new hover top menu, optimized to add maximum size for important views
216 - new hover top menu, optimized to add maximum size for important views
216 - configurable clone url template with possibility to specify protocol like
217 - configurable clone url template with possibility to specify protocol like
217 ssh:// or http:// and also manually alter other parts of clone_url.
218 ssh:// or http:// and also manually alter other parts of clone_url.
218 - enabled largefiles extension by default
219 - enabled largefiles extension by default
219 - optimized summary file pages and saved a lot of unused space in them
220 - optimized summary file pages and saved a lot of unused space in them
220 - #239 option to manually mark repository as fork
221 - #239 option to manually mark repository as fork
221 - #320 mapping of commit authors to RhodeCode users
222 - #320 mapping of commit authors to RhodeCode users
222 - #304 hashes are displayed using monospace font
223 - #304 hashes are displayed using monospace font
223 - diff configuration, toggle white lines and context lines
224 - diff configuration, toggle white lines and context lines
224 - #307 configurable diffs, whitespace toggle, increasing context lines
225 - #307 configurable diffs, whitespace toggle, increasing context lines
225 - sorting on branches, tags and bookmarks using YUI datatable
226 - sorting on branches, tags and bookmarks using YUI datatable
226 - improved file filter on files page
227 - improved file filter on files page
227 - implements #330 api method for listing nodes ar particular revision
228 - implements #330 api method for listing nodes ar particular revision
228 - #73 added linking issues in commit messages to chosen issue tracker url
229 - #73 added linking issues in commit messages to chosen issue tracker url
229 based on user defined regular expression
230 based on user defined regular expression
230 - added linking of changesets in commit messages
231 - added linking of changesets in commit messages
231 - new compact changelog with expandable commit messages
232 - new compact changelog with expandable commit messages
232 - firstname and lastname are optional in user creation
233 - firstname and lastname are optional in user creation
233 - #348 added post-create repository hook
234 - #348 added post-create repository hook
234 - #212 global encoding settings is now configurable from .ini files
235 - #212 global encoding settings is now configurable from .ini files
235 - #227 added repository groups permissions
236 - #227 added repository groups permissions
236 - markdown gets codehilite extensions
237 - markdown gets codehilite extensions
237 - new API methods, delete_repositories, grante/revoke permissions for groups
238 - new API methods, delete_repositories, grante/revoke permissions for groups
238 and repos
239 and repos
239
240
240
241
241 fixes
242 fixes
242 +++++
243 +++++
243
244
244 - rewrote dbsession management for atomic operations, and better error handling
245 - rewrote dbsession management for atomic operations, and better error handling
245 - fixed sorting of repo tables
246 - fixed sorting of repo tables
246 - #326 escape of special html entities in diffs
247 - #326 escape of special html entities in diffs
247 - normalized user_name => username in api attributes
248 - normalized user_name => username in api attributes
248 - fixes #298 ldap created users with mixed case emails created conflicts
249 - fixes #298 ldap created users with mixed case emails created conflicts
249 on saving a form
250 on saving a form
250 - fixes issue when owner of a repo couldn't revoke permissions for users
251 - fixes issue when owner of a repo couldn't revoke permissions for users
251 and groups
252 and groups
252 - fixes #271 rare JSON serialization problem with statistics
253 - fixes #271 rare JSON serialization problem with statistics
253 - fixes #337 missing validation check for conflicting names of a group with a
254 - fixes #337 missing validation check for conflicting names of a group with a
254 repositories group
255 repositories group
255 - #340 fixed session problem for mysql and celery tasks
256 - #340 fixed session problem for mysql and celery tasks
256 - fixed #331 RhodeCode mangles repository names if the a repository group
257 - fixed #331 RhodeCode mangles repository names if the a repository group
257 contains the "full path" to the repositories
258 contains the "full path" to the repositories
258 - #355 RhodeCode doesn't store encrypted LDAP passwords
259 - #355 RhodeCode doesn't store encrypted LDAP passwords
259
260
260 1.2.5 (**2012-01-28**)
261 1.2.5 (**2012-01-28**)
261 ----------------------
262 ----------------------
262
263
263 news
264 news
264 ++++
265 ++++
265
266
266 fixes
267 fixes
267 +++++
268 +++++
268
269
269 - #340 Celery complains about MySQL server gone away, added session cleanup
270 - #340 Celery complains about MySQL server gone away, added session cleanup
270 for celery tasks
271 for celery tasks
271 - #341 "scanning for repositories in None" log message during Rescan was missing
272 - #341 "scanning for repositories in None" log message during Rescan was missing
272 a parameter
273 a parameter
273 - fixed creating archives with subrepos. Some hooks were triggered during that
274 - fixed creating archives with subrepos. Some hooks were triggered during that
274 operation leading to crash.
275 operation leading to crash.
275 - fixed missing email in account page.
276 - fixed missing email in account page.
276 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
277 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
277 forking on windows impossible
278 forking on windows impossible
278
279
279 1.2.4 (**2012-01-19**)
280 1.2.4 (**2012-01-19**)
280 ----------------------
281 ----------------------
281
282
282 news
283 news
283 ++++
284 ++++
284
285
285 - RhodeCode is bundled with mercurial series 2.0.X by default, with
286 - RhodeCode is bundled with mercurial series 2.0.X by default, with
286 full support to largefiles extension. Enabled by default in new installations
287 full support to largefiles extension. Enabled by default in new installations
287 - #329 Ability to Add/Remove Groups to/from a Repository via AP
288 - #329 Ability to Add/Remove Groups to/from a Repository via AP
288 - added requires.txt file with requirements
289 - added requires.txt file with requirements
289
290
290 fixes
291 fixes
291 +++++
292 +++++
292
293
293 - fixes db session issues with celery when emailing admins
294 - fixes db session issues with celery when emailing admins
294 - #331 RhodeCode mangles repository names if the a repository group
295 - #331 RhodeCode mangles repository names if the a repository group
295 contains the "full path" to the repositories
296 contains the "full path" to the repositories
296 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
297 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
297 - DB session cleanup after hg protocol operations, fixes issues with
298 - DB session cleanup after hg protocol operations, fixes issues with
298 `mysql has gone away` errors
299 `mysql has gone away` errors
299 - #333 doc fixes for get_repo api function
300 - #333 doc fixes for get_repo api function
300 - #271 rare JSON serialization problem with statistics enabled
301 - #271 rare JSON serialization problem with statistics enabled
301 - #337 Fixes issues with validation of repository name conflicting with
302 - #337 Fixes issues with validation of repository name conflicting with
302 a group name. A proper message is now displayed.
303 a group name. A proper message is now displayed.
303 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
304 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
304 doesn't work
305 doesn't work
305 - #316 fixes issues with web description in hgrc files
306 - #316 fixes issues with web description in hgrc files
306
307
307 1.2.3 (**2011-11-02**)
308 1.2.3 (**2011-11-02**)
308 ----------------------
309 ----------------------
309
310
310 news
311 news
311 ++++
312 ++++
312
313
313 - added option to manage repos group for non admin users
314 - added option to manage repos group for non admin users
314 - added following API methods for get_users, create_user, get_users_groups,
315 - added following API methods for get_users, create_user, get_users_groups,
315 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
316 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
316 get_repo, create_repo, add_user_to_repo
317 get_repo, create_repo, add_user_to_repo
317 - implements #237 added password confirmation for my account
318 - implements #237 added password confirmation for my account
318 and admin edit user.
319 and admin edit user.
319 - implements #291 email notification for global events are now sent to all
320 - implements #291 email notification for global events are now sent to all
320 administrator users, and global config email.
321 administrator users, and global config email.
321
322
322 fixes
323 fixes
323 +++++
324 +++++
324
325
325 - added option for passing auth method for smtp mailer
326 - added option for passing auth method for smtp mailer
326 - #276 issue with adding a single user with id>10 to usergroups
327 - #276 issue with adding a single user with id>10 to usergroups
327 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
328 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
328 - #288 fixes managing of repos in a group for non admin user
329 - #288 fixes managing of repos in a group for non admin user
329
330
330 1.2.2 (**2011-10-17**)
331 1.2.2 (**2011-10-17**)
331 ----------------------
332 ----------------------
332
333
333 news
334 news
334 ++++
335 ++++
335
336
336 - #226 repo groups are available by path instead of numerical id
337 - #226 repo groups are available by path instead of numerical id
337
338
338 fixes
339 fixes
339 +++++
340 +++++
340
341
341 - #259 Groups with the same name but with different parent group
342 - #259 Groups with the same name but with different parent group
342 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
343 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
343 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
344 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
344 - #265 ldap save fails sometimes on converting attributes to booleans,
345 - #265 ldap save fails sometimes on converting attributes to booleans,
345 added getter and setter into model that will prevent from this on db model level
346 added getter and setter into model that will prevent from this on db model level
346 - fixed problems with timestamps issues #251 and #213
347 - fixed problems with timestamps issues #251 and #213
347 - fixes #266 RhodeCode allows to create repo with the same name and in
348 - fixes #266 RhodeCode allows to create repo with the same name and in
348 the same parent as group
349 the same parent as group
349 - fixes #245 Rescan of the repositories on Windows
350 - fixes #245 Rescan of the repositories on Windows
350 - fixes #248 cannot edit repos inside a group on windows
351 - fixes #248 cannot edit repos inside a group on windows
351 - fixes #219 forking problems on windows
352 - fixes #219 forking problems on windows
352
353
353 1.2.1 (**2011-10-08**)
354 1.2.1 (**2011-10-08**)
354 ----------------------
355 ----------------------
355
356
356 news
357 news
357 ++++
358 ++++
358
359
359
360
360 fixes
361 fixes
361 +++++
362 +++++
362
363
363 - fixed problems with basic auth and push problems
364 - fixed problems with basic auth and push problems
364 - gui fixes
365 - gui fixes
365 - fixed logger
366 - fixed logger
366
367
367 1.2.0 (**2011-10-07**)
368 1.2.0 (**2011-10-07**)
368 ----------------------
369 ----------------------
369
370
370 news
371 news
371 ++++
372 ++++
372
373
373 - implemented #47 repository groups
374 - implemented #47 repository groups
374 - implemented #89 Can setup google analytics code from settings menu
375 - implemented #89 Can setup google analytics code from settings menu
375 - implemented #91 added nicer looking archive urls with more download options
376 - implemented #91 added nicer looking archive urls with more download options
376 like tags, branches
377 like tags, branches
377 - implemented #44 into file browsing, and added follow branch option
378 - implemented #44 into file browsing, and added follow branch option
378 - implemented #84 downloads can be enabled/disabled for each repository
379 - implemented #84 downloads can be enabled/disabled for each repository
379 - anonymous repository can be cloned without having to pass default:default
380 - anonymous repository can be cloned without having to pass default:default
380 into clone url
381 into clone url
381 - fixed #90 whoosh indexer can index chooses repositories passed in command
382 - fixed #90 whoosh indexer can index chooses repositories passed in command
382 line
383 line
383 - extended journal with day aggregates and paging
384 - extended journal with day aggregates and paging
384 - implemented #107 source code lines highlight ranges
385 - implemented #107 source code lines highlight ranges
385 - implemented #93 customizable changelog on combined revision ranges -
386 - implemented #93 customizable changelog on combined revision ranges -
386 equivalent of githubs compare view
387 equivalent of githubs compare view
387 - implemented #108 extended and more powerful LDAP configuration
388 - implemented #108 extended and more powerful LDAP configuration
388 - implemented #56 users groups
389 - implemented #56 users groups
389 - major code rewrites optimized codes for speed and memory usage
390 - major code rewrites optimized codes for speed and memory usage
390 - raw and diff downloads are now in git format
391 - raw and diff downloads are now in git format
391 - setup command checks for write access to given path
392 - setup command checks for write access to given path
392 - fixed many issues with international characters and unicode. It uses utf8
393 - fixed many issues with international characters and unicode. It uses utf8
393 decode with replace to provide less errors even with non utf8 encoded strings
394 decode with replace to provide less errors even with non utf8 encoded strings
394 - #125 added API KEY access to feeds
395 - #125 added API KEY access to feeds
395 - #109 Repository can be created from external Mercurial link (aka. remote
396 - #109 Repository can be created from external Mercurial link (aka. remote
396 repository, and manually updated (via pull) from admin panel
397 repository, and manually updated (via pull) from admin panel
397 - beta git support - push/pull server + basic view for git repos
398 - beta git support - push/pull server + basic view for git repos
398 - added followers page and forks page
399 - added followers page and forks page
399 - server side file creation (with binary file upload interface)
400 - server side file creation (with binary file upload interface)
400 and edition with commits powered by codemirror
401 and edition with commits powered by codemirror
401 - #111 file browser file finder, quick lookup files on whole file tree
402 - #111 file browser file finder, quick lookup files on whole file tree
402 - added quick login sliding menu into main page
403 - added quick login sliding menu into main page
403 - changelog uses lazy loading of affected files details, in some scenarios
404 - changelog uses lazy loading of affected files details, in some scenarios
404 this can improve speed of changelog page dramatically especially for
405 this can improve speed of changelog page dramatically especially for
405 larger repositories.
406 larger repositories.
406 - implements #214 added support for downloading subrepos in download menu.
407 - implements #214 added support for downloading subrepos in download menu.
407 - Added basic API for direct operations on rhodecode via JSON
408 - Added basic API for direct operations on rhodecode via JSON
408 - Implemented advanced hook management
409 - Implemented advanced hook management
409
410
410 fixes
411 fixes
411 +++++
412 +++++
412
413
413 - fixed file browser bug, when switching into given form revision the url was
414 - fixed file browser bug, when switching into given form revision the url was
414 not changing
415 not changing
415 - fixed propagation to error controller on simplehg and simplegit middlewares
416 - fixed propagation to error controller on simplehg and simplegit middlewares
416 - fixed error when trying to make a download on empty repository
417 - fixed error when trying to make a download on empty repository
417 - fixed problem with '[' chars in commit messages in journal
418 - fixed problem with '[' chars in commit messages in journal
418 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
419 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
419 - journal fork fixes
420 - journal fork fixes
420 - removed issue with space inside renamed repository after deletion
421 - removed issue with space inside renamed repository after deletion
421 - fixed strange issue on formencode imports
422 - fixed strange issue on formencode imports
422 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
423 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
423 - #150 fixes for errors on repositories mapped in db but corrupted in
424 - #150 fixes for errors on repositories mapped in db but corrupted in
424 filesystem
425 filesystem
425 - fixed problem with ascendant characters in realm #181
426 - fixed problem with ascendant characters in realm #181
426 - fixed problem with sqlite file based database connection pool
427 - fixed problem with sqlite file based database connection pool
427 - whoosh indexer and code stats share the same dynamic extensions map
428 - whoosh indexer and code stats share the same dynamic extensions map
428 - fixes #188 - relationship delete of repo_to_perm entry on user removal
429 - fixes #188 - relationship delete of repo_to_perm entry on user removal
429 - fixes issue #189 Trending source files shows "show more" when no more exist
430 - fixes issue #189 Trending source files shows "show more" when no more exist
430 - fixes issue #197 Relative paths for pidlocks
431 - fixes issue #197 Relative paths for pidlocks
431 - fixes issue #198 password will require only 3 chars now for login form
432 - fixes issue #198 password will require only 3 chars now for login form
432 - fixes issue #199 wrong redirection for non admin users after creating a repository
433 - fixes issue #199 wrong redirection for non admin users after creating a repository
433 - fixes issues #202, bad db constraint made impossible to attach same group
434 - fixes issues #202, bad db constraint made impossible to attach same group
434 more than one time. Affects only mysql/postgres
435 more than one time. Affects only mysql/postgres
435 - fixes #218 os.kill patch for windows was missing sig param
436 - fixes #218 os.kill patch for windows was missing sig param
436 - improved rendering of dag (they are not trimmed anymore when number of
437 - improved rendering of dag (they are not trimmed anymore when number of
437 heads exceeds 5)
438 heads exceeds 5)
438
439
439 1.1.8 (**2011-04-12**)
440 1.1.8 (**2011-04-12**)
440 ----------------------
441 ----------------------
441
442
442 news
443 news
443 ++++
444 ++++
444
445
445 - improved windows support
446 - improved windows support
446
447
447 fixes
448 fixes
448 +++++
449 +++++
449
450
450 - fixed #140 freeze of python dateutil library, since new version is python2.x
451 - fixed #140 freeze of python dateutil library, since new version is python2.x
451 incompatible
452 incompatible
452 - setup-app will check for write permission in given path
453 - setup-app will check for write permission in given path
453 - cleaned up license info issue #149
454 - cleaned up license info issue #149
454 - fixes for issues #137,#116 and problems with unicode and accented characters.
455 - fixes for issues #137,#116 and problems with unicode and accented characters.
455 - fixes crashes on gravatar, when passed in email as unicode
456 - fixes crashes on gravatar, when passed in email as unicode
456 - fixed tooltip flickering problems
457 - fixed tooltip flickering problems
457 - fixed came_from redirection on windows
458 - fixed came_from redirection on windows
458 - fixed logging modules, and sql formatters
459 - fixed logging modules, and sql formatters
459 - windows fixes for os.kill issue #133
460 - windows fixes for os.kill issue #133
460 - fixes path splitting for windows issues #148
461 - fixes path splitting for windows issues #148
461 - fixed issue #143 wrong import on migration to 1.1.X
462 - fixed issue #143 wrong import on migration to 1.1.X
462 - fixed problems with displaying binary files, thanks to Thomas Waldmann
463 - fixed problems with displaying binary files, thanks to Thomas Waldmann
463 - removed name from archive files since it's breaking ui for long repo names
464 - removed name from archive files since it's breaking ui for long repo names
464 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
465 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
465 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
466 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
466 Thomas Waldmann
467 Thomas Waldmann
467 - fixed issue #166 summary pager was skipping 10 revisions on second page
468 - fixed issue #166 summary pager was skipping 10 revisions on second page
468
469
469
470
470 1.1.7 (**2011-03-23**)
471 1.1.7 (**2011-03-23**)
471 ----------------------
472 ----------------------
472
473
473 news
474 news
474 ++++
475 ++++
475
476
476 fixes
477 fixes
477 +++++
478 +++++
478
479
479 - fixed (again) #136 installation support for FreeBSD
480 - fixed (again) #136 installation support for FreeBSD
480
481
481
482
482 1.1.6 (**2011-03-21**)
483 1.1.6 (**2011-03-21**)
483 ----------------------
484 ----------------------
484
485
485 news
486 news
486 ++++
487 ++++
487
488
488 fixes
489 fixes
489 +++++
490 +++++
490
491
491 - fixed #136 installation support for FreeBSD
492 - fixed #136 installation support for FreeBSD
492 - RhodeCode will check for python version during installation
493 - RhodeCode will check for python version during installation
493
494
494 1.1.5 (**2011-03-17**)
495 1.1.5 (**2011-03-17**)
495 ----------------------
496 ----------------------
496
497
497 news
498 news
498 ++++
499 ++++
499
500
500 - basic windows support, by exchanging pybcrypt into sha256 for windows only
501 - basic windows support, by exchanging pybcrypt into sha256 for windows only
501 highly inspired by idea of mantis406
502 highly inspired by idea of mantis406
502
503
503 fixes
504 fixes
504 +++++
505 +++++
505
506
506 - fixed sorting by author in main page
507 - fixed sorting by author in main page
507 - fixed crashes with diffs on binary files
508 - fixed crashes with diffs on binary files
508 - fixed #131 problem with boolean values for LDAP
509 - fixed #131 problem with boolean values for LDAP
509 - fixed #122 mysql problems thanks to striker69
510 - fixed #122 mysql problems thanks to striker69
510 - fixed problem with errors on calling raw/raw_files/annotate functions
511 - fixed problem with errors on calling raw/raw_files/annotate functions
511 with unknown revisions
512 with unknown revisions
512 - fixed returned rawfiles attachment names with international character
513 - fixed returned rawfiles attachment names with international character
513 - cleaned out docs, big thanks to Jason Harris
514 - cleaned out docs, big thanks to Jason Harris
514
515
515 1.1.4 (**2011-02-19**)
516 1.1.4 (**2011-02-19**)
516 ----------------------
517 ----------------------
517
518
518 news
519 news
519 ++++
520 ++++
520
521
521 fixes
522 fixes
522 +++++
523 +++++
523
524
524 - fixed formencode import problem on settings page, that caused server crash
525 - fixed formencode import problem on settings page, that caused server crash
525 when that page was accessed as first after server start
526 when that page was accessed as first after server start
526 - journal fixes
527 - journal fixes
527 - fixed option to access repository just by entering http://server/<repo_name>
528 - fixed option to access repository just by entering http://server/<repo_name>
528
529
529 1.1.3 (**2011-02-16**)
530 1.1.3 (**2011-02-16**)
530 ----------------------
531 ----------------------
531
532
532 news
533 news
533 ++++
534 ++++
534
535
535 - implemented #102 allowing the '.' character in username
536 - implemented #102 allowing the '.' character in username
536 - added option to access repository just by entering http://server/<repo_name>
537 - added option to access repository just by entering http://server/<repo_name>
537 - celery task ignores result for better performance
538 - celery task ignores result for better performance
538
539
539 fixes
540 fixes
540 +++++
541 +++++
541
542
542 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
543 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
543 apollo13 and Johan Walles
544 apollo13 and Johan Walles
544 - small fixes in journal
545 - small fixes in journal
545 - fixed problems with getting setting for celery from .ini files
546 - fixed problems with getting setting for celery from .ini files
546 - registration, password reset and login boxes share the same title as main
547 - registration, password reset and login boxes share the same title as main
547 application now
548 application now
548 - fixed #113: to high permissions to fork repository
549 - fixed #113: to high permissions to fork repository
549 - fixed problem with '[' chars in commit messages in journal
550 - fixed problem with '[' chars in commit messages in journal
550 - removed issue with space inside renamed repository after deletion
551 - removed issue with space inside renamed repository after deletion
551 - db transaction fixes when filesystem repository creation failed
552 - db transaction fixes when filesystem repository creation failed
552 - fixed #106 relation issues on databases different than sqlite
553 - fixed #106 relation issues on databases different than sqlite
553 - fixed static files paths links to use of url() method
554 - fixed static files paths links to use of url() method
554
555
555 1.1.2 (**2011-01-12**)
556 1.1.2 (**2011-01-12**)
556 ----------------------
557 ----------------------
557
558
558 news
559 news
559 ++++
560 ++++
560
561
561
562
562 fixes
563 fixes
563 +++++
564 +++++
564
565
565 - fixes #98 protection against float division of percentage stats
566 - fixes #98 protection against float division of percentage stats
566 - fixed graph bug
567 - fixed graph bug
567 - forced webhelpers version since it was making troubles during installation
568 - forced webhelpers version since it was making troubles during installation
568
569
569 1.1.1 (**2011-01-06**)
570 1.1.1 (**2011-01-06**)
570 ----------------------
571 ----------------------
571
572
572 news
573 news
573 ++++
574 ++++
574
575
575 - added force https option into ini files for easier https usage (no need to
576 - added force https option into ini files for easier https usage (no need to
576 set server headers with this options)
577 set server headers with this options)
577 - small css updates
578 - small css updates
578
579
579 fixes
580 fixes
580 +++++
581 +++++
581
582
582 - fixed #96 redirect loop on files view on repositories without changesets
583 - fixed #96 redirect loop on files view on repositories without changesets
583 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
584 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
584 and server crashed with errors
585 and server crashed with errors
585 - fixed large tooltips problems on main page
586 - fixed large tooltips problems on main page
586 - fixed #92 whoosh indexer is more error proof
587 - fixed #92 whoosh indexer is more error proof
587
588
588 1.1.0 (**2010-12-18**)
589 1.1.0 (**2010-12-18**)
589 ----------------------
590 ----------------------
590
591
591 news
592 news
592 ++++
593 ++++
593
594
594 - rewrite of internals for vcs >=0.1.10
595 - rewrite of internals for vcs >=0.1.10
595 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
596 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
596 with older clients
597 with older clients
597 - anonymous access, authentication via ldap
598 - anonymous access, authentication via ldap
598 - performance upgrade for cached repos list - each repository has its own
599 - performance upgrade for cached repos list - each repository has its own
599 cache that's invalidated when needed.
600 cache that's invalidated when needed.
600 - performance upgrades on repositories with large amount of commits (20K+)
601 - performance upgrades on repositories with large amount of commits (20K+)
601 - main page quick filter for filtering repositories
602 - main page quick filter for filtering repositories
602 - user dashboards with ability to follow chosen repositories actions
603 - user dashboards with ability to follow chosen repositories actions
603 - sends email to admin on new user registration
604 - sends email to admin on new user registration
604 - added cache/statistics reset options into repository settings
605 - added cache/statistics reset options into repository settings
605 - more detailed action logger (based on hooks) with pushed changesets lists
606 - more detailed action logger (based on hooks) with pushed changesets lists
606 and options to disable those hooks from admin panel
607 and options to disable those hooks from admin panel
607 - introduced new enhanced changelog for merges that shows more accurate results
608 - introduced new enhanced changelog for merges that shows more accurate results
608 - new improved and faster code stats (based on pygments lexers mapping tables,
609 - new improved and faster code stats (based on pygments lexers mapping tables,
609 showing up to 10 trending sources for each repository. Additionally stats
610 showing up to 10 trending sources for each repository. Additionally stats
610 can be disabled in repository settings.
611 can be disabled in repository settings.
611 - gui optimizations, fixed application width to 1024px
612 - gui optimizations, fixed application width to 1024px
612 - added cut off (for large files/changesets) limit into config files
613 - added cut off (for large files/changesets) limit into config files
613 - whoosh, celeryd, upgrade moved to paster command
614 - whoosh, celeryd, upgrade moved to paster command
614 - other than sqlite database backends can be used
615 - other than sqlite database backends can be used
615
616
616 fixes
617 fixes
617 +++++
618 +++++
618
619
619 - fixes #61 forked repo was showing only after cache expired
620 - fixes #61 forked repo was showing only after cache expired
620 - fixes #76 no confirmation on user deletes
621 - fixes #76 no confirmation on user deletes
621 - fixes #66 Name field misspelled
622 - fixes #66 Name field misspelled
622 - fixes #72 block user removal when he owns repositories
623 - fixes #72 block user removal when he owns repositories
623 - fixes #69 added password confirmation fields
624 - fixes #69 added password confirmation fields
624 - fixes #87 RhodeCode crashes occasionally on updating repository owner
625 - fixes #87 RhodeCode crashes occasionally on updating repository owner
625 - fixes #82 broken annotations on files with more than 1 blank line at the end
626 - fixes #82 broken annotations on files with more than 1 blank line at the end
626 - a lot of fixes and tweaks for file browser
627 - a lot of fixes and tweaks for file browser
627 - fixed detached session issues
628 - fixed detached session issues
628 - fixed when user had no repos he would see all repos listed in my account
629 - fixed when user had no repos he would see all repos listed in my account
629 - fixed ui() instance bug when global hgrc settings was loaded for server
630 - fixed ui() instance bug when global hgrc settings was loaded for server
630 instance and all hgrc options were merged with our db ui() object
631 instance and all hgrc options were merged with our db ui() object
631 - numerous small bugfixes
632 - numerous small bugfixes
632
633
633 (special thanks for TkSoh for detailed feedback)
634 (special thanks for TkSoh for detailed feedback)
634
635
635
636
636 1.0.2 (**2010-11-12**)
637 1.0.2 (**2010-11-12**)
637 ----------------------
638 ----------------------
638
639
639 news
640 news
640 ++++
641 ++++
641
642
642 - tested under python2.7
643 - tested under python2.7
643 - bumped sqlalchemy and celery versions
644 - bumped sqlalchemy and celery versions
644
645
645 fixes
646 fixes
646 +++++
647 +++++
647
648
648 - fixed #59 missing graph.js
649 - fixed #59 missing graph.js
649 - fixed repo_size crash when repository had broken symlinks
650 - fixed repo_size crash when repository had broken symlinks
650 - fixed python2.5 crashes.
651 - fixed python2.5 crashes.
651
652
652
653
653 1.0.1 (**2010-11-10**)
654 1.0.1 (**2010-11-10**)
654 ----------------------
655 ----------------------
655
656
656 news
657 news
657 ++++
658 ++++
658
659
659 - small css updated
660 - small css updated
660
661
661 fixes
662 fixes
662 +++++
663 +++++
663
664
664 - fixed #53 python2.5 incompatible enumerate calls
665 - fixed #53 python2.5 incompatible enumerate calls
665 - fixed #52 disable mercurial extension for web
666 - fixed #52 disable mercurial extension for web
666 - fixed #51 deleting repositories don't delete it's dependent objects
667 - fixed #51 deleting repositories don't delete it's dependent objects
667
668
668
669
669 1.0.0 (**2010-11-02**)
670 1.0.0 (**2010-11-02**)
670 ----------------------
671 ----------------------
671
672
672 - security bugfix simplehg wasn't checking for permissions on commands
673 - security bugfix simplehg wasn't checking for permissions on commands
673 other than pull or push.
674 other than pull or push.
674 - fixed doubled messages after push or pull in admin journal
675 - fixed doubled messages after push or pull in admin journal
675 - templating and css corrections, fixed repo switcher on chrome, updated titles
676 - templating and css corrections, fixed repo switcher on chrome, updated titles
676 - admin menu accessible from options menu on repository view
677 - admin menu accessible from options menu on repository view
677 - permissions cached queries
678 - permissions cached queries
678
679
679 1.0.0rc4 (**2010-10-12**)
680 1.0.0rc4 (**2010-10-12**)
680 --------------------------
681 --------------------------
681
682
682 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
683 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
683 - removed cache_manager settings from sqlalchemy meta
684 - removed cache_manager settings from sqlalchemy meta
684 - added sqlalchemy cache settings to ini files
685 - added sqlalchemy cache settings to ini files
685 - validated password length and added second try of failure on paster setup-app
686 - validated password length and added second try of failure on paster setup-app
686 - fixed setup database destroy prompt even when there was no db
687 - fixed setup database destroy prompt even when there was no db
687
688
688
689
689 1.0.0rc3 (**2010-10-11**)
690 1.0.0rc3 (**2010-10-11**)
690 -------------------------
691 -------------------------
691
692
692 - fixed i18n during installation.
693 - fixed i18n during installation.
693
694
694 1.0.0rc2 (**2010-10-11**)
695 1.0.0rc2 (**2010-10-11**)
695 -------------------------
696 -------------------------
696
697
697 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
698 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
698 occure. After vcs is fixed it'll be put back again.
699 occure. After vcs is fixed it'll be put back again.
699 - templating/css rewrites, optimized css. No newline at end of file
700 - templating/css rewrites, optimized css.
@@ -1,182 +1,183
1 from rhodecode.lib.rcmail.response import MailResponse
1 from rhodecode.lib.rcmail.response import MailResponse
2
2
3 from rhodecode.lib.rcmail.exceptions import BadHeaders
3 from rhodecode.lib.rcmail.exceptions import BadHeaders
4 from rhodecode.lib.rcmail.exceptions import InvalidMessage
4 from rhodecode.lib.rcmail.exceptions import InvalidMessage
5
5
6
6 class Attachment(object):
7 class Attachment(object):
7 """
8 """
8 Encapsulates file attachment information.
9 Encapsulates file attachment information.
9
10
10 :param filename: filename of attachment
11 :param filename: filename of attachment
11 :param content_type: file mimetype
12 :param content_type: file mimetype
12 :param data: the raw file data, either as string or file obj
13 :param data: the raw file data, either as string or file obj
13 :param disposition: content-disposition (if any)
14 :param disposition: content-disposition (if any)
14 """
15 """
15
16
16 def __init__(self,
17 def __init__(self,
17 filename=None,
18 filename=None,
18 content_type=None,
19 content_type=None,
19 data=None,
20 data=None,
20 disposition=None):
21 disposition=None):
21
22
22 self.filename = filename
23 self.filename = filename
23 self.content_type = content_type
24 self.content_type = content_type
24 self.disposition = disposition or 'attachment'
25 self.disposition = disposition or 'attachment'
25 self._data = data
26 self._data = data
26
27
27 @property
28 @property
28 def data(self):
29 def data(self):
29 if isinstance(self._data, basestring):
30 if isinstance(self._data, basestring):
30 return self._data
31 return self._data
31 self._data = self._data.read()
32 self._data = self._data.read()
32 return self._data
33 return self._data
33
34
34
35
35 class Message(object):
36 class Message(object):
36 """
37 """
37 Encapsulates an email message.
38 Encapsulates an email message.
38
39
39 :param subject: email subject header
40 :param subject: email subject header
40 :param recipients: list of email addresses
41 :param recipients: list of email addresses
41 :param body: plain text message
42 :param body: plain text message
42 :param html: HTML message
43 :param html: HTML message
43 :param sender: email sender address
44 :param sender: email sender address
44 :param cc: CC list
45 :param cc: CC list
45 :param bcc: BCC list
46 :param bcc: BCC list
46 :param extra_headers: dict of extra email headers
47 :param extra_headers: dict of extra email headers
47 :param attachments: list of Attachment instances
48 :param attachments: list of Attachment instances
48 :param recipients_separator: alternative separator for any of
49 :param recipients_separator: alternative separator for any of
49 'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
50 'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
50 """
51 """
51
52
52 def __init__(self,
53 def __init__(self,
53 subject=None,
54 subject=None,
54 recipients=None,
55 recipients=None,
55 body=None,
56 body=None,
56 html=None,
57 html=None,
57 sender=None,
58 sender=None,
58 cc=None,
59 cc=None,
59 bcc=None,
60 bcc=None,
60 extra_headers=None,
61 extra_headers=None,
61 attachments=None,
62 attachments=None,
62 recipients_separator="; "):
63 recipients_separator="; "):
63
64
64 self.subject = subject or ''
65 self.subject = subject or ''
65 self.sender = sender
66 self.sender = sender
66 self.body = body
67 self.body = body
67 self.html = html
68 self.html = html
68
69
69 self.recipients = recipients or []
70 self.recipients = recipients or []
70 self.attachments = attachments or []
71 self.attachments = attachments or []
71 self.cc = cc or []
72 self.cc = cc or []
72 self.bcc = bcc or []
73 self.bcc = bcc or []
73 self.extra_headers = extra_headers or {}
74 self.extra_headers = extra_headers or {}
74
75
75 self.recipients_separator = recipients_separator
76 self.recipients_separator = recipients_separator
76
77
77 @property
78 @property
78 def send_to(self):
79 def send_to(self):
79 return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
80 return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
80
81
81 def to_message(self):
82 def to_message(self):
82 """
83 """
83 Returns raw email.Message instance.Validates message first.
84 Returns raw email.Message instance.Validates message first.
84 """
85 """
85
86
86 self.validate()
87 self.validate()
87
88
88 return self.get_response().to_message()
89 return self.get_response().to_message()
89
90
90 def get_response(self):
91 def get_response(self):
91 """
92 """
92 Creates a Lamson MailResponse instance
93 Creates a Lamson MailResponse instance
93 """
94 """
94
95
95 response = MailResponse(Subject=self.subject,
96 response = MailResponse(Subject=self.subject,
96 To=self.recipients,
97 To=self.recipients,
97 From=self.sender,
98 From=self.sender,
98 Body=self.body,
99 Body=self.body,
99 Html=self.html,
100 Html=self.html,
100 separator=self.recipients_separator)
101 separator=self.recipients_separator)
101
102
102 if self.cc:
103 if self.cc:
103 response.base['Cc'] = self.cc
104 response.base['Cc'] = self.cc
104
105
105 for attachment in self.attachments:
106 for attachment in self.attachments:
106
107
107 response.attach(attachment.filename,
108 response.attach(attachment.filename,
108 attachment.content_type,
109 attachment.content_type,
109 attachment.data,
110 attachment.data,
110 attachment.disposition)
111 attachment.disposition)
111
112
112 response.update(self.extra_headers)
113 response.update(self.extra_headers)
113
114
114 return response
115 return response
115
116
116 def is_bad_headers(self):
117 def is_bad_headers(self):
117 """
118 """
118 Checks for bad headers i.e. newlines in subject, sender or recipients.
119 Checks for bad headers i.e. newlines in subject, sender or recipients.
119 """
120 """
120
121
121 headers = [self.subject, self.sender]
122 headers = [self.subject, self.sender]
122 headers += list(self.send_to)
123 headers += list(self.send_to)
123 headers += self.extra_headers.values()
124 headers += self.extra_headers.values()
124
125
125 for val in headers:
126 for val in headers:
126 for c in '\r\n':
127 for c in '\r\n':
127 if c in val:
128 if c in val:
128 return True
129 return True
129 return False
130 return False
130
131
131 def validate(self):
132 def validate(self):
132 """
133 """
133 Checks if message is valid and raises appropriate exception.
134 Checks if message is valid and raises appropriate exception.
134 """
135 """
135
136
136 if not self.recipients:
137 if not self.recipients:
137 raise InvalidMessage, "No recipients have been added"
138 raise InvalidMessage("No recipients have been added")
138
139
139 if not self.body and not self.html:
140 if not self.body and not self.html:
140 raise InvalidMessage, "No body has been set"
141 raise InvalidMessage("No body has been set")
141
142
142 if not self.sender:
143 if not self.sender:
143 raise InvalidMessage, "No sender address has been set"
144 raise InvalidMessage("No sender address has been set")
144
145
145 if self.is_bad_headers():
146 if self.is_bad_headers():
146 raise BadHeaders
147 raise BadHeaders
147
148
148 def add_recipient(self, recipient):
149 def add_recipient(self, recipient):
149 """
150 """
150 Adds another recipient to the message.
151 Adds another recipient to the message.
151
152
152 :param recipient: email address of recipient.
153 :param recipient: email address of recipient.
153 """
154 """
154
155
155 self.recipients.append(recipient)
156 self.recipients.append(recipient)
156
157
157 def add_cc(self, recipient):
158 def add_cc(self, recipient):
158 """
159 """
159 Adds an email address to the CC list.
160 Adds an email address to the CC list.
160
161
161 :param recipient: email address of recipient.
162 :param recipient: email address of recipient.
162 """
163 """
163
164
164 self.cc.append(recipient)
165 self.cc.append(recipient)
165
166
166 def add_bcc(self, recipient):
167 def add_bcc(self, recipient):
167 """
168 """
168 Adds an email address to the BCC list.
169 Adds an email address to the BCC list.
169
170
170 :param recipient: email address of recipient.
171 :param recipient: email address of recipient.
171 """
172 """
172
173
173 self.bcc.append(recipient)
174 self.bcc.append(recipient)
174
175
175 def attach(self, attachment):
176 def attach(self, attachment):
176 """
177 """
177 Adds an attachment to the message.
178 Adds an attachment to the message.
178
179
179 :param attachment: an **Attachment** instance.
180 :param attachment: an **Attachment** instance.
180 """
181 """
181
182
182 self.attachments.append(attachment)
183 self.attachments.append(attachment)
@@ -1,449 +1,453
1 # The code in this module is entirely lifted from the Lamson project
1 # The code in this module is entirely lifted from the Lamson project
2 # (http://lamsonproject.org/). Its copyright is:
2 # (http://lamsonproject.org/). Its copyright is:
3
3
4 # Copyright (c) 2008, Zed A. Shaw
4 # Copyright (c) 2008, Zed A. Shaw
5 # All rights reserved.
5 # All rights reserved.
6
6
7 # It is provided under this license:
7 # It is provided under this license:
8
8
9 # Redistribution and use in source and binary forms, with or without
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
10 # modification, are permitted provided that the following conditions are met:
11
11
12 # * Redistributions of source code must retain the above copyright notice, this
12 # * Redistributions of source code must retain the above copyright notice, this
13 # list of conditions and the following disclaimer.
13 # list of conditions and the following disclaimer.
14
14
15 # * Redistributions in binary form must reproduce the above copyright notice,
15 # * Redistributions in binary form must reproduce the above copyright notice,
16 # this list of conditions and the following disclaimer in the documentation
16 # this list of conditions and the following disclaimer in the documentation
17 # and/or other materials provided with the distribution.
17 # and/or other materials provided with the distribution.
18
18
19 # * Neither the name of the Zed A. Shaw nor the names of its contributors may
19 # * Neither the name of the Zed A. Shaw nor the names of its contributors may
20 # be used to endorse or promote products derived from this software without
20 # be used to endorse or promote products derived from this software without
21 # specific prior written permission.
21 # specific prior written permission.
22
22
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 # POSSIBILITY OF SUCH DAMAGE.
34 # POSSIBILITY OF SUCH DAMAGE.
35
35
36 import os
36 import os
37 import mimetypes
37 import mimetypes
38 import string
38 import string
39 from email import encoders
39 from email import encoders
40 from email.charset import Charset
40 from email.charset import Charset
41 from email.utils import parseaddr
41 from email.utils import parseaddr
42 from email.mime.base import MIMEBase
42 from email.mime.base import MIMEBase
43
43
44 ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
44 ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
45 DEFAULT_ENCODING = "utf-8"
45 DEFAULT_ENCODING = "utf-8"
46 VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
46 VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
47
47
48
48
49 def normalize_header(header):
49 def normalize_header(header):
50 return string.capwords(header.lower(), '-')
50 return string.capwords(header.lower(), '-')
51
51
52
52
53 class EncodingError(Exception):
53 class EncodingError(Exception):
54 """Thrown when there is an encoding error."""
54 """Thrown when there is an encoding error."""
55 pass
55 pass
56
56
57
57
58 class MailBase(object):
58 class MailBase(object):
59 """MailBase is used as the basis of lamson.mail and contains the basics of
59 """MailBase is used as the basis of lamson.mail and contains the basics of
60 encoding an email. You actually can do all your email processing with this
60 encoding an email. You actually can do all your email processing with this
61 class, but it's more raw.
61 class, but it's more raw.
62 """
62 """
63 def __init__(self, items=()):
63 def __init__(self, items=()):
64 self.headers = dict(items)
64 self.headers = dict(items)
65 self.parts = []
65 self.parts = []
66 self.body = None
66 self.body = None
67 self.content_encoding = {'Content-Type': (None, {}),
67 self.content_encoding = {'Content-Type': (None, {}),
68 'Content-Disposition': (None, {}),
68 'Content-Disposition': (None, {}),
69 'Content-Transfer-Encoding': (None, {})}
69 'Content-Transfer-Encoding': (None, {})}
70
70
71 def __getitem__(self, key):
71 def __getitem__(self, key):
72 return self.headers.get(normalize_header(key), None)
72 return self.headers.get(normalize_header(key), None)
73
73
74 def __len__(self):
74 def __len__(self):
75 return len(self.headers)
75 return len(self.headers)
76
76
77 def __iter__(self):
77 def __iter__(self):
78 return iter(self.headers)
78 return iter(self.headers)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return normalize_header(key) in self.headers
81 return normalize_header(key) in self.headers
82
82
83 def __setitem__(self, key, value):
83 def __setitem__(self, key, value):
84 self.headers[normalize_header(key)] = value
84 self.headers[normalize_header(key)] = value
85
85
86 def __delitem__(self, key):
86 def __delitem__(self, key):
87 del self.headers[normalize_header(key)]
87 del self.headers[normalize_header(key)]
88
88
89 def __nonzero__(self):
89 def __nonzero__(self):
90 return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
90 return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
91
91
92 def keys(self):
92 def keys(self):
93 """Returns the sorted keys."""
93 """Returns the sorted keys."""
94 return sorted(self.headers.keys())
94 return sorted(self.headers.keys())
95
95
96 def attach_file(self, filename, data, ctype, disposition):
96 def attach_file(self, filename, data, ctype, disposition):
97 """
97 """
98 A file attachment is a raw attachment with a disposition that
98 A file attachment is a raw attachment with a disposition that
99 indicates the file name.
99 indicates the file name.
100 """
100 """
101 assert filename, "You can't attach a file without a filename."
101 assert filename, "You can't attach a file without a filename."
102 ctype = ctype.lower()
102 ctype = ctype.lower()
103
103
104 part = MailBase()
104 part = MailBase()
105 part.body = data
105 part.body = data
106 part.content_encoding['Content-Type'] = (ctype, {'name': filename})
106 part.content_encoding['Content-Type'] = (ctype, {'name': filename})
107 part.content_encoding['Content-Disposition'] = (disposition,
107 part.content_encoding['Content-Disposition'] = (disposition,
108 {'filename': filename})
108 {'filename': filename})
109 self.parts.append(part)
109 self.parts.append(part)
110
110
111 def attach_text(self, data, ctype):
111 def attach_text(self, data, ctype):
112 """
112 """
113 This attaches a simpler text encoded part, which doesn't have a
113 This attaches a simpler text encoded part, which doesn't have a
114 filename.
114 filename.
115 """
115 """
116 ctype = ctype.lower()
116 ctype = ctype.lower()
117
117
118 part = MailBase()
118 part = MailBase()
119 part.body = data
119 part.body = data
120 part.content_encoding['Content-Type'] = (ctype, {})
120 part.content_encoding['Content-Type'] = (ctype, {})
121 self.parts.append(part)
121 self.parts.append(part)
122
122
123 def walk(self):
123 def walk(self):
124 for p in self.parts:
124 for p in self.parts:
125 yield p
125 yield p
126 for x in p.walk():
126 for x in p.walk():
127 yield x
127 yield x
128
128
129
129
130 class MailResponse(object):
130 class MailResponse(object):
131 """
131 """
132 You are given MailResponse objects from the lamson.view methods, and
132 You are given MailResponse objects from the lamson.view methods, and
133 whenever you want to generate an email to send to someone. It has the
133 whenever you want to generate an email to send to someone. It has the
134 same basic functionality as MailRequest, but it is designed to be written
134 same basic functionality as MailRequest, but it is designed to be written
135 to, rather than read from (although you can do both).
135 to, rather than read from (although you can do both).
136
136
137 You can easily set a Body or Html during creation or after by passing it
137 You can easily set a Body or Html during creation or after by passing it
138 as __init__ parameters, or by setting those attributes.
138 as __init__ parameters, or by setting those attributes.
139
139
140 You can initially set the From, To, and Subject, but they are headers so
140 You can initially set the From, To, and Subject, but they are headers so
141 use the dict notation to change them: msg['From'] = 'joe@test.com'.
141 use the dict notation to change them: msg['From'] = 'joe@test.com'.
142
142
143 The message is not fully crafted until right when you convert it with
143 The message is not fully crafted until right when you convert it with
144 MailResponse.to_message. This lets you change it and work with it, then
144 MailResponse.to_message. This lets you change it and work with it, then
145 send it out when it's ready.
145 send it out when it's ready.
146 """
146 """
147 def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
147 def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
148 separator="; "):
148 separator="; "):
149 self.Body = Body
149 self.Body = Body
150 self.Html = Html
150 self.Html = Html
151 self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
151 self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
152 self.multipart = self.Body and self.Html
152 self.multipart = self.Body and self.Html
153 self.attachments = []
153 self.attachments = []
154 self.separator = separator
154 self.separator = separator
155
155
156 def __contains__(self, key):
156 def __contains__(self, key):
157 return self.base.__contains__(key)
157 return self.base.__contains__(key)
158
158
159 def __getitem__(self, key):
159 def __getitem__(self, key):
160 return self.base.__getitem__(key)
160 return self.base.__getitem__(key)
161
161
162 def __setitem__(self, key, val):
162 def __setitem__(self, key, val):
163 return self.base.__setitem__(key, val)
163 return self.base.__setitem__(key, val)
164
164
165 def __delitem__(self, name):
165 def __delitem__(self, name):
166 del self.base[name]
166 del self.base[name]
167
167
168 def attach(self, filename=None, content_type=None, data=None,
168 def attach(self, filename=None, content_type=None, data=None,
169 disposition=None):
169 disposition=None):
170 """
170 """
171
171
172 Simplifies attaching files from disk or data as files. To attach
172 Simplifies attaching files from disk or data as files. To attach
173 simple text simple give data and a content_type. To attach a file,
173 simple text simple give data and a content_type. To attach a file,
174 give the data/content_type/filename/disposition combination.
174 give the data/content_type/filename/disposition combination.
175
175
176 For convenience, if you don't give data and only a filename, then it
176 For convenience, if you don't give data and only a filename, then it
177 will read that file's contents when you call to_message() later. If
177 will read that file's contents when you call to_message() later. If
178 you give data and filename then it will assume you've filled data
178 you give data and filename then it will assume you've filled data
179 with what the file's contents are and filename is just the name to
179 with what the file's contents are and filename is just the name to
180 use.
180 use.
181 """
181 """
182
182
183 assert filename or data, ("You must give a filename or some data to "
183 assert filename or data, ("You must give a filename or some data to "
184 "attach.")
184 "attach.")
185 assert data or os.path.exists(filename), ("File doesn't exist, and no "
185 assert data or os.path.exists(filename), ("File doesn't exist, and no "
186 "data given.")
186 "data given.")
187
187
188 self.multipart = True
188 self.multipart = True
189
189
190 if filename and not content_type:
190 if filename and not content_type:
191 content_type, encoding = mimetypes.guess_type(filename)
191 content_type, encoding = mimetypes.guess_type(filename)
192
192
193 assert content_type, ("No content type given, and couldn't guess "
193 assert content_type, ("No content type given, and couldn't guess "
194 "from the filename: %r" % filename)
194 "from the filename: %r" % filename)
195
195
196 self.attachments.append({'filename': filename,
196 self.attachments.append({'filename': filename,
197 'content_type': content_type,
197 'content_type': content_type,
198 'data': data,
198 'data': data,
199 'disposition': disposition,})
199 'disposition': disposition,})
200
200
201 def attach_part(self, part):
201 def attach_part(self, part):
202 """
202 """
203 Attaches a raw MailBase part from a MailRequest (or anywhere)
203 Attaches a raw MailBase part from a MailRequest (or anywhere)
204 so that you can copy it over.
204 so that you can copy it over.
205 """
205 """
206 self.multipart = True
206 self.multipart = True
207
207
208 self.attachments.append({'filename': None,
208 self.attachments.append({'filename': None,
209 'content_type': None,
209 'content_type': None,
210 'data': None,
210 'data': None,
211 'disposition': None,
211 'disposition': None,
212 'part': part,
212 'part': part,
213 })
213 })
214
214
215 def attach_all_parts(self, mail_request):
215 def attach_all_parts(self, mail_request):
216 """
216 """
217 Used for copying the attachment parts of a mail.MailRequest
217 Used for copying the attachment parts of a mail.MailRequest
218 object for mailing lists that need to maintain attachments.
218 object for mailing lists that need to maintain attachments.
219 """
219 """
220 for part in mail_request.all_parts():
220 for part in mail_request.all_parts():
221 self.attach_part(part)
221 self.attach_part(part)
222
222
223 self.base.content_encoding = mail_request.base.content_encoding.copy()
223 self.base.content_encoding = mail_request.base.content_encoding.copy()
224
224
225 def clear(self):
225 def clear(self):
226 """
226 """
227 Clears out the attachments so you can redo them. Use this to keep the
227 Clears out the attachments so you can redo them. Use this to keep the
228 headers for a series of different messages with different attachments.
228 headers for a series of different messages with different attachments.
229 """
229 """
230 del self.attachments[:]
230 del self.attachments[:]
231 del self.base.parts[:]
231 del self.base.parts[:]
232 self.multipart = False
232 self.multipart = False
233
233
234 def update(self, message):
234 def update(self, message):
235 """
235 """
236 Used to easily set a bunch of heading from another dict
236 Used to easily set a bunch of heading from another dict
237 like object.
237 like object.
238 """
238 """
239 for k in message.keys():
239 for k in message.keys():
240 self.base[k] = message[k]
240 self.base[k] = message[k]
241
241
242 def __str__(self):
242 def __str__(self):
243 """
243 """
244 Converts to a string.
244 Converts to a string.
245 """
245 """
246 return self.to_message().as_string()
246 return self.to_message().as_string()
247
247
248 def _encode_attachment(self, filename=None, content_type=None, data=None,
248 def _encode_attachment(self, filename=None, content_type=None, data=None,
249 disposition=None, part=None):
249 disposition=None, part=None):
250 """
250 """
251 Used internally to take the attachments mentioned in self.attachments
251 Used internally to take the attachments mentioned in self.attachments
252 and do the actual encoding in a lazy way when you call to_message.
252 and do the actual encoding in a lazy way when you call to_message.
253 """
253 """
254 if part:
254 if part:
255 self.base.parts.append(part)
255 self.base.parts.append(part)
256 elif filename:
256 elif filename:
257 if not data:
257 if not data:
258 data = open(filename).read()
258 data = open(filename).read()
259
259
260 self.base.attach_file(filename, data, content_type,
260 self.base.attach_file(filename, data, content_type,
261 disposition or 'attachment')
261 disposition or 'attachment')
262 else:
262 else:
263 self.base.attach_text(data, content_type)
263 self.base.attach_text(data, content_type)
264
264
265 ctype = self.base.content_encoding['Content-Type'][0]
265 ctype = self.base.content_encoding['Content-Type'][0]
266
266
267 if ctype and not ctype.startswith('multipart'):
267 if ctype and not ctype.startswith('multipart'):
268 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
268 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
269
269
270 def to_message(self):
270 def to_message(self):
271 """
271 """
272 Figures out all the required steps to finally craft the
272 Figures out all the required steps to finally craft the
273 message you need and return it. The resulting message
273 message you need and return it. The resulting message
274 is also available as a self.base attribute.
274 is also available as a self.base attribute.
275
275
276 What is returned is a Python email API message you can
276 What is returned is a Python email API message you can
277 use with those APIs. The self.base attribute is the raw
277 use with those APIs. The self.base attribute is the raw
278 lamson.encoding.MailBase.
278 lamson.encoding.MailBase.
279 """
279 """
280 del self.base.parts[:]
280 del self.base.parts[:]
281
281
282 if self.Body and self.Html:
282 if self.Body and self.Html:
283 self.multipart = True
283 self.multipart = True
284 self.base.content_encoding['Content-Type'] = (
284 self.base.content_encoding['Content-Type'] = (
285 'multipart/alternative', {})
285 'multipart/alternative', {})
286
286
287 if self.multipart:
287 if self.multipart:
288 self.base.body = None
288 self.base.body = None
289 if self.Body:
289 if self.Body:
290 self.base.attach_text(self.Body, 'text/plain')
290 self.base.attach_text(self.Body, 'text/plain')
291
291
292 if self.Html:
292 if self.Html:
293 self.base.attach_text(self.Html, 'text/html')
293 self.base.attach_text(self.Html, 'text/html')
294
294
295 for args in self.attachments:
295 for args in self.attachments:
296 self._encode_attachment(**args)
296 self._encode_attachment(**args)
297
297
298 elif self.Body:
298 elif self.Body:
299 self.base.body = self.Body
299 self.base.body = self.Body
300 self.base.content_encoding['Content-Type'] = ('text/plain', {})
300 self.base.content_encoding['Content-Type'] = ('text/plain', {})
301
301
302 elif self.Html:
302 elif self.Html:
303 self.base.body = self.Html
303 self.base.body = self.Html
304 self.base.content_encoding['Content-Type'] = ('text/html', {})
304 self.base.content_encoding['Content-Type'] = ('text/html', {})
305
305
306 return to_message(self.base, separator=self.separator)
306 return to_message(self.base, separator=self.separator)
307
307
308 def all_parts(self):
308 def all_parts(self):
309 """
309 """
310 Returns all the encoded parts. Only useful for debugging
310 Returns all the encoded parts. Only useful for debugging
311 or inspecting after calling to_message().
311 or inspecting after calling to_message().
312 """
312 """
313 return self.base.parts
313 return self.base.parts
314
314
315 def keys(self):
315 def keys(self):
316 return self.base.keys()
316 return self.base.keys()
317
317
318
318
319 def to_message(mail, separator="; "):
319 def to_message(mail, separator="; "):
320 """
320 """
321 Given a MailBase message, this will construct a MIMEPart
321 Given a MailBase message, this will construct a MIMEPart
322 that is canonicalized for use with the Python email API.
322 that is canonicalized for use with the Python email API.
323 """
323 """
324 ctype, params = mail.content_encoding['Content-Type']
324 ctype, params = mail.content_encoding['Content-Type']
325
325
326 if not ctype:
326 if not ctype:
327 if mail.parts:
327 if mail.parts:
328 ctype = 'multipart/mixed'
328 ctype = 'multipart/mixed'
329 else:
329 else:
330 ctype = 'text/plain'
330 ctype = 'text/plain'
331 else:
331 else:
332 if mail.parts:
332 if mail.parts:
333 assert ctype.startswith(("multipart", "message")), \
333 assert ctype.startswith(("multipart", "message")), \
334 "Content type should be multipart or message, not %r" % ctype
334 "Content type should be multipart or message, not %r" % ctype
335
335
336 # adjust the content type according to what it should be now
336 # adjust the content type according to what it should be now
337 mail.content_encoding['Content-Type'] = (ctype, params)
337 mail.content_encoding['Content-Type'] = (ctype, params)
338
338
339 try:
339 try:
340 out = MIMEPart(ctype, **params)
340 out = MIMEPart(ctype, **params)
341 except TypeError, exc: # pragma: no cover
341 except TypeError, exc: # pragma: no cover
342 raise EncodingError("Content-Type malformed, not allowed: %r; "
342 raise EncodingError("Content-Type malformed, not allowed: %r; "
343 "%r (Python ERROR: %s" %
343 "%r (Python ERROR: %s" %
344 (ctype, params, exc.message))
344 (ctype, params, exc.message))
345
345
346 for k in mail.keys():
346 for k in mail.keys():
347 if k in ADDRESS_HEADERS_WHITELIST:
347 if k in ADDRESS_HEADERS_WHITELIST:
348 out[k.encode('ascii')] = header_to_mime_encoding(
348 out[k.encode('ascii')] = header_to_mime_encoding(
349 mail[k],
349 mail[k],
350 not_email=False,
350 not_email=False,
351 separator=separator
351 separator=separator
352 )
352 )
353 else:
353 else:
354 out[k.encode('ascii')] = header_to_mime_encoding(
354 out[k.encode('ascii')] = header_to_mime_encoding(
355 mail[k],
355 mail[k],
356 not_email=True
356 not_email=True
357 )
357 )
358
358
359 out.extract_payload(mail)
359 out.extract_payload(mail)
360
360
361 # go through the children
361 # go through the children
362 for part in mail.parts:
362 for part in mail.parts:
363 out.attach(to_message(part))
363 out.attach(to_message(part))
364
364
365 return out
365 return out
366
366
367
367 class MIMEPart(MIMEBase):
368 class MIMEPart(MIMEBase):
368 """
369 """
369 A reimplementation of nearly everything in email.mime to be more useful
370 A reimplementation of nearly everything in email.mime to be more useful
370 for actually attaching things. Rather than one class for every type of
371 for actually attaching things. Rather than one class for every type of
371 thing you'd encode, there's just this one, and it figures out how to
372 thing you'd encode, there's just this one, and it figures out how to
372 encode what you ask it.
373 encode what you ask it.
373 """
374 """
374 def __init__(self, type, **params):
375 def __init__(self, type, **params):
375 self.maintype, self.subtype = type.split('/')
376 self.maintype, self.subtype = type.split('/')
376 MIMEBase.__init__(self, self.maintype, self.subtype, **params)
377 MIMEBase.__init__(self, self.maintype, self.subtype, **params)
377
378
378 def add_text(self, content):
379 def add_text(self, content):
379 # this is text, so encode it in canonical form
380 # this is text, so encode it in canonical form
380 try:
381 try:
381 encoded = content.encode('ascii')
382 encoded = content.encode('ascii')
382 charset = 'ascii'
383 charset = 'ascii'
383 except UnicodeError:
384 except UnicodeError:
384 encoded = content.encode('utf-8')
385 encoded = content.encode('utf-8')
385 charset = 'utf-8'
386 charset = 'utf-8'
386
387
387 self.set_payload(encoded, charset=charset)
388 self.set_payload(encoded, charset=charset)
388
389
389 def extract_payload(self, mail):
390 def extract_payload(self, mail):
390 if mail.body == None: return # only None, '' is still ok
391 if mail.body == None:
392 return # only None, '' is still ok
391
393
392 ctype, ctype_params = mail.content_encoding['Content-Type']
394 ctype, ctype_params = mail.content_encoding['Content-Type']
393 cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
395 cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
394
396
395 assert ctype, ("Extract payload requires that mail.content_encoding "
397 assert ctype, ("Extract payload requires that mail.content_encoding "
396 "have a valid Content-Type.")
398 "have a valid Content-Type.")
397
399
398 if ctype.startswith("text/"):
400 if ctype.startswith("text/"):
399 self.add_text(mail.body)
401 self.add_text(mail.body)
400 else:
402 else:
401 if cdisp:
403 if cdisp:
402 # replicate the content-disposition settings
404 # replicate the content-disposition settings
403 self.add_header('Content-Disposition', cdisp, **cdisp_params)
405 self.add_header('Content-Disposition', cdisp, **cdisp_params)
404
406
405 self.set_payload(mail.body)
407 self.set_payload(mail.body)
406 encoders.encode_base64(self)
408 encoders.encode_base64(self)
407
409
408 def __repr__(self):
410 def __repr__(self):
409 return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
411 return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
410 self.subtype,
412 self.subtype,
411 self.maintype,
413 self.maintype,
412 self['Content-Type'],
414 self['Content-Type'],
413 self['Content-Disposition'],
415 self['Content-Disposition'],
414 self.is_multipart())
416 self.is_multipart())
415
417
416
418
417 def header_to_mime_encoding(value, not_email=False, separator=", "):
419 def header_to_mime_encoding(value, not_email=False, separator=", "):
418 if not value: return ""
420 if not value:
421 return ""
419
422
420 encoder = Charset(DEFAULT_ENCODING)
423 encoder = Charset(DEFAULT_ENCODING)
421 if type(value) == list:
424 if type(value) == list:
422 return separator.join(properly_encode_header(
425 return separator.join(properly_encode_header(
423 v, encoder, not_email) for v in value)
426 v, encoder, not_email) for v in value)
424 else:
427 else:
425 return properly_encode_header(value, encoder, not_email)
428 return properly_encode_header(value, encoder, not_email)
426
429
430
427 def properly_encode_header(value, encoder, not_email):
431 def properly_encode_header(value, encoder, not_email):
428 """
432 """
429 The only thing special (weird) about this function is that it tries
433 The only thing special (weird) about this function is that it tries
430 to do a fast check to see if the header value has an email address in
434 to do a fast check to see if the header value has an email address in
431 it. Since random headers could have an email address, and email addresses
435 it. Since random headers could have an email address, and email addresses
432 have weird special formatting rules, we have to check for it.
436 have weird special formatting rules, we have to check for it.
433
437
434 Normally this works fine, but in Librelist, we need to "obfuscate" email
438 Normally this works fine, but in Librelist, we need to "obfuscate" email
435 addresses by changing the '@' to '-AT-'. This is where
439 addresses by changing the '@' to '-AT-'. This is where
436 VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
440 VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
437 to check if a header value has an email address. If you need to make this
441 to check if a header value has an email address. If you need to make this
438 check different, then change this.
442 check different, then change this.
439 """
443 """
440 try:
444 try:
441 return value.encode("ascii")
445 return value.encode("ascii")
442 except UnicodeEncodeError:
446 except UnicodeEncodeError:
443 if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
447 if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
444 # this could have an email address, make sure we don't screw it up
448 # this could have an email address, make sure we don't screw it up
445 name, address = parseaddr(value)
449 name, address = parseaddr(value)
446 return '"%s" <%s>' % (
450 return '"%s" <%s>' % (
447 encoder.header_encode(name.encode("utf-8")), address)
451 encoder.header_encode(name.encode("utf-8")), address)
448
452
449 return encoder.header_encode(value.encode("utf-8"))
453 return encoder.header_encode(value.encode("utf-8"))
@@ -1,94 +1,98
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.rcmail.smtp_mailer
3 rhodecode.lib.rcmail.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Simple smtp mailer used in RhodeCode
6 Simple smtp mailer used in RhodeCode
7
7
8 :created_on: Sep 13, 2010
8 :created_on: Sep 13, 2010
9 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24 import time
25 import logging
25 import logging
26 import smtplib
26 import smtplib
27 from socket import sslerror
27 from socket import sslerror
28 from email.utils import formatdate
28 from rhodecode.lib.rcmail.message import Message
29 from rhodecode.lib.rcmail.message import Message
29
30
30
31
31 class SmtpMailer(object):
32 class SmtpMailer(object):
32 """SMTP mailer class
33 """SMTP mailer class
33
34
34 mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
35 mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
35 mail_port, ssl, tls)
36 mail_port, ssl, tls)
36 mailer.send(recipients, subject, body, attachment_files)
37 mailer.send(recipients, subject, body, attachment_files)
37
38
38 :param recipients might be a list of string or single string
39 :param recipients might be a list of string or single string
39 :param attachment_files is a dict of {filename:location}
40 :param attachment_files is a dict of {filename:location}
40 it tries to guess the mimetype and attach the file
41 it tries to guess the mimetype and attach the file
41
42
42 """
43 """
43
44
44 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
45 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
45 mail_port=None, ssl=False, tls=False, debug=False):
46 mail_port=None, ssl=False, tls=False, debug=False):
46
47
47 self.mail_from = mail_from
48 self.mail_from = mail_from
48 self.mail_server = mail_server
49 self.mail_server = mail_server
49 self.mail_port = mail_port
50 self.mail_port = mail_port
50 self.user = user
51 self.user = user
51 self.passwd = passwd
52 self.passwd = passwd
52 self.ssl = ssl
53 self.ssl = ssl
53 self.tls = tls
54 self.tls = tls
54 self.debug = debug
55 self.debug = debug
55 self.auth = smtp_auth
56 self.auth = smtp_auth
56
57
57 def send(self, recipients=[], subject='', body='', html='',
58 def send(self, recipients=[], subject='', body='', html='',
58 attachment_files=None):
59 attachment_files=None):
59
60
60 if isinstance(recipients, basestring):
61 if isinstance(recipients, basestring):
61 recipients = [recipients]
62 recipients = [recipients]
63 headers = {
64 'Date': formatdate(time.time())
65 }
62 msg = Message(subject, recipients, body, html, self.mail_from,
66 msg = Message(subject, recipients, body, html, self.mail_from,
63 recipients_separator=", ")
67 recipients_separator=", ", extra_headers=headers)
64 raw_msg = msg.to_message()
68 raw_msg = msg.to_message()
65
69
66 if self.ssl:
70 if self.ssl:
67 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
71 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
68 else:
72 else:
69 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
73 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
70
74
71 if self.tls:
75 if self.tls:
72 smtp_serv.ehlo()
76 smtp_serv.ehlo()
73 smtp_serv.starttls()
77 smtp_serv.starttls()
74
78
75 if self.debug:
79 if self.debug:
76 smtp_serv.set_debuglevel(1)
80 smtp_serv.set_debuglevel(1)
77
81
78 smtp_serv.ehlo()
82 smtp_serv.ehlo()
79 if self.auth:
83 if self.auth:
80 smtp_serv.esmtp_features["auth"] = self.auth
84 smtp_serv.esmtp_features["auth"] = self.auth
81
85
82 # if server requires authorization you must provide login and password
86 # if server requires authorization you must provide login and password
83 # but only if we have them
87 # but only if we have them
84 if self.user and self.passwd:
88 if self.user and self.passwd:
85 smtp_serv.login(self.user, self.passwd)
89 smtp_serv.login(self.user, self.passwd)
86
90
87 smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
91 smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
88 logging.info('MAIL SEND TO: %s' % recipients)
92 logging.info('MAIL SEND TO: %s' % recipients)
89
93
90 try:
94 try:
91 smtp_serv.quit()
95 smtp_serv.quit()
92 except sslerror:
96 except sslerror:
93 # sslerror is raised in tls connections on closing sometimes
97 # sslerror is raised in tls connections on closing sometimes
94 pass
98 pass
General Comments 0
You need to be logged in to leave comments. Login now