1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2008 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
# Contains common utility functions.
28
class IVLEError(Exception):
29
"""Legacy general IVLE exception.
31
This is the old "standard" exception class for IVLE errors. It is only
32
used in fileservice, and should not be used in any new code.
34
def __init__(self, httpcode, message=None):
35
self.httpcode = httpcode
36
self.message = message
37
self.args = (httpcode, message)
39
class IVLEJailError(Exception):
40
"""Exception proxying an in-jail error.
42
This exception indicates an error that occurred inside an IVLE CGI script
43
inside the jail. It should never be raised directly - only by the
46
Information will be retrieved from it, and then treated as a normal
49
def __init__(self, type_str, message, info):
50
self.type_str = type_str
51
self.message = message
54
class FakeObject(object):
55
""" A representation of an object that can't be Pickled """
56
def __init__(self, type, name, attrib={}):
62
return "<Fake %s %s>"%(self.type, self.name)
65
"""Given a path, returns a tuple consisting of the top-level directory in
66
the path, and the rest of the path. Note that both items in the tuple will
67
NOT begin with a slash, regardless of whether the original path did. Also
70
Always returns a pair of strings, except for one special case, in which
71
the path is completely empty (or just a single slash). In this case the
72
return value will be (None, ''). But still always returns a pair.
80
>>> split_path("home")
82
>>> split_path("home/docs/files")
83
('home', 'docs/files')
84
>>> split_path("//home/docs/files")
85
('', 'home/docs/files')
87
path = os.path.normpath(path)
88
# Ignore the opening slash
89
if path.startswith(os.sep):
90
path = path[len(os.sep):]
91
if path == '' or path == '.':
93
splitpath = path.split(os.sep, 1)
94
if len(splitpath) == 1:
95
return (splitpath[0], '')
97
return tuple(splitpath)
99
def relpath(path, start=os.path.curdir):
100
"""Return a relative version of a path.
101
XXX Backported from Python 2.6 posixpath.py.
105
raise ValueError("no path specified")
107
start_list = os.path.abspath(start).split(os.path.sep)
108
path_list = os.path.abspath(path).split(os.path.sep)
110
# Work out how much of the filepath is shared by start and path.
111
i = len(os.path.commonprefix([start_list, path_list]))
113
rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
115
return os.path.curdir
116
return os.path.join(*rel_list)
118
def incomplete_utf8_sequence(byteseq):
119
"""Calculate the completeness of a UTF-8 encoded string.
121
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
122
the end of the string which comprise an incomplete UTF-8 character
125
If the string is empty or ends with a complete character OR INVALID
127
Otherwise, returns 1-3 indicating the number of bytes in the final
128
incomplete (but valid) character sequence.
130
Does not check any bytes before the final sequence for correctness.
132
>>> incomplete_utf8_sequence("")
134
>>> incomplete_utf8_sequence("xy")
136
>>> incomplete_utf8_sequence("xy\xc3\xbc")
138
>>> incomplete_utf8_sequence("\xc3")
140
>>> incomplete_utf8_sequence("\xbc\xc3")
142
>>> incomplete_utf8_sequence("xy\xbc\xc3")
144
>>> incomplete_utf8_sequence("xy\xe0\xa0")
146
>>> incomplete_utf8_sequence("xy\xf4")
148
>>> incomplete_utf8_sequence("xy\xf4\x8f")
150
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
155
for b in byteseq[::-1]:
159
# 0xxxxxxx (single-byte character)
162
elif b & 0xc0 == 0x80:
163
# 10xxxxxx (subsequent byte)
165
elif b & 0xe0 == 0xc0:
166
# 110xxxxx (start of 2-byte sequence)
169
elif b & 0xf0 == 0xe0:
170
# 1110xxxx (start of 3-byte sequence)
173
elif b & 0xf8 == 0xf0:
174
# 11110xxx (start of 4-byte sequence)
182
# Seen too many "subsequent bytes", invalid
186
# We never saw a "first byte", invalid
189
# We now know expect and count
191
# Complete, or we saw an invalid sequence
197
def object_to_dict(attrnames, obj):
198
"""Convert an object into a dictionary.
200
This takes a shallow copy of the object.
202
@param attrnames: Set (or iterable) of names of attributes to be copied
203
into the dictionary. (We don't auto-lookup, because this
204
function needs to be used on magical objects).
206
return dict((k, getattr(obj, k))
207
for k in attrnames if not k.startswith('_'))
209
def safe_rmtree(path, ignore_errors=False, onerror=None):
210
"""Recursively delete a directory tree.
212
Copied from shutil.rmtree from Python 2.6, which does not follow symbolic
213
links (it is otherwise unsafe to call as root on untrusted directories; do
214
not use shutil.rmtree in this case, as you may be running Python 2.5).
216
If ignore_errors is set, errors are ignored; otherwise, if onerror
217
is set, it is called to handle the error with arguments (func,
218
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
219
path is the argument to that function that caused it to fail; and
220
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
221
is false and onerror is None, an exception is raised.
227
elif onerror is None:
231
if os.path.islink(path):
232
# symlinks to directories are forbidden, see bug #1669
233
raise OSError("Cannot call safe_rmtree on a symbolic link")
235
onerror(os.path.islink, path, sys.exc_info())
236
# can't continue even if onerror hook returns
240
names = os.listdir(path)
241
except os.error, err:
242
onerror(os.listdir, path, sys.exc_info())
244
fullname = os.path.join(path, name)
246
mode = os.lstat(fullname).st_mode
249
if stat.S_ISDIR(mode):
250
safe_rmtree(fullname, ignore_errors, onerror)
254
except os.error, err:
255
onerror(os.remove, fullname, sys.exc_info())
259
onerror(os.rmdir, path, sys.exc_info())