Deploy and update script repository via signed jar bundles
This commit is contained in:
parent
d88fd57e9f
commit
6819fdc978
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package net.filebot.cli;
|
||||
|
||||
public interface ScriptProvider {
|
||||
|
||||
String getScript(String name) throws Exception;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
Loading…
Reference in New Issue