<commit_before>/**
 *  Copyright (C) 2004 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program 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 Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.orbeon.oxf.cache.CacheKey;
import org.orbeon.oxf.cache.InternalCacheKey;
import org.orbeon.oxf.cache.ObjectCache;
import org.orbeon.oxf.cache.SoftCacheImpl;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.resources.URLFactory;
import org.orbeon.oxf.util.ContentHandlerOutputStream;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.util.NumberUtils;
import org.orbeon.oxf.xml.XPathUtils;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
import org.orbeon.oxf.xml.dom4j.NonLazyUserDataDocument;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.*;
import java.util.List;

/**
 * ImageServer directly serves or converts to its "data" output images from URLs while performing
 * various operations on them such as scaling or cropping. It also handles a disk cache of
 * transformed images.
 *
 * NOTE: The JPEG quality parameter only applies when a transformation is done. There is no
 * provision to do a quality conversion only.
 */
public class ImageServer extends ProcessorImpl {

    private static Logger logger = LoggerFactory.createLogger(ImageServer.class);

    public static final String IMAGE_SERVER_CONFIG_NAMESPACE_URI = "http://orbeon.org/oxf/xml/image-server-config";
    public static final String IMAGE_SERVER_IMAGE_NAMESPACE_URI = "http://orbeon.org/oxf/xml/image-server-image";

    private static final String INPUT_IMAGE = "image";

    private static final float DEFAULT_QUALITY = 0.5f;
    private static final boolean DEFAULT_USE_SANDBOX = true;
    private static final boolean DEFAULT_USE_CACHE = true;
    private static final boolean DEFAULT_SCALE_UP = true;

    private SoftCacheImpl cache;

