Fixed AR-1321: Allow '&' in filename

The following filename is valid now: "x ¬ £ % & + ; x.txt"
This was a restriction imposed by WebDAV, but the encoding of the repsonses is working well and these restrictions be removed as a result.

Fixed AR-1281: WebDAV upload was assigning incorrect encoding

I added a bean 'charset.finder', which can be fetched from the MimetypeService.
Various pluggins now exist to decode a stream and figure out what the encoding is.
WebDAV and CIFS/FTP are now hooked into this so that they guess a little better.

Fixed others:
Added retrying transactions to WebDAV.
Read/write transactions for WebDAV.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6073 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-06-22 21:27:17 +00:00
parent 838e14cc85
commit 54d7208f7b
14 changed files with 346 additions and 27 deletions

View File

@@ -94,9 +94,12 @@
</bean> </bean>
<bean id="mimetypeService" class="org.alfresco.repo.content.MimetypeMap" init-method="init" > <bean id="mimetypeService" class="org.alfresco.repo.content.MimetypeMap" init-method="init" >
<constructor-arg> <property name="configService">
<ref bean="mimetypeConfigService" /> <ref bean="mimetypeConfigService" />
</constructor-arg> </property>
<property name="contentCharsetFinder">
<ref bean="charset.finder"/>
</property>
</bean> </bean>
<bean id="contentFilterLanguagesConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init"> <bean id="contentFilterLanguagesConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init">

View File

@@ -144,6 +144,21 @@
</property> </property>
</bean> </bean>
<!-- Characterset decoder -->
<bean id="charset.finder" class="org.alfresco.repo.content.encoding.ContentCharsetFinder">
<property name="defaultCharset">
<value>UTF-8</value>
</property>
<property name="mimetypeService">
<ref bean="mimetypeService"/>
</property>
<property name="charactersetFinders">
<list>
<bean class="org.alfresco.encoding.GuessEncodingCharsetFinder" />
</list>
</property>
</bean>
<!-- transaction service --> <!-- transaction service -->
<alias name="transactionService" alias="transactionComponent"/> <alias name="transactionService" alias="transactionComponent"/>
<bean id="transactionService" class="org.alfresco.repo.transaction.TransactionServiceImpl"> <bean id="transactionService" class="org.alfresco.repo.transaction.TransactionServiceImpl">

View File

