Issue #1210: Fixed imaplib
Patch by Victor Stinner, reviewed by Barry Warsaw.
diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst
index 01249b2..3cbb45c 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -75,7 +75,7 @@
 
    This is a subclass derived from :class:`IMAP4` that connects to the
    ``stdin/stdout`` file descriptors created by passing *command* to
-   ``os.popen2()``.
+   ``subprocess.Popen()``.
 
 
 The following utility functions are defined:
@@ -468,13 +468,6 @@
 
    Allow simple extension commands notified by server in ``CAPABILITY`` response.
 
-Instances of :class:`IMAP4_SSL` have just one additional method:
-
-
-.. method:: IMAP4_SSL.ssl()
-
-   Returns SSLObject instance used for the secure connection with the server.
-
 The following attributes are defined on instances of :class:`IMAP4`:
 
 
diff --git a/Lib/imaplib.py b/Lib/imaplib.py
index 156b15d..6469df2 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -22,14 +22,14 @@
 
 __version__ = "2.58"
 
-import binascii, os, random, re, socket, sys, time
+import binascii, random, re, socket, subprocess, sys, time
 
 __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
            "Int2AP", "ParseFlags", "Time2Internaldate"]
 
 #       Globals
 
-CRLF = '\r\n'
+CRLF = b'\r\n'
 Debug = 0
 IMAP4_PORT = 143
 IMAP4_SSL_PORT = 993
@@ -81,19 +81,19 @@
 
 #       Patterns to match server responses
 
-Continuation = re.compile(r'\+( (?P<data>.*))?')
-Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
-InternalDate = re.compile(r'.*INTERNALDATE "'
-        r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
-        r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
-        r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
-        r'"')
-Literal = re.compile(r'.*{(?P<size>\d+)}$', re.ASCII)
-MapCRLF = re.compile(r'\r\n|\r|\n')
-Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
-Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
+Continuation = re.compile(br'\+( (?P<data>.*))?')
+Flags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)')
+InternalDate = re.compile(br'.*INTERNALDATE "'
+        br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
+        br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
+        br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
+        br'"')
+Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
+MapCRLF = re.compile(br'\r\n|\r|\n')
+Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
+Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
 Untagged_status = re.compile(
-    r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
+    br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII)
 
 
 
@@ -147,7 +147,7 @@
     class abort(error): pass        # Service errors - close and retry
     class readonly(abort): pass     # Mailbox status changed to READ-ONLY
 
-    mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]", re.ASCII)
+    mustquote = re.compile(br"[^\w!#$%&'*+,.:;<=>?^`|~-]", re.ASCII)
 
     def __init__(self, host = '', port = IMAP4_PORT):
         self.debug = Debug
@@ -167,9 +167,9 @@
         # and compile tagged response matcher.
 
         self.tagpre = Int2AP(random.randint(4096, 65535))
