/* * ==================================================================== * 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.io.dav.handlers; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.text.ParseException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.io.dav.DAVElement; import org.tmatesoft.svn.core.internal.io.dav.http.HTTPStatus; import org.tmatesoft.svn.core.internal.util.SVNBase64; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNXMLUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNLogType; import org.xml.sax.Attributes; /** * @author TMate Software Ltd. * @version 1.3 */ public class DAVProppatchHandler extends BasicDAVHandler { private static final Collection NAMESPACES = new LinkedList(); static { NAMESPACES.add(DAVElement.DAV_NAMESPACE); NAMESPACES.add(DAVElement.SVN_DAV_PROPERTY_NAMESPACE); NAMESPACES.add(DAVElement.SVN_SVN_PROPERTY_NAMESPACE); NAMESPACES.add(DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE); } public static StringBuffer generatePropertyRequest(StringBuffer buffer, String name, SVNPropertyValue value) { SVNProperties props = new SVNProperties(); props.put(name, value); return generatePropertyRequest(buffer, props); } public static StringBuffer generatePropertyRequest(StringBuffer buffer, String name, byte[] value) { SVNProperties props = new SVNProperties(); props.put(name, value); return generatePropertyRequest(buffer, props); } public static StringBuffer generatePropertyRequest(StringBuffer xmlBuffer, SVNProperties properties) { xmlBuffer = xmlBuffer == null ? new StringBuffer() : xmlBuffer; SVNXMLUtil.addXMLHeader(xmlBuffer); SVNXMLUtil.openNamespaceDeclarationTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "propertyupdate", NAMESPACES, SVNXMLUtil.PREFIX_MAP, xmlBuffer); // if there are non-null values if (hasNotNullValues(properties)) { SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "set", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "prop", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer); for (Iterator names = properties.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNPropertyValue value = properties.getSVNPropertyValue(name); if (value != null) { xmlBuffer = appendProperty(xmlBuffer, name, value); } } SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "prop", xmlBuffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "set", xmlBuffer); } // if there are null values if (hasNullValues(properties)) { SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "remove", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "prop", SVNXMLUtil.XML_STYLE_NORMAL, null, xmlBuffer); for (Iterator names = properties.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNPropertyValue value = properties.getSVNPropertyValue(name); if (value == null) { xmlBuffer = appendProperty(xmlBuffer, name, value); } } SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "prop", xmlBuffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "remove", xmlBuffer); } SVNXMLUtil.addXMLFooter(SVNXMLUtil.DAV_NAMESPACE_PREFIX, "propertyupdate", xmlBuffer); return xmlBuffer; } private static StringBuffer appendProperty(StringBuffer xmlBuffer, String name, SVNPropertyValue value) { String prefix = SVNProperty.isSVNProperty(name) && !SVNProperty.isSVNKitProperty(name) ? SVNXMLUtil.SVN_SVN_PROPERTY_PREFIX : SVNXMLUtil.SVN_CUSTOM_PROPERTY_PREFIX; String tagName = SVNProperty.shortPropertyName(name); if (value == null){ return SVNXMLUtil.openXMLTag(prefix, tagName, SVNXMLUtil.XML_STYLE_SELF_CLOSING, null, xmlBuffer); } Map attrs = null; String stringValue = value.getString(); boolean isXMLSafe = true; if (value.isBinary()) { CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPORT); decoder.onMalformedInput(CodingErrorAction.REPORT); try { stringValue = decoder.decode(ByteBuffer.wrap(value.getBytes())).toString(); } catch (CharacterCodingException e) { isXMLSafe = false; } } if (stringValue != null) { isXMLSafe = SVNEncodingUtil.isXMLSafe(stringValue); } if (!isXMLSafe) { attrs = new SVNHashMap(); String attrPrefix = (String) SVNXMLUtil.PREFIX_MAP.get(DAVElement.SVN_DAV_PROPERTY_NAMESPACE); attrs.put(attrPrefix + ":encoding", "base64"); byte[] toDecode = null; if (stringValue != null) { try { toDecode = stringValue.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { toDecode = stringValue.getBytes(); } } else { toDecode = value.getBytes(); } stringValue = SVNBase64.byteArrayToBase64(toDecode); } return SVNXMLUtil.openCDataTag(prefix, tagName, stringValue, attrs, xmlBuffer); } private static boolean hasNullValues(SVNProperties props) { if (props.isEmpty()) { return false; } return props.containsValue(null); } private static boolean hasNotNullValues(SVNProperties props) { if (props.isEmpty()) { return false; } if (!hasNullValues(props)) { return true; } for (Iterator entries = props.nameSet().iterator(); entries.hasNext();) { String propName = (String) entries.next(); if (props.getSVNPropertyValue(propName) != null) { return true; } } return false; } //fields for multistatus response handling private StringBuffer myPropertyName; private StringBuffer myPropstatDescription; private StringBuffer myDescription; private boolean myPropstatContainsError; private boolean myResponseContainsError; private SVNErrorMessage myError; public DAVProppatchHandler() { init(); } public SVNErrorMessage getError(){ return myError; } private StringBuffer getPropertyName() { if (myPropertyName == null){ myPropertyName = new StringBuffer(); } return myPropertyName; } private StringBuffer getPropstatDescription() { if (myPropstatDescription == null){ myPropstatDescription = new StringBuffer(); } return myPropstatDescription; } private StringBuffer getDescription() { if (myDescription == null){ myDescription = new StringBuffer(); } return myDescription; } protected void startElement(DAVElement parent, DAVElement element, Attributes attrs) throws SVNException { if (parent == DAVElement.PROP) { getPropertyName().setLength(0); if (DAVElement.SVN_DAV_PROPERTY_NAMESPACE.equals(element.getNamespace())) { getPropertyName().append(SVNProperty.SVN_PREFIX); } else if (DAVElement.DAV_NAMESPACE.equals(element.getNamespace())) { getPropertyName().append(DAVElement.DAV_NAMESPACE); } getPropertyName().append(element.getName()); } else if (element == DAVElement.PROPSTAT) { myPropstatContainsError = false; } } protected void endElement(DAVElement parent, DAVElement element, StringBuffer cdata) throws SVNException { if (element == DAVElement.MULTISTATUS) { if (myResponseContainsError) { String description = null; if (getDescription().length() == 0) { description = "The request response contained at least one error"; } else { description = getDescription().toString(); } myError = SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, description); } } else if (element == DAVElement.RESPONSE_DESCRIPTION) { if (parent == DAVElement.PROPSTAT) { getPropstatDescription().append(cdata); } else { if (getDescription().length() != 0) { getDescription().append('\n'); } getDescription().append(cdata); } } else if (element == DAVElement.STATUS) { try { HTTPStatus status = HTTPStatus.createHTTPStatus(cdata.toString()); if (parent != DAVElement.PROPSTAT) { myResponseContainsError |= status.getCodeClass() != 2; } else { myPropstatContainsError = status.getCodeClass() != 2; } } catch (ParseException e) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.RA_DAV_REQUEST_FAILED, "The response contains a non-conforming HTTP status line"), SVNLogType.NETWORK); } } else if (element == DAVElement.PROPSTAT) { myResponseContainsError |= myPropstatContainsError; getDescription().append("Error setting property "); getDescription().append(getPropertyName()); getDescription().append(":"); getDescription().append(getPropstatDescription()); } } }