From 6819fdc9789c385fdcec0e86b5baf5bb5c445e6b Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Wed, 30 Mar 2016 03:09:46 +0000 Subject: [PATCH] Deploy and update script repository via signed jar bundles --- source/net/filebot/CachedResource.java | 2 +- source/net/filebot/Settings.properties | 4 +- source/net/filebot/cli/ArgumentProcessor.java | 116 +---------------- source/net/filebot/cli/GroovyPad.java | 7 +- source/net/filebot/cli/ScriptBundle.java | 53 ++++++++ source/net/filebot/cli/ScriptProvider.java | 7 + source/net/filebot/cli/ScriptShell.java | 16 +-- source/net/filebot/cli/ScriptSource.java | 121 ++++++++++++++++++ source/net/filebot/cli/bundle.cer | Bin 0 -> 747 bytes 9 files changed, 190 insertions(+), 136 deletions(-) create mode 100644 source/net/filebot/cli/ScriptBundle.java create mode 100644 source/net/filebot/cli/ScriptProvider.java create mode 100644 source/net/filebot/cli/ScriptSource.java create mode 100644 source/net/filebot/cli/bundle.cer diff --git a/source/net/filebot/CachedResource.java b/source/net/filebot/CachedResource.java index 10de0347..1777eac1 100644 --- a/source/net/filebot/CachedResource.java +++ b/source/net/filebot/CachedResource.java @@ -204,7 +204,7 @@ public class CachedResource implements Resource { } private static ByteBuffer fileNotFound(URL url, FileNotFoundException e) { - debug.warning(format("Resource not found: %s => %s", url, e.getMessage())); + debug.warning(format("Resource not found: %s", url)); return ByteBuffer.allocate(0); } diff --git a/source/net/filebot/Settings.properties b/source/net/filebot/Settings.properties index a9b8ea41..c4ffa6dd 100644 --- a/source/net/filebot/Settings.properties +++ b/source/net/filebot/Settings.properties @@ -8,8 +8,8 @@ update.url: https://app.filebot.net/update.xml donate.url: https://app.filebot.net/donate.php # base URL for resolving script resources -script.fn: https://raw.githubusercontent.com/filebot/scripts/m1/%s.groovy -script.dev: https://raw.githubusercontent.com/filebot/scripts/devel/%s.groovy +github.stable: https://raw.githubusercontent.com/filebot/scripts/m1/ +github.master: https://raw.githubusercontent.com/filebot/scripts/devel/ # native links link.mas: macappstore://itunes.apple.com/app/id905384638 diff --git a/source/net/filebot/cli/ArgumentProcessor.java b/source/net/filebot/cli/ArgumentProcessor.java index 5a4c99fc..782cd0dd 100644 --- a/source/net/filebot/cli/ArgumentProcessor.java +++ b/source/net/filebot/cli/ArgumentProcessor.java @@ -1,31 +1,21 @@ package net.filebot.cli; import static net.filebot.Logging.*; -import static net.filebot.Settings.*; import static net.filebot.util.ExceptionUtilities.*; import static net.filebot.util.FileUtilities.*; import java.io.File; -import java.io.FileNotFoundException; -import java.net.URI; -import java.net.URL; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; -import java.util.MissingResourceException; import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.script.Bindings; import javax.script.SimpleBindings; -import net.filebot.Cache; -import net.filebot.CacheType; import net.filebot.MediaTypes; import net.filebot.StandardRenameAction; import net.filebot.WebServices; -import net.filebot.cli.ScriptShell.ScriptProvider; public class ArgumentProcessor { @@ -119,109 +109,9 @@ public class ArgumentProcessor { bindings.put(ScriptShell.SHELL_ARGV_BINDING_NAME, args.getArray()); bindings.put(ScriptShell.ARGV_BINDING_NAME, args.getFiles(false)); - DefaultScriptProvider scriptProvider = new DefaultScriptProvider(); - URI script = scriptProvider.getScriptLocation(args.script); - - if (!scriptProvider.isInline(script)) { - if (scriptProvider.resolveTemplate(script) != null) { - scriptProvider.setBaseScheme(new URI(script.getScheme(), "%s", null)); - } else if (scriptProvider.isFile(script)) { - File parent = new File(script).getParentFile(); - File template = new File(parent, "%s.groovy"); - scriptProvider.setBaseScheme(template.toURI()); - } else { - File parent = new File(script.getPath()).getParentFile(); - String template = normalizePathSeparators(new File(parent, "%s.groovy").getPath()); - scriptProvider.setBaseScheme(new URI(script.getScheme(), script.getHost(), template, script.getQuery(), script.getFragment())); - } - } - - ScriptShell shell = new ScriptShell(scriptProvider, args.defines); - shell.runScript(script, bindings); - } - - public static class DefaultScriptProvider implements ScriptProvider { - - public static final String SCHEME_REMOTE_STABLE = "fn"; - public static final String SCHEME_REMOTE_DEVEL = "dev"; - public static final String SCHEME_INLINE_GROOVY = "g"; - public static final String SCHEME_LOCAL_FILE = "file"; - - public static final Pattern TEMPLATE_SCHEME = Pattern.compile("([a-z]{1,5}):(.+)"); - - public static final String NAME = "script"; - - private URI baseScheme; - - public void setBaseScheme(URI baseScheme) { - this.baseScheme = baseScheme; - } - - public String resolveTemplate(URI uri) { - try { - String template = getApplicationProperty(NAME + '.' + uri.getScheme()); - return String.format(template, uri.getSchemeSpecificPart()); - } catch (MissingResourceException e) { - return null; - } - } - - public boolean isInline(URI r) { - return SCHEME_INLINE_GROOVY.equals(r.getScheme()); - } - - public boolean isFile(URI r) { - return SCHEME_LOCAL_FILE.equals(r.getScheme()); - } - - @Override - public URI getScriptLocation(String input) throws Exception { - // e.g. dev:amc - Matcher template = TEMPLATE_SCHEME.matcher(input); - if (template.matches()) { - URI uri = new URI(template.group(1), template.group(2), null); - - // 1. fn:amc - // 2. dev:sysinfo - // 3. g:println 'hello world' - switch (uri.getScheme()) { - case SCHEME_REMOTE_STABLE: - case SCHEME_REMOTE_DEVEL: - case SCHEME_INLINE_GROOVY: - return uri; - default: - return new URL(input).toURI(); - } - } - - File file = new File(input); - if (baseScheme != null && !file.isAbsolute()) { - return new URI(baseScheme.getScheme(), String.format(baseScheme.getSchemeSpecificPart(), input), null); - } - - // e.g. /path/to/script.groovy - if (!file.isFile()) { - throw new FileNotFoundException(file.getPath()); - } - return file.getCanonicalFile().toURI(); - } - - @Override - public String fetchScript(URI uri) throws Exception { - if (isFile(uri)) { - return readTextFile(new File(uri)); - } - - if (isInline(uri)) { - return uri.getSchemeSpecificPart(); - } - - // check remote script for updates (weekly for stable and daily for devel branches) - Cache cache = Cache.getCache(NAME, CacheType.Persistent); - return cache.text(uri.toString(), s -> { - return new URL(resolveTemplate(uri)); - }).expire(SCHEME_REMOTE_DEVEL.equals(uri.getScheme()) ? Cache.ONE_DAY : Cache.ONE_WEEK).get(); - } + ScriptSource source = ScriptSource.findScriptProvider(args.script); + ScriptShell shell = new ScriptShell(source.getScriptProvider(args.script), args.defines); + shell.runScript(source.accept(args.script), bindings); } } diff --git a/source/net/filebot/cli/GroovyPad.java b/source/net/filebot/cli/GroovyPad.java index 4c64e5d4..a46d2373 100644 --- a/source/net/filebot/cli/GroovyPad.java +++ b/source/net/filebot/cli/GroovyPad.java @@ -14,7 +14,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.util.HashMap; import javax.script.Bindings; @@ -39,7 +38,6 @@ import org.fife.ui.rtextarea.RTextScrollPane; import net.filebot.ResourceManager; import net.filebot.Settings; import net.filebot.Settings.ApplicationFolder; -import net.filebot.cli.ArgumentProcessor.DefaultScriptProvider; import net.filebot.util.TeePrintStream; public class GroovyPad extends JFrame { @@ -155,10 +153,7 @@ public class GroovyPad extends JFrame { protected ScriptShell createScriptShell() { try { - DefaultScriptProvider scriptProvider = new DefaultScriptProvider(); - scriptProvider.setBaseScheme(new URI("fn", "%s", null)); - - return new ScriptShell(scriptProvider, new HashMap()); + return new ScriptShell(ScriptSource.GITHUB_STABLE.getScriptProvider(null), new HashMap()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/source/net/filebot/cli/ScriptBundle.java b/source/net/filebot/cli/ScriptBundle.java new file mode 100644 index 00000000..8157aed2 --- /dev/null +++ b/source/net/filebot/cli/ScriptBundle.java @@ -0,0 +1,53 @@ +package net.filebot.cli; + +import static java.nio.charset.StandardCharsets.*; +import static java.util.Arrays.*; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +import com.google.common.io.ByteStreams; + +public class ScriptBundle implements ScriptProvider { + + private byte[] bytes; + private Certificate certificate; + + public ScriptBundle(byte[] bytes, InputStream certificate) throws CertificateException { + this.bytes = bytes; + this.certificate = CertificateFactory.getInstance("X.509").generateCertificate(certificate); + } + + @Override + public String getScript(String name) throws Exception { + try (JarInputStream jar = new JarInputStream(new ByteArrayInputStream(bytes), true)) { + for (JarEntry f = jar.getNextJarEntry(); f != null; f = jar.getNextJarEntry()) { + if (f.isDirectory() || !f.getName().startsWith(name) || !f.getName().substring(name.length()).equals(".groovy")) + continue; + + // completely read and verify current jar entry + byte[] bytes = ByteStreams.toByteArray(jar); + jar.closeEntry(); + + // file must be signed + Certificate[] certificates = f.getCertificates(); + + if (certificates == null || stream(f.getCertificates()).noneMatch(certificate::equals)) + throw new SecurityException(String.format("BAD certificate: %s", Arrays.toString(certificates))); + + return new String(bytes, UTF_8); + } + } + + // script does not exist + throw new FileNotFoundException("Script not found: " + name); + } + +} diff --git a/source/net/filebot/cli/ScriptProvider.java b/source/net/filebot/cli/ScriptProvider.java new file mode 100644 index 00000000..e31d68ae --- /dev/null +++ b/source/net/filebot/cli/ScriptProvider.java @@ -0,0 +1,7 @@ +package net.filebot.cli; + +public interface ScriptProvider { + + String getScript(String name) throws Exception; + +} \ No newline at end of file diff --git a/source/net/filebot/cli/ScriptShell.java b/source/net/filebot/cli/ScriptShell.java index 7bdc6bb4..173f568a 100644 --- a/source/net/filebot/cli/ScriptShell.java +++ b/source/net/filebot/cli/ScriptShell.java @@ -1,6 +1,5 @@ package net.filebot.cli; -import java.net.URI; import java.util.Map; import java.util.ResourceBundle; @@ -67,19 +66,8 @@ public class ScriptShell { } } - public static interface ScriptProvider { - - public URI getScriptLocation(String input) throws Exception; - - public String fetchScript(URI uri) throws Exception; - } - - public Object runScript(String input, Bindings bindings) throws Throwable { - return runScript(scriptProvider.getScriptLocation(input), bindings); - } - - public Object runScript(URI resource, Bindings bindings) throws Throwable { - return evaluate(scriptProvider.fetchScript(resource), bindings); + public Object runScript(String name, Bindings bindings) throws Throwable { + return evaluate(scriptProvider.getScript(name), bindings); } } diff --git a/source/net/filebot/cli/ScriptSource.java b/source/net/filebot/cli/ScriptSource.java new file mode 100644 index 00000000..0d456e31 --- /dev/null +++ b/source/net/filebot/cli/ScriptSource.java @@ -0,0 +1,121 @@ +package net.filebot.cli; + +import static java.util.Arrays.*; +import static net.filebot.Settings.*; +import static net.filebot.util.FileUtilities.*; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.time.Duration; + +import net.filebot.Cache; +import net.filebot.CacheType; + +public enum ScriptSource { + + GITHUB_STABLE { + + @Override + public String accept(String input) { + return input.startsWith("fn:") ? input.substring(3) : null; + } + + @Override + public ScriptProvider getScriptProvider(String input) throws Exception { + return getScriptBundle(this, "github.stable", Cache.ONE_WEEK); + } + + }, + + GITHUB_MASTER { + + @Override + public String accept(String input) { + return input.startsWith("dev:") ? input.substring(4) : null; + } + + @Override + public ScriptProvider getScriptProvider(String input) throws Exception { + return getScriptBundle(this, "github.master", Cache.ONE_DAY); + } + + }, + + INLINE_GROOVY { + + @Override + public String accept(String input) { + return input.startsWith("g:") ? input.substring(2) : null; + } + + @Override + public ScriptProvider getScriptProvider(String input) throws Exception { + return g -> g; + } + + }, + + REMOTE_URL { + + @Override + public String accept(String input) { + try { + URI uri = new URI(input); + if (uri.isAbsolute()) { + return getName(new File(uri.getPath())); + } + } catch (Exception e) { + } + return null; + } + + @Override + public ScriptProvider getScriptProvider(String input) throws Exception { + Cache cache = Cache.getCache(name(), CacheType.Persistent); + URI parent = new URI(input).resolve("."); + + return f -> cache.text(f, s -> parent.resolve(s + ".groovy").toURL()).expire(Duration.ZERO).get(); + } + + }, + + LOCAL_FILE { + + @Override + public String accept(String input) { + try { + return getName(new File(input).getCanonicalFile()); + } catch (Exception e) { + return null; + } + } + + @Override + public ScriptProvider getScriptProvider(String input) throws Exception { + File base = new File(input).getParentFile(); + + return f -> readTextFile(new File(base, f + ".groovy")); + } + + }; + + public abstract String accept(String input); + + public abstract ScriptProvider getScriptProvider(String input) throws Exception; + + public static ScriptSource findScriptProvider(String input) throws Exception { + return stream(values()).filter(s -> s.accept(input) != null).findFirst().get(); + } + + private static ScriptProvider getScriptBundle(ScriptSource source, String branch, Duration expirationTime) throws Exception { + Cache cache = Cache.getCache(source.name(), CacheType.Persistent); + byte[] bytes = cache.bytes("bundle.jar", f -> new URL(getApplicationProperty(branch) + f)).expire(expirationTime).get(); + + return new ScriptBundle(bytes, source.getClass().getResourceAsStream("bundle.cer")); + } + +} diff --git a/source/net/filebot/cli/bundle.cer b/source/net/filebot/cli/bundle.cer new file mode 100644 index 0000000000000000000000000000000000000000..d6d09b4dea1386e523b43a2b6820c1cd8bb97759 GIT binary patch literal 747 zcmXqLVtQ`S#CU!IGZP~d6HAd7<3|HtHcqWJkGAi;jEvl@3Q*_F}%7X8x=WMT|S<_Q#|wk$T6n@}Z0Bo7PxaOFgr+=Sdm+1@yx@rk%LDGt%&+ zYFgyhP2vI9+Rjed*w&-4EJ5j7R)BrVq+(?;)-@sSYg@PLq?Wxgle%>}?8_ad%*3tS`Z#X=AvJy1+ORt@Ef3d@CMYGa^Ef2Cb`&3qU{u2Ct zx++KfXY37&V?oshF3w?cj0??A9PTRmb~-y)b&lrBg4%Mcqy=vjpNO(>`0Ib0v`NKc zt6=>1yT+Oq?oP;JJEI;@WdCb{|5+wxMh3>kiU#rqvcMRTazyA_BE%J7v1LHW-at>pI)R#!!^rY>sjN=mhO3E zG+VFq-*GMzqMRE z*4bF{@48vIS3a+2Q&q?Ps*u+IRRvG3XWjoG{^;R?12#ujc&m#~I@IY?k^f$#w;U*2?zT=%;TGKnxR~xhvTB| z=>Ug)Wr54HEl(W1_$cjhz)aSE7hNBEO$eR8x}3jtv-r0sHu0{~PrDS(D&N1gGdOIL i!Q4p2osJSrP0=TsG$tKmwPDifeZ6&Fi~3Hl-w^=bKO}Yl literal 0 HcmV?d00001