/*
* eXist Open Source Native XML Database
* Copyright (C) 2009 The eXist Project
* http://exist-db.org
*
* 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
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.backup.xquery;
import org.exist.backup.ZipArchiveBackupDescriptor;
import org.exist.dom.QName;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.storage.BrokerPool;
import org.exist.util.FileUtils;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Variable;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
public class RetrieveBackup extends BasicFunction
{
public final static FunctionSignature signature = new FunctionSignature( new QName( "retrieve", BackupModule.NAMESPACE_URI, BackupModule.PREFIX ), "Retrieves a zipped backup archive, $name, and directly streams it to the HTTP response. " + "For security reasons, the function will only read .zip files in the specified directory, $directory.", new SequenceType[] {
new FunctionParameterSequenceType( "directory", Type.STRING, Cardinality.EXACTLY_ONE, "The path to the directory where the backup file is located." ),
new FunctionParameterSequenceType( "name", Type.STRING, Cardinality.EXACTLY_ONE, "The name of the file to retrieve." )
}, new SequenceType( Type.ITEM, Cardinality.EMPTY ) );
public RetrieveBackup( XQueryContext context )
{
super( context, signature );
}
public Sequence eval( Sequence[] args, Sequence contextSequence ) throws XPathException
{
if(!context.getEffectiveUser().hasDbaRole()) {
throw new XPathException("You must be a DBA to retrieve a backup");
}
final String exportDir = args[0].getStringValue();
Path dir = Paths.get( exportDir );
if( !dir.isAbsolute() ) {
dir = ((Path)context.getBroker().getConfiguration().getProperty(BrokerPool.PROPERTY_DATA_DIR)).resolve(exportDir);
}
final String name = args[1].getStringValue();
final Path backupFile = dir.resolve(name);
if( !Files.isReadable(backupFile)) {
return( Sequence.EMPTY_SEQUENCE );
}
if( !name.endsWith( ".zip" ) ) {
throw( new XPathException( this, "for security reasons, the function only allows " + "reading zipped backup archives" ) );
}
try {
final ZipArchiveBackupDescriptor descriptor = new ZipArchiveBackupDescriptor( backupFile );
final Properties properties = descriptor.getProperties();
if( ( properties == null ) || ( properties.size() == 0 ) ) {
throw( new XPathException( this, "the file does not see to be a valid backup archive" ) );
}
}
catch( final IOException e ) {
throw( new XPathException( this, "the file does not see to be a valid backup archive" ) );
}
// directly stream the backup contents to the HTTP response
final ResponseModule myModule = (ResponseModule)context.getModule( ResponseModule.NAMESPACE_URI );
// response object is read from global variable $response
final Variable respVar = myModule.resolveVariable( ResponseModule.RESPONSE_VAR );
if( respVar == null ) {
throw( new XPathException( this, "No response object found in the current XQuery context." ) );
}
if( respVar.getValue().getItemType() != Type.JAVA_OBJECT ) {
throw( new XPathException( this, "Variable $response is not bound to an Java object." ) );
}
final JavaObjectValue respValue = (JavaObjectValue)respVar.getValue().itemAt( 0 );
if( !"org.exist.http.servlets.HttpResponseWrapper".equals( respValue.getObject().getClass().getName() ) ) {
throw( new XPathException( this, signature.toString() + " can only be used within the EXistServlet or XQueryServlet" ) );
}
final ResponseWrapper response = (ResponseWrapper)respValue.getObject();
response.setContentType("application/zip");
response.setHeader("Content-Length", String.valueOf(FileUtils.sizeQuietly(backupFile)));
try {
try(final OutputStream os = response.getOutputStream()) {
Files.copy(backupFile, os);
}
response.flushBuffer();
}
catch( final IOException e ) {
throw( new XPathException( this, "An IO error occurred while reading the backup archive" ) );
}
return( Sequence.EMPTY_SEQUENCE );
}
}