0001"""
0002Represents the Cache-Control header
0003"""
0004
0005import re
0006from webob.updatedict import UpdateDict
0007try:
0008 sorted
0009except NameError:
0010 from webob.compat import sorted
0011
0012token_re = re.compile(
0013 r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
0014need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')
0015
0016class exists_property(object):
0017 """
0018 Represents a property that either is listed in the Cache-Control
0019 header, or is not listed (has no value)
0020 """
0021 def __init__(self, prop, type=None):
0022 self.prop = prop
0023 self.type = type
0024
0025 def __get__(self, obj, type=None):
0026 if obj is None:
0027 return self
0028 return self.prop in obj.properties
0029 def __set__(self, obj, value):
0030 if (self.type is not None
0031 and self.type != obj.type):
0032 raise AttributeError(
0033 "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
0034 if value:
0035 obj.properties[self.prop] = None
0036 else:
0037 if self.prop in obj.properties:
0038 del obj.properties[self.prop]
0039 def __delete__(self, obj):
0040 self.__set__(obj, False)
0041
0042class value_property(object):
0043 """
0044 Represents a property that has a value in the Cache-Control header.
0045
0046 When no value is actually given, the value of self.none is returned.
0047 """
0048 def __init__(self, prop, default=None, none=None, type=None):
0049 self.prop = prop
0050 self.default = default
0051 self.none = none
0052 self.type = type
0053 def __get__(self, obj, type=None):
0054 if obj is None:
0055 return self
0056 if self.prop in obj.properties:
0057 value = obj.properties[self.prop]
0058 if value is None:
0059 return self.none
0060 else:
0061 return value
0062 else:
0063 return self.default
0064 def __set__(self, obj, value):
0065 if (self.type is not None
0066 and self.type != obj.type):
0067 raise AttributeError(
0068 "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
0069 if value == self.default:
0070 if self.prop in obj.properties:
0071 del obj.properties[self.prop]
0072 elif value is True:
0073 obj.properties[self.prop] = None
0074 else:
0075 obj.properties[self.prop] = value
0076 def __delete__(self, obj):
0077 if self.prop in obj.properties:
0078 del obj.properties[self.prop]
0079
0080class CacheControl(object):
0081
0082 """
0083 Represents the Cache-Control header.
0084
0085 By giving a type of ``'request'`` or ``'response'`` you can
0086 control what attributes are allowed (some Cache-Control values
0087 only apply to requests or responses).
0088 """
0089
0090 def __init__(self, properties, type):
0091 self.properties = properties
0092 self.type = type
0093
0094
0095 def parse(cls, header, updates_to=None, type=None):
0096 """
0097 Parse the header, returning a CacheControl object.
0098
0099 The object is bound to the request or response object
0100 ``updates_to``, if that is given.
0101 """
0102 if updates_to:
0103 props = UpdateDict()
0104 props.updated = updates_to
0105 else:
0106 props = {}
0107 for match in token_re.finditer(header):
0108 name = match.group(1)
0109 value = match.group(2) or match.group(3) or None
0110 if value:
0111 try:
0112 value = int(value)
0113 except ValueError:
0114 pass
0115 props[name] = value
0116 obj = cls(props, type=type)
0117 if updates_to:
0118 props.updated_args = (obj,)
0119 return obj
0120
0121 parse = classmethod(parse)
0122
0123 def __repr__(self):
0124 return '<CacheControl %r>' % str(self)
0125
0126
0127
0128
0129
0130 max_stale = value_property('max-stale', none='*', type='request')
0131 min_fresh = value_property('min-fresh', type='request')
0132
0133 only_if_cached = exists_property('only-if-cached', type='request')
0134
0135
0136 public = exists_property('public', type='response')
0137 private = value_property('private', none='*', type='response')
0138 no_cache = value_property('no-cache', none='*')
0139 no_store = exists_property('no-store')
0140 no_transform = exists_property('no-transform')
0141 must_revalidate = exists_property('must-revalidate', type='response')
0142 proxy_revalidate = exists_property('proxy-revalidate', type='response')
0143 max_age = value_property('max-age', none=-1)
0144 s_maxage = value_property('s-maxage', type='response')
0145 s_max_age = s_maxage
0146
0147 def __str__(self):
0148 return serialize_cache_control(self.properties)
0149
0150 def copy(self):
0151 """
0152 Returns a copy of this object.
0153 """
0154 return self.__class__(self.properties.copy(), type=self.type)
0155
0156def serialize_cache_control(properties):
0157 if isinstance(properties, CacheControl):
0158 properties = properties.properties
0159 parts = []
0160 for name, value in sorted(properties.items()):
0161 if value is None:
0162 parts.append(name)
0163 continue
0164 value = str(value)
0165 if need_quote_re.search(value):
0166 value = '"%s"' % value
0167 parts.append('%s=%s' % (name, value))
0168 return ', '.join(parts)