Program: CWE-79
Project: CVE-2013-7250
Buggy source:
/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2013 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.wicket.autocompletion;

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.Strings;
import org.projectforge.web.core.JsonBuilder;
import org.projectforge.web.wicket.WicketRenderHeadUtils;

public abstract class PFAutoCompleteBehavior<T> extends AbstractDefaultAjaxBehavior
{
  private static final long serialVersionUID = -6532710378025987377L;

  protected PFAutoCompleteSettings settings;

  protected IAutoCompleteRenderer<String> renderer;

  public PFAutoCompleteBehavior(final IAutoCompleteRenderer<String> renderer, final PFAutoCompleteSettings settings)
  {
    this.renderer = renderer;
    this.settings = settings;
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#renderHead(org.apache.wicket.Component,
   *      org.apache.wicket.markup.html.IHeaderResponse)
   */
  @Override
  public void renderHead(final Component component, final IHeaderResponse response)
  {
    super.renderHead(component, response);
    WicketRenderHeadUtils.renderMainJavaScriptIncludes(response);
    response.render(JavaScriptReferenceHeaderItem.forUrl("scripts/jquery.wicket-autocomplete.js"));
    renderAutocompleteHead(response);
  }

  /**
   * Render autocomplete init javascript and other head contributions
   * 
   * @param response
   */
  private void renderAutocompleteHead(final IHeaderResponse response)
  {
    final String id = getComponent().getMarkupId();
    String indicatorId = findIndicatorId();
    if (Strings.isEmpty(indicatorId)) {
      indicatorId = "null";
    } else {
      indicatorId = "'" + indicatorId + "'";
    }
    final StringBuffer buf = new StringBuffer();
    buf.append("var favorite" + id + " = ");
    final List<T> favorites = getFavorites();
    final MyJsonBuilder builder = new MyJsonBuilder();
    if (favorites != null) {
      buf.append(builder.append(favorites).getAsString());
    } else {
      buf.append(builder.append(getRecentUserInputs()).getAsString());
    }
    buf.append(";").append("var z = $(\"#").append(id).append("\");\n").append("z.autocomplete(\"").append(getCallbackUrl()).append("\",{");
    boolean first = true;
    for (final String setting : getSettingsJS()) {
      if (first == true)
        first = false;
      else buf.append(", ");
      buf.append(setting);
    }
    if (first == true)
      first = false;
    else buf.append(", ");
    buf.append("favoriteEntries:favorite" + id);
    buf.append("});");
    if (settings.isHasFocus() == true) {
      buf.append("\nz.focus();");
    }
    final String initJS = buf.toString();
    // String initJS = String.format("new Wicket.AutoComplete('%s','%s',%s,%s);", id, getCallbackUrl(), constructSettingsJS(), indicatorId);
    response.render(OnDomReadyHeaderItem.forScript(initJS));
  }

  protected final List<String> getSettingsJS()
  {
    final List<String> result = new ArrayList<String>();
    addSetting(result, "matchContains", settings.isMatchContains());
    addSetting(result, "minChars", settings.getMinChars());
    addSetting(result, "delay", settings.getDelay());
    addSetting(result, "matchCase", settings.isMatchCase());
    addSetting(result, "matchSubset", settings.isMatchSubset());
    addSetting(result, "cacheLength", settings.getCacheLength());
    addSetting(result, "mustMatch", settings.isMustMatch());
    addSetting(result, "selectFirst", settings.isSelectFirst());
    addSetting(result, "selectOnly", settings.isSelectOnly());
    addSetting(result, "maxItemsToShow", settings.getMaxItemsToShow());
    addSetting(result, "autoFill", settings.isAutoFill());
    addSetting(result, "autoSubmit", settings.isAutoSubmit());
    addSetting(result, "scroll", settings.isScroll());
    addSetting(result, "scrollHeight", settings.getScrollHeight());
    addSetting(result, "width", settings.getWidth());
    addSetting(result, "deletableItem", settings.isDeletableItem());
    if (settings.isLabelValue() == true) {
      addSetting(result, "labelValue", settings.isLabelValue());
    }
    return result;
  }

  private final void addSetting(final List<String> result, final String name, final Boolean value)
  {
    if (value == null) {
      return;
    }
    result.add(name + ":" + ((value == true) ? "1" : "0"));
  }

