diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt index 20c2710b1651..f65db2d5377c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt @@ -2444,6 +2444,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm "postType" to postType, "postTitle" to editPostRepository.getPost()?.title, "postContent" to editPostRepository.getPost()?.content, + "siteURL" to site.url, "siteApiRoot" to siteApiRoot, "authHeader" to authHeader, "siteApiNamespace" to siteApiNamespace, @@ -2457,7 +2458,9 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm gutenbergPropsBuilder, jetpackFeatureRemovalPhaseHelper.shouldShowJetpackPoweredEditorFeatures(), isNewGutenbergEditor, - settings + settings, + site.isPrivate || site.isComingSoon, + site.isPrivateWPComAtomic ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c284121e9255..3c12d41bbb8b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ google-play-services-auth = '20.4.1' google-services = '4.4.2' gravatar = '1.0.0' greenrobot-eventbus = '3.3.1' -gutenberg-kit = 'trunk-a58a46f3fbb892f311b562e3c122d7ef4ebbfe33' +gutenberg-kit = 'trunk-d6fbfc7bc28ae6db2cce09950f24bc3080374596' gutenberg-mobile = 'v1.121.0' indexos-media-for-mobile = '43a9026f0973a2f0a74fa813132f6a16f7499c3a' jackson-databind = '2.12.7.1' diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index fabee0a89ddc..8e25e9b265cc 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -21,6 +21,8 @@ import android.view.inputmethod.InputMethodManager; import android.webkit.URLUtil; import android.webkit.ValueCallback; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -68,6 +70,7 @@ import org.wordpress.android.util.helpers.MediaFile; import org.wordpress.android.util.helpers.MediaGallery; import org.wordpress.aztec.IHistoryListener; +import org.wordpress.gutenberg.GutenbergRequestInterceptor; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.LogExceptionCallback; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergEmbedWebViewActivity; import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException; @@ -97,18 +100,29 @@ import org.wordpress.gutenberg.GutenbergView.ContentChangeListener; import org.wordpress.gutenberg.GutenbergWebViewPool; +import java.io.IOException; import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + import static org.wordpress.mobile.WPAndroidGlue.Media.createRNMediaUsingMimeType; public class GutenbergEditorFragment extends EditorFragmentAbstract implements @@ -117,7 +131,8 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements EditorThemeUpdateListener, GutenbergDialogPositiveClickInterface, GutenbergDialogNegativeClickInterface, - GutenbergNetworkConnectionListener { + GutenbergNetworkConnectionListener, + GutenbergRequestInterceptor { @Nullable private GutenbergView mGutenbergView; private static final String GUTENBERG_EDITOR_NAME = "gutenberg"; private static final String KEY_HTML_MODE_ENABLED = "KEY_HTML_MODE_ENABLED"; @@ -181,6 +196,9 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private ProgressDialog mSavingContentProgressDialog; @Nullable private static Map mSettings; + private static boolean mIsPrivate = false; + private static boolean mIsPrivateAtomic = false; + @NonNull OkHttpClient mHttpClient = new OkHttpClient(); public static GutenbergEditorFragment newInstance(Context context, boolean isNewPost, @@ -188,7 +206,9 @@ public static GutenbergEditorFragment newInstance(Context context, GutenbergPropsBuilder gutenbergPropsBuilder, boolean jetpackFeaturesEnabled, boolean newGutenbergEnabled, - @Nullable Map settings) { + @Nullable Map settings, + boolean isPrivate, + boolean isPrivateAtomic) { GutenbergEditorFragment fragment = new GutenbergEditorFragment(); Bundle args = new Bundle(); args.putBoolean(ARG_IS_NEW_POST, isNewPost); @@ -199,6 +219,8 @@ public static GutenbergEditorFragment newInstance(Context context, SavedInstanceDatabase db = SavedInstanceDatabase.Companion.getDatabase(context); mIsNewGutenbergEnabled = newGutenbergEnabled; mSettings = settings; + mIsPrivate = isPrivate; + mIsPrivateAtomic = isPrivateAtomic; if (db != null) { db.addParcel(ARG_GUTENBERG_WEB_VIEW_AUTH_DATA, webViewAuthorizationData); db.addParcel(ARG_GUTENBERG_PROPS_BUILDER, gutenbergPropsBuilder); @@ -291,6 +313,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mGutenbergView.setEditorDidBecomeAvailable(view -> { mEditorFragmentListener.onEditorFragmentContentReady(new ArrayList(), false); }); + mGutenbergView.setRequestInterceptor(this); Integer postId = (Integer) mSettings.get("postId"); if (postId != null && postId == 0) { @@ -1635,4 +1658,101 @@ public void onConnectionStatusChange(boolean isConnected) { mEditorFragmentListener.onMediaRetryAll(mFailedMediaIds); } } + + @Override + public boolean canIntercept(@NonNull WebResourceRequest request) { + Uri url = request.getUrl(); + + return mIsPrivate && isSiteHostedMediaFile(url.toString()); + } + + boolean isSiteHostedMediaFile(@NonNull String urlString) { + String siteURL = (String) (mSettings != null ? mSettings.get("siteURL") : ""); + Set mediaExtensions = new HashSet<>(Arrays.asList( + "jpg", "jpeg", "png", "gif", "bmp", "webp", + "mp4", "mov", "avi", "mkv", + "mp3", "wav", "flac" + )); + + try { + URL url = new URL(urlString); + URL siteUrlObj = new URL(siteURL); + + // Check if the URL is from the same host as the site URL + if (!url.getHost().equalsIgnoreCase(siteUrlObj.getHost())) { + return false; + } + + // Extract the file name and extension + String path = url.getPath(); + int lastDotIndex = path.lastIndexOf('.'); + if (lastDotIndex == -1) { + return false; + } + + String extension = path.substring(lastDotIndex + 1).toLowerCase(Locale.ROOT); + + // Check if the extension is in the list of media extensions + return mediaExtensions.contains(extension); + } catch (MalformedURLException e) { + // Handle invalid URLs + return false; + } + } + + @Nullable @Override + public WebResourceResponse handleRequest(@NonNull WebResourceRequest request) { + Uri url = request.getUrl(); + + String proxyUrl = url.toString(); + if (mIsPrivateAtomic) { + proxyUrl = getPrivateResourceProxyUrl(url); + } + + try { + Request okHttpRequest = new Request.Builder() + .url(proxyUrl) + .headers(Headers.of(request.getRequestHeaders())) + .addHeader("Authorization", mSettings.get("authHeader").toString()) + .build(); + + Response response = mHttpClient.newCall(okHttpRequest).execute(); + + ResponseBody body = response.body(); + if (body == null) { + return null; + } + + okhttp3.MediaType contentType = body.contentType(); + if (contentType == null) { + return null; + } + + return new WebResourceResponse( + contentType.toString(), + response.header("content-encoding"), + body.byteStream() + ); + } catch (IOException e) { + // We don't need to handle this ourselves, just tell the WebView that + // we weren't able to fetch the resource + return null; + } + } + + private static @NonNull String getPrivateResourceProxyUrl(@NonNull Uri url) { + Uri newUri = new Uri.Builder() + .scheme("https") + .authority("public-api.wordpress.com") + .appendPath("wpcom") + .appendPath("v2") + .appendPath("sites") + .appendPath(url.getAuthority()) + .appendPath("atomic-auth-proxy") + .appendPath("file") + .appendEncodedPath(url.getPath().substring(1)) // Remove leading '/' + .build(); + + return newUri.toString(); + } }