Index: src/test/java/com/xpn/xwiki/web/ExportURLFactoryTest.java
===================================================================
--- src/test/java/com/xpn/xwiki/web/ExportURLFactoryTest.java (revision 21763)
+++ src/test/java/com/xpn/xwiki/web/ExportURLFactoryTest.java (working copy)
@@ -64,6 +64,7 @@
new File(this.tmpDir, "attachment").mkdir();
this.urlFactory.init(null, this.tmpDir, getContext());
+ urlFactory.setAttachmentsPath("attachment");
}
/**
@@ -84,6 +85,30 @@
assertEquals(new URL("file://attachment/x.%20Space%20.Pa%20ge.img%20.jpg"), url);
}
+ /**
+ * Test that
+ * {@link ExportURLFactory#createAttachmentURL(String, String, String, String, String, com.xpn.xwiki.XWikiContext)}
+ * correctly uses hashcodes for filenames longer than 255 characters.
+ *
+ * @throws MalformedURLException
+ */
+ public void testCreateAttachmentURLWithHashedName() throws MalformedURLException
+ {
+ String pageName =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+ + "Etiam ullamcorper nulla vitae augue cursus ac rhoncus turpis facilisis. Etiam quis massa velit. "
+ + "Donec id ante vitae justo mollis sagittis. Proin fermentum, justo at dignissim imperdiet";
+ String fileName = "risuspuruslobortisipsum,quisaliquetnibh";
+ XWikiDocument doc = new XWikiDocument("Space", pageName);
+ XWikiAttachment attach = new XWikiAttachment(doc, fileName);
+ attach.setContent("risus purus lobortis ipsum, quis aliquet nibh".getBytes());
+ doc.getAttachmentList().add(attach);
+ this.mockXWiki.stubs().method("getDocument").will(returnValue(doc));
+
+ URL url = this.urlFactory.createAttachmentURL(fileName, "Space", pageName, "view", "", "x", getContext());
+ assertEquals(new URL("file://attachment/" + ("x.Space." + pageName).hashCode() + "." + fileName), url);
+ }
+
/** When the test is over, delete the folder where the exported attachments were placed. */
@Override
protected void tearDown() throws Exception
Index: src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java
===================================================================
--- src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java (revision 21763)
+++ src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java (working copy)
@@ -11,6 +11,7 @@
import java.util.zip.ZipOutputStream;
import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
@@ -19,10 +20,11 @@
import org.xwiki.context.ExecutionContextException;
import org.xwiki.context.ExecutionContextManager;
import org.xwiki.velocity.VelocityManager;
+
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.util.Util;
-import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.web.ExportURLFactory;
import com.xpn.xwiki.web.Utils;
@@ -257,22 +259,24 @@
ZipOutputStream zos = new ZipOutputStream(context.getResponse().getOutputStream());
+ // Create custom URL factory
+ ExportURLFactory urlf = new ExportURLFactory();
+ urlf.setAttachmentsPath("attachment");
+ urlf.setSkinsPath("skins");
+
File dir = context.getWiki().getTempDirectory(context);
File tempdir = new File(dir, RandomStringUtils.randomAlphanumeric(8));
tempdir.mkdirs();
- File attachmentDir = new File(tempdir, "attachment");
+ File attachmentDir = new File(tempdir, urlf.getAttachmentsPath());
attachmentDir.mkdirs();
- // Create custom URL factory
- ExportURLFactory urlf = new ExportURLFactory();
-
// Render pages to export
renderDocuments(zos, tempdir, urlf, context);
// Add required skins to ZIP file
for (Iterator it = urlf.getNeededSkins().iterator(); it.hasNext();) {
String skinName = (String) it.next();
- addSkinToZip(skinName, zos, context);
+ addSkinToZip(skinName, urlf.getSkinsPath(), zos, context);
}
// Add resources files to ZIP file
@@ -320,19 +324,25 @@
}
/**
- * Add skin to the package in sub-directory "skins".
+ * Add skin to the package in sub-directory indicated by the {@code skinsPath} parameter.
*
* @param skinName the name of the skin.
+ * @param skinsPath the directory where the skin files / folders should be added. If this is {@code null} or empty,
+ * they are to be added to the root of the zip.
* @param out the ZIP output stream where to put the skin.
* @param context the XWiki context.
* @throws IOException error when adding the skin to package.
*/
- private static void addSkinToZip(String skinName, ZipOutputStream out, XWikiContext context)
+ private static void addSkinToZip(String skinName, String skinsPath, ZipOutputStream out, XWikiContext context)
throws IOException
{
File file =
new File(context.getWiki().getEngineContext().getRealPath("/skins/" + skinName));
- addDirToZip(file, out, "skins" + ZIPPATH_SEPARATOR + skinName + ZIPPATH_SEPARATOR);
+ String path = skinName + ZIPPATH_SEPARATOR;
+ if (!StringUtils.isEmpty(skinsPath)) {
+ path = skinsPath + ZIPPATH_SEPARATOR + path;
+ }
+ addDirToZip(file, out, path);
}
/**
Index: src/main/java/com/xpn/xwiki/web/ExportURLFactory.java
===================================================================
--- src/main/java/com/xpn/xwiki/web/ExportURLFactory.java (revision 21763)
+++ src/main/java/com/xpn/xwiki/web/ExportURLFactory.java (working copy)
@@ -11,6 +11,8 @@
import java.util.Iterator;
import java.util.Set;
+import org.apache.commons.lang.StringUtils;
+
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiAttachment;
@@ -26,6 +28,13 @@
public class ExportURLFactory extends XWikiServletURLFactory
{
/**
+ * Limit for the exported files length: if a filename longer than this limit is to be created, the wiki, space and
+ * page components of it will be hashed. We need this to workaround the issues that some filesystems seem to have
+ * with these lengths.
+ */
+ protected final int FILE_LENGTH_LIMIT = 255;
+
+ /**
* Pages for which to convert URL to local.
*/
protected Set exportedPages = new HashSet();
@@ -36,6 +45,18 @@
protected File exportDir;
/**
+ * Directory, relative to the export dir, where attachments are to be stored. If this is {@code null} or empty, the
+ * export dir will be used.
+ */
+ protected String attachmentsPath;
+
+ /**
+ * Directory, relative to the the export dir, where skin files are to be stored and referenced from. If this is
+ * {@code null} or empty, the export dir will be used.
+ */
+ protected String skinsPath;
+
+ /**
* Names of skins needed by rendered page(s).
*/
private Set neededSkins = new HashSet();
@@ -56,6 +77,46 @@
}
/**
+ * @return the attachmentsPath
+ */
+ public String getAttachmentsPath()
+ {
+ return attachmentsPath;
+ }
+
+ /**
+ * @param attachmentsPath the attachmentsPath to set
+ */
+ public void setAttachmentsPath(String attachmentsPath)
+ {
+ this.attachmentsPath = attachmentsPath;
+ }
+
+ /**
+ * @return the skinsPath
+ */
+ public String getSkinsPath()
+ {
+ return skinsPath;
+ }
+
+ /**
+ * @param skinsPath the skinsPath to set
+ */
+ public void setSkinsPath(String skinsPath)
+ {
+ this.skinsPath = skinsPath;
+ }
+
+ /**
+ * @return the exportDir
+ */
+ public File getExportDir()
+ {
+ return exportDir;
+ }
+
+ /**
* Init the url factory.
*
* @param exportedPages the pages that will be exported.
@@ -99,10 +160,11 @@
}
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiServletURLFactory#createSkinURL(java.lang.String, java.lang.String,
- * com.xpn.xwiki.XWikiContext)
+ * com.xpn.xwiki.XWikiContext)
*/
@Override
public URL createSkinURL(String filename, String skin, XWikiContext context)
@@ -114,7 +176,9 @@
newpath.append("file://");
- newpath.append("skins/");
+ if (!StringUtils.isEmpty(skinsPath)) {
+ newpath.append(skinsPath + "/");
+ }
newpath.append(skin);
addFileName(newpath, filename, false, context);
@@ -127,10 +191,11 @@
return super.createSkinURL(filename, skin, context);
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiServletURLFactory#createSkinURL(java.lang.String, java.lang.String, java.lang.String,
- * java.lang.String, com.xpn.xwiki.XWikiContext)
+ * java.lang.String, com.xpn.xwiki.XWikiContext)
*/
@Override
public URL createSkinURL(String filename, String web, String name, String xwikidb, XWikiContext context)
@@ -145,8 +210,9 @@
StringBuffer newpath = new StringBuffer();
newpath.append("file://");
-
- newpath.append("skins/");
+ if (!StringUtils.isEmpty(skinsPath)) {
+ newpath.append(skinsPath + "/");
+ }
newpath.append(name);
addFileName(newpath, filename, false, context);
@@ -159,10 +225,11 @@
return super.createSkinURL(filename, web, name, xwikidb, context);
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiServletURLFactory#createURL(java.lang.String, java.lang.String, java.lang.String,
- * java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
+ * java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
*/
@Override
public URL createURL(String web, String name, String action, String querystring, String anchor, String xwikidb,
@@ -174,23 +241,25 @@
if (this.exportedPages.contains(wikiname + XWikiDocument.DB_SPACE_SEP + web + XWikiDocument.SPACE_NAME_SEP
+ name)
&& "view".equals(action) && context.getLinksAction() == null) {
+ // point to the already exported file
StringBuffer newpath = new StringBuffer();
newpath.append("file://");
+ // FIXME: this is HTML specific, move in overriden helper function when paths to exported pages will
+ // be needed by other exporters.
newpath.append(wikiname);
newpath.append(".");
newpath.append(web);
newpath.append(".");
newpath.append(name);
+ newpath.append(".html");
if ((anchor != null) && (!anchor.equals(""))) {
newpath.append("#");
newpath.append(anchor);
}
- newpath.append(".html");
-
return new URL(newpath.toString());
}
} catch (Exception e) {
@@ -201,31 +270,49 @@
}
/**
- * Generate an url targeting attachment in provided wiki page.
+ * Builds an attachment URL for the passed file throwing exceptions if any error occurs, allowing all methods in the
+ * {@link XWikiURLFactory} to delegate to it and fall back on default in case of exceptions. To be overridden by
+ * subclasses to modify the default behavior.
*
- * @param filename the name of the attachment.
- * @param space the space of the page containing the attachment.
- * @param name the name of the page containing the attachment.
- * @param xwikidb the wiki of the page containing the attachment.
- * @param context the XWiki context.
- * @return the generated url.
- * @throws XWikiException error when retrieving document attachment.
- * @throws IOException error when retrieving document attachment.
- * @throws URISyntaxException when retrieving document attachment.
+ * @param filename the attachment name to export
+ * @param web the space of the document holding the attachment
+ * @param name the name of the document holding the attachment
+ * @param revision the revision of the attachment
+ * @param xwikidb the database of the document
+ * @param context the xwiki context
+ * @return the URL to the passed attachment
+ * @throws XWikiException if something went wrong getting the file
+ * @throws IOException if something went wrong saving the file on the disk in the temporary export directory
+ * @throws URISyntaxException if something went wrong while generating the URIs
*/
- private URL createAttachmentURL(String filename, String space, String name, String xwikidb, XWikiContext context)
- throws XWikiException, IOException, URISyntaxException
+ protected URL getAttachmentURL(String filename, String web, String name, String xwikidb, String revision,
+ XWikiContext context) throws XWikiException, IOException, URISyntaxException
{
String db = (xwikidb == null ? context.getDatabase() : xwikidb);
- String path = "attachment/" + db + "." + space + "." + name + "." + filename;
+ String path = "";
+ if (!StringUtils.isEmpty(attachmentsPath)) {
+ path += attachmentsPath + "/";
+ }
+ // build the page component in the file path
+ String pagePathComponent = db + "." + web + "." + name;
+ if (pagePathComponent.length() + filename.length() > FILE_LENGTH_LIMIT) {
+ // hash it in the path because too long names create problems under some filesystems, it seems
+ path += pagePathComponent.hashCode() + "." + filename;
+ } else {
+ path += pagePathComponent + "." + filename;
+ }
File tempdir = this.exportDir;
File file = new File(tempdir, path);
if (!file.exists()) {
XWikiDocument doc =
context.getWiki().getDocument(
- db + XWikiDocument.DB_SPACE_SEP + space + XWikiDocument.SPACE_NAME_SEP + name, context);
+ db + XWikiDocument.DB_SPACE_SEP + web + XWikiDocument.SPACE_NAME_SEP + name, context);
XWikiAttachment attachment = doc.getAttachment(filename);
+ // if a revision is provided, get the specific revision
+ if (!StringUtils.isEmpty(revision)) {
+ attachment = attachment.getAttachmentRevision(revision, context);
+ }
byte[] data = attachment.getContent(context);
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
@@ -235,42 +322,45 @@
return new URI("file://" + path.replace(" ", "%20")).toURL();
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiServletURLFactory#createAttachmentURL(java.lang.String, java.lang.String,
- * java.lang.String, java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
+ * java.lang.String, java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
*/
@Override
public URL createAttachmentURL(String filename, String web, String name, String action, String querystring,
String xwikidb, XWikiContext context)
{
try {
- return createAttachmentURL(filename, web, name, xwikidb, context);
+ return getAttachmentURL(filename, web, name, xwikidb, null, context);
} catch (Exception e) {
e.printStackTrace();
- return super.createAttachmentURL(filename, web, name, action, null, xwikidb, context);
+ return super.createAttachmentURL(filename, web, name, action, querystring, xwikidb, context);
}
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiDefaultURLFactory#createAttachmentRevisionURL(java.lang.String, java.lang.String,
- * java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
+ * java.lang.String, java.lang.String, java.lang.String, com.xpn.xwiki.XWikiContext)
*/
@Override
public URL createAttachmentRevisionURL(String filename, String web, String name, String revision, String xwikidb,
XWikiContext context)
{
try {
- return createAttachmentURL(filename, web, name, xwikidb, context);
+ return getAttachmentURL(filename, web, name, xwikidb, revision, context);
} catch (Exception e) {
e.printStackTrace();
return super.createAttachmentRevisionURL(filename, web, name, revision, xwikidb, context);
}
}
- /*
- * (non-Javadoc)
+ /**
+ * {@inheritDoc}
+ *
* @see com.xpn.xwiki.web.XWikiServletURLFactory#getURL(java.net.URL, com.xpn.xwiki.XWikiContext)
*/
@Override
Index: src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java
===================================================================
--- src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java (revision 21763)
+++ src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java (working copy)
@@ -20,73 +20,33 @@
*/
package com.xpn.xwiki.pdf.impl;
-import com.xpn.xwiki.XWikiContext;
-import com.xpn.xwiki.doc.XWikiAttachment;
-import com.xpn.xwiki.doc.XWikiDocument;
-import com.xpn.xwiki.util.Util;
-import com.xpn.xwiki.web.XWikiServletURLFactory;
-
-import java.io.File;
-import java.io.FileOutputStream;
import java.net.URL;
-public class PdfURLFactory extends XWikiServletURLFactory
-{
- public PdfURLFactory()
- {
- }
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.web.ExportURLFactory;
- public URL createAttachmentURL(String filename, String web, String name, String action, String querystring,
- String xwikidb, XWikiContext context)
- {
- try {
- File tempdir = (File) context.get("pdfexportdir");
- File file = new File(tempdir, web + "." + name + "." + filename);
- if (!file.exists()) {
- XWikiDocument doc = null;
- doc = context.getWiki().getDocument(web + "." + name, context);
- XWikiAttachment attachment = doc.getAttachment(filename);
- byte[] data = new byte[0];
- data = attachment.getContent(context);
- FileOutputStream fos = new FileOutputStream(file);
- fos.write(data);
- fos.close();
- }
- return file.toURL();
- } catch (Exception e) {
- e.printStackTrace();
- return super.createAttachmentURL(filename, web, name, action, null, xwikidb, context);
- }
- }
-
- public URL createAttachmentRevisionURL(String filename, String web, String name, String revision, String xwikidb,
+/**
+ * Custom URL factory to be used for PDF export, extending the {@link ExportURLFactory}, so that it copies attachments
+ * in temporary folder so that they are accessible to Apache Fop, regardless of the access rights to the pages where the
+ * attachments are from.
+ * FIXME: this will also copy and generate relative URLs for the links to attached files, even if we need this copy only
+ * to make images accessible.
+ * FIXME: because of the {@link ExportURLFactory} inheritance, this will also handle skin URLs and make them relative to
+ * the export directory, although the pdf export doesn't need special handling of skins. This way, the skin links become
+ * invalid in the rendered html for the pdf.
+ *
+ * @version $Id$
+ */
+public class PdfURLFactory extends ExportURLFactory
+{
+ /**
+ * {@inheritDoc}. Override to prevent any relative page URL to be generated, all links to wiki pages should be http
+ * URLs.
+ */
+ @Override
+ public URL createURL(String web, String name, String action, String querystring, String anchor, String xwikidb,
XWikiContext context)
{
- try {
- File tempdir = (File) context.get("pdfexportdir");
- File file = new File(tempdir, web + "." + name + "." + filename);
- if (!file.exists()) {
- XWikiDocument doc = null;
- doc = context.getWiki().getDocument(web + "." + name, context);
- XWikiAttachment attachment = doc.getAttachment(filename).getAttachmentRevision(revision, context);
- byte[] data = new byte[0];
- data = attachment.getContent(context);
- FileOutputStream fos = new FileOutputStream(file);
- fos.write(data);
- fos.close();
- }
- return file.toURL();
- } catch (Exception e) {
- e.printStackTrace();
- return super.createAttachmentRevisionURL(filename, web, name, revision, xwikidb, context);
- }
+ return super.createURL(web, name, action, querystring, anchor, xwikidb, context);
}
-
- public String getURL(URL url, XWikiContext context)
- {
- if (url == null) {
- return "";
- }
- return Util.escapeURL(url.toString());
- }
}
Index: src/main/java/com/xpn/xwiki/pdf/impl/PdfExportImpl.java
===================================================================
--- src/main/java/com/xpn/xwiki/pdf/impl/PdfExportImpl.java (revision 21763)
+++ src/main/java/com/xpn/xwiki/pdf/impl/PdfExportImpl.java (working copy)
@@ -35,6 +35,8 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
@@ -76,8 +78,10 @@
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.pdf.api.PdfExport;
import com.xpn.xwiki.util.Util;
+import com.xpn.xwiki.web.ExportURLFactory;
import com.xpn.xwiki.web.Utils;
import com.xpn.xwiki.web.XWikiRequest;
+import com.xpn.xwiki.web.XWikiURLFactory;
public class PdfExportImpl implements PdfExport
{
@@ -182,16 +186,24 @@
log.debug("XSL-FO source: " + xmlfo);
}
- exportXMLFO(xmlfo, out, type);
+ exportXMLFO(xmlfo, out, type, context);
}
- public void exportXMLFO(String xmlfo, OutputStream out, int type) throws XWikiException
+ public void exportXMLFO(String xmlfo, OutputStream out, int type, XWikiContext context) throws XWikiException
{
// XSL Transformation to XML-FO
try {
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// configure foUserAgent as desired
+ // set the base url of fop to the export dir, to use relative, safe URIs in the pdf, as returned by the
+ // ExportURLFactory
+ XWikiURLFactory pdfURLFactory = context.getURLFactory();
+ // set the export dir if the url factory is an export url factory
+ if (pdfURLFactory instanceof ExportURLFactory) {
+ File exportDir = ((ExportURLFactory) pdfURLFactory).getExportDir();
+ foUserAgent.setBaseURL(exportDir.toURL().toString());
+ }
// Construct fop with desired output format
Fop fop =
@@ -247,13 +259,21 @@
// This could be improved by setting a specific context using the passed document but we
// would also need to get the translations and set them too.
+ // to use for the exportURLfactory, although it's not needed by the pdf URL factory
+ List exportedPages = new ArrayList();
+ exportedPages.add(context.getDoc().getFullName());
+
File dir = context.getWiki().getTempDirectory(context);
File tempdir = new File(dir, RandomStringUtils.randomAlphanumeric(8));
this.tidy.setOutputEncoding(context.getWiki().getEncoding());
this.tidy.setInputEncoding(context.getWiki().getEncoding());
try {
tempdir.mkdirs();
- context.put("pdfexportdir", tempdir);
+ XWikiURLFactory pdfURLFactory = context.getURLFactory();
+ // set the export dir if the url factory is an export url factory
+ if (pdfURLFactory instanceof ExportURLFactory) {
+ ((ExportURLFactory) pdfURLFactory).init(exportedPages, tempdir, context);
+ }
boolean useLocalPlaceholders = !Utils.arePlaceholdersEnabled(context);
if (useLocalPlaceholders) {
Utils.enablePlaceholders(context);
@@ -265,12 +285,39 @@
}
exportHtml(content, out, type, context);
} finally {
- File[] filelist = tempdir.listFiles();
- for (int i = 0; i < filelist.length; i++) {
- filelist[i].delete();
+ deleteDirectory(tempdir);
+ }
+ }
+
+ /**
+ * Deletes a directory with all its contents.
+ *
+ * @param directory the directory to delete.
+ */
+ private static void deleteDirectory(File directory)
+ {
+ if (!directory.isDirectory()) {
+ return;
+ }
+
+ File[] files = directory.listFiles();
+
+ if (files == null) {
+ return;
+ }
+
+ for (int i = 0; i < files.length; ++i) {
+ File file = files[i];
+
+ if (file.isDirectory()) {
+ deleteDirectory(file);
+ continue;
}
- tempdir.delete();
+
+ file.delete();
}
+
+ directory.delete();
}
public String convertToStrictXHtml(String input)
@@ -462,7 +509,7 @@
content = Util.getFileContent(new File(inputfile));
// XML-FO2 PDF
FileOutputStream fos = new FileOutputStream(new File(outputfile));
- pdf.exportXMLFO(content, fos, PdfExportImpl.PDF);
+ pdf.exportXMLFO(content, fos, PdfExportImpl.PDF, context);
fos.close();
} else if (param.equals("-html2pdf")) {
inputfile = argv[1];