  private final void addSetting(final List<String> result, final String name, final Integer value)
  {
    if (value == null) {
      return;
    }
    result.add(name + ":" + value);
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#onBind()
   */
  @Override
  protected void onBind()
  {
    // add empty AbstractDefaultAjaxBehavior to the component, to force
    // rendering wicket-ajax.js reference if no other ajax behavior is on
    // page
    getComponent().add(new AbstractDefaultAjaxBehavior() {
      private static final long serialVersionUID = 1L;

      @Override
      protected void respond(final AjaxRequestTarget target)
      {
      }
    });
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#respond(org.apache.wicket.ajax.AjaxRequestTarget)
   */
  @Override
  protected void respond(final AjaxRequestTarget target)
  {
    final RequestCycle requestCycle = RequestCycle.get();
    final org.apache.wicket.util.string.StringValue val = requestCycle.getRequest().getQueryParameters().getParameterValue("q");
    onRequest(val != null ? val.toString() : null, requestCycle);
  }

  protected final void onRequest(final String val, final RequestCycle requestCycle)
  {
    // final PageParameters pageParameters = new PageParameters(requestCycle.getRequest().getParameterMap());
    final List<T> choices = getChoices(val);
    final MyJsonBuilder builder = new MyJsonBuilder();
    final String json = builder.append(choices).getAsString();
    requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "utf-8", json));

    /*
     * IRequestTarget target = new IRequestTarget() {
     * 
     * public void respond(RequestCycle requestCycle) {
     * 
     * WebResponse r = (WebResponse) requestCycle.getResponse(); // Determine encoding final String encoding =
     * Application.get().getRequestCycleSettings().getResponseRequestEncoding(); r.setCharacterEncoding(encoding);
     * r.setContentType("application/json"); // Make sure it is not cached by a r.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
     * r.setHeader("Cache-Control", "no-cache, must-revalidate"); r.setHeader("Pragma", "no-cache");
     * 
     * final List<T> choices = getChoices(val); renderer.renderHeader(r); renderer.render(JsonBuilder.buildRows(false, choices), r, val);
     * renderer.renderFooter(r); }
     * 
     * public void detach(RequestCycle requestCycle) { } }; requestCycle.setRequestTarget(target);
     */
  }

  /**
   * Callback method that should return an iterator over all possible choice objects. These objects will be passed to the renderer to
   * generate output. Usually it is enough to return an iterator over strings.
   * 
   * @param input current input
   * @return iterator over all possible choice objects
   */
  protected abstract List<T> getChoices(String input);

  /**
   * Callback method that should return a list of all possible default choice objects to show, if the user double clicks the empty input
   * field. These objects will be passed to the renderer to generate output. Usually it is enough to return an iterator over strings.
   */
  protected abstract List<T> getFavorites();

  /**
   * Callback method that should return a list of all recent user inputs in the text input field. They will be shown, if the user double
   * clicks the empty input field. These objects will be passed to the renderer to generate output. Usually it is enough to return an
   * iterator over strings. <br/>
   * Please note: Please, use only getFavorites() OR getRecentUserInputs()!
   */
  protected abstract List<String> getRecentUserInputs();

  /**
   * Used for formatting the values.
   */
  protected abstract String formatValue(T value);

  /**
   * Used for formatting the labels if labelValue is set to true.
   * @return null at default (if not overload).
   */
  protected String formatLabel(final T value)
  {
    return null;
  }

  private class MyJsonBuilder extends JsonBuilder
  {
    @SuppressWarnings("unchecked")
    @Override
    protected String formatValue(final Object obj)
    {
      if (obj instanceof String) {
        return obj.toString();
      } else {
        return PFAutoCompleteBehavior.this.formatValue((T) obj);
      }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Object transform(final Object obj)
    {
      if (settings.isLabelValue() == true) {
        final Object[] oa = new Object[2];
        if (obj instanceof String) {
          oa[0] = obj;
          oa[1] = obj;
        } else {
          oa[0] = PFAutoCompleteBehavior.this.formatLabel((T) obj);
          oa[1] = PFAutoCompleteBehavior.this.formatValue((T) obj);
        }
        return oa;
      } else {
        return obj;
      }
    }
  };
}


Fixed version (ground truth):
/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2013 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.wicket.autocompletion;

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.Strings;
import org.projectforge.web.core.JsonBuilder;
import org.projectforge.web.wicket.WicketRenderHeadUtils;

public abstract class PFAutoCompleteBehavior<T> extends AbstractDefaultAjaxBehavior
{
  private static final long serialVersionUID = -6532710378025987377L;

