python: Support status 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         self.last_statuscb = None
68
69     def __del__(self):
70         if not pygpgme:
71             # At interpreter shutdown, pygpgme is set to NONE.
72             return
73
74         self._free_passcb()
75         self._free_progresscb()
76         if self.own and pygpgme.gpgme_release:
77             pygpgme.gpgme_release(self.wrapped)
78
79     def _free_passcb(self):
80         if self.last_passcb != None:
81             if pygpgme.pygpgme_clear_generic_cb:
82                 pygpgme.pygpgme_clear_generic_cb(self.last_passcb)
83             if pygpgme.delete_PyObject_p_p:
84                 pygpgme.delete_PyObject_p_p(self.last_passcb)
85             self.last_passcb = None
86
87     def _free_progresscb(self):
88         if self.last_progresscb != None:
89             if pygpgme.pygpgme_clear_generic_cb:
90                 pygpgme.pygpgme_clear_generic_cb(self.last_progresscb)
91             if pygpgme.delete_PyObject_p_p:
92                 pygpgme.delete_PyObject_p_p(self.last_progresscb)
93             self.last_progresscb = None
94
95     def _free_statuscb(self):
96         if self.last_statuscb != None:
97             if pygpgme.pygpgme_clear_generic_cb:
98                 pygpgme.pygpgme_clear_generic_cb(self.last_statuscb)
99             if pygpgme.delete_PyObject_p_p:
100                 pygpgme.delete_PyObject_p_p(self.last_statuscb)
101             self.last_statuscb = None
102
103     def op_keylist_all(self, *args, **kwargs):
104         self.op_keylist_start(*args, **kwargs)
105         key = self.op_keylist_next()
106         while key:
107             yield key
108             key = self.op_keylist_next()
109
110     def op_keylist_next(self):
111         """Returns the next key in the list created
112         by a call to op_keylist_start().  The object returned
113         is of type Key."""
114         ptr = pygpgme.new_gpgme_key_t_p()
115         try:
116             errorcheck(pygpgme.gpgme_op_keylist_next(self.wrapped, ptr))
117             key = pygpgme.gpgme_key_t_p_value(ptr)
118         except errors.GPGMEError as excp:
119             key = None
120             if excp.getcode() != errors.EOF:
121                 raise excp
122         pygpgme.delete_gpgme_key_t_p(ptr)
123         if key:
124             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
125             return key
126
127     def get_key(self, fpr, secret):
128         """Return the key corresponding to the fingerprint 'fpr'"""
129         ptr = pygpgme.new_gpgme_key_t_p()
130         errorcheck(pygpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
131         key = pygpgme.gpgme_key_t_p_value(ptr)
132         pygpgme.delete_gpgme_key_t_p(ptr)
133         if key:
134             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
135             return key
136
137     def op_trustlist_all(self, *args, **kwargs):
138         self.op_trustlist_start(*args, **kwargs)
139         trust = self.ctx.op_trustlist_next()
140         while trust:
141             yield trust
142             trust = self.ctx.op_trustlist_next()
143
144     def op_trustlist_next(self):
145         """Returns the next trust item in the list created
146         by a call to op_trustlist_start().  The object returned
147         is of type TrustItem."""
148         ptr = pygpgme.new_gpgme_trust_item_t_p()
149         try:
150             errorcheck(pygpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
151             trust = pygpgme.gpgme_trust_item_t_p_value(ptr)
152         except errors.GPGMEError as excp:
153             trust = None
154             if excp.getcode() != errors.EOF:
155                 raise
156         pygpgme.delete_gpgme_trust_item_t_p(ptr)
157         return trust
158
159     def set_passphrase_cb(self, func, hook=None):
160         """Sets the passphrase callback to the function specified by func.
161
162         When the system needs a passphrase, it will call func with three args:
163         hint, a string describing the key it needs the passphrase for;
164         desc, a string describing the passphrase it needs;
165         prev_bad, a boolean equal True if this is a call made after
166         unsuccessful previous attempt.
167
168         If hook has a value other than None it will be passed into the func
169         as a forth argument.
170
171         Please see the GPGME manual for more information.
172         """
173         self._free_passcb()
174         if func == None:
175             hookdata = None
176         else:
177             self.last_passcb = pygpgme.new_PyObject_p_p()
178             if hook == None:
179                 hookdata = (self, func)
180             else:
181                 hookdata = (self, func, hook)
182         pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
183
184     def set_progress_cb(self, func, hook=None):
185         """Sets the progress meter callback to the function specified by FUNC.
186         If FUNC is None, the callback will be cleared.
187
188         This function will be called to provide an interactive update
189         of the system's progress.  The function will be called with
190         three arguments, type, total, and current.  If HOOK is not
191         None, it will be supplied as fourth argument.
192
193         Please see the GPGME manual for more information.
194
195         """
196         self._free_progresscb()
197         if func == None:
198             hookdata = None
199         else:
200             self.last_progresscb = pygpgme.new_PyObject_p_p()
201             if hook == None:
202                 hookdata = (self, func)
203             else:
204                 hookdata = (self, func, hook)
205         pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
206
207     def set_status_cb(self, func, hook=None):
208         """Sets the status callback to the function specified by FUNC.  If
209         FUNC is None, the callback will be cleared.
210
211         The function will be called with two arguments, keyword and
212         args.  If HOOK is not None, it will be supplied as third
213         argument.
214
215         Please see the GPGME manual for more information.
216
217         """
218         self._free_statuscb()
219         if func == None:
220             hookdata = None
221         else:
222             self.last_statuscb = pygpgme.new_PyObject_p_p()
223             if hook == None:
224                 hookdata = (self, func)
225             else:
226                 hookdata = (self, func, hook)
227         pygpgme.pygpgme_set_status_cb(self.wrapped, hookdata,
228                                       self.last_statuscb)
229
230     def get_engine_info(self):
231         """Returns this context specific engine info"""
232         return pygpgme.gpgme_ctx_get_engine_info(self.wrapped)
233
234     def set_engine_info(self, proto, file_name, home_dir=None):
235         """Changes the configuration of the crypto engine implementing the
236     protocol 'proto' for the context. 'file_name' is the file name of
237     the executable program implementing this protocol. 'home_dir' is the
238     directory name of the configuration directory (engine's default is
239     used if omitted)."""
240         errorcheck(pygpgme.gpgme_ctx_set_engine_info(self.wrapped, proto, file_name, home_dir))
241
242     def wait(self, hang):
243         """Wait for asynchronous call to finish. Wait forever if hang is True
244
245         Return:
246             On an async call completion its return status.
247             On timeout - None.
248
249         Please read the GPGME manual for more information."""
250         ptr = pygpgme.new_gpgme_error_t_p()
251         context = pygpgme.gpgme_wait(self.wrapped, ptr, hang)
252         status = pygpgme.gpgme_error_t_p_value(ptr)
253         pygpgme.delete_gpgme_error_t_p(ptr)
254
255         if context == None:
256             errorcheck(status)
257             return None
258         else:
259             return status
260
261     def op_edit(self, key, func, fnc_value, out):
262         """Start key editing using supplied callback function"""
263         if key == None:
264             raise ValueError("op_edit: First argument cannot be None")
265         if fnc_value:
266             opaquedata = (self, func, fnc_value)
267         else:
268             opaquedata = (self, func)
269
270         result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
271         if self._callback_excinfo:
272             pygpgme.pygpgme_raise_callback_exception(self)
273         errorcheck(result)
274
275 class Data(GpgmeWrapper):
276     """From the GPGME C manual:
277
278 * A lot of data has to be exchanged between the user and the crypto
279 * engine, like plaintext messages, ciphertext, signatures and information
280 * about the keys.  The technical details about exchanging the data
281 * information are completely abstracted by GPGME.  The user provides and
282 * receives the data via `gpgme_data_t' objects, regardless of the
283 * communication protocol between GPGME and the crypto engine in use.
284
285         This Data class is the implementation of the GpgmeData objects.
286
287         Please see the information about __init__ for instantiation."""
288
289     def _getctype(self):
290         return 'gpgme_data_t'
291
292     def _getnameprepend(self):
293         return 'gpgme_data_'
294
295     def _errorcheck(self, name):
296         """This function should list all functions returning gpgme_error_t"""
297         if name == 'gpgme_data_release_and_get_mem' or \
298                name == 'gpgme_data_get_encoding' or \
299                name == 'gpgme_data_seek':
300             return 0
301         return 1
302
303     def __init__(self, string = None, file = None, offset = None,
304                  length = None, cbs = None):
305         """Initialize a new gpgme_data_t object.
306
307         If no args are specified, make it an empty object.
308
309         If string alone is specified, initialize it with the data
310         contained there.
311
312         If file, offset, and length are all specified, file must
313         be either a filename or a file-like object, and the object
314         will be initialized by reading the specified chunk from the file.
315
316         If cbs is specified, it MUST be a tuple of the form:
317
318         ((read_cb, write_cb, seek_cb, release_cb), hook)
319
320         where func is a callback function taking two arguments (count,
321         hook) and returning a string of read data, or None on EOF.
322         This will supply the read() method for the system.
323
324         If file is specified without any other arguments, then
325         it must be a filename, and the object will be initialized from
326         that file.
327
328         Any other use will result in undefined or erroneous behavior."""
329         super().__init__(None)
330         self.last_readcb = None
331
332         if cbs != None:
333             self.new_from_cbs(*cbs)
334         elif string != None:
335             self.new_from_mem(string)
336         elif file != None and offset != None and length != None:
337             self.new_from_filepart(file, offset, length)
338         elif file != None:
339             if type(file) == type("x"):
340                 self.new_from_file(file)
341             else:
342                 self.new_from_fd(file)
343         else:
344             self.new()
345
346     def __del__(self):
347         if not pygpgme:
348             # At interpreter shutdown, pygpgme is set to NONE.
349             return
350
351         if self.wrapped != None and pygpgme.gpgme_data_release:
352             pygpgme.gpgme_data_release(self.wrapped)
353         self._free_readcb()
354
355     def _free_readcb(self):
356         if self.last_readcb != None:
357             if pygpgme.pygpgme_clear_generic_cb:
358                 pygpgme.pygpgme_clear_generic_cb(self.last_readcb)
359             if pygpgme.delete_PyObject_p_p:
360                 pygpgme.delete_PyObject_p_p(self.last_readcb)
361             self.last_readcb = None
362
363     def new(self):
364         tmp = pygpgme.new_gpgme_data_t_p()
365         errorcheck(pygpgme.gpgme_data_new(tmp))
366         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
367         pygpgme.delete_gpgme_data_t_p(tmp)
368
369     def new_from_mem(self, string, copy = 1):
370         tmp = pygpgme.new_gpgme_data_t_p()
371         errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
372         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
373         pygpgme.delete_gpgme_data_t_p(tmp)
374
375     def new_from_file(self, filename, copy = 1):
376         tmp = pygpgme.new_gpgme_data_t_p()
377         errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
378         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
379         pygpgme.delete_gpgme_data_t_p(tmp)
380
381     def new_from_cbs(self, funcs, hook):
382         """Argument funcs must be a 4 element tuple with callbacks:
383         (read_cb, write_cb, seek_cb, release_cb)"""
384         tmp = pygpgme.new_gpgme_data_t_p()
385         self._free_readcb()
386         self.last_readcb = pygpgme.new_PyObject_p_p()
387         hookdata = (funcs, hook)
388         pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.last_readcb)
389         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
390         pygpgme.delete_gpgme_data_t_p(tmp)
391
392     def new_from_filepart(self, file, offset, length):
393         """This wraps the GPGME gpgme_data_new_from_filepart() function.
394         The argument "file" may be:
395
396         1. a string specifying a file name, or
397         3. a a file-like object. supporting the fileno() call and the mode
398            attribute."""
399
400         tmp = pygpgme.new_gpgme_data_t_p()
401         filename = None
402         fp = None
403
404         if type(file) == type("x"):
405             filename = file
406         else:
407             fp = pygpgme.fdopen(file.fileno(), file.mode)
408             if fp == None:
409                 raise ValueError("Failed to open file from %s arg %s" % \
410                       (str(type(file)), str(file)))
411
412         errorcheck(pygpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
413                                                       offset, length))
414         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
415         pygpgme.delete_gpgme_data_t_p(tmp)
416
417     def new_from_fd(self, file):
418         """This wraps the GPGME gpgme_data_new_from_fd() function.
419         The argument "file" may be a file-like object, supporting the fileno()
420         call and the mode attribute."""
421
422         tmp = pygpgme.new_gpgme_data_t_p()
423         fp = pygpgme.fdopen(file.fileno(), file.mode)
424         if fp == None:
425             raise ValueError("Failed to open file from %s arg %s" % \
426                   (str(type(file)), str(file)))
427         errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, fp))
428         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
429         pygpgme.delete_gpgme_data_t_p(tmp)
430
431     def new_from_stream(self, file):
432         """This wrap around gpgme_data_new_from_stream is an alias for
433         new_from_fd() method since in python there's not difference
434         between file stream and file descriptor"""
435         self.new_from_fd(file)
436
437     def write(self, buffer):
438         """Write buffer given as string or bytes.
439
440         If a string is given, it is implicitly encoded using UTF-8."""
441         written = pygpgme.gpgme_data_write(self.wrapped, buffer)
442         if written < 0:
443             raise GPGMEError.fromSyserror()
444         return written
445
446     def read(self, size = -1):
447         """Read at most size bytes, returned as bytes.
448
449         If the size argument is negative or omitted, read until EOF is reached.
450
451         Returns the data read, or the empty string if there was no data
452         to read before EOF was reached."""
453
454         if size == 0:
455             return ''
456
457         if size > 0:
458             return pygpgme.gpgme_data_read(self.wrapped, size)
459         else:
460             chunks = []
461             while 1:
462                 result = pygpgme.gpgme_data_read(self.wrapped, 4096)
463                 if len(result) == 0:
464                     break
465                 chunks.append(result)
466             return b''.join(chunks)
467
468 def pubkey_algo_name(algo):
469     return pygpgme.gpgme_pubkey_algo_name(algo)
470
471 def hash_algo_name(algo):
472     return pygpgme.gpgme_hash_algo_name(algo)
473
474 def get_protocol_name(proto):
475     return pygpgme.gpgme_get_protocol_name(proto)
476
477 def check_version(version=None):
478     return pygpgme.gpgme_check_version(version)
479
480 def engine_check_version (proto):
481     try:
482         errorcheck(pygpgme.gpgme_engine_check_version(proto))
483         return True
484     except errors.GPGMEError:
485         return False
486
487 def get_engine_info():
488     ptr = pygpgme.new_gpgme_engine_info_t_p()
489     try:
490         errorcheck(pygpgme.gpgme_get_engine_info(ptr))
491         info = pygpgme.gpgme_engine_info_t_p_value(ptr)
492     except errors.GPGMEError:
493         info = None
494     pygpgme.delete_gpgme_engine_info_t_p(ptr)
495     return info
496
497 def set_engine_info(proto, file_name, home_dir=None):
498     """Changes the default configuration of the crypto engine implementing
499     the protocol 'proto'. 'file_name' is the file name of
500     the executable program implementing this protocol. 'home_dir' is the
501     directory name of the configuration directory (engine's default is
502     used if omitted)."""
503     errorcheck(pygpgme.gpgme_set_engine_info(proto, file_name, home_dir))
504
505 def set_locale(category, value):
506     """Sets the default locale used by contexts"""
507     errorcheck(pygpgme.gpgme_set_locale(None, category, value))
508
509 def wait(hang):
510     """Wait for asynchronous call on any Context  to finish.
511     Wait forever if hang is True.
512
513     For finished anynch calls it returns a tuple (status, context):
514         status  - status return by asnynchronous call.
515         context - context which caused this call to return.
516
517     Please read the GPGME manual of more information."""
518     ptr = pygpgme.new_gpgme_error_t_p()
519     context = pygpgme.gpgme_wait(None, ptr, hang)
520     status = pygpgme.gpgme_error_t_p_value(ptr)
521     pygpgme.delete_gpgme_error_t_p(ptr)
522     if context == None:
523         errorcheck(status)
524     else:
525         context = Context(context)
526     return (status, context)