(root)/
Python-3.11.7/
Lib/
quopri.py
       1  #! /usr/bin/env python3
       2  
       3  """Conversions to/from quoted-printable transport encoding as per RFC 1521."""
       4  
       5  # (Dec 1991 version).
       6  
       7  __all__ = ["encode", "decode", "encodestring", "decodestring"]
       8  
       9  ESCAPE = b'='
      10  MAXLINESIZE = 76
      11  HEX = b'0123456789ABCDEF'
      12  EMPTYSTRING = b''
      13  
      14  try:
      15      from binascii import a2b_qp, b2a_qp
      16  except ImportError:
      17      a2b_qp = None
      18      b2a_qp = None
      19  
      20  
      21  def needsquoting(c, quotetabs, header):
      22      """Decide whether a particular byte ordinal needs to be quoted.
      23  
      24      The 'quotetabs' flag indicates whether embedded tabs and spaces should be
      25      quoted.  Note that line-ending tabs and spaces are always encoded, as per
      26      RFC 1521.
      27      """
      28      assert isinstance(c, bytes)
      29      if c in b' \t':
      30          return quotetabs
      31      # if header, we have to escape _ because _ is used to escape space
      32      if c == b'_':
      33          return header
      34      return c == ESCAPE or not (b' ' <= c <= b'~')
      35  
      36  def quote(c):
      37      """Quote a single character."""
      38      assert isinstance(c, bytes) and len(c)==1
      39      c = ord(c)
      40      return ESCAPE + bytes((HEX[c//16], HEX[c%16]))
      41  
      42  
      43  
      44  def encode(input, output, quotetabs, header=False):
      45      """Read 'input', apply quoted-printable encoding, and write to 'output'.
      46  
      47      'input' and 'output' are binary file objects. The 'quotetabs' flag
      48      indicates whether embedded tabs and spaces should be quoted. Note that
      49      line-ending tabs and spaces are always encoded, as per RFC 1521.
      50      The 'header' flag indicates whether we are encoding spaces as _ as per RFC
      51      1522."""
      52  
      53      if b2a_qp is not None:
      54          data = input.read()
      55          odata = b2a_qp(data, quotetabs=quotetabs, header=header)
      56          output.write(odata)
      57          return
      58  
      59      def write(s, output=output, lineEnd=b'\n'):
      60          # RFC 1521 requires that the line ending in a space or tab must have
      61          # that trailing character encoded.
      62          if s and s[-1:] in b' \t':
      63              output.write(s[:-1] + quote(s[-1:]) + lineEnd)
      64          elif s == b'.':
      65              output.write(quote(s) + lineEnd)
      66          else:
      67              output.write(s + lineEnd)
      68  
      69      prevline = None
      70      while 1:
      71          line = input.readline()
      72          if not line:
      73              break
      74          outline = []
      75          # Strip off any readline induced trailing newline
      76          stripped = b''
      77          if line[-1:] == b'\n':
      78              line = line[:-1]
      79              stripped = b'\n'
      80          # Calculate the un-length-limited encoded line
      81          for c in line:
      82              c = bytes((c,))
      83              if needsquoting(c, quotetabs, header):
      84                  c = quote(c)
      85              if header and c == b' ':
      86                  outline.append(b'_')
      87              else:
      88                  outline.append(c)
      89          # First, write out the previous line
      90          if prevline is not None:
      91              write(prevline)
      92          # Now see if we need any soft line breaks because of RFC-imposed
      93          # length limitations.  Then do the thisline->prevline dance.
      94          thisline = EMPTYSTRING.join(outline)
      95          while len(thisline) > MAXLINESIZE:
      96              # Don't forget to include the soft line break `=' sign in the
      97              # length calculation!
      98              write(thisline[:MAXLINESIZE-1], lineEnd=b'=\n')
      99              thisline = thisline[MAXLINESIZE-1:]
     100          # Write out the current line
     101          prevline = thisline
     102      # Write out the last line, without a trailing newline
     103      if prevline is not None:
     104          write(prevline, lineEnd=stripped)
     105  
     106  def encodestring(s, quotetabs=False, header=False):
     107      if b2a_qp is not None:
     108          return b2a_qp(s, quotetabs=quotetabs, header=header)
     109      from io import BytesIO
     110      infp = BytesIO(s)
     111      outfp = BytesIO()
     112      encode(infp, outfp, quotetabs, header)
     113      return outfp.getvalue()
     114  
     115  
     116  
     117  def decode(input, output, header=False):
     118      """Read 'input', apply quoted-printable decoding, and write to 'output'.
     119      'input' and 'output' are binary file objects.
     120      If 'header' is true, decode underscore as space (per RFC 1522)."""
     121  
     122      if a2b_qp is not None:
     123          data = input.read()
     124          odata = a2b_qp(data, header=header)
     125          output.write(odata)
     126          return
     127  
     128      new = b''
     129      while 1:
     130          line = input.readline()
     131          if not line: break
     132          i, n = 0, len(line)
     133          if n > 0 and line[n-1:n] == b'\n':
     134              partial = 0; n = n-1
     135              # Strip trailing whitespace
     136              while n > 0 and line[n-1:n] in b" \t\r":
     137                  n = n-1
     138          else:
     139              partial = 1
     140          while i < n:
     141              c = line[i:i+1]
     142              if c == b'_' and header:
     143                  new = new + b' '; i = i+1
     144              elif c != ESCAPE:
     145                  new = new + c; i = i+1
     146              elif i+1 == n and not partial:
     147                  partial = 1; break
     148              elif i+1 < n and line[i+1:i+2] == ESCAPE:
     149                  new = new + ESCAPE; i = i+2
     150              elif i+2 < n and ishex(line[i+1:i+2]) and ishex(line[i+2:i+3]):
     151                  new = new + bytes((unhex(line[i+1:i+3]),)); i = i+3
     152              else: # Bad escape sequence -- leave it in
     153                  new = new + c; i = i+1
     154          if not partial:
     155              output.write(new + b'\n')
     156              new = b''
     157      if new:
     158          output.write(new)
     159  
     160  def decodestring(s, header=False):
     161      if a2b_qp is not None:
     162          return a2b_qp(s, header=header)
     163      from io import BytesIO
     164      infp = BytesIO(s)
     165      outfp = BytesIO()
     166      decode(infp, outfp, header=header)
     167      return outfp.getvalue()
     168  
     169  
     170  
     171  # Other helper functions
     172  def ishex(c):
     173      """Return true if the byte ordinal 'c' is a hexadecimal digit in ASCII."""
     174      assert isinstance(c, bytes)
     175      return b'0' <= c <= b'9' or b'a' <= c <= b'f' or b'A' <= c <= b'F'
     176  
     177  def unhex(s):
     178      """Get the integer value of a hexadecimal number."""
     179      bits = 0
     180      for c in s:
     181          c = bytes((c,))
     182          if b'0' <= c <= b'9':
     183              i = ord('0')
     184          elif b'a' <= c <= b'f':
     185              i = ord('a')-10
     186          elif b'A' <= c <= b'F':
     187              i = ord(b'A')-10
     188          else:
     189              assert False, "non-hex digit "+repr(c)
     190          bits = bits*16 + (ord(c) - i)
     191      return bits
     192  
     193  
     194  
     195  def main():
     196      import sys
     197      import getopt
     198      try:
     199          opts, args = getopt.getopt(sys.argv[1:], 'td')
     200      except getopt.error as msg:
     201          sys.stdout = sys.stderr
     202          print(msg)
     203          print("usage: quopri [-t | -d] [file] ...")
     204          print("-t: quote tabs")
     205          print("-d: decode; default encode")
     206          sys.exit(2)
     207      deco = False
     208      tabs = False
     209      for o, a in opts:
     210          if o == '-t': tabs = True
     211          if o == '-d': deco = True
     212      if tabs and deco:
     213          sys.stdout = sys.stderr
     214          print("-t and -d are mutually exclusive")
     215          sys.exit(2)
     216      if not args: args = ['-']
     217      sts = 0
     218      for file in args:
     219          if file == '-':
     220              fp = sys.stdin.buffer
     221          else:
     222              try:
     223                  fp = open(file, "rb")
     224              except OSError as msg:
     225                  sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
     226                  sts = 1
     227                  continue
     228          try:
     229              if deco:
     230                  decode(fp, sys.stdout.buffer)
     231              else:
     232                  encode(fp, sys.stdout.buffer, tabs)
     233          finally:
     234              if file != '-':
     235                  fp.close()
     236      if sts:
     237          sys.exit(sts)
     238  
     239  
     240  
     241  if __name__ == '__main__':
     242      main()