@@ -2,7 +2,7 @@
<config evaluator="string-compare" condition="Mimetype Map"> <config evaluator="string-compare" condition="Mimetype Map">
<mimetypes> <mimetypes>
<mimetype mimetype="text/plain" display="Plain Text"> <mimetype mimetype="text/plain" text="true" display="Plain Text">
<extension display="Plain Text" default="true">txt</extension> <extension display="Plain Text" default="true">txt</extension>
<extension display="Comma Separated Values">csv</extension> <extension display="Comma Separated Values">csv</extension>
<extension display="Java Source">java</extension> <extension display="Java Source">java</extension>
@@ -15,14 +15,14 @@
<extension display="Unix Shell Script">sh</extension> <extension display="Unix Shell Script">sh</extension>
<extension display="Log">log</extension> <extension display="Log">log</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/html" display="HTML"> <mimetype mimetype="text/html" text="true" display="HTML">
<!-- first extension will be default unless otherwise specified --> <!-- first extension will be default unless otherwise specified -->
<extension default="true">html</extension> <extension default="true">html</extension>
<extension>htm</extension> <extension>htm</extension>
<extension>shtml</extension> <extension>shtml</extension>
<extension>body</extension> <extension>body</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/xhtml+xml" display="XHTML"> <mimetype mimetype="application/xhtml+xml" text="true" display="XHTML">
<extension default="true">xhtml</extension> <extension default="true">xhtml</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/postscript" display="Postscript"> <mimetype mimetype="application/postscript" display="Postscript">
@@ -77,7 +77,7 @@
<mimetype mimetype="image/cgm" display="CGM Image"> <mimetype mimetype="image/cgm" display="CGM Image">
<extension>cgm</extension> <extension>cgm</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/java" display="Java Class"> <mimetype mimetype="application/java" text="true" display="Java Class">
<extension>class</extension> <extension>class</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-cpio" display=""> <mimetype mimetype="application/x-cpio" display="">
@@ -86,7 +86,7 @@
<mimetype mimetype="application/x-csh" display=""> <mimetype mimetype="application/x-csh" display="">
<extension>csh</extension> <extension>csh</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/css" display="Style Sheet"> <mimetype mimetype="text/css" text="true" display="Style Sheet">
<extension>css</extension> <extension>css</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/msword" display="Microsoft Word"> <mimetype mimetype="application/msword" display="Microsoft Word">
@@ -95,7 +95,7 @@
<mimetype mimetype="application/wordperfect" display="WordPerfect"> <mimetype mimetype="application/wordperfect" display="WordPerfect">
<extension>wpd</extension> <extension>wpd</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/xml" display="XML"> <mimetype mimetype="text/xml" text="true" display="XML">
<extension default="true">xml</extension> <extension default="true">xml</extension>
<extension display="DTD">dtd</extension> <extension display="DTD">dtd</extension>
<extension display="XSLT">xslt</extension> <extension display="XSLT">xslt</extension>
@@ -104,7 +104,7 @@
<mimetype mimetype="application/x-dvi" display=""> <mimetype mimetype="application/x-dvi" display="">
<extension>dvi</extension> <extension>dvi</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/x-setext" display=""> <mimetype mimetype="text/x-setext" text="true" display="">
<extension>etx</extension> <extension>etx</extension>
</mimetype> </mimetype>
<mimetype mimetype="image/gif" display="GIF Image"> <mimetype mimetype="image/gif" display="GIF Image">
@@ -125,7 +125,7 @@
<mimetype mimetype="application/mac-binhex40" display=""> <mimetype mimetype="application/mac-binhex40" display="">
<extension>hqx</extension> <extension>hqx</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/calendar" display="iCalendar File"> <mimetype mimetype="text/calendar" text="true" display="iCalendar File">
<extension>ics</extension> <extension>ics</extension>
</mimetype> </mimetype>
<mimetype mimetype="image/ief" display="IEF Image"> <mimetype mimetype="image/ief" display="IEF Image">
@@ -142,10 +142,10 @@
<mimetype mimetype="image/svg" display="Scalable Vector Graphics Image"> <mimetype mimetype="image/svg" display="Scalable Vector Graphics Image">
<extension>svg</extension> <extension>svg</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-javascript" display="Java Script"> <mimetype mimetype="application/x-javascript" text="true" display="Java Script">
<extension>js</extension> <extension>js</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-latex" display="Latex"> <mimetype mimetype="application/x-latex" text="true" display="Latex">
<extension>latex</extension> <extension>latex</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-troff-man" display="Man Page"> <mimetype mimetype="application/x-troff-man" display="Man Page">
@@ -229,10 +229,10 @@
<extension>sgml</extension> <extension>sgml</extension>
<extension>sgm</extension> <extension>sgm</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-sh" display="Shell Script"> <mimetype mimetype="application/x-sh" text="true" display="Shell Script">
<extension>sh</extension> <extension>sh</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-shar" display=""> <mimetype mimetype="application/x-shar" text="true" display="">
<extension>shar</extension> <extension>shar</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-wais-source" display=""> <mimetype mimetype="application/x-wais-source" display="">
@@ -264,7 +264,7 @@
<extension>tiff</extension> <extension>tiff</extension>
<extension>tif</extension> <extension>tif</extension>
</mimetype> </mimetype>
<mimetype mimetype="text/tab-separated-values" display="Tab Separated Values"> <mimetype mimetype="text/tab-separated-values" text="true" display="Tab Separated Values">
<extension>tsv</extension> <extension>tsv</extension>
</mimetype> </mimetype>
<mimetype mimetype="application/x-ustar" display=""> <mimetype mimetype="application/x-ustar" display="">

View File

