1616import net .minecraft .util .Pair ;
1717import org .jetbrains .annotations .Contract ;
1818import org .jetbrains .annotations .NotNull ;
19- import org .jetbrains .annotations .Nullable ;
2019import org .lwjgl .system .Platform ;
2120
2221import java .io .*;
2322import java .net .URI ;
2423import java .nio .charset .StandardCharsets ;
2524import java .nio .file .Files ;
25+ import java .nio .file .Path ;
26+ import java .nio .file .StandardCopyOption ;
27+ import java .nio .file .attribute .PosixFilePermission ;
2628import java .util .Arrays ;
29+ import java .util .UUID ;
2730import java .util .concurrent .CompletableFuture ;
31+ import java .util .concurrent .TimeUnit ;
2832
2933public class DownloadedCloudflared extends Cloudflared {
3034
@@ -91,7 +95,7 @@ public String[] buildCommand(RunningTunnel.@NotNull Access access) {
9195 return command ;
9296 }
9397
94- private CompletableFuture <Void > downloadAndSaveInfo () {
98+ private @ NotNull CompletableFuture <Void > downloadAndSaveInfo () {
9599 return downloadFile ().thenAccept (unused -> {
96100 try {
97101 save ();
@@ -116,19 +120,25 @@ public CompletableFuture<Pair<Boolean, String>> isUptoDate() {
116120 }
117121
118122 public @ NotNull CompletableFuture <Void > downloadFile () {
119- return GithubAPI .requestFileHash (download .downloadFile ()).thenAcceptAsync (fileHash -> {
123+ return GithubAPI .requestFileHash (download .downloadFile ()).thenAcceptAsync (expected -> {
120124 try {
121- for (int i = 0 ; i < 5 ; i ++) {
125+ for (int i = 0 ; i < 4 ; i ++) {
122126 Modflared .LOGGER .info ("Downloading cloudflared version {} from github. Attempt: {}" , version , i + 1 );
123- File file = syncDownloadFile ();
127+ var downloadedFile = syncDownloadFile ();
128+ Modflared .LOGGER .info ("Downloaded file preparing cloudflared binary..." );
129+ var file = new File (TunnelManager .DATA_FOLDER , download .fileName ());
130+ prepareFile (downloadedFile , file );
124131
125132 // Check if file is corrupt
126- if ( fileHash . compareTo ( file )) {
127- Modflared . LOGGER . info ( "Preparing cloudflared binary..." );
128- prepareFile ( file );
133+ Modflared . LOGGER . info ( "Checking file integrity" );
134+ var provided = GithubAPI . FileHash . computeHash ( file );
135+ if ( expected . compareTo ( provided )) {
129136 Modflared .LOGGER .info ("Download finished of cloudflared version {}!" , version );
130137 return ;
131138 } else {
139+ Modflared .LOGGER .warn ("This downloaded file does not match with the file hash provided on GitHub." );
140+ Modflared .LOGGER .warn ("Expected {}, Provided: {}" , expected .hash (), provided .hash ());
141+
132142 file .delete ();
133143 }
134144 }
@@ -141,22 +151,8 @@ public CompletableFuture<Pair<Boolean, String>> isUptoDate() {
141151 }, Modflared .EXECUTOR );
142152 }
143153
144- private void prepareFile (File file ) throws IOException , InterruptedException {
145- switch (Platform .get ()) {
146- case MACOSX :
147- new ProcessBuilder ("tar" , "-xzf" , file .getName ()).directory (file .getParentFile ()).start ().waitFor ();
148- new ProcessBuilder ("mv" , "cloudflared" , file .getName ()).directory (file .getParentFile ()).start ().waitFor ();
149- //Fallthrough
150- case LINUX :
151- new ProcessBuilder ("chmod" , "+x" , file .getName ()).directory (file .getParentFile ()).start ();
152- break ;
153- default :
154- break ;
155- }
156- }
157-
158154 private @ NotNull File syncDownloadFile () throws IOException , InterruptedException {
159- File output = new File (TunnelManager .DATA_FOLDER , download . fileName ());
155+ File output = new File (TunnelManager .DATA_FOLDER , UUID . randomUUID (). toString ());
160156 if (!output .getParentFile ().exists ()) output .getParentFile ().mkdirs ();
161157 if (!output .exists ()) output .createNewFile ();
162158 try (BufferedInputStream in = new BufferedInputStream (URI .create (GITHUB_DOWNLOAD_ENDPOINT + version + "/" + download .downloadFile ()).toURL ().openStream ()); BufferedOutputStream fileOutputStream = new BufferedOutputStream (new FileOutputStream (output ))) {
@@ -171,6 +167,56 @@ private void prepareFile(File file) throws IOException, InterruptedException {
171167 return output ;
172168 }
173169
170+ private void prepareFile (@ NotNull File downloadedFile , File targetFile ) throws IOException , InterruptedException {
171+ var platform = Platform .get ();
172+
173+ if (platform == Platform .MACOSX ) {
174+ var workingDirectory = downloadedFile .getParentFile ().toPath ();
175+ runCommand (workingDirectory , "tar" , "-xzf" , downloadedFile .getName ());
176+ Files .move (workingDirectory .resolve ("cloudflared" ), targetFile .toPath (), StandardCopyOption .REPLACE_EXISTING );
177+ } else {
178+ Files .move (downloadedFile .toPath (), targetFile .toPath (), StandardCopyOption .REPLACE_EXISTING );
179+ }
180+
181+ if (platform == Platform .LINUX || platform == Platform .MACOSX ) {
182+ makeExecutable (targetFile .toPath ());
183+ }
184+
185+ downloadedFile .delete ();
186+ }
187+
188+ private void runCommand (@ NotNull Path workingDirectory , String ... command ) throws IOException , InterruptedException {
189+ ProcessBuilder processBuilder = new ProcessBuilder (command )
190+ .directory (workingDirectory .toFile ())
191+ .redirectErrorStream (true );
192+ Process process = processBuilder .start ();
193+ boolean finished = process .waitFor (60 , TimeUnit .SECONDS );
194+ if (!finished ) {
195+ process .destroyForcibly ();
196+ throw new IOException ("Command timed out: " + String .join (" " , command ));
197+ }
198+ int code = process .exitValue ();
199+ if (code != 0 ) {
200+ throw new IOException ("Command failed (exit " + code + "): " + String .join (" " , command ));
201+ }
202+ }
203+
204+ private void makeExecutable (@ NotNull Path path ) throws IOException {
205+ try {
206+ var permissions = Files .getPosixFilePermissions (path );
207+ permissions .add (PosixFilePermission .OWNER_EXECUTE );
208+ permissions .add (PosixFilePermission .GROUP_EXECUTE );
209+ permissions .add (PosixFilePermission .OTHERS_EXECUTE );
210+ Files .setPosixFilePermissions (path , permissions );
211+ } catch (UnsupportedOperationException exception ) {
212+ // Fallback (non-POSIX)
213+ var file = path .toFile ();
214+ if (!file .setExecutable (true , false )) {
215+ throw new IOException ("Failed to set executable bit on " + file .getName ());
216+ }
217+ }
218+ }
219+
174220 private void save () throws IOException {
175221 Files .writeString (VERSION_FILE .toPath (), Modflared .GSON .toJson (this ), StandardCharsets .UTF_8 );
176222 }
0 commit comments