def test_etag_generation(): from tiddlyweb.web.util import tiddler_etag from tiddlyweb.model.bag import Bag from tiddlyweb.model.tiddler import Tiddler from tiddlyweb.config import config tiddler = Tiddler('monkey', 'bar') etag = tiddler_etag({'tiddlyweb.config': config}, tiddler) assert etag.startswith('"bar/monkey/0:') bag = Bag('bar') store.put(bag) store.put(tiddler) etag = tiddler_etag({'tiddlyweb.config': config}, tiddler) assert etag.startswith('"bar/monkey/1:')
def validate_tiddler_headers(environ, tiddler): """ Check ETAG and last modified information to see if a) the client can use its cached tiddler b) we have edit contention when trying to write. """ request_method = environ['REQUEST_METHOD'] tiddlers_etag = tiddler_etag(environ, tiddler) LOGGER.debug('attempting to validate %s with revision %s', tiddler.title.encode('utf-8'), tiddler.revision) etag = None last_modified = None if request_method == 'GET': incoming_etag = check_incoming_etag(environ, tiddlers_etag) if not incoming_etag: # only check last-modified if no etag last_modified_string = http_date_from_timestamp( tiddler.modified) last_modified = ('Last-Modified', last_modified_string) check_last_modified(environ, last_modified_string) else: incoming_etag = environ.get('HTTP_IF_MATCH', None) LOGGER.debug('attempting to validate incoming etag(PUT):' '%s against %s', incoming_etag, tiddlers_etag) if incoming_etag and not _etag_write_match(incoming_etag, tiddlers_etag): raise HTTP412('Provided ETag does not match. ' 'Server content probably newer.') etag = ('Etag', '%s' % tiddlers_etag) return last_modified, etag
def editor(environ, start_response, extant_tiddler=None, message=''): store = environ['tiddlyweb.store'] usersign = environ['tiddlyweb.usersign'] query = environ['tiddlyweb.query'] config = environ['tiddlyweb.config'] if extant_tiddler: tiddler = extant_tiddler else: bag_name = query['bag'][0] tiddler_title = query['tiddler'][0] if not (bag_name and tiddler_title): raise HTTP400('bad query: bag and tiddler required') bag = Bag(bag_name) try: bag = store.get(bag) except NoBagError: raise HTTP404('that tank does not exist') tiddler = Tiddler(tiddler_title, bag_name) tiddler_new = False try: tiddler = store.get(tiddler) except NoTiddlerError: tiddler.text = '' tiddler.type = 'text/x-markdown' tiddler_new = True if tiddler_new: bag.policy.allows(usersign, 'create') else: bag.policy.allows(usersign, 'write') edit_template = get_template(environ, EDIT_TEMPLATE) start_response('200 OK', [ ('Content-Type', 'text/html; charset=UTF-8'), ('Cache-Control', 'no-cache')]) return edit_template.generate({ 'socket_link': config.get('socket.link'), 'csrf_token': get_nonce(environ), 'gravatar': gravatar(environ), 'message': message, 'user': usersign['name'], 'tiddler': tiddler, 'etag': tiddler_etag(environ, tiddler).replace('"', '').split(':', 1)[0] })
def _tiddler_as_div(self, tiddler): """ Read in the tiddler from a div. """ recipe_name = '' if tiddler.recipe: recipe_name = tiddler.recipe try: host = server_base_url(self.environ) except KeyError: host = '' host = '%s' % host if binary_tiddler(tiddler): tiddler_output = self._binary_tiddler(tiddler) else: tiddler_output = tiddler.text if tiddler.type == 'None' or not tiddler.type: tiddler.type = '' return ('<div title="%s" server.title="%s" server.page.revision="%s" ' 'server.etag="%s" ' 'modifier="%s" creator="%s" server.workspace="bags/%s" ' 'server.type="tiddlyweb" server.host="%s" ' 'server.recipe="%s" server.bag="%s" server.permissions="%s" ' 'server.content-type="%s" ' 'modified="%s" created="%s" tags="%s" %s>\n' '<pre>%s</pre>\n</div>\n' % (escape_attribute_value(tiddler.title), escape_attribute_value(tiddler.title), tiddler.revision, escape_attribute_value(tiddler_etag( self.environ, tiddler)), escape_attribute_value(tiddler.modifier), escape_attribute_value(tiddler.creator), escape_attribute_value(tiddler.bag), host, escape_attribute_value(recipe_name), escape_attribute_value(tiddler.bag), self._tiddler_permissions(tiddler), tiddler.type, tiddler.modified, tiddler.created, escape_attribute_value(self.tags_as(tiddler.tags)), self._tiddler_fields(tiddler.fields), html_encode(tiddler_output)))
def validate_tiddler_headers(environ, tiddler): """ Check ETag and last modified header information to see if a) on ``GET`` the user agent can use its cached tiddler b) on ``PUT`` we have edit contention. """ request_method = environ['REQUEST_METHOD'] this_tiddlers_etag = tiddler_etag(environ, tiddler) LOGGER.debug('attempting to validate %s with revision %s', tiddler.title, tiddler.revision) etag = None last_modified = None if request_method == 'GET': last_modified_string = http_date_from_timestamp(tiddler.modified) last_modified = ('Last-Modified', last_modified_string) cache_header = 'no-cache' if CACHE_CONTROL_FIELD in tiddler.fields: try: cache_header = 'max-age=%s' % int( tiddler.fields[CACHE_CONTROL_FIELD]) except ValueError: pass # if the value is not an int use default header incoming_etag = check_incoming_etag(environ, this_tiddlers_etag, last_modified=last_modified_string, cache_control=cache_header) if not incoming_etag: # only check last-modified if no etag check_last_modified(environ, last_modified_string, etag=this_tiddlers_etag, cache_control=cache_header) else: incoming_etag = environ.get('HTTP_IF_MATCH', None) LOGGER.debug( 'attempting to validate incoming etag(PUT):' '%s against %s', incoming_etag, this_tiddlers_etag) if incoming_etag and not _etag_write_match(incoming_etag, this_tiddlers_etag): raise HTTP412('Provided ETag does not match. ' 'Server content probably newer.') etag = ('ETag', '%s' % this_tiddlers_etag) return last_modified, etag
def editor(environ, start_response, extant_tiddler=None, message=''): store = environ['tiddlyweb.store'] usersign = environ['tiddlyweb.usersign'] query = environ['tiddlyweb.query'] if extant_tiddler: tiddler = extant_tiddler bag = _fill_bag(store, Bag(tiddler.bag)) else: try: bag_name = query['bag'][0] tiddler_title = query['tiddler'][0] except KeyError: raise HTTP400('bad query: bag and tiddler required') if not (bag_name and tiddler_title): raise HTTP400('bad query: bag and tiddler required') bag = _fill_bag(store, Bag(bag_name)) tiddler = Tiddler(tiddler_title, bag_name) tiddler_new = False try: tiddler = store.get(tiddler) except NoTiddlerError: tiddler.text = '' tiddler.type = 'text/x-markdown' tiddler_new = True if tiddler_new: bag.policy.allows(usersign, 'create') else: bag.policy.allows(usersign, 'write') start_response('200 OK', [ ('Content-Type', 'text/html; charset=UTF-8'), ('Cache-Control', 'no-cache')]) return send_template(environ, EDIT_TEMPLATE, { 'bag': bag, 'message': message, 'tiddler': tiddler, 'etag': tiddler_etag(environ, tiddler).replace('"', '').split(':', 1)[0] })
def _validate_tiddler_headers(environ, tiddler): """ Check ETAG and last modified information to see if a) the client can use its cached tiddler b) we have edit contention when trying to write. """ request_method = environ['REQUEST_METHOD'] tiddler_etag = web.tiddler_etag(environ, tiddler) logging.debug('attempting to validate %s with revision %s', tiddler.title, tiddler.revision) etag = None last_modified = None if request_method == 'GET': incoming_etag = environ.get('HTTP_IF_NONE_MATCH', None) if incoming_etag: logging.debug( 'attempting to validate incoming etag(GET):' '%s against %s', incoming_etag, tiddler_etag) if incoming_etag == tiddler_etag: raise HTTP304(incoming_etag) else: last_modified_string = web.http_date_from_timestamp( tiddler.modified) last_modified = ('Last-Modified', last_modified_string) incoming_modified = environ.get('HTTP_IF_MODIFIED_SINCE', None) if incoming_modified and \ (web.datetime_from_http_date(incoming_modified) >= web.datetime_from_http_date(last_modified_string)): raise HTTP304('') else: incoming_etag = environ.get('HTTP_IF_MATCH', None) logging.debug( 'attempting to validate incoming etag(PUT):' '%s against %s', incoming_etag, tiddler_etag) if incoming_etag and not _etag_write_match(incoming_etag, tiddler_etag): raise HTTP412('Provided ETag does not match. ' 'Server content probably newer.') etag = ('Etag', '%s' % tiddler_etag) return last_modified, etag
def validate_tiddler_headers(environ, tiddler): """ Check ETag and last modified header information to see if a) on ``GET`` the user agent can use its cached tiddler b) on ``PUT`` we have edit contention. """ request_method = environ['REQUEST_METHOD'] this_tiddlers_etag = tiddler_etag(environ, tiddler) LOGGER.debug('attempting to validate %s with revision %s', tiddler.title, tiddler.revision) etag = None last_modified = None if request_method == 'GET': last_modified_string = http_date_from_timestamp(tiddler.modified) last_modified = ('Last-Modified', last_modified_string) cache_header = 'no-cache' if CACHE_CONTROL_FIELD in tiddler.fields: try: cache_header = 'max-age=%s' % int( tiddler.fields[CACHE_CONTROL_FIELD]) except ValueError: pass # if the value is not an int use default header incoming_etag = check_incoming_etag(environ, this_tiddlers_etag, last_modified=last_modified_string, cache_control=cache_header) if not incoming_etag: # only check last-modified if no etag check_last_modified(environ, last_modified_string, etag=this_tiddlers_etag, cache_control=cache_header) else: incoming_etag = environ.get('HTTP_IF_MATCH', None) LOGGER.debug('attempting to validate incoming etag(PUT):' '%s against %s', incoming_etag, this_tiddlers_etag) if incoming_etag and not _etag_write_match(incoming_etag, this_tiddlers_etag): raise HTTP412('Provided ETag does not match. ' 'Server content probably newer.') etag = ('ETag', '%s' % this_tiddlers_etag) return last_modified, etag
def _put_tiddler(environ, start_response, tiddler): """ The guts of putting a tiddler into the store. There's a fair bit of special handling done here depending on whether the tiddler already exists or not. """ store = environ['tiddlyweb.store'] try: bag = Bag(tiddler.bag) _check_and_validate_tiddler(environ, bag, tiddler) user = environ['tiddlyweb.usersign']['name'] if not user == 'GUEST': tiddler.modifier = user tiddler.modified = current_timestring() try: check_bag_constraint(environ, bag, 'accept') except (PermissionsError) as exc: _validate_tiddler_content(environ, tiddler) store.put(tiddler) except NoBagError as exc: raise HTTP409("Unable to put tiddler, %s. There is no bag named: " "%s (%s). Create the bag." % (tiddler.title, tiddler.bag, exc)) except NoTiddlerError as exc: raise HTTP404('Unable to put tiddler, %s. %s' % (tiddler.title, exc)) except TypeError as exc: raise HTTP409('Unable to put badly formed tiddler, %s:%s. %s' % (tiddler.bag, tiddler.title, exc)) etag = ('ETag', tiddler_etag(environ, tiddler)) response = [('Location', tiddler_url(environ, tiddler))] if etag: response.append(etag) start_response("204 No Content", response) return []
def _tiddler_as_div(self, tiddler): """ Read in the tiddler from a div. """ recipe_name = '' if tiddler.recipe: recipe_name = tiddler.recipe try: host = server_base_url(self.environ) except KeyError: host = '' host = '%s' % host if binary_tiddler(tiddler): tiddler_output = self._binary_tiddler(tiddler) else: tiddler_output = tiddler.text if tiddler.type == 'None' or not tiddler.type: tiddler.type = '' return ('<div title="%s" server.title="%s" server.page.revision="%s" ' 'server.etag="%s" ' 'modifier="%s" creator="%s" server.workspace="bags/%s" ' 'server.type="tiddlyweb" server.host="%s" ' 'server.recipe="%s" server.bag="%s" server.permissions="%s" ' 'server.content-type="%s" ' 'modified="%s" created="%s" tags="%s" %s>\n' '<pre>%s</pre>\n</div>\n' % (escape_attribute_value(tiddler.title), escape_attribute_value(tiddler.title), tiddler.revision, escape_attribute_value(tiddler_etag(self.environ, tiddler)), escape_attribute_value( tiddler.modifier), escape_attribute_value( tiddler.creator), escape_attribute_value(tiddler.bag), host, escape_attribute_value(recipe_name), escape_attribute_value( tiddler.bag), self._tiddler_permissions(tiddler), tiddler.type, tiddler.modified, tiddler.created, escape_attribute_value(self.tags_as( tiddler.tags)), self._tiddler_fields( tiddler.fields), html_encode(tiddler_output)))
def _validate_tiddler_headers(environ, tiddler): """ Check ETAG and last modified information to see if a) the client can use its cached tiddler b) we have edit contention when trying to write. """ request_method = environ['REQUEST_METHOD'] tiddler_etag = web.tiddler_etag(environ, tiddler) logging.debug('attempting to validate %s with revision %s', tiddler.title, tiddler.revision) etag = None last_modified = None if request_method == 'GET': incoming_etag = environ.get('HTTP_IF_NONE_MATCH', None) if incoming_etag: logging.debug('attempting to validate incoming etag(GET):' '%s against %s', incoming_etag, tiddler_etag) if incoming_etag == tiddler_etag: raise HTTP304(incoming_etag) else: last_modified_string = web.http_date_from_timestamp( tiddler.modified) last_modified = ('Last-Modified', last_modified_string) incoming_modified = environ.get('HTTP_IF_MODIFIED_SINCE', None) if incoming_modified and \ (web.datetime_from_http_date(incoming_modified) >= web.datetime_from_http_date(last_modified_string)): raise HTTP304('') else: incoming_etag = environ.get('HTTP_IF_MATCH', None) logging.debug('attempting to validate incoming etag(PUT):' '%s against %s', incoming_etag, tiddler_etag) if incoming_etag and not _etag_write_match(incoming_etag, tiddler_etag): raise HTTP412('Provided ETag does not match. ' 'Server content probably newer.') etag = ('Etag', '%s' % tiddler_etag) return last_modified, etag
def _put_tiddler(environ, start_response, tiddler): """ The guts of putting a tiddler into the store. There's a fair bit of special handling done here depending on whether the tiddler already exists or not. """ store = environ['tiddlyweb.store'] try: bag = Bag(tiddler.bag) _check_and_validate_tiddler(environ, bag, tiddler) user = environ['tiddlyweb.usersign']['name'] tiddler.modifier = user tiddler.modified = current_timestring() try: check_bag_constraint(environ, bag, 'accept') except (PermissionsError): _validate_tiddler_content(environ, tiddler) store.put(tiddler) except NoBagError as exc: raise HTTP409("Unable to put tiddler, %s. There is no bag named: " "%s (%s). Create the bag." % (tiddler.title, tiddler.bag, exc)) except NoTiddlerError as exc: raise HTTP404('Unable to put tiddler, %s. %s' % (tiddler.title, exc)) except TypeError as exc: raise HTTP409('Unable to put badly formed tiddler, %s:%s. %s' % (tiddler.bag, tiddler.title, exc)) etag = ('ETag', tiddler_etag(environ, tiddler)) response = [('Location', tiddler_url(environ, tiddler))] if etag: response.append(etag) start_response("204 No Content", response) return []
tiddler.modified = current_timestring() try: _check_bag_constraint(environ, bag, 'accept') except (PermissionsError), exc: _validate_tiddler_content(environ, tiddler) store.put(tiddler) except NoBagError, exc: raise HTTP409("Unable to put tiddler, %s. There is no bag named: " \ "%s (%s). Create the bag." % (tiddler.title, tiddler.bag, exc)) except NoTiddlerError, exc: raise HTTP404('Unable to put tiddler, %s. %s' % (tiddler.title, exc)) etag = ('Etag', web.tiddler_etag(environ, tiddler)) response = [('Location', web.tiddler_url(environ, tiddler))] if etag: response.append(etag) start_response("204 No Content", response) return [] def _validate_tiddler_content(environ, tiddler): """ Unless tiddler is valid raise a 409 with the reason why things to check are presumably tags and title, but we don't want to worry about that here, we want to dispatch elsewhere. """ try:
def edit(environ, start_response): """ XXX: Lots of duplication from editor. """ store = environ['tiddlyweb.store'] usersign = environ['tiddlyweb.usersign'] query = environ['tiddlyweb.query'] try: bag_name = query['bag'][0] title = query['title'][0] text = query['text'][0] tiddler_type = query['type'][0] tags = query['tags'][0] etag = query['etag'][0] except KeyError as exc: raise HTTP400('bad query: incomplete form, %s' % exc) tags = [tag.strip() for tag in tags.split(',')] if not (bag_name and title): raise HTTP400('bad query: bag and title required') bag = Bag(bag_name) try: bag = store.get(bag) except NoBagError: raise HTTP404('that tank does not exist') tiddler = Tiddler(title, bag_name) tiddler_new = False conflict = False try: tiddler = store.get(tiddler) existing_etag = tiddler_etag(environ, tiddler).replace('"', '').split(':', 1)[0] if etag != existing_etag: conflict = True except NoTiddlerError: tiddler.type = tiddler_type tiddler_new = True if tiddler_new: bag.policy.allows(usersign, 'create') else: bag.policy.allows(usersign, 'write') tiddler.text = text tiddler.tags = tags tiddler.modifier = usersign['name'] tiddler.modified = current_timestring() if conflict: return editor(environ, start_response, tiddler, message='conflict') try: validate_tiddler(tiddler, environ) except InvalidTiddlerError as exc: return editor(environ, start_response, tiddler, message='Tiddler content is invalid: %s' % exc) store.put(tiddler) redirect_uri = tank_page_uri(environ, tiddler.bag, tiddler.title) start_response('303 See Other', [ ('Location', str(redirect_uri))]) return []