@@ -16,7 +16,7 @@
<constraints> <constraints>
<constraint name="cm:filename" type="REGEX"> <constraint name="cm:filename" type="REGEX">
<parameter name="expression"><value><![CDATA[(.*[\"\*\\\>\<\?\/\:\|\xA3\xAC\%\&\+\;]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)]]></value></parameter> <parameter name="expression"><value><![CDATA[(.*[\"\*\\\>\<\?\/\:\|]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)]]></value></parameter>
<parameter name="requiresMatch"><value>false</value></parameter> <parameter name="requiresMatch"><value>false</value></parameter>
</constraint> </constraint>
<constraint name="cm:userNameConstraint" type="org.alfresco.repo.dictionary.constraint.UserNameConstraint" /> <constraint name="cm:userNameConstraint" type="org.alfresco.repo.dictionary.constraint.UserNameConstraint" />

View File

@@ -58,6 +58,7 @@
<property name="searchService"><ref bean="SearchService" /></property> <property name="searchService"><ref bean="SearchService" /></property>
<property name="namespaceService"><ref bean="namespaceService" /></property> <property name="namespaceService"><ref bean="namespaceService" /></property>
<property name="contentService"><ref bean="ContentService" /></property> <property name="contentService"><ref bean="ContentService" /></property>
<property name="mimetypeService"><ref bean="MimetypeService" /></property>
<property name="permissionService"><ref bean="permissionService"/></property> <property name="permissionService"><ref bean="permissionService"/></property>
<property name="authenticationComponent"><ref bean="authenticationComponent"/></property> <property name="authenticationComponent"><ref bean="authenticationComponent"/></property>
<property name="authenticationService"><ref bean="authenticationService"/></property> <property name="authenticationService"><ref bean="authenticationService"/></property>

View File

@@ -68,6 +68,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.service.cmr.lock.NodeLockedException; import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
@@ -111,6 +112,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
private NodeService nodeService; private NodeService nodeService;
private SearchService searchService; private SearchService searchService;
private ContentService contentService; private ContentService contentService;
private MimetypeService mimetypeService;
private PermissionService permissionService; private PermissionService permissionService;
private FileFolderService fileFolderService; private FileFolderService fileFolderService;
@@ -280,6 +282,14 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
fileFolderService = fileService; fileFolderService = fileService;
} }
/**
* @param mimetypeService service for helping with mimetypes and encoding
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
}
/** /**
* Parse and validate the parameter string and create a device context object for this instance * Parse and validate the parameter string and create a device context object for this instance
* of the shared device. The same DeviceInterface implementation may be used for multiple * of the shared device. The same DeviceInterface implementation may be used for multiple
@@ -1240,7 +1250,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
{ {
// Create the network file // Create the network file
netFile = ContentNetworkFile.createFile(transactionService, nodeService, contentService, cifsHelper, nodeRef, params); netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params);
} }
else else
{ {
@@ -1406,7 +1416,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// Create the network file // Create the network file
NetworkFile netFile = ContentNetworkFile.createFile(transactionService, nodeService, contentService, cifsHelper, nodeRef, params); NetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params);
// Truncate the file so that the content stream is created // Truncate the file so that the content stream is created

View File

@@ -24,10 +24,14 @@
*/ */
package org.alfresco.filesys.smb.server.repo; package org.alfresco.filesys.smb.server.repo;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.server.filesys.AccessDeniedException; import org.alfresco.filesys.server.filesys.AccessDeniedException;
@@ -38,15 +42,16 @@ import org.alfresco.filesys.server.filesys.NetworkFile;
import org.alfresco.filesys.smb.SeekType; import org.alfresco.filesys.smb.SeekType;
import org.alfresco.i18n.I18NUtil; import org.alfresco.i18n.I18NUtil;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentAccessor;
import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -62,9 +67,9 @@ public class ContentNetworkFile extends NodeRefNetworkFile
{ {
private static final Log logger = LogFactory.getLog(ContentNetworkFile.class); private static final Log logger = LogFactory.getLog(ContentNetworkFile.class);
private TransactionService transactionService;
private NodeService nodeService; private NodeService nodeService;
private ContentService contentService; private ContentService contentService;
private MimetypeService mimetypeService;
// File channel to file content // File channel to file content
@@ -86,9 +91,9 @@ public class ContentNetworkFile extends NodeRefNetworkFile
* Helper method to create a {@link NetworkFile network file} given a node reference. * Helper method to create a {@link NetworkFile network file} given a node reference.
*/ */
public static ContentNetworkFile createFile( public static ContentNetworkFile createFile(
TransactionService transactionService,
NodeService nodeService, NodeService nodeService,
ContentService contentService, ContentService contentService,
MimetypeService mimetypeService,
CifsHelper cifsHelper, CifsHelper cifsHelper,
NodeRef nodeRef, NodeRef nodeRef,
FileOpenParams params) FileOpenParams params)
@@ -100,7 +105,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile
// Create the file // Create the file
ContentNetworkFile netFile = new ContentNetworkFile(transactionService, nodeService, contentService, nodeRef, path); ContentNetworkFile netFile = new ContentNetworkFile(nodeService, contentService, mimetypeService, nodeRef, path);
// Set relevant parameters // Set relevant parameters
@@ -176,17 +181,17 @@ public class ContentNetworkFile extends NodeRefNetworkFile
* @param name String * @param name String
*/ */
private ContentNetworkFile( private ContentNetworkFile(
TransactionService transactionService,
NodeService nodeService, NodeService nodeService,
ContentService contentService, ContentService contentService,
MimetypeService mimetypeService,
NodeRef nodeRef, NodeRef nodeRef,
String name) String name)
{ {
super(name, nodeRef); super(name, nodeRef);
setFullName(name); setFullName(name);
this.transactionService = transactionService;
this.nodeService = nodeService; this.nodeService = nodeService;
this.contentService = contentService; this.contentService = contentService;
this.mimetypeService = mimetypeService;
} }
/** /**
@@ -362,6 +367,13 @@ public class ContentNetworkFile extends NodeRefNetworkFile
if (modified) if (modified)
{ {
// Take a guess at the mimetype
channel.position(0);
InputStream is = new BufferedInputStream(Channels.newInputStream(channel));
ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder();
Charset charset = charsetFinder.getCharset(is, content.getMimetype());
content.setEncoding(charset.name());
// Close the channel // Close the channel
channel.close(); channel.close();

View File

@@ -25,6 +25,7 @@
package org.alfresco.repo.content; package org.alfresco.repo.content;
import org.alfresco.repo.content.cleanup.ContentStoreCleanerTest; import org.alfresco.repo.content.cleanup.ContentStoreCleanerTest;
import org.alfresco.repo.content.encoding.CharsetFinderTest;
import org.alfresco.repo.content.filestore.FileContentStoreTest; import org.alfresco.repo.content.filestore.FileContentStoreTest;
import org.alfresco.repo.content.filestore.NoRandomAccessFileContentStoreTest; import org.alfresco.repo.content.filestore.NoRandomAccessFileContentStoreTest;
import org.alfresco.repo.content.filestore.ReadOnlyFileContentStoreTest; import org.alfresco.repo.content.filestore.ReadOnlyFileContentStoreTest;
@@ -63,6 +64,7 @@ public class ContentTestSuite extends TestSuite
TestSuite suite = new TestSuite(); TestSuite suite = new TestSuite();
suite.addTestSuite(ContentStoreCleanerTest.class); suite.addTestSuite(ContentStoreCleanerTest.class);
suite.addTestSuite(CharsetFinderTest.class);
suite.addTestSuite(FileContentStoreTest.class); suite.addTestSuite(FileContentStoreTest.class);
suite.addTestSuite(NoRandomAccessFileContentStoreTest.class); suite.addTestSuite(NoRandomAccessFileContentStoreTest.class);
suite.addTestSuite(ReadOnlyFileContentStoreTest.class); suite.addTestSuite(ReadOnlyFileContentStoreTest.class);

View File

@@ -27,15 +27,19 @@ package org.alfresco.repo.content;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.alfresco.config.Config; import org.alfresco.config.Config;
import org.alfresco.config.ConfigElement; import org.alfresco.config.ConfigElement;
import org.alfresco.config.ConfigLookupContext; import org.alfresco.config.ConfigLookupContext;
import org.alfresco.config.ConfigService; import org.alfresco.config.ConfigService;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -48,6 +52,7 @@ import org.apache.commons.logging.LogFactory;
*/ */
public class MimetypeMap implements MimetypeService public class MimetypeMap implements MimetypeService
{ {
public static final String PREFIX_TEXT = "text/";
public static final String EXTENSION_BINARY = "bin"; public static final String EXTENSION_BINARY = "bin";
public static final String MIMETYPE_TEXT_PLAIN = "text/plain"; public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
@@ -118,35 +123,76 @@ public class MimetypeMap implements MimetypeService
private static final String ATTR_MIMETYPE = "mimetype"; private static final String ATTR_MIMETYPE = "mimetype";
private static final String ATTR_DISPLAY = "display"; private static final String ATTR_DISPLAY = "display";
private static final String ATTR_DEFAULT = "default"; private static final String ATTR_DEFAULT = "default";
private static final String ATTR_TEXT = "text";
private static final Log logger = LogFactory.getLog(MimetypeMap.class); private static final Log logger = LogFactory.getLog(MimetypeMap.class);
private ConfigService configService; private ConfigService configService;
private ContentCharsetFinder contentCharsetFinder;
private List<String> mimetypes; private List<String> mimetypes;
private Map<String, String> extensionsByMimetype; private Map<String, String> extensionsByMimetype;
private Map<String, String> mimetypesByExtension; private Map<String, String> mimetypesByExtension;
private Map<String, String> displaysByMimetype; private Map<String, String> displaysByMimetype;
private Map<String, String> displaysByExtension; private Map<String, String> displaysByExtension;
private Set<String> textMimetypes;
/** /**
* @param configService the config service to use to read mimetypes from * Default constructor
*
* @since 2.1
*/ */
public MimetypeMap()
{
}
@Deprecated
public MimetypeMap(ConfigService configService) public MimetypeMap(ConfigService configService)
{ {
logger.warn(
"MimetypeMap(ConfigService configService) has been deprecated. " +
"Use the default constructor and property 'configService'");
this.configService = configService; this.configService = configService;
} }
/**
* @param configService the config service to use to read mimetypes from
*/
public void setConfigService(ConfigService configService)
{
this.configService = configService;
}
/**
* {@inheritDoc}
*/
public ContentCharsetFinder getContentCharsetFinder()
{
return contentCharsetFinder;
}
/**
* Set the system default content characterset decoder
*/
public void setContentCharsetFinder(ContentCharsetFinder contentCharsetFinder)
{
this.contentCharsetFinder = contentCharsetFinder;
}
/** /**
* Initialises the map using the configuration service provided * Initialises the map using the configuration service provided
*/ */
public void init() public void init()
{ {
PropertyCheck.mandatory(this, "configService", configService);
PropertyCheck.mandatory(this, "contentCharsetFinder", contentCharsetFinder);
this.mimetypes = new ArrayList<String>(40); this.mimetypes = new ArrayList<String>(40);
this.extensionsByMimetype = new HashMap<String, String>(59); this.extensionsByMimetype = new HashMap<String, String>(59);
this.mimetypesByExtension = new HashMap<String, String>(59); this.mimetypesByExtension = new HashMap<String, String>(59);
this.displaysByMimetype = new HashMap<String, String>(59); this.displaysByMimetype = new HashMap<String, String>(59);
this.displaysByExtension = new HashMap<String, String>(59); this.displaysByExtension = new HashMap<String, String>(59);
this.textMimetypes = new HashSet<String>(23);
Config config = configService.getConfig(CONFIG_CONDITION, new ConfigLookupContext(CONFIG_AREA)); Config config = configService.getConfig(CONFIG_CONDITION, new ConfigLookupContext(CONFIG_AREA));
ConfigElement mimetypesElement = config.getConfigElement(ELEMENT_MIMETYPES); ConfigElement mimetypesElement = config.getConfigElement(ELEMENT_MIMETYPES);
@@ -176,6 +222,14 @@ public class MimetypeMap implements MimetypeService
this.displaysByMimetype.put(mimetype, mimetypeDisplay); this.displaysByMimetype.put(mimetype, mimetypeDisplay);
} }
// Check if it is a text format
String isTextStr = mimetypeElement.getAttribute(ATTR_TEXT);
boolean isText = Boolean.parseBoolean(isTextStr);
if (isText || mimetype.startsWith(PREFIX_TEXT))
{
this.textMimetypes.add(mimetype);
}
// get all the extensions // get all the extensions
boolean isFirst = true; boolean isFirst = true;
List<ConfigElement> extensions = mimetypeElement.getChildren(); List<ConfigElement> extensions = mimetypeElement.getChildren();
@@ -209,6 +263,7 @@ public class MimetypeMap implements MimetypeService
{ {
this.extensionsByMimetype.put(mimetype, extension); this.extensionsByMimetype.put(mimetype, extension);
} }
// Loop again
isFirst = false; isFirst = false;
} }
// check that there were extensions defined // check that there were extensions defined
@@ -274,6 +329,11 @@ public class MimetypeMap implements MimetypeService
return mimetypesByExtension; return mimetypesByExtension;
} }
public boolean isText(String mimetype)
{
return textMimetypes.contains(mimetype);
}
/** /**
* @see #MIMETYPE_BINARY * @see #MIMETYPE_BINARY
*/ */

View File

@@ -75,4 +75,15 @@ public class MimetypeMapTest extends TestCase
// Star Office // Star Office
assertEquals("sds", extensionsByMimetype.get("application/vnd.stardivision.chart")); assertEquals("sds", extensionsByMimetype.get("application/vnd.stardivision.chart"));
} }
public void testIsText() throws Exception
{
assertTrue(mimetypeService.isText(MimetypeMap.MIMETYPE_HTML));
}
public void testGetContentCharsetFinder() throws Exception
{
assertNotNull("No charset finder", mimetypeService.getContentCharsetFinder());
}
} }

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* 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.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.content.encoding;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import junit.framework.TestCase;
import org.alfresco.encoding.CharactersetFinder;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* @see CharsetFinderTest
* @see CharactersetFinder
*
* @author Derek Hulley
*/
public class CharsetFinderTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private ContentCharsetFinder charsetFinder;
@Override
public void setUp() throws Exception
{
charsetFinder = (ContentCharsetFinder) ctx.getBean("charset.finder");
}
public void testPlainText() throws Exception
{
File file = AbstractContentTransformerTest.loadQuickTestFile("txt");
InputStream is = new BufferedInputStream(new FileInputStream(file));
Charset charset = charsetFinder.getCharset(is, MimetypeMap.MIMETYPE_TEXT_PLAIN);
assertNotNull(charset);
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* 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.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.content.encoding;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.List;
import org.alfresco.encoding.CharactersetFinder;
import org.alfresco.service.cmr.repository.MimetypeService;
/**
* Utility bean to guess the charset given a stream and a mimetype.
*
* @since 2.1
* @author Derek Hulley
*/
public class ContentCharsetFinder
{
private Charset defaultCharset = Charset.defaultCharset();
private MimetypeService mimetypeService;
private List<CharactersetFinder> charactersetFinders;
/**
* Override the system default charset. Where the characterset cannot be determined for
* a mimetype and input stream, this mimetype will be used. The default is 'UTF-8'.
*
* @param defaultCharset the default characterset
*/
public void setDefaultCharset(String defaultCharset)
{
this.defaultCharset = Charset.forName(defaultCharset);
}
/**
* Set the mimetype service that will help determine if a particular mimetype can be
* treated as encoded text or not.
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
}
/**
* Set the list of characterset finder to execute, in order, for text based content.
* @param charactersetFinders a list of finders
*/
public void setCharactersetFinders(List<CharactersetFinder> charactersetFinders)
{
this.charactersetFinders = charactersetFinders;
}
/**
* Gets the characterset from the stream, if the mimetype is text and the text
* has enough information to give the encoding away. Otherwise, the default
* is returned.
*
* @param is a stream that will not be affected by the call, but must
* support marking
* @param mimetype the mimetype of the stream data
* @return returns a characterset and never <tt>null</tt>
*/
public Charset getCharset(InputStream is, String mimetype)
{
// Is it text?
if (!mimetypeService.isText(mimetype))
{
return defaultCharset;
}
// Try the finders
Charset charset = null;
for (CharactersetFinder finder : charactersetFinders)
{
charset = finder.detectCharset(is);
if (charset != null)
{
break;
}
}
// Done
if (charset == null)
{
return defaultCharset;
}
else
{
return charset;
}
}
}

View File

@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.service.NotAuditable; import org.alfresco.service.NotAuditable;
import org.alfresco.service.PublicService; import org.alfresco.service.PublicService;
@@ -83,6 +84,15 @@ public interface MimetypeService
@NotAuditable @NotAuditable
public Map<String, String> getMimetypesByExtension(); public Map<String, String> getMimetypesByExtension();
/**
* Check if a given mimetype represents a text format.
*
* @param mimetype the mimetype to check
* @return Returns <tt>true</tt> if it is text
*/
@NotAuditable
public boolean isText(String mimetype);
/** /**
* Get all mimetypes * Get all mimetypes
* *
@@ -101,4 +111,15 @@ public interface MimetypeService
*/ */
@NotAuditable @NotAuditable
public String guessMimetype(String filename); public String guessMimetype(String filename);
/**
* Provides the system default charset finder.
*
* @return Returns a character set finder that can be used to decode
* streams in order to get the encoding.
*
* @since 2.1
*/
@NotAuditable
public ContentCharsetFinder getContentCharsetFinder();
} }

View File

@@ -1 +1,7 @@
The quick brown fox jumps over the lazy dog The quick brown fox jumps over the lazy dog
Le renard brun rapide saute par-dessus le chien paresseux
Der schnelle braune Fuchs springt über den faulen Hund
براون وكس السريع يقفز فوق الكلب كسالي