  protected PFAutoCompleteSettings settings;

  protected IAutoCompleteRenderer<String> renderer;

  public PFAutoCompleteBehavior(final IAutoCompleteRenderer<String> renderer, final PFAutoCompleteSettings settings)
  {
    this.renderer = renderer;
    this.settings = settings;
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#renderHead(org.apache.wicket.Component,
   *      org.apache.wicket.markup.html.IHeaderResponse)
   */
  @Override
  public void renderHead(final Component component, final IHeaderResponse response)
  {
    super.renderHead(component, response);
    WicketRenderHeadUtils.renderMainJavaScriptIncludes(response);
    response.render(JavaScriptReferenceHeaderItem.forUrl("scripts/jquery.wicket-autocomplete.js"));
    renderAutocompleteHead(response);
  }

  /**
   * Render autocomplete init javascript and other head contributions
   * 
   * @param response
   */
  private void renderAutocompleteHead(final IHeaderResponse response)
  {
    final String id = getComponent().getMarkupId();
    String indicatorId = findIndicatorId();
    if (Strings.isEmpty(indicatorId)) {
      indicatorId = "null";
    } else {
      indicatorId = "'" + indicatorId + "'";
    }
    final StringBuffer buf = new StringBuffer();
    buf.append("var favorite" + id + " = ");
    final List<T> favorites = getFavorites();
    final MyJsonBuilder builder = new MyJsonBuilder();
    if (favorites != null) {
      buf.append(builder.append(favorites).getAsString());
    } else {
      buf.append(builder.append(getRecentUserInputs()).getAsString());
    }
    buf.append(";").append("var z = $(\"#").append(id).append("\");\n").append("z.autocomplete(\"").append(getCallbackUrl()).append("\",{");
    boolean first = true;
    for (final String setting : getSettingsJS()) {
      if (first == true)
        first = false;
      else buf.append(", ");
      buf.append(setting);
    }
    if (first == true)
      first = false;
    else buf.append(", ");
    buf.append("favoriteEntries:favorite" + id);
    buf.append("});");
    if (settings.isHasFocus() == true) {
      buf.append("\nz.focus();");
    }
    final String initJS = buf.toString();
    // String initJS = String.format("new Wicket.AutoComplete('%s','%s',%s,%s);", id, getCallbackUrl(), constructSettingsJS(), indicatorId);
    response.render(OnDomReadyHeaderItem.forScript(initJS));
  }

  protected final List<String> getSettingsJS()
  {
    final List<String> result = new ArrayList<String>();
    addSetting(result, "matchContains", settings.isMatchContains());
    addSetting(result, "minChars", settings.getMinChars());
    addSetting(result, "delay", settings.getDelay());
    addSetting(result, "matchCase", settings.isMatchCase());
    addSetting(result, "matchSubset", settings.isMatchSubset());
    addSetting(result, "cacheLength", settings.getCacheLength());
    addSetting(result, "mustMatch", settings.isMustMatch());
    addSetting(result, "selectFirst", settings.isSelectFirst());
    addSetting(result, "selectOnly", settings.isSelectOnly());
    addSetting(result, "maxItemsToShow", settings.getMaxItemsToShow());
    addSetting(result, "autoFill", settings.isAutoFill());
    addSetting(result, "autoSubmit", settings.isAutoSubmit());
    addSetting(result, "scroll", settings.isScroll());
    addSetting(result, "scrollHeight", settings.getScrollHeight());
    addSetting(result, "width", settings.getWidth());
    addSetting(result, "deletableItem", settings.isDeletableItem());
    if (settings.isLabelValue() == true) {
      addSetting(result, "labelValue", settings.isLabelValue());
    }
    return result;
  }

  private final void addSetting(final List<String> result, final String name, final Boolean value)
  {
    if (value == null) {
      return;
    }
    result.add(name + ":" + ((value == true) ? "1" : "0"));
  }

