.update-copyright.conf: convert %(project)s -> {project} formatting
[pyassuan.git] / pyassuan / common.py
index 8b99a4539e65e695a6d0cb17f78657a7e095589b..e8294a7d25c722de09cc0ef0c12baa74ab4b3861 100644 (file)
 """Items common to both the client and server
 """
 
+import array as _array
 import re as _re
+import socket as _socket
 
+from . import LOG as _LOG
 from . import error as _error
 
 
@@ -26,8 +29,8 @@ LINE_LENGTH = 1002  # 1000 + [CR,]LF
 _ENCODE_PATTERN = '(' + '|'.join(['%', '\r', '\n']) + ')'
 _ENCODE_STR_REGEXP = _re.compile(_ENCODE_PATTERN)
 _ENCODE_BYTE_REGEXP = _re.compile(_ENCODE_PATTERN.encode('ascii'))    
-_DECODE_STR_REGEXP = _re.compile('(%[0-9A-F]{2})')
-_DECODE_BYTE_REGEXP = _re.compile(b'(%[0-9A-F]{2})')
+_DECODE_STR_REGEXP = _re.compile('(%[0-9A-Fa-f]{2})')
+_DECODE_BYTE_REGEXP = _re.compile(b'(%[0-9A-Fa-f]{2})')
 _REQUEST_REGEXP = _re.compile('^(\w+)( *)(.*)\Z')
 
 
@@ -151,11 +154,7 @@ class Request (object):
     def from_bytes(self, line):
         if len(line) > 1000:  # TODO: byte-vs-str and newlines?
             raise _error.AssuanError(message='Line too long')
-        if line.startswith(b'D '):
-            self.command = 'D'
-            self.parameters = decode(line[2:])
-        else:
-            line = str(line, encoding='utf-8')
+        line = str(line, encoding='utf-8')
         match = _REQUEST_REGEXP.match(line)
         if not match:
             raise _error.AssuanError(message='Invalid request')
@@ -222,7 +221,7 @@ class Response (object):
     def __bytes__(self):
         if self.parameters:
             if self.type == 'D':
-                return b'{} {}'.format(b'D', self.parameters)
+                return b' '.join((b'D', self.parameters))
             else:
                 return '{} {}'.format(
                     self.type, encode(self.parameters)).encode('utf-8')
@@ -268,3 +267,48 @@ def error_response(error):
     ERR 1 General error
     """
     return Response(type='ERR', parameters=str(error))
+
+
+def send_fds(socket, msg=None, fds=None, logger=_LOG):
+    """Send a file descriptor over a Unix socket using ``sendmsg``.
+
+    ``sendmsg`` suport requires Python >= 3.3.
+
+    Code from
+    http://docs.python.org/dev/library/socket.html#socket.socket.sendmsg
+
+    Assuan equivalent is
+    http://www.gnupg.org/documentation/manuals/assuan/Client-code.html#function-assuan_005fsendfd
+    """
+    if msg is None:
+        msg = b''.join(
+            [b'# descriptors in flight: ', str(fds).encode('ascii'), b'\n'])
+    if logger is not None:
+        logger.debug('sending file descriptors {} down {}'.format(fds, socket))
+    return socket.sendmsg(
+        [msg],
+        [(_socket.SOL_SOCKET, _socket.SCM_RIGHTS, _array.array('i', fds))])
+
+def receive_fds(socket, msglen=200, maxfds=10, logger=_LOG):
+    """Recieve file descriptors using ``recvmsg``.
+
+    ``recvmsg`` suport requires Python >= 3.3.
+
+    Code from http://docs.python.org/dev/library/socket.html
+
+    Assuan equivalent is
+    http://www.gnupg.org/documentation/manuals/assuan/Client-code.html#fun_002dassuan_005freceivedfd
+    """
+    fds = _array.array('i')   # Array of ints
+    msg,ancdata,flags,addr = socket.recvmsg(
+        msglen, _socket.CMSG_LEN(maxfds * fds.itemsize))
+    for cmsg_level,cmsg_type,cmsg_data in ancdata:
+        if (cmsg_level == _socket.SOL_SOCKET and
+            cmsg_type == _socket.SCM_RIGHTS):
+            # Append data, ignoring any truncated integers at the end.
+            fds.fromstring(
+                cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
+    if logger is not None:
+        logger.debug('receiving file descriptors {} from {} ({})'.format(
+                fds, socket, msg))
+    return (msg, list(fds))