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 incomplete_utf8_sequence(byteseq):
100
"""Calculate the completeness of a UTF-8 encoded string.
102
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
103
the end of the string which comprise an incomplete UTF-8 character
106
If the string is empty or ends with a complete character OR INVALID
108
Otherwise, returns 1-3 indicating the number of bytes in the final
109
incomplete (but valid) character sequence.
111
Does not check any bytes before the final sequence for correctness.
113
>>> incomplete_utf8_sequence("")
115
>>> incomplete_utf8_sequence("xy")
117
>>> incomplete_utf8_sequence("xy\xc3\xbc")
119
>>> incomplete_utf8_sequence("\xc3")
121
>>> incomplete_utf8_sequence("\xbc\xc3")
123
>>> incomplete_utf8_sequence("xy\xbc\xc3")
125
>>> incomplete_utf8_sequence("xy\xe0\xa0")
127
>>> incomplete_utf8_sequence("xy\xf4")
129
>>> incomplete_utf8_sequence("xy\xf4\x8f")
131
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
136
for b in byteseq[::-1]:
140
# 0xxxxxxx (single-byte character)
143
elif b & 0xc0 == 0x80:
144
# 10xxxxxx (subsequent byte)
146
elif b & 0xe0 == 0xc0:
147
# 110xxxxx (start of 2-byte sequence)
150
elif b & 0xf0 == 0xe0:
151
# 1110xxxx (start of 3-byte sequence)
154
elif b & 0xf8 == 0xf0:
155
# 11110xxx (start of 4-byte sequence)
163
# Seen too many "subsequent bytes", invalid
167
# We never saw a "first byte", invalid
170
# We now know expect and count
172
# Complete, or we saw an invalid sequence
178
def object_to_dict(attrnames, obj):
179
"""Convert an object into a dictionary.
181
This takes a shallow copy of the object.
183
@param attrnames: Set (or iterable) of names of attributes to be copied
184
into the dictionary. (We don't auto-lookup, because this
185
function needs to be used on magical objects).
187
return dict((k, getattr(obj, k))
188
for k in attrnames if not k.startswith('_'))
190
def safe_rmtree(path, ignore_errors=False, onerror=None):
191
"""Recursively delete a directory tree.
193
Copied from shutil.rmtree from Python 2.6, which does not follow symbolic
194
links (it is otherwise unsafe to call as root on untrusted directories; do
195
not use shutil.rmtree in this case, as you may be running Python 2.5).
197
If ignore_errors is set, errors are ignored; otherwise, if onerror
198
is set, it is called to handle the error with arguments (func,
199
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
200
path is the argument to that function that caused it to fail; and
201
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
202
is false and onerror is None, an exception is raised.
208
elif onerror is None:
212
if os.path.islink(path):
213
# symlinks to directories are forbidden, see bug #1669
214
raise OSError("Cannot call safe_rmtree on a symbolic link")
216
onerror(os.path.islink, path, sys.exc_info())
217
# can't continue even if onerror hook returns
221
names = os.listdir(path)
222
except os.error, err:
223
onerror(os.listdir, path, sys.exc_info())
225
fullname = os.path.join(path, name)
227
mode = os.lstat(fullname).st_mode
230
if stat.S_ISDIR(mode):
231
safe_rmtree(fullname, ignore_errors, onerror)
235
except os.error, err:
236
onerror(os.remove, fullname, sys.exc_info())
240
onerror(os.rmdir, path, sys.exc_info())