/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc; import java.io.IOException; import java.io.InputStream; import java.nio.charset.CharsetDecoder; import java.util.Map; import org.tmatesoft.svn.core.ISVNCanceller; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNDumpStreamParser { private ISVNCanceller myCanceller; public SVNDumpStreamParser(ISVNCanceller canceller) { myCanceller = canceller; } public void parseDumpStream(InputStream dumpStream, ISVNLoadHandler handler, CharsetDecoder decoder) throws SVNException { String line = null; int version = -1; StringBuffer buffer = new StringBuffer(); try { line = SVNFileUtil.readLineFromStream(dumpStream, buffer, decoder); if (line == null) { SVNAdminHelper.generateIncompleteDataError(); } //parse format if (!line.startsWith(SVNAdminHelper.DUMPFILE_MAGIC_HEADER + ":")) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header"); SVNErrorManager.error(err, SVNLogType.FSFS); } try { line = line.substring(SVNAdminHelper.DUMPFILE_MAGIC_HEADER.length() + 1); line = line.trim(); version = Integer.parseInt(line); if (version > SVNAdminHelper.DUMPFILE_FORMAT_VERSION) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Unsupported dumpfile version: {0}", new Integer(version)); SVNErrorManager.error(err, SVNLogType.FSFS); } } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } while (true) { myCanceller.checkCancelled(); boolean foundNode = false; //skip empty lines buffer.setLength(0); line = SVNFileUtil.readLineFromStream(dumpStream, buffer, decoder); if (line == null) { if (buffer.length() > 0) { SVNAdminHelper.generateIncompleteDataError(); } else { break; } } if (line.length() == 0 || Character.isWhitespace(line.charAt(0))) { continue; } Map headers = readHeaderBlock(dumpStream, line, decoder); if (headers.containsKey(SVNAdminHelper.DUMPFILE_REVISION_NUMBER)) { handler.closeRevision(); handler.openRevision(headers); } else if (headers.containsKey(SVNAdminHelper.DUMPFILE_NODE_PATH)) { handler.openNode(headers); foundNode = true; } else if (headers.containsKey(SVNAdminHelper.DUMPFILE_UUID)) { String uuid = (String) headers.get(SVNAdminHelper.DUMPFILE_UUID); handler.parseUUID(uuid); } else if (headers.containsKey(SVNAdminHelper.DUMPFILE_MAGIC_HEADER)) { try { version = Integer.parseInt((String) headers.get(SVNAdminHelper.DUMPFILE_MAGIC_HEADER)); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Unrecognized record type in stream"); SVNErrorManager.error(err, SVNLogType.FSFS); } String contentLength = (String) headers.get(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH); String propContentLength = (String) headers.get(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH); String textContentLength = (String) headers.get(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH); boolean isOldVersion = version == 1 && contentLength != null && propContentLength == null && textContentLength == null; long actualPropLength = 0; if (propContentLength != null || isOldVersion) { String delta = (String) headers.get(SVNAdminHelper.DUMPFILE_PROP_DELTA); boolean isDelta = delta != null && "true".equals(delta); if (foundNode && !isDelta) { handler.removeNodeProperties(); } long length = 0; try { length = Long.parseLong(propContentLength != null ? propContentLength : contentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse property block length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } actualPropLength += parsePropertyBlock(dumpStream, handler, decoder, length, foundNode); } if (textContentLength != null) { String delta = (String) headers.get(SVNAdminHelper.DUMPFILE_TEXT_DELTA); boolean isDelta = delta != null && "true".equals(delta); long length = 0; try { length = Long.parseLong(textContentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse text block length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } handler.parseTextBlock(dumpStream, length, isDelta); } else if (isOldVersion) { long length = 0; try { length = Long.parseLong(contentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse content length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } length -= actualPropLength; if (length > 0 || SVNNodeKind.parseKind((String)headers.get(SVNAdminHelper.DUMPFILE_NODE_KIND)) == SVNNodeKind.FILE) { handler.parseTextBlock(dumpStream, length, false); } } if (contentLength != null && !isOldVersion) { long remaining = 0; try { remaining = Long.parseLong(contentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse content length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } long propertyContentLength = 0; if (propContentLength != null) { try { propertyContentLength = Long.parseLong(propContentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse property block length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } } remaining -= propertyContentLength; long txtContentLength = 0; if (textContentLength != null) { try { txtContentLength = Long.parseLong(textContentLength); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse text block length header"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } } remaining -= txtContentLength; if (remaining < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Sum of subblock sizes larger than total block content length"); SVNErrorManager.error(err, SVNLogType.FSFS); } byte buf[] = new byte[SVNFileUtil.STREAM_CHUNK_SIZE]; long numRead = 0; long numToRead = remaining; while (remaining > 0) { int readSize = remaining >= SVNFileUtil.STREAM_CHUNK_SIZE ? SVNFileUtil.STREAM_CHUNK_SIZE : (int) remaining; int r = dumpStream.read(buf, 0, readSize); if (r < 0) { break; } numRead += r; remaining -= r; } if (numRead != numToRead) { SVNAdminHelper.generateIncompleteDataError(); } } if (foundNode) { handler.closeNode(); foundNode = false; } } handler.closeRevision(); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } } private long parsePropertyBlock(InputStream dumpStream, ISVNLoadHandler handler, CharsetDecoder decoder, long contentLength, boolean isNode) throws SVNException { long actualLength = 0; StringBuffer buffer = new StringBuffer(); String line = null; try { while (contentLength != actualLength) { buffer.setLength(0); line = SVNFileUtil.readLineFromStream(dumpStream, buffer, decoder); if (line == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Incomplete or unterminated property block"); SVNErrorManager.error(err, SVNLogType.FSFS); } //including '\n' actualLength += line.length() + 1; if ("PROPS-END".equals(line)) { break; } else if (line.charAt(0) == 'K' && line.charAt(1) == ' ') { int len = 0; try { len = Integer.parseInt(line.substring(2)); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse node property key length"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } byte[] buff = new byte[len + 1]; actualLength += SVNAdminHelper.readKeyOrValue(dumpStream, buff, len + 1); String propName = new String(buff, 0, len, "UTF-8"); buffer.setLength(0); line = SVNFileUtil.readLineFromStream(dumpStream, buffer, decoder); if (line == null) { SVNAdminHelper.generateIncompleteDataError(); } //including '\n' actualLength += line.length() + 1; if (line.charAt(0) == 'V' && line.charAt(1) == ' ') { try { len = Integer.parseInt(line.substring(2)); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse node property value length"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } buff = new byte[len + 1]; actualLength += SVNAdminHelper.readKeyOrValue(dumpStream, buff, len + 1); SVNPropertyValue propValue = SVNPropertyValue.create(propName, buff, 0, len); if (isNode) { handler.setNodeProperty(propName, propValue); } else { handler.setRevisionProperty(propName, propValue); } } else { SVNAdminHelper.generateStreamMalformedError(); } } else if (line.charAt(0) == 'D' && line.charAt(1) == ' ') { int len = 0; try { len = Integer.parseInt(line.substring(2)); } catch (NumberFormatException nfe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Malformed dumpfile header: can't parse node property key length"); SVNErrorManager.error(err, nfe, SVNLogType.FSFS); } byte[] buff = new byte[len + 1]; actualLength += SVNAdminHelper.readKeyOrValue(dumpStream, buff, len + 1); if (!isNode) { SVNAdminHelper.generateStreamMalformedError(); } String propName = new String(buff, 0, len, "UTF-8"); handler.deleteNodeProperty(propName); } else { SVNAdminHelper.generateStreamMalformedError(); } } } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } return actualLength; } private Map readHeaderBlock(InputStream dumpStream, String firstHeader, CharsetDecoder decoder) throws SVNException, IOException { Map headers = new SVNHashMap(); StringBuffer buffer = new StringBuffer(); while (true) { String header = null; buffer.setLength(0); if (firstHeader != null) { header = firstHeader; firstHeader = null; } else { header = SVNFileUtil.readLineFromStream(dumpStream, buffer, decoder); if (header == null && buffer.length() > 0) { SVNAdminHelper.generateIncompleteDataError(); } else if (buffer.length() == 0) { break; } } int colonInd = header.indexOf(':'); if (colonInd == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Dump stream contains a malformed header (with no '':'') at ''{0}''", header.length() > 20 ? header.substring(0, 19) : header); SVNErrorManager.error(err, SVNLogType.FSFS); } String name = header.substring(0, colonInd); if (colonInd + 2 > header.length()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_MALFORMED_DATA, "Dump stream contains a malformed header (with no value) at ''{0}''", header.length() > 20 ? header.substring(0, 19) : header); SVNErrorManager.error(err, SVNLogType.FSFS); } String value = header.substring(colonInd + 2); headers.put(name, value); } return headers; } }