Deploy and update script repository via signed jar bundles

This commit is contained in:
Reinhard Pointner 2016-03-30 03:09:46 +00:00
parent d88fd57e9f
commit 6819fdc978
9 changed files with 190 additions and 136 deletions

View File

@ -204,7 +204,7 @@ public class CachedResource<K, R> implements Resource<R> {
}
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);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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<String, Object>());
return new ScriptShell(ScriptSource.GITHUB_STABLE.getScriptProvider(null), new HashMap<String, Object>());
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,7 @@
package net.filebot.cli;
public interface ScriptProvider {
String getScript(String name) throws Exception;
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

Binary file not shown.