Commit d52cc3fb by rfkelly0

Refactor CacheFS for efficiency and usability.

The main class is now CacheFSMixin, a mixin class that can add caching to
any FS implementation.  It operates explicitly on a PathMap of recent 
info dicts, making it more efficient and more robust than the old CacheFS.

The CacheFS wrapper class is still there, but it's a trivial class that
just mixes CacheFSMixin into the base WrapFS class.

TahoeFS has also been modified to use the mixin architecture.
parent e85a0ceb
...@@ -62,7 +62,6 @@ class Connection: ...@@ -62,7 +62,6 @@ class Connection:
def _urlopen(self, req): def _urlopen(self, req):
try: try:
#print req.get_full_url()
return urlopen(req) return urlopen(req)
except Exception, e: except Exception, e:
if not getattr(e, 'getcode', None): if not getattr(e, 'getcode', None):
......
...@@ -40,6 +40,12 @@ to subprocess.Popen:: ...@@ -40,6 +40,12 @@ to subprocess.Popen::
>>> mp = dokan.MountProcess(fs,"Q",stderr=PIPE) >>> mp = dokan.MountProcess(fs,"Q",stderr=PIPE)
>>> dokan_errors = mp.communicate()[1] >>> dokan_errors = mp.communicate()[1]
If you are exposing an untrusted filesystem, you may like to apply the
wrapper class Win32SafetyFS before passing it into dokan. This will take
a number of steps to avoid suspicious operations on windows, such as
hiding autorun files.
The binding to Dokan is created via ctypes. Due to the very stable ABI of The binding to Dokan is created via ctypes. Due to the very stable ABI of
win32, this should work without further configuration on just about all win32, this should work without further configuration on just about all
systems with Dokan installed. systems with Dokan installed.
...@@ -64,6 +70,7 @@ from fs.base import threading ...@@ -64,6 +70,7 @@ from fs.base import threading
from fs.errors import * from fs.errors import *
from fs.path import * from fs.path import *
from fs.local_functools import wraps from fs.local_functools import wraps
from fs.wrapfs import WrapFS
try: try:
import libdokan import libdokan
...@@ -881,6 +888,39 @@ class MountProcess(subprocess.Popen): ...@@ -881,6 +888,39 @@ class MountProcess(subprocess.Popen):
mount(fs,drive,**opts) mount(fs,drive,**opts)
class Win32SafetyFS(WrapFS):
"""FS wrapper for extra safety when mounting on win32.
This wrapper class provides some safety features when mounting untrusted
filesystems on win32. Specifically:
* hiding autorun files
* removing colons from paths
"""
def __init__(self,wrapped_fs,allow_autorun=False):
self.allow_autorun = allow_autorun
super(Win32SafetyFS,self).__init__(wrapped_fs)
def _encode(self,path):
path = relpath(normpath(path))
path = path.replace(":","__colon__")
if not self.allow_autorun:
if path.lower().startswith("/_autorun."):
path = "/" + path[2:]
return path
def _decode(self,path):
path = relpath(normpath(path))
path = path.replace("__colon__",":")
if not self.allow_autorun:
if path.lower().startswith("/autorun."):
path = "/_" + path[1:]
return path
if __name__ == "__main__": if __name__ == "__main__":
import os, os.path import os, os.path
import tempfile import tempfile
......
...@@ -98,6 +98,7 @@ class FSTestCases(object): ...@@ -98,6 +98,7 @@ class FSTestCases(object):
except ResourceInvalidError: except ResourceInvalidError:
pass pass
except Exception: except Exception:
raise
ecls = sys.exc_info()[0] ecls = sys.exc_info()[0]
assert False, "%s raised instead of ResourceInvalidError" % (ecls,) assert False, "%s raised instead of ResourceInvalidError" % (ecls,)
else: else:
......
...@@ -179,6 +179,17 @@ if dokan.is_available: ...@@ -179,6 +179,17 @@ if dokan.is_available:
# out. Disabling the test for now. # out. Disabling the test for now.
pass pass
def test_safety_wrapper(self):
rawfs = MemoryFS()
safefs = dokan.Win32SafetyFS(rawfs)
rawfs.setcontents("autoRun.inf","evilcodeevilcode")
self.assertFalse(safefs.exists("autoRun.inf"))
self.assertTrue(safefs.exists("_autoRun.inf"))
self.assertTrue("autoRun.inf" not in safefs.listdir("/"))
rawfs.setcontents("file:stream","test")
self.assertFalse(safefs.exists("file:stream"))
self.assertTrue(safefs.exists("file__colon__stream"))
class TestDokan(unittest.TestCase,DokanTestCases,ThreadingTestCases): class TestDokan(unittest.TestCase,DokanTestCases,ThreadingTestCases):
def setUp(self): def setUp(self):
......
...@@ -216,18 +216,51 @@ class TestRemoteFileBuffer(unittest.TestCase, FSTestCases, ThreadingTestCases): ...@@ -216,18 +216,51 @@ class TestRemoteFileBuffer(unittest.TestCase, FSTestCases, ThreadingTestCases):
self.assertEquals(f.read(), contents[:10] + contents2) self.assertEquals(f.read(), contents[:10] + contents2)
f.close() f.close()
class TestCacheFS(unittest.TestCase,FSTestCases,ThreadingTestCases): class TestCacheFS(unittest.TestCase,FSTestCases,ThreadingTestCases):
"""Test simple operation of CacheFS""" """Test simple operation of CacheFS"""
def setUp(self): def setUp(self):
self._check_interval = sys.getcheckinterval() self._check_interval = sys.getcheckinterval()
sys.setcheckinterval(10) sys.setcheckinterval(10)
self.fs = CacheFS(TempFS()) self.wrapped_fs = TempFS()
self.fs = CacheFS(self.wrapped_fs,cache_timeout=0.01)
def tearDown(self): def tearDown(self):
self.fs.close() self.fs.close()
sys.setcheckinterval(self._check_interval) sys.setcheckinterval(self._check_interval)
def test_values_are_used_from_cache(self):
old_timeout = self.fs.cache_timeout
self.fs.cache_timeout = None
try:
self.assertFalse(self.fs.isfile("hello"))
self.wrapped_fs.setcontents("hello","world")
self.assertTrue(self.fs.isfile("hello"))
self.wrapped_fs.remove("hello")
self.assertTrue(self.fs.isfile("hello"))
self.fs.clear_cache()
self.assertFalse(self.fs.isfile("hello"))
finally:
self.fs.cache_timeout = old_timeout
def test_values_are_updated_in_cache(self):
old_timeout = self.fs.cache_timeout
self.fs.cache_timeout = None
try:
self.assertFalse(self.fs.isfile("hello"))
self.wrapped_fs.setcontents("hello","world")
self.assertTrue(self.fs.isfile("hello"))
self.wrapped_fs.remove("hello")
self.assertTrue(self.fs.isfile("hello"))
self.wrapped_fs.setcontents("hello","world")
self.assertTrue(self.fs.isfile("hello"))
self.fs.remove("hello")
self.assertFalse(self.fs.isfile("hello"))
finally:
self.fs.cache_timeout = old_timeout
class TestConnectionManagerFS(unittest.TestCase,FSTestCases):#,ThreadingTestCases): class TestConnectionManagerFS(unittest.TestCase,FSTestCases):#,ThreadingTestCases):
"""Test simple operation of ConnectionManagerFS""" """Test simple operation of ConnectionManagerFS"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment