Merge fixes from branch 'xorn'
[geda-gaf.git] / xorn / src / python / base64.py
blob552cc314bb65cfabe76724bb36b9c95620b1635a
1 # gaf - Python library for manipulating gEDA files
2 #**********************************************************************
3 # _ _ __ _ _
4 # __ _ _ __ ___| |_ | |__ __ _ ___ ___ / /_ | or |
5 # / _` | '_ \ / _ \ __| | '_ \ / _` / __|/ _ \ '_ \| or |_
6 # | (_| | | | | __/ |_ | |_) | (_| \__ \ __/ (_) |__ _|
7 # \__, |_| |_|\___|\__| |_.__/ \__,_|___/\___|\___/ |_|
8 # |___/
10 # created by Alfred Reibenschuh <alfredreibenschuh@gmx.net>,
11 # under the "GNU Library General Public License" (see below).
13 #**********************************************************************
14 # Copyright (C) 2003 Free Software Foundation
15 # Copyright (C) 2013-2020 Roland Lutz
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to the Free Software Foundation,
29 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 ## \namespace xorn.base64
32 ## Reading and writing base64-encoded data
34 from gettext import gettext as _
36 BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
37 PAD64 = '='
38 RANK = [
39 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0x00-0x0f
40 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0x10-0x1f
41 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, # 0x20-0x2f
42 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255, # 0x30-0x3f
43 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, # 0x40-0x4f
44 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, # 0x50-0x5f
45 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, # 0x60-0x6f
46 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, # 0x70-0x7f
47 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0x80-0x8f
48 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0x90-0x9f
49 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xa0-0xaf
50 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xb0-0xbf
51 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xc0-0xcf
52 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xd0-0xdf
53 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xe0-0xef
54 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 0xf0-0xff
57 ## Write a binary string to a file in %base64 representation.
59 # If \a columns is not \c None, insert a newline every \a columns
60 # characters. This is required by RFC 2045, but some applications
61 # don't require it. \a columns must positive and a multiple of \c 4.
63 # If \a delim is not \c None, it is written on a separate line after
64 # the data. This argument is provided for symmetry with \ref decode.
66 # \return \c None.
68 def encode(f, src, columns = 72, delim = None):
69 # bulk encoding
70 blen = len(src) - len(src) % 3
71 ocnt = 0
73 for pos in xrange(0, blen, 3):
74 # Convert 3 bytes of src to 4 bytes of output
76 # output[0] = input[0] 7:2
77 # output[1] = input[0] 1:0 input[1] 7:4
78 # output[2] = input[1] 3:0 input[2] 7:6
79 # output[3] = input[1] 5:0
81 i0, i1, i2 = [ord(ch) for ch in src[pos:pos + 3]]
83 # Map output to the Base64 alphabet
84 f.write(BASE64[i0 >> 2] +
85 BASE64[((i0 & 0x03) << 4) + (i1 >> 4)] +
86 BASE64[((i1 & 0x0f) << 2) + (i2 >> 6)] +
87 BASE64[i2 & 0x3f])
89 if columns is not None:
90 ocnt += 1
91 if ocnt % (columns / 4) == 0 and pos != len(src) - 3:
92 f.write('\n')
94 # Now worry about padding with remaining 1 or 2 bytes
95 if blen != len(src):
96 i0 = ord(src[blen])
97 if blen == len(src) - 1:
98 i1 = 0
99 else:
100 i1 = ord(src[blen + 1])
101 i2 = 0
103 f.write(BASE64[i0 >> 2] +
104 BASE64[((i0 & 0x03) << 4) + (i1 >> 4)])
105 if blen == len(src) - 1:
106 f.write(PAD64)
107 else:
108 f.write(BASE64[((i1 & 0x0f) << 2) + (i2 >> 6)])
109 f.write(PAD64)
111 if src:
112 f.write('\n')
114 if delim is not None:
115 f.write(delim + '\n')
117 ## Raised when reading invalid or unterminated base64-encoded data.
119 class DecodingError(Exception):
120 pass
122 ## Read a string in %base64 representation from a file.
124 # This function is liberal in what it will accept. It ignores
125 # non-base64 symbols.
127 # If \a delim is \c None, read until the end of the file. If \a delim
128 # is not \c None, read until a line containing exactly \a delim is
129 # found.
131 # \return A string containing the decoded data.
133 # \throw DecodingError if reading something that is not valid
134 # base64-encoded data
135 # \throw DecodingError if the end of the file is hit and \a delim is
136 # not \c None
138 def decode(f, delim = None):
139 ch = 0
140 state = 0
141 res = 0
142 dst = []
143 pad = 0
145 while True:
146 try:
147 line = f.next()
148 except StopIteration:
149 if delim is not None:
150 raise DecodingError, _("Unexpected end-of-file")
151 break
153 if delim is not None and line == delim + '\n':
154 break
156 for ch in line:
157 if ch == PAD64:
158 pad += 1
159 continue
160 pos = RANK[ord(ch)]
161 if pos == 255:
162 # Skip any non-base64 anywhere
163 continue
164 if pad != 0:
165 raise DecodingError
167 if state == 0:
168 dst += [pos << 2]
169 state = 1
170 elif state == 1:
171 dst[-1] |= pos >> 4
172 res = (pos & 0x0f) << 4
173 state = 2
174 elif state == 2:
175 dst += [res | (pos >> 2)]
176 res = (pos & 0x03) << 6
177 state = 3
178 elif state == 3:
179 dst += [res | pos]
180 state = 0
182 # We are done decoding Base-64 chars. Let's see if we ended
183 # on a byte boundary, and/or with erroneous trailing characters.
184 if pad != 0:
185 # We got a pad char.
186 if state == 0:
187 # Invalid = in first position
188 raise DecodingError
189 elif state == 1:
190 # Invalid = in second position
191 raise DecodingError
192 elif state == 2:
193 # Valid, means one byte of info
194 # Make sure there is another trailing = sign.
195 if pad != 2:
196 raise DecodingError
197 elif state == 3:
198 # Valid, means two bytes of info
199 # We know this char is an =. Is there anything but
200 # whitespace after it?
201 if pad != 1:
202 raise DecodingError
203 if state == 2 or state == 3:
204 # Now make sure for cases 2 and 3 that the "extra"
205 # bits that slopped past the last full byte were
206 # zeros. If we don't check them, they become a
207 # subliminal channel.
208 if res != 0:
209 raise DecodingError
210 else:
211 # We ended by seeing the end of the string. Make sure we
212 # have no partial bytes lying around.
213 if state != 0:
214 raise DecodingError
215 return ''.join(chr(b) for b in dst)