-        self.tagre = re.compile(r'(?P<tag>'
+        self.tagre = re.compile(br'(?P<tag>'
                         + self.tagpre
-                        + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
+                        + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII)
 
         # Get server welcome message,
         # request and store CAPABILITY response.
@@ -193,7 +193,9 @@
         typ, dat = self.capability()
         if dat == [None]:
             raise self.error('no CAPABILITY response from server')
-        self.capabilities = tuple(dat[-1].upper().split())
+        dat = str(dat[-1], "ASCII")
+        dat = dat.upper()
+        self.capabilities = tuple(dat.split())
 
         if __debug__:
             if self.debug >= 3:
@@ -219,6 +221,11 @@
     #       Overridable methods
 
 
+    def _create_socket(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect((self.host, self.port))
+        return sock
+
     def open(self, host = '', port = IMAP4_PORT):
         """Setup connection to remote server on "host:port"
             (default: localhost:standard IMAP4 port).
@@ -227,14 +234,21 @@
         """
         self.host = host
         self.port = port
-        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.sock.connect((host, port))
+        self.sock = self._create_socket()
         self.file = self.sock.makefile('rb')
 
 
     def read(self, size):
         """Read 'size' bytes from remote."""
-        return self.file.read(size)
+        chunks = []
+        read = 0
+        while read < size:
+            data = self.file.read(min(size-read, 4096))
+            if not data:
+                break
+            read += len(data)
+            chunks.append(data)
+        return b''.join(chunks)
 
 
     def readline(self):
@@ -791,12 +805,12 @@
 
 
     def _append_untagged(self, typ, dat):
-
-        if dat is None: dat = ''
+        if dat is None:
+            dat = b''
         ur = self.untagged_responses
         if __debug__:
             if self.debug >= 5:
-                self._mesg('untagged_responses[%s] %s += ["%s"]' %
+                self._mesg('untagged_responses[%s] %s += ["%r"]' %
                         (typ, len(ur.get(typ,'')), dat))
         if typ in ur:
             ur[typ].append(dat)
@@ -828,10 +842,14 @@
             raise self.readonly('mailbox status changed to READ-ONLY')
 
         tag = self._new_tag()
-        data = '%s %s' % (tag, name)
+        name = bytes(name, 'ASCII')
+        data = tag + b' ' + name
         for arg in args:
             if arg is None: continue
-            data = '%s %s' % (data, self._checkquote(arg))
+            if isinstance(arg, str):
+                arg = bytes(arg, "ASCII")
+            #data = data + b' ' + self._checkquote(arg)
+            data = data + b' ' + arg
 
         literal = self.literal
         if literal is not None:
@@ -840,16 +858,16 @@
                 literator = literal
             else:
                 literator = None
-                data = '%s {%s}' % (data, len(literal))
+                data = data + bytes(' {%s}' % len(literal), 'ASCII')
 
         if __debug__:
             if self.debug >= 4:
-                self._mesg('> %s' % data)
+                self._mesg('> %r' % data)
             else:
-                self._log('> %s' % data)
+                self._log('> %r' % data)
 
         try:
-            self.send('%s%s' % (data, CRLF))
+            self.send(data + CRLF)
         except (socket.error, OSError) as val:
             raise self.abort('socket error: %s' % val)
 
@@ -915,6 +933,7 @@
                 raise self.abort('unexpected tagged response: %s' % resp)
 
             typ = self.mo.group('type')
+            typ = str(typ, 'ASCII')
             dat = self.mo.group('data')
             self.tagged_commands[tag] = (typ, [dat])
         else:
@@ -936,9 +955,10 @@
                 raise self.abort("unexpected response: '%s'" % resp)
 
             typ = self.mo.group('type')
+            typ = str(typ, 'ascii')
             dat = self.mo.group('data')
-            if dat is None: dat = ''        # Null untagged response
-            if dat2: dat = dat + ' ' + dat2
+            if dat is None: dat = b''        # Null untagged response
+            if dat2: dat = dat + b' ' + dat2
 
             # Is there a literal to come?
 
@@ -965,11 +985,13 @@
         # Bracketed response information?
 
         if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
-            self._append_untagged(self.mo.group('type'), self.mo.group('data'))
+            typ = self.mo.group('type')
+            typ = str(typ, "ASCII")
+            self._append_untagged(typ, self.mo.group('data'))
 
         if __debug__:
             if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
-                self._mesg('%s response: %s' % (typ, dat))
+                self._mesg('%s response: %r' % (typ, dat))
 
         return resp
 
@@ -1007,9 +1029,9 @@
         line = line[:-2]
         if __debug__:
             if self.debug >= 4:
-                self._mesg('< %s' % line)
+                self._mesg('< %r' % line)
             else:
-                self._log('< %s' % line)
+                self._log('< %r' % line)
         return line
 
 
@@ -1021,13 +1043,13 @@
         self.mo = cre.match(s)
         if __debug__:
             if self.mo is not None and self.debug >= 5:
-                self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
+                self._mesg("\tmatched r'%r' => %r" % (cre.pattern, self.mo.groups()))
         return self.mo is not None
 
 
     def _new_tag(self):
 
-        tag = '%s%s' % (self.tagpre, self.tagnum)
+        tag = self.tagpre + bytes(str(self.tagnum), 'ASCII')
         self.tagnum = self.tagnum + 1
         self.tagged_commands[tag] = None
         return tag
@@ -1038,8 +1060,6 @@
         # Must quote command args if non-alphanumeric chars present,
         # and not already quoted.
 
-        if type(arg) is not type(''):
-            return arg
         if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
             return arg
         if arg and self.mustquote.search(arg) is None:
@@ -1049,10 +1069,10 @@
 
     def _quote(self, arg):
 
-        arg = arg.replace('\\', '\\\\')
-        arg = arg.replace('"', '\\"')
+        arg = arg.replace(b'\\', b'\\\\')
+        arg = arg.replace(b'"', b'\\"')
 
-        return '"%s"' % arg
+        return b'"' + arg + b'"'
 
 
     def _simple_command(self, name, *args):
@@ -1061,7 +1081,6 @@
 
 
     def _untagged_response(self, typ, dat, name):
-
         if typ == 'NO':
             return typ, dat
         if not name in self.untagged_responses:
@@ -1137,73 +1156,17 @@
             self.certfile = certfile
             IMAP4.__init__(self, host, port)
 
+        def _create_socket(self):
+            sock = IMAP4._create_socket(self)
+            return ssl.wrap_socket(sock, self.keyfile, self.certfile)
 
-        def open(self, host = '', port = IMAP4_SSL_PORT):
+        def open(self, host='', port=IMAP4_SSL_PORT):
             """Setup connection to remote server on "host:port".
                 (default: localhost:standard IMAP4 SSL port).
             This connection will be used by the routines:
                 read, readline, send, shutdown.
             """
-            self.host = host
-            self.port = port
-            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            sock.connect((host, port))
-            self.sock = ssl.wrap_socket(sock, self.keyfile, self.certfile)
-            self.file = self.sock.makefile('rb')
-
-
-        def read(self, size):
-            """Read 'size' bytes from remote."""
-            # sslobj.read() sometimes returns < size bytes
-            chunks = []
-            read = 0
-            while read < size:
-                data = self.sslobj.read(min(size-read, 16384))
-                read += len(data)
-                chunks.append(data)
-
-            return b''.join(chunks)
-
-
-        def readline(self):
-            """Read line from remote."""
-            line = []
-            while 1:
-                char = self.sslobj.read(1)
-                line.append(char)
-                if char == b"\n": return b''.join(line)
-
-
-        def send(self, data):
-            """Send data to remote."""
-            bytes = len(data)
-            while bytes > 0:
-                sent = self.sslobj.write(data)
-                if sent == bytes:
-                    break    # avoid copy
-                data = data[sent:]
-                bytes = bytes - sent
-
-
-        def shutdown(self):
-            """Close I/O established in "open"."""
-            self.sock.close()
-
-
-        def socket(self):
-            """Return socket instance used to connect to IMAP4 server.
-
-            socket = <instance>.socket()
-            """
-            return self.sock
-
-
-        def ssl(self):
-            """Return SSLObject instance used to communicate with the IMAP4 server.
-
-            ssl = ssl.wrap_socket(<instance>.socket)
-            """
-            return self.sock
+            IMAP4.open(self, host, port)
 
     __all__.append("IMAP4_SSL")
 
@@ -1214,7 +1177,7 @@
 
     Instantiate with: IMAP4_stream(command)
 
-            where "command" is a string that can be passed to os.popen2()
+            where "command" is a string that can be passed to subprocess.Popen()
 
     for more documentation see the docstring of the parent class IMAP4.
     """
@@ -1234,8 +1197,11 @@
         self.port = None
         self.sock = None
         self.file = None
-        self.writefile, self.readfile = os.popen2(self.command)
-
+        self.process = subprocess.Popen(self.command,
+            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+            shell=True, close_fds=True)
+        self.writefile = self.process.stdin
+        self.readfile = self.process.stdout
 
     def read(self, size):
         """Read 'size' bytes from remote."""
@@ -1257,6 +1223,7 @@
         """Close I/O established in "open"."""
         self.readfile.close()
         self.writefile.close()
+        self.process.wait()
 
 
 
@@ -1355,11 +1322,11 @@
 
     """Convert integer to A-P string representation."""
 
-    val = ''; AP = 'ABCDEFGHIJKLMNOP'
+    val = b''; AP = b'ABCDEFGHIJKLMNOP'
     num = int(abs(num))
     while num:
         num, mod = divmod(num, 16)
-        val = AP[mod] + val
+        val = AP[mod:mod+1] + val
     return val
 
 
diff --git a/Misc/NEWS b/Misc/NEWS
index f17e758..9684cee 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -15,6 +15,8 @@
 Core and Builtins
 -----------------
 
+- Issue #1210: Fixed imaplib and its documentation.
+
 - Issue #4233: Changed semantic of ``_fileio.FileIO``'s ``close()`` 
   method on file objects with closefd=False. The file descriptor is still
   kept open but the file object behaves like a closed file. The ``FileIO``