Compare commits

..

20 Commits

Author SHA1 Message Date
b8fd687ec2 Merge branch 'master' into ssh_agent_support 2020-12-17 02:51:24 -06:00
4e1156d4bc when testing servers, don't propagate exceptions but do note them 2020-12-17 02:50:28 -06:00
e4ea50ac66 remove unused imports 2020-12-17 02:47:10 -06:00
33491e3904 if WebDAV request returns error code, throw exception 2020-12-17 02:46:01 -06:00
80cc767c25 Merge branch 'master' into ssh_agent_support 2020-12-13 22:17:55 -06:00
be5b64789a whitespace 2020-12-13 22:17:42 -06:00
b894978f92 readme: expand on currently supported server types 2020-12-13 22:17:01 -06:00
ef252f5a19 ok, NOW add WebDavServer... 2020-12-13 22:06:59 -06:00
21a7b6e870 Merge branch 'master' into ssh_agent_support 2020-12-13 22:04:46 -06:00
6ed87b738f lib: Add WebDav server type, make this the default server type (since it's relatively simple, standardized, and requires the least configuration). 2020-12-13 22:03:18 -06:00
c0cb1b4f2e lib: add GoFile server type. Note this isn't as useful as a direct file upload because GoFile forces clients to visit a page before accessing the file, meaning e.g. the file won't embed in a chat service.
Also included is "ByteSourceUploadable" which wraps a Guava ByteSource as a jodd-http Uploadable, so that we can implement multipart-upload services as appropriate.
2020-12-13 21:04:03 -06:00
212996521a Merge branch 'master' into ssh_agent_support 2020-12-13 07:36:50 -06:00
0ffe462710 desktop: add --test command to test all defined servers 2020-12-13 07:32:54 -06:00
cf920c57c6 lib: add FtpServer type 2020-12-13 06:26:04 -06:00
77cf4f5820 desktop/piecannon: capture output of mvn command so we can display it to the user, capture error code so we can pass it upstream
(cherry picked from commit 2f7761bac7)
2020-12-13 06:20:57 -06:00
0d29b03dce remove unused imports 2020-12-13 04:18:51 -06:00
b004320ffe app: downgrade jsch back to old unmaintaned version for compatibility with jsch-agent-proxy 2020-11-05 04:05:27 -06:00
da0eceacea lib: make password optional for SftpServer 2020-11-05 04:05:18 -06:00
2f7761bac7 desktop/piecannon: capture output of mvn command so we can display it to the user, capture error code so we can pass it upstream 2020-11-05 03:56:08 -06:00
a84a307eb5 WIP: ssh-agent support using jsch-agent-proxy 2020-11-05 03:35:31 -06:00
18 changed files with 405 additions and 23 deletions

View File

@@ -28,4 +28,16 @@ TODO
## How to use ## How to use
Place a `servers.json` in the Pie Cannon data directory. For GNU/Linux this is `XDG_DATA_HOME` (by default this would be `~/.local/share/piecannon`) (create this directory if it does not exist. For Android this would be the app's "externalFilesDir" (probably something like `Android/data/net.monarchpass.piecannon/files` - the app will tell you when you launch it). See `servers.example.json` for an example of such a file. Whenever you initiate a file upload, Pie Cannon will select one of your defined servers at random and upload the file. Place a `servers.json` in the Pie Cannon data directory. For GNU/Linux this is `XDG_DATA_HOME` (by default this would be `~/.local/share/piecannon`) (create this directory if it does not exist. For Android this would be the app's "externalFilesDir" (probably something like `Android/data/net.monarchpass.piecannon/files` - the app will tell you when you launch it). See `servers.example.json` for an example of such a file. Whenever you initiate a file upload, Pie Cannon will select one of your defined servers at random and upload the file.
For SFTP servers (the only supported type as of now), the `path` is relative to your home directory and should be where `url` points to. i.e. a file uploaded to `path` should be downloadable at `url`. In the future Pie Cannon should be able to get SSH credentials from your agent so you won't need to put a password in this file. #### Server Types
The `type` field in the server configuration tells Pie Cannon what type of server to use. The default value is `webdav`.
##### WebDAV (`webdav`)
For WebDAV servers, `url` is where the file is uploaded to and served from. `username` and `password`, if given, are HTTP Basic authentication credentials.
For setting this up with nginx, see [ngx_http_dav_module](https://nginx.org/en/docs/http/ngx_http_dav_module.html) documentation and also [HTTP Basic Authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) guide.
##### FTP/SFTP (`ftp`/`sftp`)
For FTP/SFTP servers, the `path` is relative to your home directory and should be where `url` points to. i.e. a file uploaded to `path` should be downloadable at `url`. In the future Pie Cannon should be able to get SSH credentials from your agent so you won't need to put a password in this file.
##### GoFile.io (`gofile`)
No configuration options are supported for this currently, however in the future there may be support for using a token. Note that it's not possible to get a direct download link for this service without visiting a page first, so Pie Cannon returns the URL for that page instead of a direct download link.

View File

@@ -6,7 +6,6 @@
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch/0.1.55/jsch-0.1.55.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch/0.1.55/jsch-0.1.55.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-util/6.0.0/jodd-util-6.0.0.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-util/6.0.0/jodd-util-6.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/android/android/4.1.1.4/android-4.1.1.4.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/android/android/4.1.1.4/android-4.1.1.4.jar" enabled="true" runInBatchMode="false"/>

View File

@@ -1,6 +1,9 @@
<factorypath> <factorypath>
<factorypathentry kind="VARJAR" id="M2_REPO/net/monarchpass/libpiecannon/0.0.1-SNAPSHOT/libpiecannon-0.0.1-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/net/monarchpass/libpiecannon/0.0.1-SNAPSHOT/libpiecannon-0.0.1-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/github/mwiede/jsch/0.1.60/jsch-0.1.60.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/github/mwiede/jsch/0.1.60/jsch-0.1.60.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-http/6.0.3/jodd-http-6.0.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-util/6.0.0/jodd-util-6.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/guava/30.0-jre/guava-30.0-jre.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/guava/30.0-jre/guava-30.0-jre.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" enabled="true" runInBatchMode="false"/>
@@ -9,6 +12,10 @@
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/kothar/xdg-java/0.1.1/xdg-java-0.1.1.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/net/kothar/xdg-java/0.1.1/xdg-java-0.1.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" enabled="true" runInBatchMode="false"/> <<<<<<< HEAD
<factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch.agentproxy.jsch/0.0.9/jsch.agentproxy.jsch-0.0.9.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch.agentproxy.core/0.0.9/jsch.agentproxy.core-0.0.9.jar" enabled="true" runInBatchMode="false"/>
=======
>>>>>>> master
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/>
</factorypath> </factorypath>

View File

@@ -8,5 +8,13 @@ STUB_POM="<project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://w
STUB_POM_FILE=/tmp/pc.xml STUB_POM_FILE=/tmp/pc.xml
echo $STUB_POM > $STUB_POM_FILE echo $STUB_POM > $STUB_POM_FILE
RESULT=$(mvn -f $STUB_POM_FILE -B exec:java -Dexec.mainClass=$PC_CLASS -Dexec.arguments=$1 -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN | tail -n 1) OUTPUT=$(mvn -f $STUB_POM_FILE -B exec:java -Dexec.mainClass=$PC_CLASS -Dexec.arguments=$1 -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN)
EXEC_RESULT=$?
echo "$OUTPUT"
if [ $EXEC_RESULT != 0 ]; then
exit $EXEC_RESULT;
fi
RESULT=$(echo "$OUTPUT" | tail -n 1)
xdg-open $RESULT xdg-open $RESULT

View File

@@ -16,6 +16,17 @@
<groupId>net.monarchpass</groupId> <groupId>net.monarchpass</groupId>
<artifactId>libpiecannon</artifactId> <artifactId>libpiecannon</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
@@ -27,6 +38,22 @@
<artifactId>xdg-java</artifactId> <artifactId>xdg-java</artifactId>
<version>0.1.1</version> <version>0.1.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch.agentproxy.jsch</artifactId>
<version>0.0.9</version>
<exclusions>
<exclusion>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch.agentproxy.connector-factory</artifactId>
<version>0.0.9</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@@ -4,8 +4,6 @@ import java.net.URI;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import org.freedesktop.BaseDirectory; import org.freedesktop.BaseDirectory;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@@ -13,11 +11,19 @@ import java.util.logging.Level;
import net.monarchpass.piecannon.util.ServerFactory; import net.monarchpass.piecannon.util.ServerFactory;
import com.jcraft.jsch.IdentityRepository;
import com.jcraft.jsch.agentproxy.Connector;
import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
import com.jcraft.jsch.agentproxy.ConnectorFactory;
@Log @Log
public class App { public class App {
public static void main (final String... args) throws Exception { public static void main (final String... args) throws Exception {
final File serversJson = getServersJson(); final File serversJson = getServersJson();
final PieCannon cannon = new PieCannon(); final PieCannon cannon = new PieCannon();
cannon.getServerFactory().setIdentityRepository(createIdentityRepository());
final List<Server> servers = cannon.loadServersFrom(serversJson); final List<Server> servers = cannon.loadServersFrom(serversJson);
log.log(Level.INFO, "{0} servers loaded from {1}", new Object[] { log.log(Level.INFO, "{0} servers loaded from {1}", new Object[] {
servers.size(), serversJson servers.size(), serversJson
@@ -28,15 +34,21 @@ public class App {
System.exit(1); System.exit(1);
} }
final Server server = cannon.selectServer();
log.log(Level.INFO, "Randomly selected server: {0}", server.getLabel());
if (args.length == 0) { if (args.length == 0) {
log.log(Level.SEVERE, "No filename provided"); log.log(Level.SEVERE, "No filename provided");
System.exit(1); System.exit(1);
} }
final File source = new File(args[0]); final Server server = cannon.selectServer();
log.log(Level.INFO, "Randomly selected server: {0}", server.getLabel());
final String arg = args[0];
if (arg.equals("--test")) {
testServers(servers);
return;
}
final File source = new File(arg);
if (!source.exists()) { if (!source.exists()) {
log.log(Level.SEVERE, "No such file: {0}", source); log.log(Level.SEVERE, "No such file: {0}", source);
System.exit(1); System.exit(1);
@@ -46,6 +58,23 @@ public class App {
System.out.println(result); System.out.println(result);
} }
public static void testServers (final List<Server> servers) {
log.log(Level.INFO, "Testing all defined servers");
for (final Server server : servers) {
Object result;
try {
result = PieCannon.testServer(server);
}
catch (final Exception exception) {
result = exception;
}
log.log(Level.INFO, "{0} {1}: {2}", new Object[] {
server.getClass().getSimpleName(), server.getLabel(), result
});
}
}
public static File getServersJson () { public static File getServersJson () {
return new File(getDataDirectory(), "servers.json"); return new File(getDataDirectory(), "servers.json");
} }
@@ -53,4 +82,20 @@ public class App {
public static File getDataDirectory () { public static File getDataDirectory () {
return new File(BaseDirectory.get(BaseDirectory.XDG_DATA_HOME), "piecannon"); return new File(BaseDirectory.get(BaseDirectory.XDG_DATA_HOME), "piecannon");
} }
private static IdentityRepository createIdentityRepository () {
try {
final ConnectorFactory connectorFactory = ConnectorFactory.getDefault();
final Connector connector = connectorFactory.createConnector();
if (connector != null) {
return new RemoteIdentityRepository(connector);
}
}
catch (final AgentProxyException exception) {
log.log(Level.WARNING, "Failed to create agent identity repository", exception);
}
return null;
}
} }

View File

@@ -7,5 +7,8 @@
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-http/6.0.3/jodd-http-6.0.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-util/6.0.0/jodd-util-6.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/>
</factorypath> </factorypath>

View File

@@ -27,6 +27,11 @@
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.8.6</version> <version>2.8.6</version>
</dependency> </dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-http</artifactId>
<version>6.0.3</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@@ -10,24 +10,24 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Random; import java.util.Random;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.CharSource; import com.google.common.io.CharSource;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import java.util.stream.Collectors;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.monarchpass.piecannon.util.ServerFactory; import net.monarchpass.piecannon.util.ServerFactory;
import lombok.Setter;
import lombok.Getter;
public class PieCannon { public class PieCannon {
public List<Server> servers; public List<Server> servers;
public @Getter @Setter ServerFactory serverFactory = new ServerFactory();
public List<Server> loadServersFrom (final File serversJson) throws IOException { public List<Server> loadServersFrom (final File serversJson) throws IOException {
try (final InputStream in = new FileInputStream(serversJson)) { try (final InputStream in = new FileInputStream(serversJson)) {
@@ -36,7 +36,6 @@ public class PieCannon {
} }
public List<Server> loadServersFrom (final InputStream in) throws IOException { public List<Server> loadServersFrom (final InputStream in) throws IOException {
final ServerFactory factory = new ServerFactory();
final JsonParser parser = new JsonParser(); final JsonParser parser = new JsonParser();
final List<Server> loadedServers = new ArrayList<>(); final List<Server> loadedServers = new ArrayList<>();
for (final JsonElement element : parser.parse(new InputStreamReader(in)).getAsJsonArray()) { for (final JsonElement element : parser.parse(new InputStreamReader(in)).getAsJsonArray()) {
@@ -44,7 +43,7 @@ public class PieCannon {
continue; continue;
} }
loadedServers.add(factory.apply((JsonObject)element)); loadedServers.add(serverFactory.apply((JsonObject)element));
} }
servers = loadedServers; servers = loadedServers;
return loadedServers; return loadedServers;

View File

@@ -0,0 +1,45 @@
package net.monarchpass.piecannon.impl;
import net.monarchpass.piecannon.Server;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import lombok.Data;
@Data
public class FtpServer implements Server {
private final String label;
private final String host;
private final int port;
private final String username;
private final String password;
private final String path;
private final URI uri;
public URI upload (String name, ByteSource source) {
name = name.replace(" ", "%20");
try {
final URI target = URI.create(uri.toString() + "/" + name);
final URL ftpUrl = new URL(String.format("ftp://%s:%s@%s:%d/%s/%s;type=i", username, password, host, port, path, name));
final URLConnection connection = ftpUrl.openConnection();
try (InputStream in = source.openStream()) {
try (OutputStream out = connection.getOutputStream()) {
ByteStreams.copy(in, out);
}
}
return target;
} catch (final IOException exception) {
throw new RuntimeException(exception);
}
}
}

View File

@@ -0,0 +1,46 @@
package net.monarchpass.piecannon.impl;
import java.net.URI;
import java.util.Map;
import com.google.common.io.ByteSource;
import com.google.gson.Gson;
import jodd.http.HttpRequest;
import lombok.Data;
import net.monarchpass.piecannon.Server;
import net.monarchpass.piecannon.util.ByteSourceUploadable;
@Data
public class GoFileServer implements Server {
public static final String DEFAULT_HOST = "gofile.io";
public static final String DEFAULT_API_HOST = "apiv2." + DEFAULT_HOST;
private final Gson gson = new Gson();
private final String label;
private String host = DEFAULT_HOST;
private String apiHost = DEFAULT_API_HOST;
@Override
public URI upload (String name, ByteSource source) {
Response response = request(HttpRequest.get(String.format("https://%s/getServer", apiHost)));
final String serverId = (String)response.getData().get("server");
response = request(HttpRequest.post(String.format("https://%s.%s/uploadFile", serverId, host))
.form("file", new ByteSourceUploadable(name, source)));
final String code = (String)response.getData().get("code");
final URI uri = URI.create(String.format("https://%s/d/%s", host, code));
return uri;
}
private Response request (final HttpRequest request) {
return gson.fromJson(request.send().bodyText(), Response.class);
}
@Data
public static class Response {
public final String status;
public final Map<String, Object> data;
}
}

View File

@@ -13,6 +13,7 @@ import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException; import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException; import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.IdentityRepository;
import lombok.Data; import lombok.Data;
@@ -25,12 +26,17 @@ public class SftpServer implements Server {
private final String password; private final String password;
private final String path; private final String path;
private final URI uri; private final URI uri;
private final IdentityRepository identityRepository;
public URI upload (String name, ByteSource source) { public URI upload (String name, ByteSource source) {
try { try {
final URI target = URI.create(uri.toString() + "/" + name.replace(" ", "%20")); final URI target = URI.create(uri.toString() + "/" + name.replace(" ", "%20"));
final JSch jsch = new JSch(); final JSch jsch = new JSch();
if (identityRepository != null) {
jsch.setIdentityRepository(identityRepository);
}
final Session session = jsch.getSession(username, host, port); final Session session = jsch.getSession(username, host, port);
session.setPassword(password); session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no"); session.setConfig("StrictHostKeyChecking", "no");

View File

@@ -0,0 +1,42 @@
package net.monarchpass.piecannon.impl;
import java.io.IOException;
import java.net.URI;
import com.google.common.io.ByteSource;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import lombok.Data;
import net.monarchpass.piecannon.Server;
@Data
public class WebDavServer implements Server {
private final String label;
private final URI uri;
private final String username;
private final String password;
@Override
public URI upload (String name, ByteSource source) {
try {
final URI target = URI.create(uri.toString() + "/" + name.replace(" ", "%20"));
final HttpRequest request = HttpRequest.put(target.toString())
.body(source.read(), "");
if (username != null && password != null) {
request.basicAuthentication(username, password);
}
final HttpResponse response = request.send();
if (response.statusCode() >= 400) {
throw new RuntimeException(response.statusCode() + ": " + response.statusPhrase());
}
return target;
} catch (final IOException exception) {
throw new RuntimeException(exception);
}
}
}

View File

@@ -0,0 +1,54 @@
package net.monarchpass.piecannon.util;
import java.io.IOException;
import java.io.InputStream;
import com.google.common.io.ByteSource;
import jodd.http.HttpException;
import jodd.http.upload.Uploadable;
import lombok.Data;
@Data
public class ByteSourceUploadable implements Uploadable<ByteSource> {
private final String fileName;
private final ByteSource source;
@Override
public ByteSource getContent () {
return source;
}
@Override
public byte[] getBytes () {
try {
return source.read();
} catch (final IOException exception) {
throw new HttpException(exception);
}
}
@Override
public String getFileName () {
return fileName;
}
@Override
public String getMimeType () {
return null;
}
@Override
public int getSize () {
try {
return (int) source.size();
} catch (final IOException exception) {
throw new HttpException(exception);
}
}
@Override
public InputStream openInputStream () throws IOException {
return source.openStream();
}
}

View File

@@ -1,16 +1,57 @@
package net.monarchpass.piecannon.util; package net.monarchpass.piecannon.util;
import java.net.URI; import java.net.URI;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.jcraft.jsch.IdentityRepository;
import com.google.gson.JsonPrimitive;
import net.monarchpass.piecannon.Server; import net.monarchpass.piecannon.Server;
import net.monarchpass.piecannon.impl.FtpServer;
import net.monarchpass.piecannon.impl.GoFileServer;
import net.monarchpass.piecannon.impl.SftpServer; import net.monarchpass.piecannon.impl.SftpServer;
import net.monarchpass.piecannon.impl.WebDavServer;
import lombok.Setter;
public class ServerFactory implements Function<JsonObject, Server> { public class ServerFactory implements Function<JsonObject, Server> {
private @Setter IdentityRepository identityRepository;
public Server apply (final JsonObject object) { public Server apply (final JsonObject object) {
return makeSftpServer(object); final String type = Optional.ofNullable(object.getAsJsonPrimitive("type"))
.map(JsonPrimitive::getAsString)
.orElse("webdav");
if (type.equals("webdav")) {
return makeWebDavServer(object);
} else if (type.equalsIgnoreCase("gofile")) {
return new GoFileServer(Optional.ofNullable(object.getAsJsonPrimitive("label")).map(JsonPrimitive::getAsString).orElse("GoFile"));
} else if (type.equalsIgnoreCase("ftp")) {
return makeFtpServer(object);
} else if (type.equalsIgnoreCase("sftp")) {
return makeSftpServer(object);
}
throw new IllegalArgumentException("Invalid server type: " + type);
}
private Server makeWebDavServer (final JsonObject object) {
final String label = Optional.ofNullable(object.getAsJsonPrimitive("label"))
.map(JsonPrimitive::getAsString)
.orElse("WebDav");
final String username = Optional.ofNullable(object.getAsJsonPrimitive("username"))
.map(JsonPrimitive::getAsString)
.orElse(null);
final String password = Optional.ofNullable(object.getAsJsonPrimitive("password"))
.map(JsonPrimitive::getAsString)
.orElse(null);
final String url = object.getAsJsonPrimitive("url").getAsString();
return new WebDavServer(
label, URI.create(url), username, password
);
} }
private Server makeSftpServer (final JsonObject object) { private Server makeSftpServer (final JsonObject object) {
@@ -18,11 +59,26 @@ public class ServerFactory implements Function<JsonObject, Server> {
final String label = object.has("label") ? object.getAsJsonPrimitive("label").getAsString() : host; final String label = object.has("label") ? object.getAsJsonPrimitive("label").getAsString() : host;
final int port = object.has("port") ? object.getAsJsonPrimitive("port").getAsInt() : 22; final int port = object.has("port") ? object.getAsJsonPrimitive("port").getAsInt() : 22;
final String username = object.getAsJsonPrimitive("username").getAsString(); final String username = object.getAsJsonPrimitive("username").getAsString();
final String password = object.getAsJsonPrimitive("password").getAsString(); final String password = object.has("password") ? object.getAsJsonPrimitive("password").getAsString() : null;
final String path = object.getAsJsonPrimitive("path").getAsString(); final String path = object.getAsJsonPrimitive("path").getAsString();
final String url = object.getAsJsonPrimitive("url").getAsString(); final String url = object.getAsJsonPrimitive("url").getAsString();
return new SftpServer( return new SftpServer(
label, host, port, username, password,
path, URI.create(url), identityRepository
);
}
private Server makeFtpServer (final JsonObject object) {
final String host = object.getAsJsonPrimitive("host").getAsString();
final String label = object.has("label") ? object.getAsJsonPrimitive("label").getAsString() : host;
final int port = object.has("port") ? object.getAsJsonPrimitive("port").getAsInt() : 21;
final String username = object.getAsJsonPrimitive("username").getAsString();
final String password = object.getAsJsonPrimitive("password").getAsString();
final String path = object.getAsJsonPrimitive("path").getAsString();
final String url = object.getAsJsonPrimitive("url").getAsString();
return new FtpServer(
label, host, port, username, password, label, host, port, username, password,
path, URI.create(url) path, URI.create(url)
); );

View File

@@ -0,0 +1,27 @@
package net.monarchpass.piecannon;
import static com.google.common.truth.Truth.assertThat;
import java.util.logging.Level;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import lombok.extern.java.Log;
import net.monarchpass.piecannon.impl.GoFileServer;
@Log
public class GoFileTest {
@Test
@Disabled
public void test () throws Exception {
try {
System.setProperty("https.protocols", "TLSv1.2");
final GoFileServer server = new GoFileServer("GoFileTest");
assertThat(PieCannon.testServer(server)).isTrue();
} catch (final Exception exception) {
log.log(Level.SEVERE, "", exception);
throw exception;
}
}
}

View File

@@ -22,7 +22,7 @@ public class SftpTest {
final Server server = new SftpServer( final Server server = new SftpServer(
"Test Server Instance", testServerHost, 22, "Test Server Instance", testServerHost, 22,
testServerUser, testServerPassword, testServerPath, testServerUser, testServerPassword, testServerPath,
URI.create(testServerURL) URI.create(testServerURL), null
); );
assertThat(PieCannon.testServer(server)).isTrue(); assertThat(PieCannon.testServer(server)).isTrue();

View File

@@ -1,4 +1,5 @@
[{ [{
"type": "ftp",
"host": "example.com", "host": "example.com",
"username": "username", "username": "username",
"password": "password", "password": "password",