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
Esempio n. 2
0
 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
Esempio n. 4
0
 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
Esempio n. 5
0
 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
Esempio n. 7
0
 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
Esempio n. 8
0
 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
Esempio n. 9
0
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()
Esempio n. 10
0
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()
Esempio n. 11
0
        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))
Esempio n. 12
0
        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))
Esempio n. 13
0
 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
Esempio n. 14
0
 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
Esempio n. 15
0
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