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