Skip to content

Commit

Permalink
Open up link previews to work with all sites.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal committed Aug 14, 2020
1 parent d569419 commit 6e6105a
Show file tree
Hide file tree
Showing 18 changed files with 377 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.thoughtcrime.securesms.giph.model.GiphyImage;
import org.thoughtcrime.securesms.giph.model.GiphyResponse;
import org.thoughtcrime.securesms.net.ContentProxySelector;
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.JsonUtils;
Expand Down Expand Up @@ -43,7 +43,7 @@ protected GiphyLoader(@NonNull Context context, @Nullable String searchString) {
this.searchString = searchString;
this.client = new OkHttpClient.Builder()
.proxySelector(new ContentProxySelector())
.addInterceptor(new UserAgentInterceptor())
.addInterceptor(new StandardUserAgentInterceptor())
.dns(SignalServiceNetworkAccess.DNS)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor;
import org.thoughtcrime.securesms.net.ContentProxySelector;
import org.thoughtcrime.securesms.net.CustomDns;
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;

import java.io.InputStream;
Expand Down Expand Up @@ -45,7 +44,7 @@ public Factory() {
this.client = new OkHttpClient.Builder()
.proxySelector(new ContentProxySelector())
.cache(null)
.addInterceptor(new UserAgentInterceptor())
.addInterceptor(new StandardUserAgentInterceptor())
.addNetworkInterceptor(new ContentProxySafetyInterceptor())
.addNetworkInterceptor(new PaddedHeadersInterceptor())
.dns(SignalServiceNetworkAccess.DNS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.bumptech.glide.load.model.MultiModelLoaderFactory;

import org.thoughtcrime.securesms.net.ContentProxySelector;
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;

import java.io.InputStream;
Expand Down Expand Up @@ -48,7 +48,7 @@ private static OkHttpClient getInternalClient() {
if (internalClient == null) {
internalClient = new OkHttpClient.Builder()
.proxySelector(new ContentProxySelector())
.addInterceptor(new UserAgentInterceptor())
.addInterceptor(new StandardUserAgentInterceptor())
.dns(SignalServiceNetworkAccess.DNS)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.util.UuidUtil;

import java.io.IOException;
import java.security.SecureRandom;
Expand All @@ -135,7 +134,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public final class PushProcessMessageJob extends BaseJob {
Expand Down Expand Up @@ -1695,7 +1693,7 @@ private static Optional<List<LinkPreview>> getLinkPreviews(Optional<List<Preview
Optional<String> title = Optional.fromNullable(preview.getTitle());
boolean hasContent = !TextUtils.isEmpty(title.or("")) || thumbnail.isPresent();
boolean presentInBody = url.isPresent() && Stream.of(LinkPreviewUtil.findWhitelistedUrls(message)).map(Link::getUrl).collect(Collectors.toSet()).contains(url.get());
boolean validDomain = url.isPresent() && LinkPreviewUtil.isWhitelistedLinkUrl(url.get());
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());

if (hasContent && presentInBody && validDomain) {
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), thumbnail);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,45 @@

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import androidx.annotation.NonNull;
import android.text.Html;
import android.text.TextUtils;

import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.FutureTarget;

import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.OpenGraph;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.net.CallRequestController;
import org.thoughtcrime.securesms.net.CompositeRequestController;
import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor;
import org.thoughtcrime.securesms.net.ContentProxySelector;
import org.thoughtcrime.securesms.net.RequestController;
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
import org.thoughtcrime.securesms.stickers.StickerUrl;
import org.thoughtcrime.securesms.util.ByteUnit;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.OkHttpUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest;
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest.StickerInfo;
import org.whispersystems.signalservice.api.util.OptionalUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.CancellationException;
import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -54,20 +57,22 @@ public class LinkPreviewRepository {

private static final CacheControl NO_CACHE = new CacheControl.Builder().noCache().build();

private static final long FAILSAFE_MAX_TEXT_SIZE = ByteUnit.MEGABYTES.toBytes(2);
private static final long FAILSAFE_MAX_IMAGE_SIZE = ByteUnit.MEGABYTES.toBytes(2);

private final OkHttpClient client;

public LinkPreviewRepository() {
this.client = new OkHttpClient.Builder()
.proxySelector(new ContentProxySelector())
.addNetworkInterceptor(new ContentProxySafetyInterceptor())
.cache(null)
.addInterceptor(new UserAgentInterceptor("WhatsApp"))
.build();
}

RequestController getLinkPreview(@NonNull Context context, @NonNull String url, @NonNull Callback<Optional<LinkPreview>> callback) {
CompositeRequestController compositeController = new CompositeRequestController();

if (!LinkPreviewUtil.isWhitelistedLinkUrl(url)) {
if (!LinkPreviewUtil.isValidPreviewUrl(url)) {
Log.w(TAG, "Tried to get a link preview for a non-whitelisted domain.");
callback.onComplete(Optional.absent());
return compositeController;
Expand All @@ -89,7 +94,7 @@ RequestController getLinkPreview(@NonNull Context context, @NonNull String url,
return;
}

RequestController imageController = fetchThumbnail(context, metadata.getImageUrl().get(), attachment -> {
RequestController imageController = fetchThumbnail(metadata.getImageUrl().get(), attachment -> {
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
callback.onComplete(Optional.absent());
} else {
Expand Down Expand Up @@ -127,11 +132,12 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO
return;
}

String body = response.body().string();
Optional<String> title = getProperty(body, "title");
Optional<String> imageUrl = getProperty(body, "image");
String body = OkHttpUtil.readAsString(response.body(), FAILSAFE_MAX_TEXT_SIZE);
OpenGraph openGraph = LinkPreviewUtil.parseOpenGraphFields(body);
Optional<String> title = openGraph.getTitle();
Optional<String> imageUrl = openGraph.getImageUrl();

if (imageUrl.isPresent() && !LinkPreviewUtil.isWhitelistedMediaUrl(imageUrl.get())) {
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
imageUrl = Optional.absent();
}
Expand All @@ -143,20 +149,23 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO
return new CallRequestController(call);
}

private @NonNull RequestController fetchThumbnail(@NonNull Context context, @NonNull String imageUrl, @NonNull Callback<Optional<Attachment>> callback) {
FutureTarget<Bitmap> bitmapFuture = GlideApp.with(context).asBitmap()
.load(new ChunkedImageUrl(imageUrl))
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerInside()
.submit(1024, 1024);

RequestController controller = () -> bitmapFuture.cancel(false);
private @NonNull RequestController fetchThumbnail(@NonNull String imageUrl, @NonNull Callback<Optional<Attachment>> callback) {
Call call = client.newCall(new Request.Builder().url(imageUrl).build());
CallRequestController controller = new CallRequestController(call);

SignalExecutors.UNBOUNDED.execute(() -> {
try {
Bitmap bitmap = bitmapFuture.get();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Response response = call.execute();
if (!response.isSuccessful() || response.body() == null) {
return;
}

InputStream bodyStream = response.body().byteStream();
controller.setStream(bodyStream);

byte[] data = OkHttpUtil.readAsBytes(bodyStream, FAILSAFE_MAX_IMAGE_SIZE);
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
ByteArrayOutputStream baos = new ByteArrayOutputStream();

bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);

Expand All @@ -181,27 +190,14 @@ public void onResponse(@NonNull Call call, @NonNull Response response) throws IO
null));

callback.onComplete(thumbnail);
} catch (CancellationException | ExecutionException | InterruptedException e) {
} catch (IOException e) {
Log.w(TAG, "Exception during link preview image retrieval.", e);
controller.cancel();
callback.onComplete(Optional.absent());
} finally {
bitmapFuture.cancel(false);
}
});

return () -> bitmapFuture.cancel(true);
}

private @NonNull Optional<String> getProperty(@NonNull String searchText, @NonNull String property) {
Pattern pattern = Pattern.compile("<\\s*meta\\s+property\\s*=\\s*\"\\s*og:" + property + "\\s*\"\\s+[^>]*content\\s*=\\s*\"(.*?)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
Matcher matcher = pattern.matcher(searchText);

if (matcher.find()) {
String text = Html.fromHtml(matcher.group(1)).toString();
return TextUtils.isEmpty(text) ? Optional.absent() : Optional.of(text);
}

return Optional.absent();
return controller;
}

private RequestController fetchStickerPackLinkPreview(@NonNull Context context,
Expand Down

0 comments on commit 6e6105a

Please sign in to comment.