All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.javadpkg.impl.DebianPackageParserImpl Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 * dpkg - Debian Package library and the Debian Package Maven plugin
 * (c) Copyright 2015 Gerrit Hohl
 *
 * This program 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; 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 */
package net.sourceforge.javadpkg.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;

import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;

import net.sourceforge.javadpkg.ChangeLog;
import net.sourceforge.javadpkg.ChangeLogParser;
import net.sourceforge.javadpkg.ConfigFiles;
import net.sourceforge.javadpkg.ConfigFilesParser;
import net.sourceforge.javadpkg.Context;
import net.sourceforge.javadpkg.Copyright;
import net.sourceforge.javadpkg.CopyrightParser;
import net.sourceforge.javadpkg.DebianPackage;
import net.sourceforge.javadpkg.DebianPackageConstants;
import net.sourceforge.javadpkg.DebianPackageParseHandler;
import net.sourceforge.javadpkg.DebianPackageParser;
import net.sourceforge.javadpkg.DocumentPaths;
import net.sourceforge.javadpkg.MD5Sums;
import net.sourceforge.javadpkg.MD5SumsParser;
import net.sourceforge.javadpkg.ParseException;
import net.sourceforge.javadpkg.Script;
import net.sourceforge.javadpkg.ScriptParser;
import net.sourceforge.javadpkg.SharedLibraries;
import net.sourceforge.javadpkg.SharedLibrariesParser;
import net.sourceforge.javadpkg.Symbols;
import net.sourceforge.javadpkg.SymbolsParser;
import net.sourceforge.javadpkg.Templates;
import net.sourceforge.javadpkg.TemplatesParser;
import net.sourceforge.javadpkg.control.BinaryControl;
import net.sourceforge.javadpkg.control.Control;
import net.sourceforge.javadpkg.control.ControlParser;
import net.sourceforge.javadpkg.control.impl.ControlParserImpl;
import net.sourceforge.javadpkg.io.DataSource;
import net.sourceforge.javadpkg.io.FileMetaData;
import net.sourceforge.javadpkg.io.FileMode;
import net.sourceforge.javadpkg.io.FileOwner;
import net.sourceforge.javadpkg.io.impl.DataStreamSource;
import net.sourceforge.javadpkg.io.impl.FileMetaDataImpl;
import net.sourceforge.javadpkg.io.impl.FileModeImpl;
import net.sourceforge.javadpkg.io.impl.FileOwnerImpl;
import net.sourceforge.javadpkg.io.impl.UncloseableInputStream;

