github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-runtime-src/third_party/stdlib/quopri.py (about)

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