# coding=utf-8
"""
Pinyto cloud - A secure cloud database for your personal data
Copyright (C) 2105 Johannes Merkert <jonny@pinyto.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import cffi
import os
import json
import struct
from pymongo.son_manipulator import ObjectId
from datetime import datetime
from time import mktime
_ffi = cffi.FFI()
_ffi.cdef('void _exit(int);')
_libc = _ffi.dlopen(None)
[docs]def libc_exit(n=1):
"""
Invoke _exit(2) system call.
:param n:
:type n: int
"""
_libc._exit(n)
[docs]def read_exact(fp, n):
"""
Read only the specified number of bytes
:param fp: file pointer
:type fp: file
:param n: number of bytes to read
:type n: int
:rtype: bytes
"""
buf = b''
while len(buf) < n:
buf2 = os.read(fp.fileno(), n)
if not buf2:
libc_exit(123)
buf += buf2
return buf
[docs]def write_exact(fp, s):
"""
Write only the specified number of bytes
:param fp: file pointer
:type fp: file
:param s: string to write and not a byte more than that
:type s: bytes
"""
done = 0
while done < len(s):
written = os.write(fp.fileno(), s[done:])
if not written:
libc_exit(123)
done += written
[docs]def write_to_pipe(pipe, data_dict):
"""
Writes the data_dict to the give pipe.
:param pipe: one part of socket.socketpair()
:type pipe: socket.Socket
:param data_dict:
:type data_dict: dict
"""
data_json = json.dumps(data_dict).encode('utf-8')
write_exact(pipe, struct.pack('>L', len(data_json)))
write_exact(pipe, data_json)
[docs]def read_from_pipe(pipe):
"""
Reads a json string from the pipe and decodes the json of that string.
:param pipe: one part of socket.socketpair()
:type pipe: socket.Socket
:rtype: dict
"""
sz, = struct.unpack('>L', read_exact(pipe, 4))
return json.loads(str(read_exact(pipe, sz), encoding='utf-8'))
[docs]def escape_all_objectids_and_datetime(conv_dict):
"""
This function escapes all ObjectId objects to make the dict json serializable.
:param conv_dict:
:type conv_dict: dict
"""
for key in conv_dict.keys():
if type(conv_dict[key]) == dict:
conv_dict[key] = escape_all_objectids_and_datetime(conv_dict[key])
elif type(conv_dict[key]) == ObjectId:
conv_dict[key] = {'ObjectId': str(conv_dict[key])}
elif type(conv_dict[key]) == datetime:
conv_dict[key] = {'Datetime': mktime(conv_dict[key].timetuple())}
return conv_dict
[docs]def unescape_all_objectids_and_datetime(conv_dict):
"""
This function reverses the escape of all ObjectId objects done by escape_all_objectids_and_datetime.
:param conv_dict:
:type conv_dict: dict
"""
for key in conv_dict.keys():
if type(conv_dict[key]) == dict:
if 'ObjectId' in conv_dict[key]:
conv_dict[key] = ObjectId(conv_dict[key]['ObjectId'])
elif 'Datetime' in conv_dict[key]:
conv_dict[key] = datetime.fromtimestamp(conv_dict[key]['Datetime'])
else:
conv_dict[key] = unescape_all_objectids_and_datetime(conv_dict[key])
return conv_dict
[docs]def piped_command(pipe, command_dict):
"""
Writes the command_dict to the pipe end reads the answer.
:param pipe: one part of socket.socketpair()
:type pipe: socket.Socket
:param command_dict:
:type command_dict: dict
"""
write_to_pipe(pipe, command_dict)
answer = read_from_pipe(pipe)
if 'response' in answer:
return answer['response']
else:
raise NoResponseFromHostException(
str(command_dict) + ' returned no valid response. ' +
'This means the host process lacks an implementation for this command.')
[docs]class NoResponseFromHostException(Exception):
"""
This is a custom exception which gets returned if no valid response is returned.
"""
[docs]class EmptyRequest():
"""
This class is used for processing jobs. They need request.body but it can be empty.
"""
def __init__(self):
self.body = ""