/**
 * 

* A {@link DebianPackageParser} implementation. *

*

* This implementation currently only supports Debian binary packages. *

* * @author Gerrit Hohl ([email protected]) * @version 1.0, 27.12.2015 by Gerrit Hohl */ public class DebianPackageParserImpl implements DebianPackageParser, DebianPackageConstants { /** The parser for the control. */ private ControlParser controlParser; /** The parser for the MD5 sums. */ private MD5SumsParser md5SumsParser; /** The parser for scripts. */ private ScriptParser scriptParser; /** The parser for the templates. */ private TemplatesParser templatesParser; /** The parser for the configuration files. */ private ConfigFilesParser configFilesParser; /** The parser for the shared libraries. */ private SharedLibrariesParser sharedLibrariesParser; /** The parser for the symbols. */ private SymbolsParser symbolsParser; /** The parser for the copyright. */ private CopyrightParser copyrightParser; /** The parser for the change log. */ private ChangeLogParser changeLogParser; /** *

* Creates a parser. *

*/ protected DebianPackageParserImpl() { super(); this.controlParser = new ControlParserImpl(); this.md5SumsParser = new MD5SumsParserImpl(); this.scriptParser = new ScriptParserImpl(); this.templatesParser = new TemplatesParserImpl(); this.configFilesParser = new ConfigFilesParserImpl(); this.sharedLibrariesParser = new SharedLibrariesParserImpl(); this.symbolsParser = new SymbolsParserImpl(); this.copyrightParser = new CopyrightParserImpl(); this.changeLogParser = new ChangeLogParserImpl(); } @Override public DebianPackage parseDebianPackage(DataSource source, Context context) throws IOException, ParseException { DebianPackage debianPackage; DebianPackageParseHandler handler; if (source == null) throw new IllegalArgumentException("Argument source is null."); if (context == null) throw new IllegalArgumentException("Argument context is null."); handler = new DebianPackageParseHandlerImpl(); debianPackage = this.parseDebianPackage(source, handler, context); return debianPackage; } @Override public DebianPackage parseDebianPackage(DataSource source, DebianPackageParseHandler handler, Context context) throws IOException, ParseException { DebianPackageImpl debianPackage; if (source == null) throw new IllegalArgumentException("Argument source is null."); if (handler == null) throw new IllegalArgumentException("Argument handler is null."); if (context == null) throw new IllegalArgumentException("Argument context is null."); debianPackage = new DebianPackageImpl(); try (InputStream in = source.getInputStream()) { try (ArArchiveInputStream arIn = new ArArchiveInputStream(in)) { // --- Read the version of the package --- this.readVersion(debianPackage, source, arIn); // --- Read the meta information --- this.readControl(debianPackage, source, context, arIn); // --- Read the contents --- this.readData(handler, debianPackage, source, context, arIn); } } return debianPackage; } /** *

* Reads the version of the Debian package. *

* * @param debianPackage * The Debian package. * @param source * The source. * @param in * The stream on the archive. * @throws IOException * If an I/O error occurs. * @throws ParseException * If an error occurs during the parsing. */ private void readVersion(DebianPackageImpl debianPackage, DataSource source, ArArchiveInputStream in) throws IOException, ParseException { ArArchiveEntry entry; String line; entry = in.getNextArEntry(); if (!DEBIAN_BINARY.equals(entry.getName())) throw new ParseException( "Couldn't find entry |" + DEBIAN_BINARY + "| in AR archive |" + source.getName() + "|: " + entry.getName()); try { try (BufferedReader reader = new BufferedReader( new InputStreamReader(new UncloseableInputStream(in), UTF_8_CHARSET))) { line = reader.readLine(); } } catch (IOException e) { throw new IOException("Couldn't read entry |" + entry.getName() + "| from AR archive |" + source.getName() + "|: " + e.getMessage()); } if (line == null) throw new ParseException( "Couldn't read content of |" + DEBIAN_BINARY + "| in AR archive |" + source.getName() + "|."); // TODO Add a warning if the version is not "2.0" as we currently only support this version. debianPackage.setFileFormatVersion(line); } /** *

* Reads the meta information of the Debian package. *

* * @param debianPackage * The Debian package. * @param source * The source. * @param context * The context. * @param in * The stream on the archive. * @throws IOException * If an I/O error occurs. * @throws ParseException * If an error occurs during the parsing. */ private void readControl(DebianPackageImpl debianPackage, DataSource source, Context context, ArArchiveInputStream in) throws IOException, ParseException { ArArchiveEntry entry; TarArchiveEntry tarEntry; String name; Control control = null; MD5Sums md5Sums = null; Script preInstall = null, postInstall = null, preRemove = null, postRemove = null, config = null; Templates templates = null; ConfigFiles configFiles = null; SharedLibraries sharedLibraries = null; Symbols symbols = null; entry = in.getNextArEntry(); if (!entry.getName().startsWith(CONTROL_TAR_PREFIX)) throw new ParseException("Couldn't find entry |" + CONTROL_TAR_PREFIX + "*| in AR archive |" + source.getName() + "|. Found |" + entry.getName() + "| instead."); try { try (TarArchiveInputStream tarIn = this.openTarArchive(in, entry.getName())) { while ((tarEntry = tarIn.getNextTarEntry()) != null) { name = tarEntry.getName(); // --- Remove leading path --- if (name.startsWith("./")) { name = name.substring(2); } // --- Ignore empty names (the root directory of the archive) --- if (name.isEmpty()) { continue; } switch (name) { case CONTROL_ENTRY: if (control != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { control = this.controlParser.parseControl(entrySource, context); debianPackage.setControl(control); } break; case MD5SUMS_ENTRY: // TODO Warn if no MD5 sums are available. if (md5Sums != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { md5Sums = this.md5SumsParser.parseMD5Sums(entrySource, context); debianPackage.setMD5Sums(md5Sums); } break; case PREINST_ENTRY: if (preInstall != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { preInstall = this.scriptParser.parseScript(entrySource, context); debianPackage.setPreInstall(preInstall); } break; case POSTINST_ENTRY: if (postInstall != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { postInstall = this.scriptParser.parseScript(entrySource, context); debianPackage.setPostInstall(postInstall); } break; case PRERM_ENTRY: if (preRemove != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { preRemove = this.scriptParser.parseScript(entrySource, context); debianPackage.setPreRemove(preRemove); } break; case POSTRM_ENTRY: if (postRemove != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { postRemove = this.scriptParser.parseScript(entrySource, context); debianPackage.setPostRemove(postRemove); } break; case TEMPLATES_ENTRY: if (templates != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { templates = this.templatesParser.parseTemplates(entrySource, context); debianPackage.setTemplates(templates); } break; case CONFIG_ENTRY: if (config != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { config = this.scriptParser.parseScript(entrySource, context); debianPackage.setConfig(config); } break; case CONFFILES_ENTRY: if (configFiles != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { configFiles = this.configFilesParser.parseConfigFiles(entrySource, context); debianPackage.setConfigFiles(configFiles); } break; case SHLIBS_ENTRY: if (sharedLibraries != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { sharedLibraries = this.sharedLibrariesParser.parseSharedLibraries(entrySource, context); debianPackage.setSharedLibraries(sharedLibraries); } break; case SYMBOLS_ENTRY: if (symbols != null) throw new ParseException("Found entry |" + name + "| in archive |" + entry.getName() + "| in source |" + source.getName() + "| more than one time."); try (DataStreamSource entrySource = new DataStreamSource(tarIn, name, false)) { symbols = this.symbolsParser.parseSymbols(entrySource, context); debianPackage.setSymbols(symbols); } break; // TODO Implement "triggers" in Debian package control. default: context.addWarning(new ControlUnsupportedEntryWarning(name)); } } } } catch (IOException e) { throw new IOException("Couldn't read entry |" + entry.getName() + "| from AR archive |" + source.getName() + "|: " + e.getMessage()); } } /** *

* Reads the contents of the Debian package. *

* * @param handler * The handler. * @param debianPackage * The Debian package. * @param source * The source. * @param context * The context. * @param in * The stream on the archive. * @throws IOException * If an I/O error occurs. * @throws ParseException * If an error occurs during the parsing. */ private void readData(DebianPackageParseHandler handler, DebianPackageImpl debianPackage, DataSource source, Context context, ArArchiveInputStream in) throws IOException, ParseException { ArArchiveEntry entry; BinaryControl control; DocumentPaths paths; TarArchiveEntry tarEntry; FileOwner owner; FileMode mode; FileMetaData file; String name = null; Copyright copyright; ChangeLog changeLog; boolean changeLogDebianRead = false; entry = in.getNextArEntry(); if (!entry.getName().startsWith(DATA_TAR_PREFIX)) throw new ParseException("Couldn't find entry |" + DATA_TAR_PREFIX + "*| in AR archive |" + source.getName() + "|. Found |" + entry.getName() + "| instead."); // --- Create the document paths --- if (debianPackage.getControl() instanceof BinaryControl) { control = (BinaryControl) debianPackage.getControl(); } else throw new ParseException("Found control |" + debianPackage.getControl() + "| of type |" + (debianPackage.getControl() == null ? "null" : debianPackage.getControl().getClass().getCanonicalName()) + ", but only control of type |" + BinaryControl.class.getCanonicalName() + "| is supported."); paths = new DocumentPathsImpl(control.getPackage()); try { try (TarArchiveInputStream tarIn = this.openTarArchive(in, entry.getName())) { while ((tarEntry = tarIn.getNextTarEntry()) != null) { // --- Get name without leading '.' --- name = tarEntry.getName(); if (name.startsWith(".")) { name = name.substring(1); } owner = new FileOwnerImpl(tarEntry.getLongGroupId(), tarEntry.getGroupName(), tarEntry.getLongUserId(), tarEntry.getUserName()); mode = new FileModeImpl(tarEntry.getMode()); if (tarEntry.isDirectory()) { file = FileMetaDataImpl.createDirectoryMetaData(name, owner, mode, tarEntry.getLastModifiedDate()); } else if (tarEntry.isFile()) { file = FileMetaDataImpl.createFileMetaData(name, owner, mode, tarEntry.getSize(), tarEntry.getLastModifiedDate()); } else if (tarEntry.isSymbolicLink()) { file = FileMetaDataImpl.createSymbolicLinkMetaData(name, tarEntry.getLinkName(), owner, mode, tarEntry.getLastModifiedDate()); } else throw new IOException("Entry |" + name + "| is |" + this.getEntryType(tarEntry) + "| entry, but only entries of te types directory, file and symbolic link are supported."); // TODO Memorize the files, their attributes and sizes. // TODO Check MD5 sums (if available). // --- Copyright and change log (if the file is a regular file) --- if (file.isFile() && !file.isSymbolicLink() && (paths.isCopyrightPath(name) || paths.isChangeLogPath(name))) { // --- Copyright --- if (paths.isCopyrightPath(name)) { if (debianPackage.getCopyright() != null) throw new ParseException("Copyright is already set."); copyright = this.readCopyright(tarIn, name, context); debianPackage.setCopyright(copyright); } // --- Change log --- else if (paths.isChangeLogPath(name)) { if (tarEntry.getSize() > 0) { if (debianPackage.getChangeLog() != null) { /* * --- Ignore the file if the Debian change log has already been read * and the current file is the "normal" change log. --- */ if (changeLogDebianRead && !paths.isChangeLogDebianPath(name)) { continue; } /* * --- Otherwise throw an error if this is not the Debian change log * or we have already read the Debian change log. --- */ else if (!paths.isChangeLogDebianPath(name)) throw new ParseException("Change log is already set."); } changeLog = this.readChangeLog(tarIn, name, paths, context); debianPackage.setChangeLog(changeLog); if (paths.isChangeLogDebianPath(name)) { changeLogDebianRead = true; } } else { context.addWarning(new CopyrightEmptyWarning()); } } else if (!name.endsWith("/changelog-old.Debian.gz")) throw new ParseException("Unsupported copyright or change log file: " + name); } else { if (file.isDirectory() || file.isSymbolicLink()) { handler.handleData(file, null); } else { try (DataSource entrySource = new DataStreamSource(tarIn, name, false)) { handler.handleData(file, entrySource); } } } } } // TODO Warn if no copyright, change log and/or MD5 sums have been found. } catch (IOException e) { throw new IOException("Couldn't read entry |" + entry.getName() + "| from AR archive |" + source.getName() + "|: " + e.getMessage(), e); } catch (ParseException e) { throw new ParseException("Couldn't parse |" + name + "| of entry |" + entry.getName() + "| from AR archive |" + source.getName() + "|: " + e.getMessage(), e); } } /** *

* Returns the type of the entry. *

* * @param entry * The entry. * @return The type. */ private String getEntryType(TarArchiveEntry entry) { if (entry.isBlockDevice()) return "block device"; else if (entry.isCharacterDevice()) return "character device"; else if (entry.isDirectory()) return "directory"; else if (entry.isFIFO()) return "FIFO"; else if (entry.isLink()) return "link"; else if (entry.isSymbolicLink()) return "symbolic link"; return "unknown"; } /** *

* Reads the copyright from the stream. *

* * @param in * The stream. * @param name * The name. * @param context * The context. * @return The copyright. * @throws IOException * If an I/O error occurs. * @throws ParseException * If an error occurs during the parsing. */ private Copyright readCopyright(InputStream in, String name, Context context) throws IOException, ParseException { Copyright copyright; try (DataSource fileSource = new DataStreamSource(in, name, false)) { copyright = this.copyrightParser.parseCopyright(fileSource, context); } return copyright; } /** *

* Reads the change log from the stream. *

* * @param in * The stream. * @param name * The name. * @param paths * The paths of the files in the document folder of the Debian * package. * @param context * The context. * @return The change log. * @throws IOException * If an I/O error occurs. * @throws ParseException * If an error occurs during the parsing. */ private ChangeLog readChangeLog(InputStream in, String name, DocumentPaths paths, Context context) throws IOException, ParseException { ChangeLog changeLog; if (paths.isChangeLogGzipPath(name)) { try (GZIPInputStream gzipIn = new GZIPInputStream(new UncloseableInputStream(in))) { if (paths.isChangeLogHtmlPath(name)) { try (DataSource fileSource = new DataStreamSource(gzipIn, name, false)) { changeLog = this.changeLogParser.parseChangeLogHtml(fileSource, context); } } else { try (DataSource fileSource = new DataStreamSource(gzipIn, name, false)) { changeLog = this.changeLogParser.parseChangeLog(fileSource, context); } } } } else { if (paths.isChangeLogHtmlPath(name)) { try (DataSource fileSource = new DataStreamSource(in, name, false)) { changeLog = this.changeLogParser.parseChangeLogHtml(fileSource, context); } } else { try (DataSource fileSource = new DataStreamSource(in, name, false)) { changeLog = this.changeLogParser.parseChangeLog(fileSource, context); } } } return changeLog; } /** *

* Opens an entry with the specified name as TAR archive. *

* * @param in * The underlying stream. * @param name * The name of the entry. * @return The stream on the TAR archive. * @throws IOException * If an I/O error occurs while opening the TAR archive. */ @SuppressWarnings("resource") private TarArchiveInputStream openTarArchive(InputStream in, String name) throws IOException { UncloseableInputStream uncloseIn; CompressorInputStream compressorIn; TarArchiveInputStream tarIn; uncloseIn = new UncloseableInputStream(in); // TODO Memorize which format was used. if (name.endsWith(TAR_GZIP_SUFFIX)) { compressorIn = new GzipCompressorInputStream(uncloseIn); } else if (name.endsWith(TAR_XZ_SUFFIX)) { compressorIn = new XZCompressorInputStream(uncloseIn); } else if (name.endsWith(TAR_BZIP2_SUFFIX)) { compressorIn = new BZip2CompressorInputStream(uncloseIn); } else throw new IOException("Found entry |" + name + "| with unsupported compression."); tarIn = new TarArchiveInputStream(compressorIn); return tarIn; } /* ********************************************************************** * ********************************************************************** * ********************************************************************** * ********************************************************************** * ********************************************************************** */ /** *

* The {@link DebianPackageParseHandler} implementation of this class. *

* * @author Gerrit Hohl ([email protected]) * @version 1.0, 12.05.2016 by Gerrit Hohl */ private class DebianPackageParseHandlerImpl implements DebianPackageParseHandler { /** *

* Creates a handler. *

*/ public DebianPackageParseHandlerImpl() { super(); } @Override public void handleData(FileMetaData metaData, DataSource source) throws IOException, ParseException { if (metaData == null) throw new IllegalArgumentException("Argument metaData is null."); if (metaData.isFile() && (source == null)) throw new IllegalArgumentException("Argument source for file |" + metaData.getAbsolutePath() + "| is null also the file is a regular file."); // --- Do nothing. --- } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy