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