From c5ef274916fb521d52566f17c55ac24b15d7363e Mon Sep 17 00:00:00 2001 From: le-firehawk Date: Mon, 6 Oct 2025 00:14:24 +1030 Subject: [PATCH] fix: Glide module incorrectly encoding IPv6 addresses --- .../tempo/glide/CustomGlideModule.java | 9 ++ .../tempo/glide/CustomGlideRequest.java | 2 +- .../tempo/glide/IPv6StringLoader.java | 110 ++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/cappielloantonio/tempo/glide/IPv6StringLoader.java diff --git a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideModule.java b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideModule.java index b2fd1a06..ccbffb21 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideModule.java +++ b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideModule.java @@ -4,14 +4,18 @@ import android.content.Context; import androidx.annotation.NonNull; +import com.bumptech.glide.Glide; import com.bumptech.glide.GlideBuilder; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; +import com.bumptech.glide.Registry; import com.bumptech.glide.module.AppGlideModule; import com.bumptech.glide.request.RequestOptions; import com.cappielloantonio.tempo.util.Preferences; +import java.io.InputStream; + @GlideModule public class CustomGlideModule extends AppGlideModule { @Override @@ -20,4 +24,9 @@ public class CustomGlideModule extends AppGlideModule { builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "cache", diskCacheSize)); builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565)); } + + @Override + public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { + registry.replace(String.class, InputStream.class, new IPv6StringLoader.Factory()); + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java index 8e49111f..a6e650e2 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java +++ b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java @@ -125,7 +125,7 @@ public class CustomGlideRequest { public static class Builder { private final RequestManager requestManager; - private Object item; + private String item; private Builder(Context context, String item, ResourceType type) { this.requestManager = Glide.with(context); diff --git a/app/src/main/java/com/cappielloantonio/tempo/glide/IPv6StringLoader.java b/app/src/main/java/com/cappielloantonio/tempo/glide/IPv6StringLoader.java new file mode 100644 index 00000000..85307ac9 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/glide/IPv6StringLoader.java @@ -0,0 +1,110 @@ +package com.cappielloantonio.tempo.glide; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; +import com.bumptech.glide.signature.ObjectKey; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class IPv6StringLoader implements ModelLoader { + private static final int DEFAULT_TIMEOUT_MS = 2500; + + @Override + public boolean handles(@NonNull String model) { + return model.startsWith("http://") || model.startsWith("https://"); + } + + @Override + public LoadData buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) { + if (!handles(model)) { + return null; + } + return new LoadData<>(new ObjectKey(model), new IPv6StreamFetcher(model)); + } + + private static class IPv6StreamFetcher implements DataFetcher { + private final String model; + private InputStream stream; + private HttpURLConnection connection; + + IPv6StreamFetcher(String model) { + this.model = model; + } + + @Override + public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { + try { + URL url = new URL(model); + connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(DEFAULT_TIMEOUT_MS); + connection.setReadTimeout(DEFAULT_TIMEOUT_MS); + connection.setUseCaches(true); + connection.setDoInput(true); + connection.connect(); + + if (connection.getResponseCode() / 100 != 2) { + callback.onLoadFailed(new IOException("Request failed with status code: " + connection.getResponseCode())); + return; + } + + stream = connection.getInputStream(); + callback.onDataReady(stream); + } catch (IOException e) { + callback.onLoadFailed(e); + } + } + + @Override + public void cleanup() { + if (stream != null) { + try { + stream.close(); + } catch (IOException ignored) { + } + } + if (connection != null) { + connection.disconnect(); + } + } + + @Override + public void cancel() { + // HttpURLConnection does not provide a direct cancel mechanism. + } + + @NonNull + @Override + public Class getDataClass() { + return InputStream.class; + } + + @NonNull + @Override + public DataSource getDataSource() { + return DataSource.REMOTE; + } + } + + public static class Factory implements ModelLoaderFactory { + @NonNull + @Override + public ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { + return new IPv6StringLoader(); + } + + @Override + public void teardown() { + // No-op + } + } +} \ No newline at end of file