* * @author Bernhard Posselt * @author Christoph Wurst * @author Joas Schilling * @author Julius Härtl * @author Lukas Reschke * @author Morris Jobke * @author Roeland Jago Douma * @author Stefan Weil * @author Thomas Müller * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see * */ namespace OC\App; use OCP\IL10N; class DependencyAnalyzer { /** @var Platform */ private $platform; /** @var \OCP\IL10N */ private $l; /** @var array */ private $appInfo; /** * @param Platform $platform * @param \OCP\IL10N $l */ public function __construct(Platform $platform, IL10N $l) { $this->platform = $platform; $this->l = $l; } /** * @param array $app * @returns array of missing dependencies */ public function analyze(array $app, bool $ignoreMax = false) { $this->appInfo = $app; if (isset($app['dependencies'])) { $dependencies = $app['dependencies']; } else { $dependencies = []; } return array_merge( $this->analyzeArchitecture($dependencies), $this->analyzePhpVersion($dependencies), $this->analyzeDatabases($dependencies), $this->analyzeCommands($dependencies), $this->analyzeLibraries($dependencies), $this->analyzeOS($dependencies), $this->analyzeOC($dependencies, $app, $ignoreMax) ); } public function isMarkedCompatible(array $app): bool { if (isset($app['dependencies'])) { $dependencies = $app['dependencies']; } else { $dependencies = []; } $maxVersion = $this->getMaxVersion($dependencies, $app); if ($maxVersion === null) { return true; } return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion); } /** * Truncates both versions to the lowest common version, e.g. * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1, * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1 * @param string $first * @param string $second * @return string[] first element is the first version, second element is the * second version */ private function normalizeVersions($first, $second) { $first = explode('.', $first); $second = explode('.', $second); // get both arrays to the same minimum size $length = min(count($second), count($first)); $first = array_slice($first, 0, $length); $second = array_slice($second, 0, $length); return [implode('.', $first), implode('.', $second)]; } /** * Parameters will be normalized and then passed into version_compare * in the same order they are specified in the method header * @param string $first * @param string $second * @param string $operator * @return bool result similar to version_compare */ private function compare($first, $second, $operator) { // we can't normalize versions if one of the given parameters is not a // version string but null. In case one parameter is null normalization // will therefore be skipped if ($first !== null && $second !== null) { [$first, $second] = $this->normalizeVersions($first, $second); } return version_compare($first, $second, $operator); } /** * Checks if a version is bigger than another version * @param string $first * @param string $second * @return bool true if the first version is bigger than the second */ private function compareBigger($first, $second) { return $this->compare($first, $second, '>'); } /** * Checks if a version is smaller than another version * @param string $first * @param string $second * @return bool true if the first version is smaller than the second */ private function compareSmaller($first, $second) { return $this->compare($first, $second, '<'); } /** * @param array $dependencies * @return array */ private function analyzePhpVersion(array $dependencies) { $missing = []; if (isset($dependencies['php']['@attributes']['min-version'])) { $minVersion = $dependencies['php']['@attributes']['min-version']; if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) { $missing[] = $this->l->t('PHP %s or higher is required.', [$minVersion]); } } if (isset($dependencies['php']['@attributes']['max-version'])) { $maxVersion = $dependencies['php']['@attributes']['max-version']; if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) { $missing[] = $this->l->t('PHP with a version lower than %s is required.', [$maxVersion]); } } if (isset($dependencies['php']['@attributes']['min-int-size'])) { $intSize = $dependencies['php']['@attributes']['min-int-size']; if ($intSize > $this->platform->getIntSize() * 8) { $missing[] = $this->l->t('%sbit or higher PHP required.', [$intSize]); } } return $missing; } private function analyzeArchitecture(array $dependencies) { $missing = []; if (!isset($dependencies['architecture'])) { return $missing; } $supportedArchitectures = $dependencies['architecture']; if (empty($supportedArchitectures)) { return $missing; } if (!is_array($supportedArchitectures)) { $supportedArchitectures = [$supportedArchitectures]; } $supportedArchitectures = array_map(function ($architecture) { return $this->getValue($architecture); }, $supportedArchitectures); $currentArchitecture = $this->platform->getArchitecture(); if (!in_array($currentArchitecture, $supportedArchitectures, true)) { $missing[] = $this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]); } return $missing; } /** * @param array $dependencies * @return array */ private function analyzeDatabases(array $dependencies) { $missing = []; if (!isset($dependencies['database'])) { return $missing; } $supportedDatabases = $dependencies['database']; if (empty($supportedDatabases)) { return $missing; } if (!is_array($supportedDatabases)) { $supportedDatabases = [$supportedDatabases]; } $supportedDatabases = array_map(function ($db) { return $this->getValue($db); }, $supportedDatabases); $currentDatabase = $this->platform->getDatabase(); if (!in_array($currentDatabase, $supportedDatabases)) { $missing[] = $this->l->t('The following databases are supported: %s', [implode(', ', $supportedDatabases)]); } return $missing; } /** * @param array $dependencies * @return array */ private function analyzeCommands(array $dependencies) { $missing = []; if (!isset($dependencies['command'])) { return $missing; } $commands = $dependencies['command']; if (!is_array($commands)) { $commands = [$commands]; } if (isset($commands['@value'])) { $commands = [$commands]; } $os = $this->platform->getOS(); foreach ($commands as $command) { if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) { continue; } $commandName = $this->getValue($command); if (!$this->platform->isCommandKnown($commandName)) { $missing[] = $this->l->t('The command line tool %s could not be found', [$commandName]); } } return $missing; } /** * @param array $dependencies * @return array */ private function analyzeLibraries(array $dependencies) { $missing = []; if (!isset($dependencies['lib'])) { return $missing; } $libs = $dependencies['lib']; if (!is_array($libs)) { $libs = [$libs]; } if (isset($libs['@value'])) { $libs = [$libs]; } foreach ($libs as $lib) { $libName = $this->getValue($lib); $libVersion = $this->platform->getLibraryVersion($libName); if (is_null($libVersion)) { $missing[] = $this->l->t('The library %s is not available.', [$libName]); continue; } if (is_array($lib)) { if (isset($lib['@attributes']['min-version'])) { $minVersion = $lib['@attributes']['min-version']; if ($this->compareSmaller($libVersion, $minVersion)) { $missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.', [$libName, $minVersion, $libVersion]); } } if (isset($lib['@attributes']['max-version'])) { $maxVersion = $lib['@attributes']['max-version']; if ($this->compareBigger($libVersion, $maxVersion)) { $missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.', [$libName, $maxVersion, $libVersion]); } } } } return $missing; } /** * @param array $dependencies * @return array */ private function analyzeOS(array $dependencies) { $missing = []; if (!isset($dependencies['os'])) { return $missing; } $oss = $dependencies['os']; if (empty($oss)) { return $missing; } if (is_array($oss)) { $oss = array_map(function ($os) { return $this->getValue($os); }, $oss); } else { $oss = [$oss]; } $currentOS = $this->platform->getOS(); if (!in_array($currentOS, $oss)) { $missing[] = $this->l->t('The following platforms are supported: %s', [implode(', ', $oss)]); } return $missing; } /** * @param array $dependencies * @param array $appInfo * @return array */ private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) { $missing = []; $minVersion = null; if (isset($dependencies['nextcloud']['@attributes']['min-version'])) { $minVersion = $dependencies['nextcloud']['@attributes']['min-version']; } elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) { $minVersion = $dependencies['owncloud']['@attributes']['min-version']; } elseif (isset($appInfo['requiremin'])) { $minVersion = $appInfo['requiremin']; } elseif (isset($appInfo['require'])) { $minVersion = $appInfo['require']; } $maxVersion = $this->getMaxVersion($dependencies, $appInfo); if (!is_null($minVersion)) { if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) { $missing[] = $this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]); } } if (!$ignoreMax && !is_null($maxVersion)) { if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) { $missing[] = $this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]); } } return $missing; } private function getMaxVersion(array $dependencies, array $appInfo): ?string { if (isset($dependencies['nextcloud']['@attributes']['max-version'])) { return $dependencies['nextcloud']['@attributes']['max-version']; } if (isset($dependencies['owncloud']['@attributes']['max-version'])) { return $dependencies['owncloud']['@attributes']['max-version']; } if (isset($appInfo['requiremax'])) { return $appInfo['requiremax']; } return null; } /** * Map the internal version number to the Nextcloud version * * @param string $version * @return string */ protected function toVisibleVersion($version) { switch ($version) { case '9.1': return '10'; default: if (strpos($version, '9.1.') === 0) { $version = '10.0.' . substr($version, 4); } return $version; } } /** * @param $element * @return mixed */ private function getValue($element) { if (isset($element['@value'])) { return $element['@value']; } return (string)$element; } } 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
/*
 * $Id$
 * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved.
 * For details on use and redistribution please refer to the
 * LICENSE file included with these sources.
 */

package org.apache.fop.servlet;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

// Avalon
import org.apache.avalon.framework.logger.ConsoleLogger;
import org.apache.avalon.framework.logger.Logger;

//FOP
import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;

/**
 * Example servlet to generate a PDF from a servlet.
 * <br/>
 * Servlet param is:
 * <ul>
 *   <li>fo: the path to a XSL-FO file to render
 * </ul>
 * or
 * <ul>
 *   <li>xml: the path to an XML file to render</li>
 *   <li>xslt: the path to an XSLT file that can transform the above XML to XSL-FO</li>
 * </ul>
 * <br/>
 * Example URL: http://servername/fop/servlet/FopServlet?fo=readme.fo
 * <br/>
 * Example URL: http://servername/fop/servlet/FopServlet?xml=data.xml&xslt=format.xsl
 * <br/>
 * For this to work with Internet Explorer, you might need to append "&ext=.pdf" 
 * to the URL.
 * 
 * @author <a href="mailto:fop-dev@xml.apache.org">Apache XML FOP Development Team</a>
 * @version $Id$
 * @todo Ev. add caching mechanism for Templates objects
 */
public class FopServlet extends HttpServlet {
    
    /** Name of the parameter used for the XSL-FO file */
    protected static final String FO_REQUEST_PARAM = "fo";
    /** Name of the parameter used for the XML file */
    protected static final String XML_REQUEST_PARAM = "xml";
    /** Name of the parameter used for the XSLT file */
    protected static final String XSLT_REQUEST_PARAM = "xslt";
    
    /** Logger to give to FOP */
    protected Logger log = null;
    /** The TransformerFactory to use to create Transformer instances */
    protected TransformerFactory transFactory = null;

    /**
     * @see javax.servlet.GenericServlet#init()
     */
    public void init() throws ServletException {
        this.log = new ConsoleLogger(ConsoleLogger.LEVEL_WARN);
        this.transFactory = TransformerFactory.newInstance();
    }

    /**
     * @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
     */
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException {
        try {
            //Get parameters
            String foParam = request.getParameter(FO_REQUEST_PARAM);
            String xmlParam = request.getParameter(XML_REQUEST_PARAM);
            String xsltParam = request.getParameter(XSLT_REQUEST_PARAM);
            
            //Analyze parameters and decide with method to use
            byte[] content = null;
            if (foParam != null) {
                content = renderFO(foParam);
            } else if ((xmlParam != null) && (xsltParam != null)) {
                content = renderXML(xmlParam, xsltParam);
            } else {
                PrintWriter out = response.getWriter();
                out.println("<html><head><title>Error</title></head>\n"
                          + "<body><h1>FopServlet Error</h1><h3>No 'fo' "
                          + "request param given.</body></html>");
            }

            if (content != null) {
                //Send the result back to the client
                response.setContentType("application/pdf");
                response.setContentLength(content.length);
                response.getOutputStream().write(content);
                response.getOutputStream().flush();
            }

        } catch (Exception ex) {
            throw new ServletException(ex);
        }
    }

    /**
     * Converts a String parameter to a JAXP Source object.
     * @param param a String parameter
     * @return Source the generated Source object
     */
    protected Source convertString2Source(String param) {
        return new StreamSource(new File(param));
    }

    /**
     * Renders an XSL-FO file into a PDF file. The PDF is written to a byte 
     * array that is returned as the method's result.
     * @param fo the XSL-FO file
     * @return byte[] the rendered PDF file
     * @throws FOPException If an error occurs during the rendering of the 
     * XSL-FO
     * @throws TransformerException If an error occurs while parsing the input
     * file
     */
    protected byte[] renderFO(String fo)
                throws FOPException, TransformerException {
                    
        //Setup source
        Source foSrc = convertString2Source(fo);

        //Setup the identity transformation
        Transformer transformer = this.transFactory.newTransformer();
        
        //Start transformation and rendering process
        return render(foSrc, transformer);
    }

    /**
     * Renders an XML file into a PDF file by applying a stylesheet
     * that converts the XML to XSL-FO. The PDF is written to a byte array
     * that is returned as the method's result.
     * @param xml the XML file
     * @param xslt the XSLT file
     * @return byte[] the rendered PDF file
     * @throws FOPException If an error occurs during the rendering of the 
     * XSL-FO
     * @throws TransformerException If an error occurs during XSL
     * transformation
     */
    protected byte[] renderXML(String xml, String xslt) 
                throws FOPException, TransformerException {

        //Setup sources
        Source xmlSrc = convertString2Source(xml);
        Source xsltSrc = convertString2Source(xslt);

        //Setup the XSL transformation
        Transformer transformer = this.transFactory.newTransformer(xsltSrc);

        //Start transformation and rendering process
        return render(xmlSrc, transformer);
    }

    /**
     * Renders an input file (XML or XSL-FO) into a PDF file. It uses the JAXP
     * transformer given to optionally transform the input document to XSL-FO.
     * The transformer may be an identity transformer in which case the input 
     * must already be XSL-FO. The PDF is written to a byte array that is
     * returned as the method's result.
     * @param src Input XML or XSL-FO
     * @param transformer Transformer to use for optional transformation
     * @return byte[] the rendered PDF file
     * @throws FOPException If an error occurs during the rendering of the 
     * XSL-FO
     * @throws TransformerException If an error occurs during XSL
     * transformation
     */
    protected byte[] render(Source src, Transformer transformer) 
                throws FOPException, TransformerException {

        //Setup FOP
        Driver driver = new Driver();
        driver.enableLogging(this.log);
        driver.setRenderer(Driver.RENDER_PDF);
        driver.initialize();

        //Setup output
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        driver.setOutputStream(out);
        
        //Make sure the XSL transformation's result is piped through to FOP
        Result res = new SAXResult(driver.getContentHandler());
        
        //Start the transformation and rendering process
        transformer.transform(src, res);

        //Return the result
        return out.toByteArray();
    }

}