<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.apache.shindig.gadgets.rewrite.image;

import static java.awt.RenderingHints.KEY_INTERPOLATION;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC;
import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;

import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.rewrite.ResponseRewriter;
import org.apache.shindig.gadgets.rewrite.image.BaseOptimizer.ImageIOOutputter;
import org.apache.shindig.gadgets.rewrite.image.BaseOptimizer.ImageOutputter;
import org.apache.shindig.gadgets.uri.UriCommon.Param;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;

/**
 * Rewrite images to more efficiently compress their content. Can output to a different format file
 * for better efficiency.
 *
 * <p>Security Note: Uses the Sanselan library to parse image content and metadata to avoid security
 * issues in the ImageIO library. Uses ImageIO for output.
 */
public class BasicImageRewriter implements ResponseRewriter {

  static final String
      CONTENT_TYPE_AND_EXTENSION_MISMATCH =
        "Content is not an image but file extension asserts it is";
  static final String
      CONTENT_TYPE_AND_MIME_MISMATCH =
          "Content is not an image but mime type asserts it is";
    
  private static final String CONTENT_TYPE_IMAGE_PNG = "image/png";
  /** Returned as the output message if a huge image is submitted to be scaled */
  private static final String RESIZE_IMAGE_TOO_LARGE = "The image is too large to resize";
  /** With resizing active, all images become PNGs */
  private static final String RESIZE_OUTPUT_FORMAT = "png";

  private static final String CONTENT_LENGTH = "Content-Length";

  /** Parameter used to request image rendering quality */
  private static final String PARAM_RESIZE_QUALITY = Param.RESIZE_QUALITY.getKey();
  /** Parameter used to request image width change */
  private static final String PARAM_RESIZE_WIDTH = Param.RESIZE_WIDTH.getKey();
  /** Parameter used to request image height change */
  private static final String PARAM_RESIZE_HEIGHT = Param.RESIZE_HEIGHT.getKey();
  /** Parameter used to request resizing will not expand image */
  private static final String PARAM_NO_EXPAND = Param.NO_EXPAND.getKey();

  private static final int DEFAULT_QUALITY = 100;
  private static final int BITS_PER_BYTE = 8;
  private static final Color COLOR_TRANSPARENT = new Color(255, 255, 255, 0);
  private static final String CONTENT_TYPE = "Content-Type";
  private static final Logger LOG = Logger.getLogger(BasicImageRewriter.class.getName());

  private static final Set<String> SUPPORTED_MIME_TYPES = ImmutableSet.of(
      "image/gif", CONTENT_TYPE_IMAGE_PNG, "image/jpeg", "image/bmp");

  private static final Set<String> SUPPORTED_FILE_EXTENSIONS = ImmutableSet.of(
      ".gif", ".png", ".jpeg", ".jpg", ".bmp");

  private final OptimizerConfig config;

  @Inject
  public BasicImageRewriter(OptimizerConfig config) {
    this.config = config;
  }

