@@ -525,7 +525,7 @@ def append(self, mailbox, flags, date_time, message, *,
525525 if translate_line_endings :
526526 message = MapCRLF .sub (CRLF , message )
527527 self .literal = message
528- return self ._simple_command (name , self ._astring (mailbox ), flags , date_time )
528+ return self ._simple_command (name , self ._mailbox (mailbox ), flags , date_time )
529529
530530
531531 def authenticate (self , mechanism , authobject ):
@@ -596,30 +596,30 @@ def copy(self, message_set, new_mailbox):
596596 (typ, [data]) = <instance>.copy(message_set, new_mailbox)
597597 """
598598 return self ._simple_command ('COPY' , self ._sequence_set (message_set ),
599- self ._astring (new_mailbox ))
599+ self ._mailbox (new_mailbox ))
600600
601601
602602 def create (self , mailbox ):
603603 """Create new mailbox.
604604
605605 (typ, [data]) = <instance>.create(mailbox)
606606 """
607- return self ._simple_command ('CREATE' , self ._astring (mailbox ))
607+ return self ._simple_command ('CREATE' , self ._mailbox (mailbox ))
608608
609609
610610 def delete (self , mailbox ):
611611 """Delete old mailbox.
612612
613613 (typ, [data]) = <instance>.delete(mailbox)
614614 """
615- return self ._simple_command ('DELETE' , self ._astring (mailbox ))
615+ return self ._simple_command ('DELETE' , self ._mailbox (mailbox ))
616616
617617 def deleteacl (self , mailbox , who ):
618618 """Delete the ACLs (remove any rights) set for who on mailbox.
619619
620620 (typ, [data]) = <instance>.deleteacl(mailbox, who)
621621 """
622- return self ._simple_command ('DELETEACL' , self ._astring (mailbox ),
622+ return self ._simple_command ('DELETEACL' , self ._mailbox (mailbox ),
623623 self ._astring (who ))
624624
625625 def enable (self , capability ):
@@ -669,15 +669,15 @@ def getacl(self, mailbox):
669669
670670 (typ, [data]) = <instance>.getacl(mailbox)
671671 """
672- typ , dat = self ._simple_command ('GETACL' , self ._astring (mailbox ))
672+ typ , dat = self ._simple_command ('GETACL' , self ._mailbox (mailbox ))
673673 return self ._untagged_response (typ , dat , 'ACL' )
674674
675675
676676 def getannotation (self , mailbox , entry , attribute ):
677677 """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute)
678678 Retrieve ANNOTATIONs."""
679679
680- typ , dat = self ._simple_command ('GETANNOTATION' , self ._astring (mailbox ),
680+ typ , dat = self ._simple_command ('GETANNOTATION' , self ._mailbox (mailbox ),
681681 entry , attribute )
682682 return self ._untagged_response (typ , dat , 'ANNOTATION' )
683683
@@ -689,7 +689,7 @@ def getquota(self, root):
689689
690690 (typ, [data]) = <instance>.getquota(root)
691691 """
692- typ , dat = self ._simple_command ('GETQUOTA' , self ._astring (root ))
692+ typ , dat = self ._simple_command ('GETQUOTA' , self ._mailbox (root ))
693693 return self ._untagged_response (typ , dat , 'QUOTA' )
694694
695695
@@ -698,7 +698,7 @@ def getquotaroot(self, mailbox):
698698
699699 (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
700700 """
701- typ , dat = self ._simple_command ('GETQUOTAROOT' , self ._astring (mailbox ))
701+ typ , dat = self ._simple_command ('GETQUOTAROOT' , self ._mailbox (mailbox ))
702702 typ , quota = self ._untagged_response (typ , dat , 'QUOTA' )
703703 typ , quotaroot = self ._untagged_response (typ , dat , 'QUOTAROOT' )
704704 return typ , [quotaroot , quota ]
@@ -747,7 +747,7 @@ def list(self, directory='', pattern='*'):
747747 'data' is list of LIST responses.
748748 """
749749 name = 'LIST'
750- typ , dat = self ._simple_command (name , self ._astring (directory ),
750+ typ , dat = self ._simple_command (name , self ._mailbox (directory ),
751751 self ._list_mailbox (pattern ))
752752 return self ._untagged_response (typ , dat , name )
753753
@@ -838,7 +838,7 @@ def lsub(self, directory='', pattern='*'):
838838 'data' are tuples of message part envelope and data.
839839 """
840840 name = 'LSUB'
841- typ , dat = self ._simple_command (name , self ._astring (directory ),
841+ typ , dat = self ._simple_command (name , self ._mailbox (directory ),
842842 self ._list_mailbox (pattern ))
843843 return self ._untagged_response (typ , dat , name )
844844
@@ -848,14 +848,14 @@ def move(self, message_set, new_mailbox):
848848 (typ, [data]) = <instance>.move(message_set, new_mailbox)
849849 """
850850 return self ._simple_command ('MOVE' , self ._sequence_set (message_set ),
851- self ._astring (new_mailbox ))
851+ self ._mailbox (new_mailbox ))
852852
853853 def myrights (self , mailbox ):
854854 """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
855855
856856 (typ, [data]) = <instance>.myrights(mailbox)
857857 """
858- typ ,dat = self ._simple_command ('MYRIGHTS' , self ._astring (mailbox ))
858+ typ ,dat = self ._simple_command ('MYRIGHTS' , self ._mailbox (mailbox ))
859859 return self ._untagged_response (typ , dat , 'MYRIGHTS' )
860860
861861 def namespace (self ):
@@ -909,8 +909,8 @@ def rename(self, oldmailbox, newmailbox):
909909
910910 (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
911911 """
912- return self ._simple_command ('RENAME' , self ._astring (oldmailbox ),
913- self ._astring (newmailbox ))
912+ return self ._simple_command ('RENAME' , self ._mailbox (oldmailbox ),
913+ self ._mailbox (newmailbox ))
914914
915915
916916 def search (self , charset , * criteria ):
@@ -950,7 +950,7 @@ def select(self, mailbox='INBOX', readonly=False):
950950 name = 'EXAMINE'
951951 else :
952952 name = 'SELECT'
953- typ , dat = self ._simple_command (name , self ._astring (mailbox ))
953+ typ , dat = self ._simple_command (name , self ._mailbox (mailbox ))
954954 if typ != 'OK' :
955955 self .state = 'AUTH' # Might have been 'SELECTED'
956956 return typ , dat
@@ -969,15 +969,15 @@ def setacl(self, mailbox, who, what):
969969
970970 (typ, [data]) = <instance>.setacl(mailbox, who, what)
971971 """
972- return self ._simple_command ('SETACL' , self ._astring (mailbox ),
972+ return self ._simple_command ('SETACL' , self ._mailbox (mailbox ),
973973 self ._astring (who ), self ._astring (what ))
974974
975975
976976 def setannotation (self , mailbox , * args ):
977977 """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+)
978978 Set ANNOTATIONs."""
979979
980- typ , dat = self ._simple_command ('SETANNOTATION' , self ._astring (mailbox ), * args )
980+ typ , dat = self ._simple_command ('SETANNOTATION' , self ._mailbox (mailbox ), * args )
981981 return self ._untagged_response (typ , dat , 'ANNOTATION' )
982982
983983
@@ -986,7 +986,7 @@ def setquota(self, root, limits):
986986
987987 (typ, [data]) = <instance>.setquota(root, limits)
988988 """
989- typ , dat = self ._simple_command ('SETQUOTA' , self ._astring (root ),
989+ typ , dat = self ._simple_command ('SETQUOTA' , self ._mailbox (root ),
990990 self ._set_quote (limits ))
991991 return self ._untagged_response (typ , dat , 'QUOTA' )
992992
@@ -1038,7 +1038,7 @@ def status(self, mailbox, names):
10381038 name = 'STATUS'
10391039 #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide!
10401040 # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
1041- typ , dat = self ._simple_command (name , self ._astring (mailbox ),
1041+ typ , dat = self ._simple_command (name , self ._mailbox (mailbox ),
10421042 self ._set_quote (names ))
10431043 return self ._untagged_response (typ , dat , name )
10441044
@@ -1059,7 +1059,7 @@ def subscribe(self, mailbox):
10591059
10601060 (typ, [data]) = <instance>.subscribe(mailbox)
10611061 """
1062- return self ._simple_command ('SUBSCRIBE' , self ._astring (mailbox ))
1062+ return self ._simple_command ('SUBSCRIBE' , self ._mailbox (mailbox ))
10631063
10641064
10651065 def thread (self , threading_algorithm , charset , * search_criteria ):
@@ -1095,7 +1095,7 @@ def uid(self, command, *args):
10951095 if command in ('COPY' , 'MOVE' ):
10961096 message_set , new_mailbox = args
10971097 args = (self ._sequence_set (message_set ),
1098- self ._astring (new_mailbox ))
1098+ self ._mailbox (new_mailbox ))
10991099 elif command == 'FETCH' :
11001100 message_set , message_parts = args
11011101 args = (self ._sequence_set (message_set ),
@@ -1129,7 +1129,7 @@ def unsubscribe(self, mailbox):
11291129
11301130 (typ, [data]) = <instance>.unsubscribe(mailbox)
11311131 """
1132- return self ._simple_command ('UNSUBSCRIBE' , self ._astring (mailbox ))
1132+ return self ._simple_command ('UNSUBSCRIBE' , self ._mailbox (mailbox ))
11331133
11341134
11351135 def unselect (self ):
@@ -1545,9 +1545,36 @@ def _astring(self, arg):
15451545 return arg
15461546 return self ._quote (arg )
15471547
1548+ def _encode_mailbox (self , arg , safe ):
1549+ # Encode a mailbox name (or LIST pattern) for the wire. In the default
1550+ # (ASCII) mode a non-ASCII name uses modified UTF-7 (RFC 3501, 5.1.3).
1551+ # For backward compatibility a str that is already a valid modified
1552+ # UTF-7 token -- one that needs no quoting, or an explicitly quoted
1553+ # string -- is passed through unchanged, so that a name obtained as raw
1554+ # bytes from LIST (and decoded as ASCII) round-trips without being
1555+ # encoded again. Pass bytes to bypass encoding entirely.
1556+ if self ._encoding != 'ascii' :
1557+ return bytes (arg , self ._encoding )
1558+ try :
1559+ raw = arg .encode ('ascii' )
1560+ except UnicodeEncodeError :
1561+ return arg .encode ('utf-7-imap' )
1562+ if _quoted .fullmatch (raw ) or (raw and safe .search (raw ) is None ):
1563+ try :
1564+ raw .decode ('utf-7-imap' )
1565+ return raw
1566+ except UnicodeDecodeError :
1567+ pass
1568+ return arg .encode ('utf-7-imap' )
1569+
1570+ def _mailbox (self , arg ):
1571+ if isinstance (arg , str ):
1572+ arg = self ._encode_mailbox (arg , _non_astring_char )
1573+ return self ._astring (arg )
1574+
15481575 def _list_mailbox (self , arg ):
15491576 if isinstance (arg , str ):
1550- arg = bytes (arg , self . _encoding )
1577+ arg = self . _encode_mailbox (arg , _non_list_char )
15511578 if _quoted .fullmatch (arg ):
15521579 return arg
15531580 if arg and _non_list_char .search (arg ) is None :
0 commit comments