    public ImageServer() {
        addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, IMAGE_SERVER_CONFIG_NAMESPACE_URI));
        addInputInfo(new ProcessorInputOutputInfo(INPUT_IMAGE, IMAGE_SERVER_IMAGE_NAMESPACE_URI));
        //addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); // optional
    }

    private static class Config {
        public URL imageDirectoryURL;
        public File cacheDir;
        public float defaultQuality;
        public boolean useSandbox;
        public String cachePathEncoding;
    }

    private static class ImageConfig {
        public String urlString;
        public Float quality;
        public Boolean useCache;
        public Object transforms;
        public int transformCount;
        public Iterator transformIterator;
    }

    public void processImage(PipelineContext pipelineContext, ImageResponse imageResponse) {

        try {
            // Read global configuration
            final Config config = (ImageServer.Config) readCacheInputAsObject(pipelineContext, getInputByName(INPUT_CONFIG), new CacheableInputReader() {
                public Object read(PipelineContext pipelineContext, ProcessorInput processorInput) {

                    final Document configDocument = readInputAsDOM4J(pipelineContext, processorInput);
                    final Config result = new Config();

                    String imageDirectoryString = XPathUtils.selectStringValueNormalize(configDocument, "/config/image-directory");
                    imageDirectoryString = imageDirectoryString.replace('\\', '/');

                    // Make sure this ends with a '/' so that it is considered a directory
                    if (!imageDirectoryString.endsWith("/"))
                        imageDirectoryString = imageDirectoryString + '/';

                    try {
                        result.imageDirectoryURL = URLFactory.createURL(imageDirectoryString);
                    } catch (MalformedURLException e) {
                        throw new OXFException(e);
                    }

                    final String cacheDirectoryString = XPathUtils.selectStringValueNormalize(configDocument, "/config/cache/directory");
                    result.cacheDir = (cacheDirectoryString == null) ? null : new File(cacheDirectoryString);
                    if (result.cacheDir != null && !result.cacheDir.isDirectory())
                        throw new IllegalArgumentException("Invalid cache directory: " + cacheDirectoryString);

                    result.defaultQuality = selectFloatValue(configDocument, "/config/default-quality", DEFAULT_QUALITY);
                    if (result.defaultQuality < 0.0f || result.defaultQuality > 1.0f)
                        throw new IllegalArgumentException("default-quality must be comprised between 0.0 and 1.0");

                    result.useSandbox = selectBooleanValue(configDocument, "/config/use-sandbox", DEFAULT_USE_SANDBOX);
                    result.cachePathEncoding = XPathUtils.selectStringValueNormalize(configDocument, "/config/cache/path-encoding");

                    return result;
                }
            });

            // Read image configuration
            final ImageConfig imageConfig = (ImageServer.ImageConfig) readCacheInputAsObject(pipelineContext, getInputByName(INPUT_IMAGE), new CacheableInputReader() {
                public Object read(PipelineContext pipelineContext, ProcessorInput processorInput) {

                    final Document imageConfigDocument = readCacheInputAsDOM4J(pipelineContext, INPUT_IMAGE);
                    final ImageConfig result = new ImageConfig();

                    // Read URL
                    result.urlString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/url");

                    // For backward compatibility, try to get path element (which also contained an URL!)
                    if (result.urlString == null) {
                        result.urlString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/path");
                    }

                    String qualityString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/quality");
                    result.quality = (qualityString == null) ? null : new Float(qualityString);

                    String useCacheString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/use-cache");
                    result.useCache = (useCacheString == null) ? null : new Boolean(useCacheString);

                    result.transformCount = XPathUtils.selectIntegerValue(imageConfigDocument, "count(/image/transform)").intValue();
                    Object transforms = XPathUtils.selectObjectValue(imageConfigDocument, "/image/transform");
                    if (transforms != null && transforms instanceof Node)
                        transforms = Collections.singletonList(transforms);
                    result.transformIterator = XPathUtils.selectIterator(imageConfigDocument, "/image/transform");

                    return result;
                }
            });

            final float quality = (imageConfig.quality == null) ? config.defaultQuality : imageConfig.quality.floatValue();
            final boolean useCache = config.cacheDir != null && ((imageConfig.useCache == null) ? DEFAULT_USE_CACHE : imageConfig.useCache.booleanValue());

            URLConnection urlConnection = null;
            InputStream urlConnectionInputStream = null;
            try {
                // Make sure the requested resource exists and is valid
                URL newURL = null;
                try {
                    newURL = URLFactory.createURL(config.imageDirectoryURL, imageConfig.urlString);
                    // Check if new URL is relative to image directory URL
                    boolean relative = NetUtils.relativeURL(config.imageDirectoryURL, newURL);
                    if (config.useSandbox && !relative) {
                        imageResponse.setStatus(ExternalContext.SC_NOT_FOUND);
                        return;
                    }
                    // Try to open the connection
                    urlConnection = newURL.openConnection();
                    // Get InputStream and make sure it supprorts marks
                    urlConnectionInputStream = urlConnection.getInputStream();
                    if (!urlConnectionInputStream.markSupported())
                        urlConnectionInputStream = new BufferedInputStream(urlConnectionInputStream);
                    // Make sure the resource looks like a JPEG file
                    String contentType = URLConnection.guessContentTypeFromStream(urlConnectionInputStream);<commit_msg>Fix [ #304492 ] Image server cache broken provided by Hank<commit_after>/**
 *  Copyright (C) 2004 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program 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 Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.orbeon.oxf.cache.CacheKey;
import org.orbeon.oxf.cache.InternalCacheKey;
import org.orbeon.oxf.cache.ObjectCache;
import org.orbeon.oxf.cache.SoftCacheImpl;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.resources.URLFactory;
import org.orbeon.oxf.util.ContentHandlerOutputStream;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.util.NumberUtils;
import org.orbeon.oxf.xml.XPathUtils;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
import org.orbeon.oxf.xml.dom4j.NonLazyUserDataDocument;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.*;
import java.util.List;

/**
 * ImageServer directly serves or converts to its "data" output images from URLs while performing
 * various operations on them such as scaling or cropping. It also handles a disk cache of
 * transformed images.
 *
 * NOTE: The JPEG quality parameter only applies when a transformation is done. There is no
 * provision to do a quality conversion only.
 */
public class ImageServer extends ProcessorImpl {

    private static Logger logger = LoggerFactory.createLogger(ImageServer.class);

    public static final String IMAGE_SERVER_CONFIG_NAMESPACE_URI = "http://orbeon.org/oxf/xml/image-server-config";
    public static final String IMAGE_SERVER_IMAGE_NAMESPACE_URI = "http://orbeon.org/oxf/xml/image-server-image";

    private static final String INPUT_IMAGE = "image";

    private static final float DEFAULT_QUALITY = 0.5f;
    private static final boolean DEFAULT_USE_SANDBOX = true;
    private static final boolean DEFAULT_USE_CACHE = true;
    private static final boolean DEFAULT_SCALE_UP = true;

    private SoftCacheImpl cache;

    public ImageServer() {
        addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, IMAGE_SERVER_CONFIG_NAMESPACE_URI));
        addInputInfo(new ProcessorInputOutputInfo(INPUT_IMAGE, IMAGE_SERVER_IMAGE_NAMESPACE_URI));
        //addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); // optional
    }

    private static class Config {
        public URL imageDirectoryURL;
        public File cacheDir;
        public float defaultQuality;
        public boolean useSandbox;
        public String cachePathEncoding;
    }

    private static class ImageConfig {
        public String urlString;
        public Float quality;
        public Boolean useCache;
        public Object transforms;
        public int transformCount;
        public Iterator transformIterator;
    }

    public void processImage(PipelineContext pipelineContext, ImageResponse imageResponse) {

        try {
            // Read global configuration
            final Config config = (ImageServer.Config) readCacheInputAsObject(pipelineContext, getInputByName(INPUT_CONFIG), new CacheableInputReader() {
                public Object read(PipelineContext pipelineContext, ProcessorInput processorInput) {

                    final Document configDocument = readInputAsDOM4J(pipelineContext, processorInput);
                    final Config result = new Config();

                    String imageDirectoryString = XPathUtils.selectStringValueNormalize(configDocument, "/config/image-directory");
                    imageDirectoryString = imageDirectoryString.replace('\\', '/');

                    // Make sure this ends with a '/' so that it is considered a directory
                    if (!imageDirectoryString.endsWith("/"))
                        imageDirectoryString = imageDirectoryString + '/';

                    try {
                        result.imageDirectoryURL = URLFactory.createURL(imageDirectoryString);
                    } catch (MalformedURLException e) {
                        throw new OXFException(e);
                    }

                    final String cacheDirectoryString = XPathUtils.selectStringValueNormalize(configDocument, "/config/cache/directory");
                    result.cacheDir = (cacheDirectoryString == null) ? null : new File(cacheDirectoryString);
                    if (result.cacheDir != null && !result.cacheDir.isDirectory())
                        throw new IllegalArgumentException("Invalid cache directory: " + cacheDirectoryString);

                    result.defaultQuality = selectFloatValue(configDocument, "/config/default-quality", DEFAULT_QUALITY);
                    if (result.defaultQuality < 0.0f || result.defaultQuality > 1.0f)
                        throw new IllegalArgumentException("default-quality must be comprised between 0.0 and 1.0");

                    result.useSandbox = selectBooleanValue(configDocument, "/config/use-sandbox", DEFAULT_USE_SANDBOX);
                    result.cachePathEncoding = XPathUtils.selectStringValueNormalize(configDocument, "/config/cache/path-encoding");

                    return result;
                }
            });

            // Read image configuration
            final ImageConfig imageConfig = (ImageServer.ImageConfig) readCacheInputAsObject(pipelineContext, getInputByName(INPUT_IMAGE), new CacheableInputReader() {
                public Object read(PipelineContext pipelineContext, ProcessorInput processorInput) {

                    final Document imageConfigDocument = readCacheInputAsDOM4J(pipelineContext, INPUT_IMAGE);
                    final ImageConfig result = new ImageConfig();

                    // Read URL
                    result.urlString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/url");

                    // For backward compatibility, try to get path element (which also contained an URL!)
                    if (result.urlString == null) {
                        result.urlString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/path");
                    }

                    String qualityString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/quality");
                    result.quality = (qualityString == null) ? null : new Float(qualityString);

                    String useCacheString = XPathUtils.selectStringValueNormalize(imageConfigDocument, "/image/use-cache");
                    result.useCache = (useCacheString == null) ? null : new Boolean(useCacheString);

                    result.transformCount = XPathUtils.selectIntegerValue(imageConfigDocument, "count(/image/transform)").intValue();
                    Object transforms = XPathUtils.selectObjectValue(imageConfigDocument, "/image/transform");
                    if (transforms != null && transforms instanceof Node)
                        transforms = Collections.singletonList(transforms);
                    result.transforms = transforms;
                    result.transformIterator = XPathUtils.selectIterator(imageConfigDocument, "/image/transform");

                    return result;
                }
            });

            final float quality = (imageConfig.quality == null) ? config.defaultQuality : imageConfig.quality.floatValue();
            final boolean useCache = config.cacheDir != null && ((imageConfig.useCache == null) ? DEFAULT_USE_CACHE : imageConfig.useCache.booleanValue());

            URLConnection urlConnection = null;
            InputStream urlConnectionInputStream = null;
            try {
                // Make sure the requested resource exists and is valid
                URL newURL = null;
                try {
                    newURL = URLFactory.createURL(config.imageDirectoryURL, imageConfig.urlString);
                    // Check if new URL is relative to image directory URL
                    boolean relative = NetUtils.relativeURL(config.imageDirectoryURL, newURL);
                    if (config.useSandbox && !relative) {
                        imageResponse.setStatus(ExternalContext.SC_NOT_FOUND);
                        return;
                    }
                    // Try to open the connection
                    urlConnection = newURL.openConnection();
                    // Get InputStream and make sure it supprorts marks
                    urlConnectionInputStream = urlConnection.getInputStream();
                    if (!urlConnectionInputStream.markSupported())
                        urlConnectionInputStream = new BufferedInputStream(urlConnectionInputStream);
                    // Make sure the resource looks like a JPEG file
                    String contentType = URLConnection.guessContentTypeFromStream(urlConnectionInputStream);
{'fraction_non_alphanumeric': 0.0787289300809696, 'fraction_numerical': 0.0029536079849264143, 'mean_word_length': 2.6522224288636784, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1455343', 'n_tokens_mistral': 4913, 'n_tokens_neox': 4678, 'n_words': 1265}