def join(self, states, debug_hint=None): out_state = functools.reduce(context_update.context_union, states) if context.is_error_context(out_state): # Report an error only if none was reported when the states were # produced. for state in states: if context.is_error_context(state): return out_state self.error(debug_hint, 'branches end in incompatible contexts: %s' % ', '.join([debug.context_to_string(state) for state in states])) return out_state
def join(self, states, debug_hint=None): out_state = functools.reduce(context_update.context_union, states) if context.is_error_context(out_state): # Report an error only if none was reported when the states were # produced. for state in states: if context.is_error_context(state): return out_state self.error( debug_hint, 'branches end in incompatible contexts: %s' % ', '.join([debug.context_to_string(state) for state in states])) return out_state
def no_steady_state(self, states, debug_hint=None): for state in states: if context.is_error_context(state): return state self.error(debug_hint, 'loop switches between states (%s)' % ( ', '.join([debug.context_to_string(state) for state in states]))) return context.STATE_ERROR
def no_steady_state(self, states, debug_hint=None): for state in states: if context.is_error_context(state): return state self.error( debug_hint, 'loop switches between states (%s)' % (', '.join([debug.context_to_string(state) for state in states]))) return context.STATE_ERROR
def write_safe(self, *safe_strs): ctx = self.ctx_ underlying = self.underlying_ for safe_str in safe_strs: end_ctx, safe_text, before_error, unprocessed = ( context_update.process_raw_text(safe_str, ctx)) if context.is_error_context(end_ctx): raise AutoescapeError(safe_text[:-len(unprocessed)], unprocessed) ctx = end_ctx underlying.write(safe_str) self.ctx_ = ctx
def step(self, start_state, step_value, debug_hint=None): if context.is_error_context(start_state): # Simplifies error checking below. return start_state if hasattr(step_value, 'to_raw_content'): # Handle text nodes specified by the template author. raw_content = step_value.to_raw_content() if raw_content is not None: try: end_state, new_content, error_ctx, error_text = ( context_update.process_raw_text( raw_content, start_state)) if context.is_error_context(end_state): self.error(debug_hint, 'bad content in %s: `%s`' % ( debug.context_to_string(error_ctx), error_text)) elif new_content != raw_content: self.text_values[step_value] = new_content except context_update.ContextUpdateFailure, err: self.error(debug_hint, str(err)) end_state = context.STATE_ERROR return end_state
def write_safe(self, *safe_strs): ctx = self.ctx_ underlying = self.underlying_ for safe_str in safe_strs: end_ctx, safe_text, before_error, unprocessed = ( context_update.process_raw_text(safe_str, ctx)) if context.is_error_context(end_ctx): raise AutoescapeError( safe_text[:-len(unprocessed)], unprocessed) ctx = end_ctx underlying.write(safe_str) self.ctx_ = ctx
def step(self, start_state, step_value, debug_hint=None): if context.is_error_context(start_state): # Simplifies error checking below. return start_state if hasattr(step_value, 'to_raw_content'): # Handle text nodes specified by the template author. raw_content = step_value.to_raw_content() if raw_content is not None: try: end_state, new_content, error_ctx, error_text = ( context_update.process_raw_text( raw_content, start_state)) if context.is_error_context(end_state): self.error( debug_hint, 'bad content in %s: `%s`' % (debug.context_to_string(error_ctx), error_text)) elif new_content != raw_content: self.text_values[step_value] = new_content except context_update.ContextUpdateFailure, err: self.error(debug_hint, str(err)) end_state = context.STATE_ERROR return end_state
def escape(name_to_body, public_template_names, start_state=context.STATE_TEXT): """ name_to_body - maps template names to template bodies. A template body is an object that implements 1. reduce_traces(start_state, analyzer) -> end_state 2. clone() -> a structural copy of the body that is distinct according to == and is also a template body. 3. the body node interface described below. public_template_names - the names that might be called with an empty output buffer in the given start state. start_state - the state in which the named templates might be called. A body node is an object that implements 1. children() -> a series of nodes 2. with_children(children) -> produces a structural copy of the body but with the given children instead of children(). step values must also be body nodes, and the transitively enumerated nodes of a body must include all step values encountered when following traces that do not include external calls. name_to_body may be augmented with new template definitions as a result of this call. If escape exits with an exception, then it is unsafe to use the templates in name_to_body. """ analyzer = _Analyzer(name_to_body, start_state) has_errors = False for name in public_template_names: end_state = analyzer.external_call(name, start_state, None) if context.is_error_context(end_state): has_errors = True elif end_state != start_state: # Templates should start and end in the same context. # Otherwise concatenation of the output from safe templates is not # safe. analyzer.error( None, 'template %s does not start and end in the same context: %s' % (name, debug.context_to_string(end_state))) has_errors = True if has_errors: raise EscapeError('\n'.join(analyzer.errors)) analyzer.rewrite()
def ctx_filter(end_ctx, analyzer): """ Checks the end context so we do not update self unless we can confidently compute an end context. end_ctx - the computed context after the call completes. analyzer - the analyzer used to type the body. """ return not ( context.is_error_context(end_ctx) # If the template is recursively called, end_ctx must be # consistent with our assumption. Otherwise our assumption # didn't factor into the computation of end_ctx, so we can # just use end_ctx. or (name_and_ctx in analyzer.called and assumed_end_ctx != end_ctx))
def _compute_end_context(self, name_and_ctx, body, debug_hint): """Propagate context over the body.""" tmpl_name, start_ctx = name_and_ctx ctx, problems = self._escape_template_body( name_and_ctx, start_ctx, body) if problems is not None: # Look for a fixed point by assuming c1 as the output context. ctx2, problems2 = self._escape_template_body( name_and_ctx, ctx, body) if problems2 is None: ctx, problems = ctx2, None if problems is not None: if not context.is_error_context(ctx): # We have not explained the problem yet. self.error(debug_hint, "cannot compute output context for template %s in %s" % ( tmpl_name, debug.context_to_string(start_ctx))) self.errors.extend(problems) return context.STATE_ERROR return ctx
def _compute_end_context(self, name_and_ctx, body, debug_hint): """Propagate context over the body.""" tmpl_name, start_ctx = name_and_ctx ctx, problems = self._escape_template_body(name_and_ctx, start_ctx, body) if problems is not None: # Look for a fixed point by assuming c1 as the output context. ctx2, problems2 = self._escape_template_body( name_and_ctx, ctx, body) if problems2 is None: ctx, problems = ctx2, None if problems is not None: if not context.is_error_context(ctx): # We have not explained the problem yet. self.error( debug_hint, "cannot compute output context for template %s in %s" % (tmpl_name, debug.context_to_string(start_ctx))) self.errors.extend(problems) return context.STATE_ERROR return ctx
class _Analyzer(trace_analysis.Analyzer): """ Applies the context_update algorithm to text nodes, builds side-tables of pipelines that need to be updated, and clones templates that are used in non-start contexts. """ def __init__(self, name_to_body, start_state, templates=None): trace_analysis.Analyzer.__init__(self) # Maps template names to bodies. self.name_to_body = name_to_body # Maps (name, start_context) -> (body, end_context) self.start_state = start_state # Maps (template_name, start_context) pairs to end contexts self.templates = dict(templates or {}) # Tracks the set of templates and the contexts in which they are # called. A set (name, start_context) self.called = set() # Maps interpolation nodes to pipelines and escaping modes self.interps = {} # Maps text nodes to replacement text. self.text_values = {} # Maps external calls (step_values) to the contexts # in which they occur. # This assumes that cloned() step_values are distinct # from the original. self.calls = {} # Messages that explain failure to escape. self.errors = [] def error(self, debug_hint, msg): """Queues a message explaining a problem noticed during escaping.""" if debug_hint: msg = '%s: %s' % (debug_hint, msg) self.errors.append(msg) def step(self, start_state, step_value, debug_hint=None): if context.is_error_context(start_state): # Simplifies error checking below. return start_state if hasattr(step_value, 'to_raw_content'): # Handle text nodes specified by the template author. raw_content = step_value.to_raw_content() if raw_content is not None: try: end_state, new_content, error_ctx, error_text = ( context_update.process_raw_text( raw_content, start_state)) if context.is_error_context(end_state): self.error( debug_hint, 'bad content in %s: `%s`' % (debug.context_to_string(error_ctx), error_text)) elif new_content != raw_content: self.text_values[step_value] = new_content except context_update.ContextUpdateFailure, err: self.error(debug_hint, str(err)) end_state = context.STATE_ERROR return end_state if hasattr(step_value, 'to_pipeline'): # Handle interpolation of untrusted values. pipeline = step_value.to_pipeline() if pipeline is not None: end_state, esc_modes, problem = ( escaping.esc_mode_for_hole(start_state)) self.interps[step_value] = pipeline, esc_modes if context.is_error_context(end_state): if problem is None: self.error( debug_hint, 'hole cannot appear in %s' % (debug.context_to_string(start_state))) else: self.error(debug_hint, problem) return end_state if hasattr(step_value, 'to_callee'): # Handle calls to other templates by recursively typing the end # context of that template. callee = step_value.to_callee() if callee is not None: end_ctx = self.external_call(callee, start_state, debug_hint) self.calls[step_value] = start_state # rely on external_call to explain failure. return end_ctx return start_state