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