ZeroNet/src/util/helper.py

357 lines
10 KiB
Python
Raw Normal View History

import os
import stat
import socket
import struct
import re
import collections
import time
import logging
import base64
import json
2016-11-07 22:50:45 +01:00
import gevent
from Config import config
2019-03-15 23:49:55 +01:00
def atomicWrite(dest, content, mode="wb"):
try:
with open(dest + "-tmpnew", mode) as f:
f.write(content)
f.flush()
os.fsync(f.fileno())
if os.path.isfile(dest + "-tmpold"): # Previous incomplete write
os.rename(dest + "-tmpold", dest + "-tmpold-%s" % time.time())
if os.path.isfile(dest): # Rename old file to -tmpold
os.rename(dest, dest + "-tmpold")
os.rename(dest + "-tmpnew", dest)
if os.path.isfile(dest + "-tmpold"):
os.unlink(dest + "-tmpold") # Remove old file
return True
except Exception as err:
from Debug import Debug
logging.error(
"File %s write failed: %s, (%s) reverting..." %
(dest, Debug.formatException(err), Debug.formatStack())
)
if os.path.isfile(dest + "-tmpold") and not os.path.isfile(dest):
os.rename(dest + "-tmpold", dest)
return False
def jsonDumps(data):
content = json.dumps(data, indent=1, sort_keys=True)
# Make it a little more compact by removing unnecessary white space
def compact_dict(match):
if "\n" in match.group(0):
return match.group(0).replace(match.group(1), match.group(1).strip())
else:
return match.group(0)
2019-10-06 03:10:20 +02:00
content = re.sub(r"\{(\n[^,\[\{]{10,100000}?)\}[, ]{0,2}\n", compact_dict, content, flags=re.DOTALL)
def compact_list(match):
if "\n" in match.group(0):
stripped_lines = re.sub("\n[ ]*", "", match.group(1))
return match.group(0).replace(match.group(1), stripped_lines)
else:
return match.group(0)
2019-10-06 03:10:20 +02:00
content = re.sub(r"\[([^\[\{]{2,100000}?)\][, ]{0,2}\n", compact_list, content, flags=re.DOTALL)
# Remove end of line whitespace
content = re.sub(r"(?m)[ ]+$", "", content)
return content
def openLocked(path, mode="wb"):
try:
if os.name == "posix":
import fcntl
f = open(path, mode)
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
elif os.name == "nt":
import msvcrt
f = open(path, mode)
msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
else:
f = open(path, mode)
except (IOError, PermissionError, BlockingIOError) as err:
raise BlockingIOError("Unable to lock file: %s" % err)
return f
2016-11-07 22:50:01 +01:00
def getFreeSpace():
free_space = -1
if "statvfs" in dir(os): # Unix
2017-01-23 12:55:03 +01:00
statvfs = os.statvfs(config.data_dir.encode("utf8"))
2016-11-07 22:50:01 +01:00
free_space = statvfs.f_frsize * statvfs.f_bavail
else: # Windows
try:
import ctypes
free_space_pointer = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(
ctypes.c_wchar_p(config.data_dir), None, None, ctypes.pointer(free_space_pointer)
)
free_space = free_space_pointer.value
2019-03-15 21:06:59 +01:00
except Exception as err:
2016-11-07 22:50:01 +01:00
logging.error("GetFreeSpace error: %s" % err)
return free_space
2019-04-29 17:18:02 +02:00
def sqlquote(value):
if type(value) is int:
return str(value)
else:
return "'%s'" % value.replace("'", "''")
def shellquote(*args):
if len(args) == 1:
return '"%s"' % args[0].replace('"', "")
else:
return tuple(['"%s"' % arg.replace('"', "") for arg in args])
def packPeers(peers):
2019-01-20 03:21:07 +01:00
packed_peers = {"ipv4": [], "ipv6": [], "onion": []}
for peer in peers:
try:
2019-01-20 03:21:07 +01:00
ip_type = getIpType(peer.ip)
if ip_type in packed_peers:
packed_peers[ip_type].append(peer.packMyAddress())
2016-11-07 22:50:33 +01:00
except Exception:
2019-12-17 15:00:23 +01:00
logging.debug("Error packing peer address: %s" % peer)
return packed_peers
2019-01-20 03:21:07 +01:00
# ip, port to packed 6byte or 18byte format
def packAddress(ip, port):
2019-01-20 03:21:07 +01:00
if ":" in ip:
return socket.inet_pton(socket.AF_INET6, ip) + struct.pack("H", port)
else:
return socket.inet_aton(ip) + struct.pack("H", port)
2019-01-20 03:21:07 +01:00
# From 6byte or 18byte format to ip, port
def unpackAddress(packed):
2019-01-20 03:21:07 +01:00
if len(packed) == 18:
return socket.inet_ntop(socket.AF_INET6, packed[0:16]), struct.unpack_from("H", packed, 16)[0]
else:
if len(packed) != 6:
raise Exception("Invalid length ip4 packed address: %s" % len(packed))
2019-01-20 03:21:07 +01:00
return socket.inet_ntoa(packed[0:4]), struct.unpack_from("H", packed, 4)[0]
# onion, port to packed 12byte format
def packOnionAddress(onion, port):
onion = onion.replace(".onion", "")
return base64.b32decode(onion.upper()) + struct.pack("H", port)
# From 12byte format to ip, port
def unpackOnionAddress(packed):
2019-03-15 23:55:40 +01:00
return base64.b32encode(packed[0:-2]).lower().decode() + ".onion", struct.unpack("H", packed[-2:])[0]
# Get dir from file
2017-08-18 14:37:56 +02:00
# Return: data/site/content.json -> data/site/
def getDirname(path):
2016-04-06 13:48:13 +02:00
if "/" in path:
return path[:path.rfind("/") + 1].lstrip("/")
2016-04-06 13:48:13 +02:00
else:
return ""
2016-11-07 22:50:33 +01:00
# Get dir from file
# Return: data/site/content.json -> content.json
def getFilename(path):
2016-11-07 22:50:33 +01:00
return path[path.rfind("/") + 1:]
2019-01-20 03:21:07 +01:00
def getFilesize(path):
try:
s = os.stat(path)
2019-08-02 14:04:18 +02:00
except Exception:
return None
if stat.S_ISREG(s.st_mode): # Test if it's file
return s.st_size
else:
return None
2019-01-20 03:21:38 +01:00
# Convert hash to hashid for hashfield
def toHashId(hash):
return int(hash[0:4], 16)
# Merge dict values
def mergeDicts(dicts):
back = collections.defaultdict(set)
for d in dicts:
2019-03-15 21:06:59 +01:00
for key, val in d.items():
back[key].update(val)
return dict(back)
# Request https url using gevent SSL error workaround
def httpRequest(url, as_file=False):
if url.startswith("http://"):
2019-03-15 21:06:59 +01:00
import urllib.request
response = urllib.request.urlopen(url)
else: # Hack to avoid Python gevent ssl errors
import socket
2019-03-15 21:06:59 +01:00
import http.client
import ssl
host, request = re.match("https://(.*?)(/.*?)$", url).groups()
2019-03-15 21:06:59 +01:00
conn = http.client.HTTPSConnection(host)
sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address)
conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file)
conn.request("GET", request)
response = conn.getresponse()
2016-03-16 22:07:11 +01:00
if response.status in [301, 302, 303, 307, 308]:
logging.info("Redirect to: %s" % response.getheader('Location'))
response = httpRequest(response.getheader('Location'))
if as_file:
2019-03-15 21:06:59 +01:00
import io
data = io.BytesIO()
while True:
buff = response.read(1024 * 16)
if not buff:
break
data.write(buff)
return data
else:
return response
2016-11-07 22:50:45 +01:00
def timerCaller(secs, func, *args, **kwargs):
gevent.spawn_later(secs, timerCaller, secs, func, *args, **kwargs)
func(*args, **kwargs)
def timer(secs, func, *args, **kwargs):
2019-12-17 15:00:09 +01:00
return gevent.spawn_later(secs, timerCaller, secs, func, *args, **kwargs)
def create_connection(address, timeout=None, source_address=None):
if address in config.ip_local:
sock = socket.create_connection_original(address, timeout, source_address)
else:
sock = socket.create_connection_original(address, timeout, socket.bind_addr)
return sock
2019-01-20 03:21:38 +01:00
def socketBindMonkeyPatch(bind_ip, bind_port):
import socket
logging.info("Monkey patching socket to bind to: %s:%s" % (bind_ip, bind_port))
socket.bind_addr = (bind_ip, int(bind_port))
socket.create_connection_original = socket.create_connection
socket.create_connection = create_connection
def limitedGzipFile(*args, **kwargs):
import gzip
2019-01-20 03:21:38 +01:00
class LimitedGzipFile(gzip.GzipFile):
def read(self, size=-1):
2019-01-20 03:21:38 +01:00
return super(LimitedGzipFile, self).read(1024 * 1024 * 25)
return LimitedGzipFile(*args, **kwargs)
2018-01-19 02:21:54 +01:00
2019-01-20 03:21:38 +01:00
2018-01-19 02:21:54 +01:00
def avg(items):
if len(items) > 0:
return sum(items) / len(items)
else:
2018-01-30 13:58:01 +01:00
return 0
2019-01-20 03:21:38 +01:00
def isIp(ip):
if ":" in ip: # IPv6
try:
socket.inet_pton(socket.AF_INET6, ip)
return True
2019-08-02 14:04:18 +02:00
except Exception:
return False
else: # IPv4
try:
socket.inet_aton(ip)
return True
2019-08-02 14:04:18 +02:00
except Exception:
return False
2019-01-20 03:21:38 +01:00
local_ip_pattern = re.compile(r"^127\.|192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|169\.254\.|::1$|fe80")
2018-01-30 13:58:01 +01:00
def isPrivateIp(ip):
return local_ip_pattern.match(ip)
def getIpType(ip):
if ip.endswith(".onion"):
return "onion"
elif ":" in ip:
return "ipv6"
2019-12-21 02:59:18 +01:00
elif re.match(r"[0-9\.]+$", ip):
return "ipv4"
else:
return "unknown"
def createSocket(ip, sock_type=socket.SOCK_STREAM):
ip_type = getIpType(ip)
if ip_type == "ipv6":
return socket.socket(socket.AF_INET6, sock_type)
else:
return socket.socket(socket.AF_INET, sock_type)
def getInterfaceIps(ip_type="ipv4"):
res = []
if ip_type == "ipv6":
test_ips = ["ff0e::c", "2606:4700:4700::1111"]
else:
test_ips = ['239.255.255.250', "8.8.8.8"]
for test_ip in test_ips:
try:
s = createSocket(test_ip, sock_type=socket.SOCK_DGRAM)
s.connect((test_ip, 1))
res.append(s.getsockname()[0])
2019-08-02 14:04:18 +02:00
except Exception:
pass
try:
res += [ip[4][0] for ip in socket.getaddrinfo(socket.gethostname(), 1)]
2019-08-02 14:04:18 +02:00
except Exception:
pass
res = [re.sub("%.*", "", ip) for ip in res if getIpType(ip) == ip_type and isIp(ip)]
return list(set(res))
def cmp(a, b):
return (a > b) - (a < b)
def encodeResponse(func): # Encode returned data from utf8 to bytes
def wrapper(*args, **kwargs):
back = func(*args, **kwargs)
if "__next__" in dir(back):
for part in back:
2019-10-06 03:10:43 +02:00
if type(part) == bytes:
yield part
else:
yield part.encode()
else:
2019-10-06 03:10:43 +02:00
if type(back) == bytes:
yield back
else:
yield back.encode()
return wrapper