/* * ==================================================================== * Copyright (c) 2004-2008 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.server.dav; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.LinkedList; import java.util.logging.Level; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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.SVNProperties; import org.tmatesoft.svn.core.SVNRevisionProperty; import org.tmatesoft.svn.core.internal.io.fs.FSCommitter; import org.tmatesoft.svn.core.internal.io.fs.FSFS; import org.tmatesoft.svn.core.internal.io.fs.FSID; import org.tmatesoft.svn.core.internal.io.fs.FSNodeHistory; import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode; import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot; import org.tmatesoft.svn.core.internal.io.fs.FSRoot; import org.tmatesoft.svn.core.internal.io.fs.FSTransactionInfo; import org.tmatesoft.svn.core.internal.io.fs.FSTransactionRoot; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.2.0 * @author TMate Software Ltd. */ public class DAVServletUtil { public static long getSafeCreatedRevision(FSRevisionRoot root, String path) { long revision = root.getRevision(); FSFS fsfs = root.getOwner(); FSID id = null; try { FSRevisionNode node = root.getRevisionNode(path); id = node.getId(); } catch (SVNException svne) { return revision; } FSNodeHistory history = null; long historyRev = -1; try { history = root.getNodeHistory(path); history = history.getPreviousHistory(false); historyRev = history.getHistoryEntry().getRevision(); } catch (SVNException svne) { return revision; } FSRevisionRoot otherRoot = null; try { otherRoot = fsfs.createRevisionRoot(historyRev); } catch (SVNException svne) { return revision; } FSID otherID = null; try { FSRevisionNode node = otherRoot.getRevisionNode(path); otherID = node.getId(); } catch (SVNException svne) { return revision; } if (id.compareTo(otherID) == 0) { return historyRev; } return revision; } public static URI lookUpURI(String uri, HttpServletRequest request, boolean mustBeAbsolute) throws DAVException { URI parsedURI = null; try { parsedURI = new URI(uri); } catch (URISyntaxException urise) { throw new DAVException("Invalid syntax in Destination URI.", HttpServletResponse.SC_BAD_REQUEST, 0); } if (parsedURI.getScheme() == null && mustBeAbsolute) { throw new DAVException("Destination URI must be an absolute URI.", HttpServletResponse.SC_BAD_REQUEST, 0); } if (parsedURI.getQuery() != null || parsedURI.getFragment() != null) { throw new DAVException("Destination URI contains invalid components (a query or a fragment).", HttpServletResponse.SC_BAD_REQUEST, 0); } if (parsedURI.getScheme() != null || parsedURI.getPort() != -1 || mustBeAbsolute) { String scheme = request.getScheme(); if (scheme == null) { //TODO: replace this code in future scheme = "http"; } int parsedPort = parsedURI.getPort(); if (parsedURI.getPort() == -1) { parsedPort = request.getServerPort(); } if (!scheme.equals(parsedURI.getScheme()) || parsedPort != request.getServerPort()) { throw new DAVException("Destination URI refers to different scheme or port ({0}://hostname:{1})\n(want: {2}://hostname:{3})", new Object[] { parsedURI.getScheme() != null ? parsedURI.getScheme() : scheme, String.valueOf(parsedPort), scheme, String.valueOf(request.getServerPort()) }, HttpServletResponse.SC_BAD_REQUEST, 0); } } String parsedHost = parsedURI.getHost(); String serverHost = request.getServerName(); String domain = null; int domainInd = serverHost != null ? serverHost.indexOf('.') : -1; if (domainInd != -1) { domain = serverHost.substring(domainInd); } if (parsedHost != null && parsedHost.indexOf('.') == -1 && domain != null) { parsedHost += domain; } if (parsedHost != null && !parsedHost.equals(request.getServerName())) { throw new DAVException("Destination URI refers to a different server.", HttpServletResponse.SC_BAD_GATEWAY, 0); } return parsedURI; } public static void setAutoRevisionProperties(DAVResource resource) throws DAVException { if (!(resource.getType() == DAVResourceType.WORKING && resource.isAutoCheckedOut())) { throw new DAVException("Set_auto_revprops called on invalid resource.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0); } try { attachAutoRevisionProperties(resource.getTxnInfo(), resource.getResourceURI().getPath(), resource.getFSFS()); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error setting a revision property ", null); } } public static void attachAutoRevisionProperties(FSTransactionInfo txn, String path, FSFS fsfs) throws SVNException { String logMessage = "Autoversioning commit: a non-deltaV client made a change to\n" + path; SVNProperties props = new SVNProperties(); props.put(SVNRevisionProperty.LOG, logMessage); props.put(SVNRevisionProperty.AUTOVERSIONED, "*"); fsfs.changeTransactionProperties(txn.getTxnId(), props); } public static void deleteActivity(DAVResource resource, String activityID) throws DAVException { File activitiesDB = resource.getActivitiesDB(); String txnName = getTxn(activitiesDB, activityID); if (txnName == null) { throw new DAVException("could not find activity.", HttpServletResponse.SC_NOT_FOUND, 0); } FSFS fsfs = resource.getFSFS(); FSTransactionInfo txn = null; if (txnName != null) { try { txn = fsfs.openTxn(txnName); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.FS_NO_SUCH_TRANSACTION) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not open transaction.", null); } } if (txn != null) { try { FSCommitter.abortTransaction(fsfs, txn.getTxnId()); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not abort transaction.", null); } } } try { SVNFileUtil.deleteFile(DAVPathUtil.getActivityPath(activitiesDB, activityID)); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unable to remove activity.", null); } } public static void storeActivity(DAVResource resource, String txnName) throws DAVException { DAVResourceURI resourceURI = resource.getResourceURI(); String activityID = resourceURI.getActivityID(); File activitiesDB = resource.getActivitiesDB(); if (!activitiesDB.exists() && !activitiesDB.mkdirs()) { throw new DAVException("could not initialize activity db.", null, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, SVNLogType.NETWORK, Level.FINE, null, null, null, 0, null); } File finalActivityFile = DAVPathUtil.getActivityPath(activitiesDB, activityID); File tmpFile = null; try { tmpFile = SVNFileUtil.createUniqueFile(finalActivityFile.getParentFile(), finalActivityFile.getName(), "tmp", false); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Can't open activity db"); throw DAVException.convertError(err, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not open files.", null); } StringBuffer activitiesContents = new StringBuffer(); activitiesContents.append(txnName); activitiesContents.append('\n'); activitiesContents.append(activityID); activitiesContents.append('\n'); try { SVNFileUtil.writeToFile(tmpFile, activitiesContents.toString(), null); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Can't write to activity db"); try { SVNFileUtil.deleteFile(tmpFile); } catch (SVNException e) { } throw DAVException.convertError(err, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not write files.", null); } try { SVNFileUtil.rename(tmpFile, finalActivityFile); } catch (SVNException svne) { try { SVNFileUtil.deleteFile(tmpFile); } catch (SVNException e) { } throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not replace files.", null); } } public static FSTransactionInfo createActivity(DAVResource resource, FSFS fsfs) throws DAVException { SVNProperties properties = new SVNProperties(); properties.put(SVNRevisionProperty.AUTHOR, resource.getUserName()); long revision = SVNRepository.INVALID_REVISION; try { revision = fsfs.getYoungestRevision(); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not determine youngest revision", null); } FSTransactionInfo txnInfo = null; try { txnInfo = FSTransactionRoot.beginTransactionForCommit(revision, properties, fsfs); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not begin a transaction", null); } return txnInfo; } public static LinkedList processIfHeader(String value) throws DAVException { if (value == null) { return null; } StringBuffer valueBuffer = new StringBuffer(value); ListType listType = ListType.UNKNOWN; String uri = null; LinkedList ifHeaders = new LinkedList(); DAVIFHeader ifHeader = null; while (valueBuffer.length() > 0) { if (valueBuffer.charAt(0) == '<') { if (listType == ListType.NO_TAGGED || (uri = DAVServletUtil.fetchNextToken(valueBuffer, '>')) == null) { throw new DAVException("Invalid If-header: unclosed \"<\" or unexpected tagged-list production.", HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_TAGGED); } URI parsedURI = null; try { parsedURI = new URI(uri); } catch (URISyntaxException urise) { throw new DAVException("Invalid URI in tagged If-header.", HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_TAGGED); } uri = parsedURI.getPath(); uri = uri.length() > 1 && uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri; listType = ListType.TAGGED; } else if (valueBuffer.charAt(0) == '(') { if (listType == ListType.UNKNOWN) { listType = ListType.NO_TAGGED; } StringBuffer listBuffer = null; String list = null; if ((list = DAVServletUtil.fetchNextToken(valueBuffer, ')')) == null) { throw new DAVException("Invalid If-header: unclosed \"(\".", HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_UNCLOSED_PAREN); } ifHeader = new DAVIFHeader(uri); ifHeaders.addFirst(ifHeader); int condition = DAVIFState.IF_CONDITION_NORMAL; String stateToken = null; listBuffer = new StringBuffer(list); while (listBuffer.length() > 0) { if (listBuffer.charAt(0) == '<') { if ((stateToken = DAVServletUtil.fetchNextToken(listBuffer, '>')) == null) { throw new DAVException(null, HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_PARSE); } addIfState(stateToken, DAVIFStateType.IF_OPAQUE_LOCK, condition, ifHeader); condition = DAVIFState.IF_CONDITION_NORMAL; } else if (listBuffer.charAt(0) == '[') { if ((stateToken = fetchNextToken(listBuffer, ']')) == null) { throw new DAVException(null, HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_PARSE); } addIfState(stateToken, DAVIFStateType.IF_ETAG, condition, ifHeader); condition = DAVIFState.IF_CONDITION_NORMAL; } else if (listBuffer.charAt(0) == 'N') { if (listBuffer.length() > 2 && listBuffer.charAt(1) == 'o' && listBuffer.charAt(2) == 't') { if (condition != DAVIFState.IF_CONDITION_NORMAL) { throw new DAVException("Invalid \"If:\" header: Multiple \"not\" entries for the same state.", HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_MULTIPLE_NOT); } condition = DAVIFState.IF_CONDITION_NOT; } listBuffer.delete(0, 2); } else if (listBuffer.charAt(0) != ' ' && listBuffer.charAt(0) != '\t') { throw new DAVException("Invalid \"If:\" header: Unexpected character encountered ({0}, ''{1}'').", new Object[] { Integer.toHexString(listBuffer.charAt(0)), new Character(listBuffer.charAt(0)) }, HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_UNK_CHAR); } listBuffer.deleteCharAt(0); } } else if (valueBuffer.charAt(0) != ' ' && valueBuffer.charAt(0) != '\t') { throw new DAVException("Invalid \"If:\" header: Unexpected character encountered ({0}, ''{1}'').", new Object[] { Integer.toHexString(valueBuffer.charAt(0)), new Character(valueBuffer.charAt(0)) }, HttpServletResponse.SC_BAD_REQUEST, DAVErrorCode.IF_UNK_CHAR); } valueBuffer.deleteCharAt(0); } return ifHeaders; } public static FSTransactionInfo openTxn(FSFS fsfs, String txnName) throws DAVException { FSTransactionInfo txnInfo = null; try { txnInfo = fsfs.openTxn(txnName); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NO_SUCH_TRANSACTION) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The transaction specified by the activity does not exist", null); } throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "There was a problem opening the transaction specified by this activity.", null); } return txnInfo; } public static String getTxn(File activitiesDB, String activityID) { File activityFile = DAVPathUtil.getActivityPath(activitiesDB, activityID); return DAVServletUtil.readTxn(activityFile); } public static String readTxn(File activityFile) { String txnName = null; for (int i = 0; i < 10; i++) { try { txnName = SVNFileUtil.readSingleLine(activityFile); } catch (IOException e) { //ignore } } return txnName; } public static SVNNodeKind checkPath(FSRoot root, String path) throws DAVException { try { return root.checkNodeKind(path); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_DIRECTORY) { return SVNNodeKind.NONE; } throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error checking kind of path ''{0}'' in repository", new Object[] { path }); } } private static void addIfState(String stateToken, DAVIFStateType type, int condition, DAVIFHeader ifHeader) { String eTag = null; String lockToken = null; if (type == DAVIFStateType.IF_OPAQUE_LOCK) { lockToken = stateToken; } else { eTag = stateToken; } DAVIFState ifState = new DAVIFState(condition, eTag, lockToken, type); ifHeader.addIFState(ifState); } private static String fetchNextToken(StringBuffer string, char term) { String token = string.substring(1); token = token.trim(); int ind = -1; if ((ind = token.indexOf(term)) == -1) { return null; } token = token.substring(0, ind); string.delete(0, string.indexOf(token) + token.length()); return token; } private static class ListType { public static final ListType NO_TAGGED = new ListType(); public static final ListType TAGGED = new ListType(); public static final ListType UNKNOWN = new ListType(); private ListType() { } } }