/*
* ====================================================================
* 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;
}
}