/* ESXX - The friendly ECMAscript/XML Application Server Copyright (C) 2007-2015 Martin Blom <martin@blom.org> 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.esxx.js.protocol; import java.io.File; import java.io.FileOutputStream; import java.net.URISyntaxException; import java.util.HashMap; import java.util.regex.Pattern; import javax.mail.internet.ContentType; import org.esxx.*; import org.esxx.js.*; import org.esxx.util.StringUtil; import org.esxx.util.XML; import org.mozilla.javascript.*; import org.w3c.dom.Document; import org.w3c.dom.Element; public class FILEHandler extends URLHandler { public FILEHandler(JSURI jsuri) throws URISyntaxException { super(jsuri); // Make sure we're not accessing compromised paths if (uriSlashPattern.matcher(jsuri.getURI().toString()).matches()) { throw new URISyntaxException(jsuri.getURI().toString(), "Encoded path separators are not allowed in ESXX file URIs"); } } @Override public Object load(Context cx, Scriptable thisObj, ContentType recv_ct) throws Exception { File file = new File(jsuri.getURI()); if (recv_ct == null) { recv_ct = new ContentType(ESXX.fileTypeMap.getContentType(file)); } if ((recv_ct.match("text/xml") || recv_ct.match("application/xml")) && file.exists() && file.isDirectory()) { recv_ct = new ContentType("application/vnd.esxx.directory+xml"); } if (recv_ct.match("application/vnd.esxx.directory+xml")) { Document result = createDirectoryListing(file); return ESXX.domToE4X(result, cx, thisObj); } return super.load(cx, thisObj, recv_ct); } @Override public Object save(Context cx, Scriptable thisObj, Object data, ContentType send_ct, ContentType recv_ct) throws Exception { recv_ct = ensureRecvTypeIsXML(recv_ct); File file = new File(jsuri.getURI()); send_ct = ESXX.getInstance().serializeObject(data, send_ct, new FileOutputStream(file), true); return ESXX.domToE4X(createDirectoryEntry(file), cx, thisObj); } @Override public Object append(Context cx, Scriptable thisObj, Object data, ContentType send_ct, ContentType recv_ct) throws Exception { recv_ct = ensureRecvTypeIsXML(recv_ct); File file = new File(jsuri.getURI()); if (file.isDirectory()) { file = File.createTempFile("esxx", null, file); } send_ct = ESXX.getInstance().serializeObject(data, send_ct, new FileOutputStream(file, true), true); return ESXX.domToE4X(createDirectoryEntry(file), cx, thisObj); } // @Override // public Object query(Context cx, Scriptable thisObj, Object[] args) // throws Exception { // return createDirectoryListing(new File(jsuri.getURI())); // } @Override public Object remove(Context cx, Scriptable thisObj, ContentType recv_ct) throws Exception { if (recv_ct != null) { throw Context.reportRuntimeError("Receive Content-Type cannot be specified"); } File file = new File(jsuri.getURI()); return new Boolean(file.delete()); } public static Document createDirectoryListing(File dir) { ESXX esxx = ESXX.getInstance(); Document document = esxx.createDocument("directory"); Element root = document.getDocumentElement(); root.setAttributeNS(null, "uri", dir.toURI().toString()); for (File f : dir.listFiles()) { root.appendChild(createDirectoryEntry(document, f)); } return document; } public static Document createDirectoryEntry(File f) { ESXX esxx = ESXX.getInstance(); Document document = esxx.createDocument("tmp"); document.replaceChild(createDirectoryEntry(document, f), document.getDocumentElement()); return document; } public static Element createDirectoryEntry(Document document, File f) { Element element; if (f.isDirectory()) { element = document.createElementNS(null, "directory"); } else if (f.isFile()) { element = document.createElementNS(null, "file"); XML.addChild(element, "length", Long.toString(f.length())); } else { element = document.createElementNS(null, "object"); } element.setAttributeNS(null, "uri", f.toURI().toString()); XML.addChild(element, "name", f.getName()); // XML.addChild(element, "path", f.getPath()); XML.addChild(element, "hidden", f.isHidden() ? "true" : "false"); XML.addChild(element, "lastModified", Long.toString(f.lastModified())); XML.addChild(element, "id", Integer.toHexString(f.hashCode())); XML.addChild(element, "type", ESXX.fileTypeMap.getContentType(f)); return element; } /** Return a Pattern that can be used to find illegal URI encoding * sequences in file URIs. * * @return A Pattern matching illegal URIs. */ private static Pattern getURISlashPattern() { String fileSeparators = System.getProperty("file.separator"); // Normal forward slashes are always disallowed. String slashPattern = "(%2[fF])"; // Disallow all characters in "file.separator" for (int i = 0; i < fileSeparators.length(); ++i) { try { String c = fileSeparators.substring(i, i + 1); String enc = StringUtil.encodeURI(c, false); slashPattern = slashPattern + "|(" + enc.toLowerCase() + ")" + "|(" + enc.toUpperCase() + ")"; } catch (URISyntaxException ex) { // Should never happen ex.printStackTrace(); } } return Pattern.compile(".*(" + slashPattern + ").*"); } private static final Pattern uriSlashPattern = getURISlashPattern(); }