# HG changeset patch # User Randy Selzler # Date 1261415298 21600 # Node ID 607a934cc6bb6e3d4294bdd06faa28d81bb4507d # Parent d973b363d32e871103415634962ccc6e5b9032f1 Add support for docs/path/pages (docs tree) diff -r d973b363d32e -r 607a934cc6bb hatta.py --- a/hatta.py Thu Dec 03 01:31:43 2009 +0100 +++ b/hatta.py Mon Dec 21 11:08:18 2009 -0600 @@ -292,6 +292,8 @@ """ self.charset = charset or 'utf-8' + # RegEx matching path components that contain ".." + self.dot_dot_re = re.compile('(^|/)\.\.(?=(/|$))') self.path = path if not os.path.exists(self.path): os.makedirs(self.path) @@ -330,11 +332,17 @@ return path def _file_path(self, title): - return os.path.join(self.path, werkzeug.url_quote(title, safe='')) + if self.dot_dot_re.search(title): + raise werkzeug.exceptions.Forbidden( + u'_file_path: Link contains ".." components: %s' % title) + return os.path.join(self.path, werkzeug.url_quote(title, safe='/')) def _title_to_file(self, title): + if self.dot_dot_re.search(title): + raise werkzeug.exceptions.Forbidden( + u'_title_to_file: Link contains "..": %s' % title) return os.path.join(self.repo_prefix, - werkzeug.url_quote(title, safe='')) + werkzeug.url_quote(title, safe='/')) def _file_to_title(self, filename): assert filename.startswith(self.repo_prefix) @@ -383,12 +391,28 @@ @locked_repo def save_file(self, title, file_name, author=u'', comment=u'', parent=None): """Save an existing file as specified page.""" + import errno user = author.encode('utf-8') or _(u'anon').encode('utf-8') text = comment.encode('utf-8') or _(u'comment').encode('utf-8') repo_file = self._title_to_file(title) file_path = self._file_path(title) - mercurial.util.rename(file_name, file_path) + path_dir = os.path.dirname(file_path) + + if os.path.isdir(file_path): + raise werkzeug.exceptions.Forbidden( + u'save_file: Page name is a directory: %s' % file_path) + + try: + if not os.path.isdir(path_dir): + os.makedirs(path_dir) + mercurial.util.rename(file_name, file_path) + except OSError, err: + # Path component is an existing page? + msg = u'save_file: [Errno %s] %s: in path %s' % (err.errno, + os.strerror(err.errno), file_path) + raise werkzeug.exceptions.Forbidden(msg) + changectx = self._changectx() try: filectx_tip = changectx[repo_file] @@ -604,12 +628,15 @@ yield title, rev, date, author, comment def all_pages(self): - """Iterate over the titles of all pages in the wiki.""" - - for filename in os.listdir(self.path): - if (os.path.isfile(os.path.join(self.path, filename)) - and not filename.startswith('.')): - yield werkzeug.url_unquote(filename) + """Iterate over the titles of all pages in the wiki tree.""" + + for dirpath, dirnames, filenames in os.walk(self.path): + dirnames[:] = [d for d in dirnames if not d.startswith('.')] + relpath = dirpath[len(self.path)+1:] + for name in filenames: + if (os.path.isfile(os.path.join(dirpath, name)) + and not name.startswith('.')): + yield werkzeug.url_unquote(os.path.join(relpath, name)) def changed_since(self, rev): """Return all pages that changed since specified repository revision.""" @@ -2106,10 +2133,26 @@ yield u'' class WikiTitleConverter(werkzeug.routing.PathConverter): - """Behaves like the path converter, except that it escapes slashes.""" + """Behaves like the path converter, except ".." components are rejected.""" + +# RLS, FIXME NOTE: to_url appears to control "Title" rendering at top of pages... + + def __init__FIXME_RLS(self, junk): + # If __init__FIXME_RLS is changed to __init__ then the class + # is initialized, but backlinks fail... Don't know why ! 2009-11-27 RLS + """ + Initialize for to_url and url_quote. + """ + # RegEx matching path components that contain ".." + self.dot_dot_re = re.compile('(^|/)\.\.(?=(/|$))') def to_url(self, value): - return werkzeug.url_quote(value, self.map.charset, safe="") + # Kludge because __init__ breaks backlinks. + self.dot_dot_re = re.compile('(^|/)\.\.(?=(/|$))') + if self.dot_dot_re.search(value): + raise werkzeug.exceptions.Forbidden( + u'to_url: Name contains ".." path component: %s' % value) + return werkzeug.url_quote(value, self.map.charset, safe='/') class WikiAllConverter(werkzeug.routing.BaseConverter): """Matches everything.""" @@ -2123,6 +2166,7 @@ application and most of the logic. """ storage_class = WikiStorage + titleConverter_class = WikiTitleConverter index_class = WikiSearch mime_map = { 'text': WikiPageText,