python: Improve support for edit callbacks.
[gpgme.git] / lang / python / pyme / core.py
1 # $Id$
2 # Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net>
3 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
4 #
5 #    This library is free software; you can redistribute it and/or
6 #    modify it under the terms of the GNU Lesser General Public
7 #    License as published by the Free Software Foundation; either
8 #    version 2.1 of the License, or (at your option) any later version.
9 #
10 #    This library is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #    Lesser General Public License for more details.
14 #
15 #    You should have received a copy of the GNU Lesser General Public
16 #    License along with this library; if not, write to the Free Software
17 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
18
19 # import generators for portability with python2.2
20
21
22 from . import pygpgme
23 from .errors import errorcheck, GPGMEError
24 from . import errors
25 from .util import GpgmeWrapper
26
27 class Context(GpgmeWrapper):
28     """From the GPGME C documentation:
29
30     * All cryptographic operations in GPGME are performed within a
31     * context, which contains the internal state of the operation as well as
32     * configuration parameters.  By using several contexts you can run
33     * several cryptographic operations in parallel, with different
34     * configuration.
35
36     Thus, this is the place that you will usually start."""
37
38     def _getctype(self):
39         return 'gpgme_ctx_t'
40
41     def _getnameprepend(self):
42         return 'gpgme_'
43
44     def _errorcheck(self, name):
45         """This function should list all functions returning gpgme_error_t"""
46         if (name.startswith('gpgme_op_') and \
47             not name.endswith('_result')) or \
48             name == 'gpgme_signers_add' or \
49             name == 'gpgme_set_locale' or \
50             name == 'gpgme_set_keylist_mode' or \
51             name == 'gpgme_set_protocol':
52             return 1
53         return 0
54
55     def __init__(self, wrapped=None):
56         if wrapped:
57             self.own = False
58         else:
59             tmp = pygpgme.new_gpgme_ctx_t_p()
60             errorcheck(pygpgme.gpgme_new(tmp))
61             wrapped = pygpgme.gpgme_ctx_t_p_value(tmp)
62             pygpgme.delete_gpgme_ctx_t_p(tmp)
63             self.own = True
64         super().__init__(wrapped)
65         self.last_passcb = None
66         self.last_progresscb = None
67
68     def __del__(self):
69         if not pygpgme:
70             # At interpreter shutdown, pygpgme is set to NONE.
71             return
72
73         self._free_passcb()
74         self._free_progresscb()
75         if self.own and pygpgme.gpgme_release:
76             pygpgme.gpgme_release(self.wrapped)
77
78     def _free_passcb(self):
79         if self.last_passcb != None:
80             if pygpgme.pygpgme_clear_generic_cb:
81                 pygpgme.pygpgme_clear_generic_cb(self.last_passcb)
82             if pygpgme.delete_PyObject_p_p:
83                 pygpgme.delete_PyObject_p_p(self.last_passcb)
84             self.last_passcb = None
85
86     def _free_progresscb(self):
87         if self.last_progresscb != None:
88             if pygpgme.pygpgme_clear_generic_cb:
89                 pygpgme.pygpgme_clear_generic_cb(self.last_progresscb)
90             if pygpgme.delete_PyObject_p_p:
91                 pygpgme.delete_PyObject_p_p(self.last_progresscb)
92             self.last_progresscb = None
93
94     def op_keylist_all(self, *args, **kwargs):
95         self.op_keylist_start(*args, **kwargs)
96         key = self.op_keylist_next()
97         while key:
98             yield key
99             key = self.op_keylist_next()
100
101     def op_keylist_next(self):
102         """Returns the next key in the list created
103         by a call to op_keylist_start().  The object returned
104         is of type Key."""
105         ptr = pygpgme.new_gpgme_key_t_p()
106         try:
107             errorcheck(pygpgme.gpgme_op_keylist_next(self.wrapped, ptr))
108             key = pygpgme.gpgme_key_t_p_value(ptr)
109         except errors.GPGMEError as excp:
110             key = None
111             if excp.getcode() != errors.EOF:
112                 raise excp
113         pygpgme.delete_gpgme_key_t_p(ptr)
114         if key:
115             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
116             return key
117
118     def get_key(self, fpr, secret):
119         """Return the key corresponding to the fingerprint 'fpr'"""
120         ptr = pygpgme.new_gpgme_key_t_p()
121         errorcheck(pygpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
122         key = pygpgme.gpgme_key_t_p_value(ptr)
123         pygpgme.delete_gpgme_key_t_p(ptr)
124         if key:
125             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
126             return key
127
128     def op_trustlist_all(self, *args, **kwargs):
129         self.op_trustlist_start(*args, **kwargs)
130         trust = self.ctx.op_trustlist_next()
131         while trust:
132             yield trust
133             trust = self.ctx.op_trustlist_next()
134
135     def op_trustlist_next(self):
136         """Returns the next trust item in the list created
137         by a call to op_trustlist_start().  The object returned
138         is of type TrustItem."""
139         ptr = pygpgme.new_gpgme_trust_item_t_p()
140         try:
141             errorcheck(pygpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
142             trust = pygpgme.gpgme_trust_item_t_p_value(ptr)
143         except errors.GPGMEError as excp:
144             trust = None
145             if excp.getcode() != errors.EOF:
146                 raise
147         pygpgme.delete_gpgme_trust_item_t_p(ptr)
148         return trust
149
150     def set_passphrase_cb(self, func, hook=None):
151         """Sets the passphrase callback to the function specified by func.
152
153         When the system needs a passphrase, it will call func with three args:
154         hint, a string describing the key it needs the passphrase for;
155         desc, a string describing the passphrase it needs;
156         prev_bad, a boolean equal True if this is a call made after
157         unsuccessful previous attempt.
158
159         If hook has a value other than None it will be passed into the func
160         as a forth argument.
161
162         Please see the GPGME manual for more information.
163         """
164         self._free_passcb()
165         if func == None:
166             hookdata = None
167         else:
168             self.last_passcb = pygpgme.new_PyObject_p_p()
169             if hook == None:
170                 hookdata = (self, func)
171             else:
172                 hookdata = (self, func, hook)
173         pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
174
175     def set_progress_cb(self, func, hook=None):
176         """Sets the progress meter callback to the function specified by
177
178         This function will be called to provide an interactive update of
179         the system's progress.
180
181         Please see the GPGME manual for more information."""
182         self._free_progresscb()
183         if func == None:
184             hookdata = None
185         else:
186             self.last_progresscb = pygpgme.new_PyObject_p_p()
187             if hook == None:
188                 hookdata = (self, func)
189             else:
190                 hookdata = (self, func, hook)
191         pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
192
193     def get_engine_info(self):
194         """Returns this context specific engine info"""
195         return pygpgme.gpgme_ctx_get_engine_info(self.wrapped)
196
197     def set_engine_info(self, proto, file_name, home_dir=None):
198         """Changes the configuration of the crypto engine implementing the
199     protocol 'proto' for the context. 'file_name' is the file name of
200     the executable program implementing this protocol. 'home_dir' is the
201     directory name of the configuration directory (engine's default is
202     used if omitted)."""
203         errorcheck(pygpgme.gpgme_ctx_set_engine_info(self.wrapped, proto, file_name, home_dir))
204
205     def wait(self, hang):
206         """Wait for asynchronous call to finish. Wait forever if hang is True
207
208         Return:
209             On an async call completion its return status.
210             On timeout - None.
211
212         Please read the GPGME manual for more information."""
213         ptr = pygpgme.new_gpgme_error_t_p()
214         context = pygpgme.gpgme_wait(self.wrapped, ptr, hang)
215         status = pygpgme.gpgme_error_t_p_value(ptr)
216         pygpgme.delete_gpgme_error_t_p(ptr)
217
218         if context == None:
219             errorcheck(status)
220             return None
221         else:
222             return status
223
224     def op_edit(self, key, func, fnc_value, out):
225         """Start key editing using supplied callback function"""
226         if key == None:
227             raise ValueError("op_edit: First argument cannot be None")
228         if fnc_value:
229             opaquedata = (self, func, fnc_value)
230         else:
231             opaquedata = (self, func)
232
233         result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
234         if self._callback_excinfo:
235             pygpgme.pygpgme_raise_callback_exception(self)
236         errorcheck(result)
237
238 class Data(GpgmeWrapper):
239     """From the GPGME C manual:
240
241 * A lot of data has to be exchanged between the user and the crypto
242 * engine, like plaintext messages, ciphertext, signatures and information
243 * about the keys.  The technical details about exchanging the data
244 * information are completely abstracted by GPGME.  The user provides and
245 * receives the data via `gpgme_data_t' objects, regardless of the
246 * communication protocol between GPGME and the crypto engine in use.
247
248         This Data class is the implementation of the GpgmeData objects.
249
250         Please see the information about __init__ for instantiation."""
251
252     def _getctype(self):
253         return 'gpgme_data_t'
254
255     def _getnameprepend(self):
256         return 'gpgme_data_'
257
258     def _errorcheck(self, name):
259         """This function should list all functions returning gpgme_error_t"""
260         if name == 'gpgme_data_release_and_get_mem' or \
261                name == 'gpgme_data_get_encoding' or \
262                name == 'gpgme_data_seek':
263             return 0
264         return 1
265
266     def __init__(self, string = None, file = None, offset = None,
267                  length = None, cbs = None):
268         """Initialize a new gpgme_data_t object.
269
270         If no args are specified, make it an empty object.
271
272         If string alone is specified, initialize it with the data
273         contained there.
274
275         If file, offset, and length are all specified, file must
276         be either a filename or a file-like object, and the object
277         will be initialized by reading the specified chunk from the file.
278
279         If cbs is specified, it MUST be a tuple of the form:
280
281         ((read_cb, write_cb, seek_cb, release_cb), hook)
282
283         where func is a callback function taking two arguments (count,
284         hook) and returning a string of read data, or None on EOF.
285         This will supply the read() method for the system.
286
287         If file is specified without any other arguments, then
288         it must be a filename, and the object will be initialized from
289         that file.
290
291         Any other use will result in undefined or erroneous behavior."""
292         super().__init__(None)
293         self.last_readcb = None
294
295         if cbs != None:
296             self.new_from_cbs(*cbs)
297         elif string != None:
298             self.new_from_mem(string)
299         elif file != None and offset != None and length != None:
300             self.new_from_filepart(file, offset, length)
301         elif file != None:
302             if type(file) == type("x"):
303                 self.new_from_file(file)
304             else:
305                 self.new_from_fd(file)
306         else:
307             self.new()
308
309     def __del__(self):
310         if not pygpgme:
311             # At interpreter shutdown, pygpgme is set to NONE.
312             return
313
314         if self.wrapped != None and pygpgme.gpgme_data_release:
315             pygpgme.gpgme_data_release(self.wrapped)
316         self._free_readcb()
317
318     def _free_readcb(self):
319         if self.last_readcb != None:
320             if pygpgme.pygpgme_clear_generic_cb:
321                 pygpgme.pygpgme_clear_generic_cb(self.last_readcb)
322             if pygpgme.delete_PyObject_p_p:
323                 pygpgme.delete_PyObject_p_p(self.last_readcb)
324             self.last_readcb = None
325
326     def new(self):
327         tmp = pygpgme.new_gpgme_data_t_p()
328         errorcheck(pygpgme.gpgme_data_new(tmp))
329         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
330         pygpgme.delete_gpgme_data_t_p(tmp)
331
332     def new_from_mem(self, string, copy = 1):
333         tmp = pygpgme.new_gpgme_data_t_p()
334         errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
335         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
336         pygpgme.delete_gpgme_data_t_p(tmp)
337
338     def new_from_file(self, filename, copy = 1):
339         tmp = pygpgme.new_gpgme_data_t_p()
340         errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
341         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
342         pygpgme.delete_gpgme_data_t_p(tmp)
343
344     def new_from_cbs(self, funcs, hook):
345         """Argument funcs must be a 4 element tuple with callbacks:
346         (read_cb, write_cb, seek_cb, release_cb)"""
347         tmp = pygpgme.new_gpgme_data_t_p()
348         self._free_readcb()
349         self.last_readcb = pygpgme.new_PyObject_p_p()
350         hookdata = (funcs, hook)
351         pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.last_readcb)
352         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
353         pygpgme.delete_gpgme_data_t_p(tmp)
354
355     def new_from_filepart(self, file, offset, length):
356         """This wraps the GPGME gpgme_data_new_from_filepart() function.
357         The argument "file" may be:
358
359         1. a string specifying a file name, or
360         3. a a file-like object. supporting the fileno() call and the mode
361            attribute."""
362
363         tmp = pygpgme.new_gpgme_data_t_p()
364         filename = None
365         fp = None
366
367         if type(file) == type("x"):
368             filename = file
369         else:
370             fp = pygpgme.fdopen(file.fileno(), file.mode)
371             if fp == None:
372                 raise ValueError("Failed to open file from %s arg %s" % \
373                       (str(type(file)), str(file)))
374
375         errorcheck(pygpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
376                                                       offset, length))
377         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
378         pygpgme.delete_gpgme_data_t_p(tmp)
379
380     def new_from_fd(self, file):
381         """This wraps the GPGME gpgme_data_new_from_fd() function.
382         The argument "file" may be a file-like object, supporting the fileno()
383         call and the mode attribute."""
384
385         tmp = pygpgme.new_gpgme_data_t_p()
386         fp = pygpgme.fdopen(file.fileno(), file.mode)
387         if fp == None:
388             raise ValueError("Failed to open file from %s arg %s" % \
389                   (str(type(file)), str(file)))
390         errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, fp))
391         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
392         pygpgme.delete_gpgme_data_t_p(tmp)
393
394     def new_from_stream(self, file):
395         """This wrap around gpgme_data_new_from_stream is an alias for
396         new_from_fd() method since in python there's not difference
397         between file stream and file descriptor"""
398         self.new_from_fd(file)
399
400     def write(self, buffer):
401         """Write buffer given as string or bytes.
402
403         If a string is given, it is implicitly encoded using UTF-8."""
404         written = pygpgme.gpgme_data_write(self.wrapped, buffer)
405         if written < 0:
406             raise GPGMEError.fromSyserror()
407         return written
408
409     def read(self, size = -1):
410         """Read at most size bytes, returned as bytes.
411
412         If the size argument is negative or omitted, read until EOF is reached.
413
414         Returns the data read, or the empty string if there was no data
415         to read before EOF was reached."""
416
417         if size == 0:
418             return ''
419
420         if size > 0:
421             return pygpgme.gpgme_data_read(self.wrapped, size)
422         else:
423             chunks = []
424             while 1:
425                 result = pygpgme.gpgme_data_read(self.wrapped, 4096)
426                 if len(result) == 0:
427                     break
428                 chunks.append(result)
429             return b''.join(chunks)
430
431 def pubkey_algo_name(algo):
432     return pygpgme.gpgme_pubkey_algo_name(algo)
433
434 def hash_algo_name(algo):
435     return pygpgme.gpgme_hash_algo_name(algo)
436
437 def get_protocol_name(proto):
438     return pygpgme.gpgme_get_protocol_name(proto)
439
440 def check_version(version=None):
441     return pygpgme.gpgme_check_version(version)
442
443 def engine_check_version (proto):
444     try:
445         errorcheck(pygpgme.gpgme_engine_check_version(proto))
446         return True
447     except errors.GPGMEError:
448         return False
449
450 def get_engine_info():
451     ptr = pygpgme.new_gpgme_engine_info_t_p()
452     try:
453         errorcheck(pygpgme.gpgme_get_engine_info(ptr))
454         info = pygpgme.gpgme_engine_info_t_p_value(ptr)
455     except errors.GPGMEError:
456         info = None
457     pygpgme.delete_gpgme_engine_info_t_p(ptr)
458     return info
459
460 def set_engine_info(proto, file_name, home_dir=None):
461     """Changes the default configuration of the crypto engine implementing
462     the protocol 'proto'. 'file_name' is the file name of
463     the executable program implementing this protocol. 'home_dir' is the
464     directory name of the configuration directory (engine's default is
465     used if omitted)."""
466     errorcheck(pygpgme.gpgme_set_engine_info(proto, file_name, home_dir))
467
468 def set_locale(category, value):
469     """Sets the default locale used by contexts"""
470     errorcheck(pygpgme.gpgme_set_locale(None, category, value))
471
472 def wait(hang):
473     """Wait for asynchronous call on any Context  to finish.
474     Wait forever if hang is True.
475
476     For finished anynch calls it returns a tuple (status, context):
477         status  - status return by asnynchronous call.
478         context - context which caused this call to return.
479
480     Please read the GPGME manual of more information."""
481     ptr = pygpgme.new_gpgme_error_t_p()
482     context = pygpgme.gpgme_wait(None, ptr, hang)
483     status = pygpgme.gpgme_error_t_p_value(ptr)
484     pygpgme.delete_gpgme_error_t_p(ptr)
485     if context == None:
486         errorcheck(status)
487     else:
488         context = Context(context)
489     return (status, context)