Sanitize input tags
[cookbook.git] / cookbook / server.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Cookbook.
4 #
5 # Cookbook is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # Cookbook is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Cookbook.  If not, see <http://www.gnu.org/licenses/>.
17
18 """Serve cookbooks over HTTP.
19 """
20
21 import os
22 import random
23 import re
24 import types
25 from xml.sax import saxutils
26
27 import cherrypy
28 from jinja2 import Environment, FileSystemLoader
29
30
31 class Server (object):
32     """Cookbook web interface."""
33
34     def __init__(self, cookbook, template_root):
35         self.cookbook = cookbook
36         self.cookbook.make_index()
37         self.env = Environment(loader=FileSystemLoader(template_root))
38         self.tag_regexp = re.compile('[a-zA-Z./ ].*')  # allowed characters
39
40     def cleanup(self):
41         #self.cookbook.save('new-recipe')
42         pass
43
44     @cherrypy.expose
45     def index(self, tag=None):
46         """Recipe index page.
47
48         Recipes can be filtered by tag.
49         """
50         if isinstance(tag, types.StringTypes):
51             tag = [tag]
52         template = self.env.get_template('recipes.html')
53         return template.render(cookbook=self.cookbook,
54                                recipes=list(self.cookbook.tagged(tag)),
55                                selected_tags=tag)
56
57     @cherrypy.expose
58     def recipe(self, name=None):
59         """Single recipe page.
60         """
61         if name == None:
62             recipe = random.choice(self.cookbook)
63         else:
64             if type(name) == types.StringType:
65                 name = unicode(name, encoding='utf-8')
66             recipe = self.cookbook.index[name]
67         template = self.env.get_template('recipe.html')
68         return template.render(cookbook=self.cookbook, recipe=recipe)
69
70     @cherrypy.expose
71     def add_tag(self, name, tag):
72         """Add a tag to a single recipe."""
73         if type(name) == types.StringType:
74             name = unicode(name, encoding='utf-8')
75         recipe = self.cookbook.index[name]
76         if recipe.tags == None:
77             recipe.tags = []
78         tag = self._clean_tag(tag)
79         if tag == None:
80             return
81         if tag not in recipe.tags:
82             recipe.tags.append(tag)
83             with open(recipe.path, 'w') as f:
84                 recipe.save(f)
85         raise cherrypy.HTTPRedirect(
86             'recipe?name=%s' % recipe.clean_name(), status=302)
87
88     @cherrypy.expose
89     def remove_tag(self, name, tag):
90         """Remove a tag from a single recipe."""
91         if type(name) == types.StringType:
92             name = unicode(name, encoding='utf-8')
93         recipe = self.cookbook.index[name]
94         if recipe.tags == None:
95             return
96         tag = self._clean_tag(tag)
97         if tag == None:
98             return
99         if tag in recipe.tags:
100             recipe.tags.remove(tag)
101             with open(recipe.path, 'w') as f:
102                 recipe.save(f)
103         raise cherrypy.HTTPRedirect(
104             'recipe?name=%s' % recipe.clean_name(), status=302)
105
106     def _clean_tag(self, tag):
107         """Sanitize tag."""
108         tags = []
109         m = self.tag_regexp.match(t)
110         if m != None:
111             return m.group()
112         return None
113
114
115 def test():
116     import doctest
117     doctest.testmod()