  public void rewrite(HttpRequest request, HttpResponseBuilder response) {
    if (request == null || response == null) return;
    
    Uri uri = request.getUri();
    if (null == uri) return;
    
    try {
      // Check resizing
      Integer resizeQuality = request.getParamAsInteger(PARAM_RESIZE_QUALITY);
      Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
      Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
      boolean isResizeRequested = (requestedWidth != null || requestedHeight != null);

      // If the path or MIME type don't match, continue
      if (!isSupportedContent(response) && !isImage(uri)) {
        return;
      }
      if (!isUsableParameter(requestedWidth) || !isUsableParameter(requestedHeight) ||
          !isUsableParameter(resizeQuality)) {
        return;
      }

      // Content header checking is fast so this is fine to do for every response.
      ImageFormat imageFormat = Sanselan
          .guessFormat(new ByteSourceInputStream(response.getContentBytes(), uri.getPath()));

      if (imageFormat == ImageFormat.IMAGE_FORMAT_UNKNOWN) {
        enforceUnreadableImageRestrictions(uri, response);
        return;
      }

      // Don't handle very small images, but check after parsing format to
      // detect attacks.
      if (response.getContentLength() < config.getMinThresholdBytes()) {
        return;
      }

      ImageInfo imageInfo = Sanselan.getImageInfo(response.getContentBytes(), uri.getPath());
      
      boolean noExpand = "1".equals(request.getParam(PARAM_NO_EXPAND));
      if (noExpand &&
          imageInfo.getHeight() <= requestedHeight &&
          imageInfo.getWidth() <= requestedWidth) {<commit_msg>Avoid NPE when no_expand=1 and resize_[w or h] is not present.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.apache.shindig.gadgets.rewrite.image;

import static java.awt.RenderingHints.KEY_INTERPOLATION;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC;
import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;

import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.rewrite.ResponseRewriter;
import org.apache.shindig.gadgets.rewrite.image.BaseOptimizer.ImageIOOutputter;
import org.apache.shindig.gadgets.rewrite.image.BaseOptimizer.ImageOutputter;
import org.apache.shindig.gadgets.uri.UriCommon.Param;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;

/**
 * Rewrite images to more efficiently compress their content. Can output to a different format file
 * for better efficiency.
 *
 * <p>Security Note: Uses the Sanselan library to parse image content and metadata to avoid security
 * issues in the ImageIO library. Uses ImageIO for output.
 */
public class BasicImageRewriter implements ResponseRewriter {

  static final String
      CONTENT_TYPE_AND_EXTENSION_MISMATCH =
        "Content is not an image but file extension asserts it is";
  static final String
      CONTENT_TYPE_AND_MIME_MISMATCH =
          "Content is not an image but mime type asserts it is";
    
  private static final String CONTENT_TYPE_IMAGE_PNG = "image/png";
  /** Returned as the output message if a huge image is submitted to be scaled */
  private static final String RESIZE_IMAGE_TOO_LARGE = "The image is too large to resize";
  /** With resizing active, all images become PNGs */
  private static final String RESIZE_OUTPUT_FORMAT = "png";

  private static final String CONTENT_LENGTH = "Content-Length";

  /** Parameter used to request image rendering quality */
  private static final String PARAM_RESIZE_QUALITY = Param.RESIZE_QUALITY.getKey();
  /** Parameter used to request image width change */
  private static final String PARAM_RESIZE_WIDTH = Param.RESIZE_WIDTH.getKey();
  /** Parameter used to request image height change */
  private static final String PARAM_RESIZE_HEIGHT = Param.RESIZE_HEIGHT.getKey();
  /** Parameter used to request resizing will not expand image */
  private static final String PARAM_NO_EXPAND = Param.NO_EXPAND.getKey();

  private static final int DEFAULT_QUALITY = 100;
  private static final int BITS_PER_BYTE = 8;
  private static final Color COLOR_TRANSPARENT = new Color(255, 255, 255, 0);
  private static final String CONTENT_TYPE = "Content-Type";
  private static final Logger LOG = Logger.getLogger(BasicImageRewriter.class.getName());

  private static final Set<String> SUPPORTED_MIME_TYPES = ImmutableSet.of(
      "image/gif", CONTENT_TYPE_IMAGE_PNG, "image/jpeg", "image/bmp");

  private static final Set<String> SUPPORTED_FILE_EXTENSIONS = ImmutableSet.of(
      ".gif", ".png", ".jpeg", ".jpg", ".bmp");

  private final OptimizerConfig config;

  @Inject
  public BasicImageRewriter(OptimizerConfig config) {
    this.config = config;
  }

  public void rewrite(HttpRequest request, HttpResponseBuilder response) {
    if (request == null || response == null) return;
    
    Uri uri = request.getUri();
    if (null == uri) return;
    
    try {
      // Check resizing
      Integer resizeQuality = request.getParamAsInteger(PARAM_RESIZE_QUALITY);
      Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
      Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
      boolean isResizeRequested = (requestedWidth != null || requestedHeight != null);

      // If the path or MIME type don't match, continue
      if (!isSupportedContent(response) && !isImage(uri)) {
        return;
      }
      if (!isUsableParameter(requestedWidth) || !isUsableParameter(requestedHeight) ||
          !isUsableParameter(resizeQuality)) {
        return;
      }

      // Content header checking is fast so this is fine to do for every response.
      ImageFormat imageFormat = Sanselan
          .guessFormat(new ByteSourceInputStream(response.getContentBytes(), uri.getPath()));

      if (imageFormat == ImageFormat.IMAGE_FORMAT_UNKNOWN) {
        enforceUnreadableImageRestrictions(uri, response);
        return;
      }

      // Don't handle very small images, but check after parsing format to
      // detect attacks.
      if (response.getContentLength() < config.getMinThresholdBytes()) {
        return;
      }

      ImageInfo imageInfo = Sanselan.getImageInfo(response.getContentBytes(), uri.getPath());
      
      boolean noExpand = "1".equals(request.getParam(PARAM_NO_EXPAND));
      if (noExpand &&
          (requestedHeight == null || imageInfo.getHeight() <= requestedHeight) &&
          (requestedWidth == null || imageInfo.getWidth() <= requestedWidth)) {
{'fraction_non_alphanumeric': 0.08742477035159962, 'fraction_numerical': 0.003246753246753247, 'mean_word_length': 4.112955465587045, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1468961', 'n_tokens_mistral': 3676, 'n_tokens_neox': 3566, 'n_words': 1090}