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