  private final void addSetting(final List<String> result, final String name, final Integer value)
  {
    if (value == null) {
      return;
    }
    result.add(name + ":" + value);
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#onBind()
   */
  @Override
  protected void onBind()
  {
    // add empty AbstractDefaultAjaxBehavior to the component, to force
    // rendering wicket-ajax.js reference if no other ajax behavior is on
    // page
    getComponent().add(new AbstractDefaultAjaxBehavior() {
      private static final long serialVersionUID = 1L;

      @Override
      protected void respond(final AjaxRequestTarget target)
      {
      }
    });
  }

  /**
   * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#respond(org.apache.wicket.ajax.AjaxRequestTarget)
   */
  @Override
  protected void respond(final AjaxRequestTarget target)
  {
    final RequestCycle requestCycle = RequestCycle.get();
    final org.apache.wicket.util.string.StringValue val = requestCycle.getRequest().getQueryParameters().getParameterValue("q");
    onRequest(val != null ? val.toString() : null, requestCycle);
  }

  protected final void onRequest(final String val, final RequestCycle requestCycle)
  {
    // final PageParameters pageParameters = new PageParameters(requestCycle.getRequest().getParameterMap());
    final List<T> choices = getChoices(val);
    final MyJsonBuilder builder = new MyJsonBuilder();
    final String json = builder.append(choices).getAsString();
    requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "utf-8", json));

    /*
     * IRequestTarget target = new IRequestTarget() {
     * 
     * public void respond(RequestCycle requestCycle) {
     * 
     * WebResponse r = (WebResponse) requestCycle.getResponse(); // Determine encoding final String encoding =
     * Application.get().getRequestCycleSettings().getResponseRequestEncoding(); r.setCharacterEncoding(encoding);
     * r.setContentType("application/json"); // Make sure it is not cached by a r.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
     * r.setHeader("Cache-Control", "no-cache, must-revalidate"); r.setHeader("Pragma", "no-cache");
     * 
     * final List<T> choices = getChoices(val); renderer.renderHeader(r); renderer.render(JsonBuilder.buildRows(false, choices), r, val);
     * renderer.renderFooter(r); }
     * 
     * public void detach(RequestCycle requestCycle) { } }; requestCycle.setRequestTarget(target);
     */
  }

  /**
   * Callback method that should return an iterator over all possible choice objects. These objects will be passed to the renderer to
   * generate output. Usually it is enough to return an iterator over strings.
   * 
   * @param input current input
   * @return iterator over all possible choice objects
   */
  protected abstract List<T> getChoices(String input);

  /**
   * Callback method that should return a list of all possible default choice objects to show, if the user double clicks the empty input
   * field. These objects will be passed to the renderer to generate output. Usually it is enough to return an iterator over strings.
   */
  protected abstract List<T> getFavorites();

  /**
   * Callback method that should return a list of all recent user inputs in the text input field. They will be shown, if the user double
   * clicks the empty input field. These objects will be passed to the renderer to generate output. Usually it is enough to return an
   * iterator over strings. <br/>
   * Please note: Please, use only getFavorites() OR getRecentUserInputs()!
   */
  protected abstract List<String> getRecentUserInputs();

  /**
   * Used for formatting the values.
   */
  protected abstract String formatValue(T value);

  /**
   * Used for formatting the labels if labelValue is set to true.
   * @return null at default (if not overload).
   */
  protected String formatLabel(final T value)
  {
    return null;
  }

  private class MyJsonBuilder extends JsonBuilder
  {
    private MyJsonBuilder()
    {
      setEscapeHtml(true);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected String formatValue(final Object obj)
    {
      if (obj instanceof String) {
        return obj.toString();
      } else {
        return PFAutoCompleteBehavior.this.formatValue((T) obj);
      }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Object transform(final Object obj)
    {
      if (settings.isLabelValue() == true) {
        final Object[] oa = new Object[2];
        if (obj instanceof String) {
          oa[0] = obj;
          oa[1] = obj;
        } else {
          oa[0] = PFAutoCompleteBehavior.this.formatLabel((T) obj);
          oa[1] = PFAutoCompleteBehavior.this.formatValue((T) obj);
        }
        return oa;
      } else {
        return obj;
      }
    }
  };
}