Include exception message in stack trace.

This commit is contained in:
Cody Henthorne
2021-12-20 12:16:05 -05:00
committed by Greyson Parrelli
parent 09a3391761
commit eaf72b194f
3 changed files with 35 additions and 2 deletions

View File

@@ -2,6 +2,8 @@ package org.signal.core.util;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.logsubmit.util.Scrubber;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
@@ -50,6 +52,36 @@ public final class ExceptionUtil {
return combinedTrace;
}
/**
* Joins the stack trace with the exception's {@link Throwable#getMessage()}.
*
* The resulting stack trace will look like this:
*
* Original
* Stack
* Trace
* [[ ↑↑ Original Trace ↑↑ ]]
* [[ ↓↓ Exception Message ↓↓ ]]
* Exception Message
*
* @return The provided original exception, for convenience.
*/
public static @NonNull <E extends Throwable> E joinStackTraceAndMessage(@NonNull E original) {
StackTraceElement[] originalTrace = original.getStackTrace();
StackTraceElement[] combinedTrace = new StackTraceElement[originalTrace.length + 3];
System.arraycopy(originalTrace, 0, combinedTrace, 0, originalTrace.length);
CharSequence message = Scrubber.scrub(original.getMessage() != null ? original.getMessage() : "null");
combinedTrace[originalTrace.length] = new StackTraceElement("[[ ↑↑ Original Trace ↑↑ ]]", "", "", 0);
combinedTrace[originalTrace.length + 1] = new StackTraceElement("[[ ↓↓ Exception Message ↓↓ ]]", "", "", 0);
combinedTrace[originalTrace.length + 2] = new StackTraceElement(message.toString(), "", "", 0);
original.setStackTrace(combinedTrace);
return original;
}
public static @NonNull String convertThrowableToString(@NonNull Throwable throwable) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
throwable.printStackTrace(new PrintStream(outputStream));

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.logsubmit.util;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Scrub data for possibly sensitive information.
*/
public final class Scrubber {
private Scrubber() {
}
/**
* The middle group will be censored.
* Supposedly, the shortest international phone numbers in use contain seven digits.
* Handles URL encoded +, %2B
*/
private static final Pattern E164_PATTERN = Pattern.compile("(\\+|%2B)(\\d{5,13})(\\d{2})");
private static final String E164_CENSOR = "*************";
/**
* The second group will be censored.
*/
private static final Pattern CRUDE_EMAIL_PATTERN = Pattern.compile("\\b([^\\s/])([^\\s/]*@[^\\s]+)");
private static final String EMAIL_CENSOR = "...@...";
/**
* The middle group will be censored.
*/
private static final Pattern GROUP_ID_V1_PATTERN = Pattern.compile("(__)(textsecure_group__![^\\s]+)([^\\s]{2})");
private static final String GROUP_ID_V1_CENSOR = "...group...";
/**
* The middle group will be censored.
*/
private static final Pattern GROUP_ID_V2_PATTERN = Pattern.compile("(__)(signal_group__v2__![^\\s]+)([^\\s]{2})");
private static final String GROUP_ID_V2_CENSOR = "...group_v2...";
/**
* The middle group will be censored.
*/
private static final Pattern UUID_PATTERN = Pattern.compile("(JOB::)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{10})([0-9a-f]{2})", Pattern.CASE_INSENSITIVE);
private static final String UUID_CENSOR = "********-****-****-****-**********";
/**
* The domain name except for TLD will be censored.
*/
private static final Pattern DOMAIN_PATTERN = Pattern.compile("([a-z0-9]+\\.)+([a-z0-9\\-]*[a-z\\-][a-z0-9\\-]*)", Pattern.CASE_INSENSITIVE);
private static final String DOMAIN_CENSOR = "***.";
private static final Set<String> TOP_100_TLDS = new HashSet<>(Arrays.asList("com", "net", "org", "jp", "de", "uk", "fr", "br", "it", "ru", "es", "me", "gov", "pl", "ca", "au", "cn", "co", "in",
"nl", "edu", "info", "eu", "ch", "id", "at", "kr", "cz", "mx", "be", "tv", "se", "tr", "tw", "al", "ua", "ir", "vn",
"cl", "sk", "ly", "cc", "to", "no", "fi", "us", "pt", "dk", "ar", "hu", "tk", "gr", "il", "news", "ro", "my", "biz",
"ie", "za", "nz", "sg", "ee", "th", "io", "xyz", "pe", "bg", "hk", "lt", "link", "ph", "club", "si", "site",
"mobi", "by", "cat", "wiki", "la", "ga", "xxx", "cf", "hr", "ng", "jobs", "online", "kz", "ug", "gq", "ae", "is",
"lv", "pro", "fm", "tips", "ms", "sa", "app"));
public static CharSequence scrub(@NonNull CharSequence in) {
in = scrubE164(in);
in = scrubEmail(in);
in = scrubGroupsV1(in);
in = scrubGroupsV2(in);
in = scrubUuids(in);
in = scrubDomains(in);
return in;
}
private static CharSequence scrubE164(@NonNull CharSequence in) {
return scrub(in,
E164_PATTERN,
(matcher, output) -> output.append(matcher.group(1))
.append(E164_CENSOR, 0, matcher.group(2).length())
.append(matcher.group(3)));
}
private static CharSequence scrubEmail(@NonNull CharSequence in) {
return scrub(in,
CRUDE_EMAIL_PATTERN,
(matcher, output) -> output.append(matcher.group(1))
.append(EMAIL_CENSOR));
}
private static CharSequence scrubGroupsV1(@NonNull CharSequence in) {
return scrub(in,
GROUP_ID_V1_PATTERN,
(matcher, output) -> output.append(matcher.group(1))
.append(GROUP_ID_V1_CENSOR)
.append(matcher.group(3)));
}
private static CharSequence scrubGroupsV2(@NonNull CharSequence in) {
return scrub(in,
GROUP_ID_V2_PATTERN,
(matcher, output) -> output.append(matcher.group(1))
.append(GROUP_ID_V2_CENSOR)
.append(matcher.group(3)));
}
private static CharSequence scrubUuids(@NonNull CharSequence in) {
return scrub(in,
UUID_PATTERN,
(matcher, output) -> {
if (matcher.group(1) != null && !matcher.group(1).isEmpty()) {
output.append(matcher.group(1))
.append(matcher.group(2))
.append(matcher.group(3));
} else {
output.append(UUID_CENSOR)
.append(matcher.group(3));
}
});
}
private static CharSequence scrubDomains(@NonNull CharSequence in) {
return scrub(in,
DOMAIN_PATTERN,
(matcher, output) -> {
String match = matcher.group(0);
if (matcher.groupCount() == 2 &&
TOP_100_TLDS.contains(matcher.group(2).toLowerCase(Locale.US)) &&
!match.endsWith("whispersystems.org") &&
!match.endsWith("signal.org")) {
output.append(DOMAIN_CENSOR)
.append(matcher.group(2));
} else {
output.append(match);
}
});
}
private static CharSequence scrub(@NonNull CharSequence in, @NonNull Pattern pattern, @NonNull ProcessMatch processMatch) {
final StringBuilder output = new StringBuilder(in.length());
final Matcher matcher = pattern.matcher(in);
int lastEndingPos = 0;
while (matcher.find()) {
output.append(in, lastEndingPos, matcher.start());
processMatch.scrubMatch(matcher, output);
lastEndingPos = matcher.end();
}
if (lastEndingPos == 0) {
// there were no matches, save copying all the data
return in;
} else {
output.append(in, lastEndingPos, in.length());
return output;
}
}
private interface ProcessMatch {
void scrubMatch(@NonNull Matcher matcher, @NonNull StringBuilder output);
}
}