Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions cr-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ repositories {
excludeGroupByRegex("org.terasology(..+)?")
}
}
// JBoss Maven Repository requried to fetch `org.jpastebin` dependency for CrashReporter
// https://developer.jboss.org/docs/DOC-11377
maven {
name = "JBoss Public Maven Repository Group"
url = uri("https://repository.jboss.org/nexus/content/repositories/public/")
content {
includeModule("org", "jpastebin")
}
}
maven {
name = "Terasology Artifactory"
url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live")
Expand All @@ -61,21 +52,6 @@ dependencies {
pmd("net.sourceforge.pmd:pmd-core:7.0.0-rc4")
pmd("net.sourceforge.pmd:pmd-java:7.0.0-rc4")

implementation("org:jpastebin:1.0.1")
// jpastebin needs these at runtime (see its own embedded META-INF/maven/org/jpastebin/pom.xml,
// which pins Jackson 2.9.7) but the POM Gradle actually resolves from the JBoss repo is an
// empty Nexus-generated stub with no <dependencies> at all - so without declaring these
// ourselves, PastebinUploadRunnable.call() throws NoClassDefFoundError the first time it
// touches a Jackson class, only when someone actually clicks "Upload". 2.9.7 is a 2018 release
// with known CVEs; Jackson's 2.x line keeps this level of API (ObjectMapper, TypeReference,
// annotations) stable, so a current release is a safe drop-in rather than matching the old pin.
// The BOM (not three separately-pinned versions) because jackson-annotations renumbered its own
// versioning away from core/databind's x.y.z scheme starting at 2.20 - the BOM is what keeps the
// three resolvable together regardless of a given module's own version string.
implementation(platform("com.fasterxml.jackson:jackson-bom:2.22.2"))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("org.apache.httpcomponents:httpclient:4.5.13")
implementation("org.apache.httpcomponents:httpmime:4.5.13")
// GitHub issue-creation API request/response bodies (GitHubIssueApiClient) - small,
Expand Down
51 changes: 23 additions & 28 deletions cr-core/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,22 @@
# To regenerate this file, run: ./gradlew :cr-core:dependencies --write-locks
ch.qos.logback:logback-classic:1.6.0=testRuntimeClasspath
ch.qos.logback:logback-core:1.6.0=testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.22=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.22.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.22.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.22.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.oowekyala.ooxml:nice-xml-messages:3.1=pmd
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.8.9=pmd
com.google.errorprone:error_prone_annotations:2.11.0=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:failureaccess:1.0.1=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:31.1-jre=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:1.3=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.11.0=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.google.guava:failureaccess:1.0.1=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.google.guava:guava:31.1-jre=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:1.3=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:10.2=checkstyle
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-codec:commons-codec:1.11=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-codec:commons-codec:1.11=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-logging:commons-logging:1.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-logging:commons-logging:1.2=compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
info.picocli:picocli:4.6.3=checkstyle
net.bytebuddy:byte-buddy-agent:1.17.7=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.17.7=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
net.sf.saxon:Saxon-HE:10.7=pmd
net.sf.saxon:Saxon-HE:11.3=checkstyle
net.sourceforge.pmd:pmd-ant:7.0.0-rc4=pmd
Expand All @@ -32,30 +28,29 @@ net.sourceforge.pmd:pmd-java:7.0.0-rc4=pmd
org.antlr:antlr4-runtime:4.10.1=checkstyle
org.antlr:antlr4-runtime:4.9.1=pmd
org.apache.commons:commons-lang3:3.8.1=pmd
org.apache.httpcomponents:httpclient:4.5.13=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.13=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpmime:4.5.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.13=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpmime:4.5.13=compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.checkerframework:checker-qual:2.5.2=pmd
org.checkerframework:checker-qual:3.12.0=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.12.0=checkstyle,compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
org.json:json:20260814=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.10.1=testCompileClasspath,testRuntimeClasspath
org.json:json:20260814=compileClasspath,mainPmdAuxClasspath,runtimeClasspath,testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.10.1=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.10.1=testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.10.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.10.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.10.1=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.10.1=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.10.1=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.10.1=testRuntimeClasspath
org.junit:junit-bom:5.10.1=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.23.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.10.1=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.23.0=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testPmdAuxClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.ow2.asm:asm:9.5=pmd
org.pcollections:pcollections:3.1.3=pmd
org.reflections:reflections:0.10.2=checkstyle
org.slf4j:jul-to-slf4j:1.7.36=pmd
org.slf4j:slf4j-api:2.0.18=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.18=testCompileClasspath,testPmdAuxClasspath,testRuntimeClasspath
org.terasology.config:codemetrics:2.2.0=codeMetrics
org.xmlresolver:xmlresolver:4.2.0=checkstyle
org:jpastebin:1.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
empty=annotationProcessor,testAnnotationProcessor
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
*/
public final class CrashSummary {

/** First line of every generated issue text, so readers know where it came from. */
static final String DISCLAIMER = "> _Generated by CrashReporter_";

private static final int MAX_STACK_LINES = 15;
private static final int MAX_MODULES_LISTED = 30;
private static final int MAX_TITLE_MESSAGE_LENGTH = 80;
Expand Down Expand Up @@ -321,9 +324,9 @@ public String buildTitle() {
* log tab it was found in), then environment info, then a link to the full logs
*/
public String buildBody(URL pastebinLink) {
StringBuilder body = new StringBuilder();
StringBuilder body = new StringBuilder(DISCLAIMER).append("\n\n");

// The link goes first: when this body is squeezed into a URL, GitHubIssueLinkBuilder
// The link goes next: when this body is squeezed into a URL, GitHubIssueLinkBuilder
// truncates the tail, and the one line that must survive a truncated trace is the pointer
// to the full log.
body.append("### Full logs\n\n");
Expand Down Expand Up @@ -388,7 +391,7 @@ public Map<String, String> buildIssueFormFields(URL pastebinLink) {
+ " (" + System.getProperty("os.arch") + ")");
putField(fields, Profile.FormField.JAVA, System.getProperty("java.version"));

StringBuilder actual = new StringBuilder();
StringBuilder actual = new StringBuilder(DISCLAIMER).append("\n\n");
for (String block : exceptionBlocks) {
actual.append(block).append("\n\n");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,123 @@

package org.terasology.crashreporter.pages;

import org.jpastebin.exceptions.PasteException;
import org.jpastebin.pastebin.PasteExpireDate;
import org.jpastebin.pastebin.Pastebin;
import org.jpastebin.pastebin.PastebinLink;
import org.jpastebin.pastebin.PastebinPaste;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Upload the content to PasteBin
*/
public class PastebinUploadRunnable implements Callable<URL> {

static final String PASTEBIN_API_URL = "https://pastebin.com/api/api_post.php";
static final int TIMEOUT_MILLIS = 30_000;
private static final long INTERRUPT_POLL_MILLIS = 250;

/**
* Username Terasology
* eMail pastebin@terasology.org
*/
private static final String PASTEBIN_DEVELOPER_KEY = "1ed92217030bd6c2570fac91bcbfee78";

private final String content;
private final URI apiUri;
private final int timeoutMillis;

public PastebinUploadRunnable(String content) {
this(content, URI.create(PASTEBIN_API_URL), TIMEOUT_MILLIS);
}

PastebinUploadRunnable(String content, URI apiUri, int timeoutMillis) {
this.content = content;
this.apiUri = apiUri;
this.timeoutMillis = timeoutMillis;
}

@Override
public URL call() throws PasteException {
String title = "Terasology Error Report";
final PastebinPaste paste = Pastebin.newPaste(PASTEBIN_DEVELOPER_KEY, content, title);
paste.setPasteFormat("apache"); // Apache Log File Format - this is the closest I could find
paste.setPasteExpireDate(PasteExpireDate.ONE_MONTH);

PastebinLink link = paste.paste();
final URL url = link.getLink();
return url;
public URL call() throws IOException {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeoutMillis)
.setConnectionRequestTimeout(timeoutMillis)
.setSocketTimeout(timeoutMillis)
Comment thread
soloturn marked this conversation as resolved.
.build();
List<NameValuePair> form = Arrays.<NameValuePair>asList(
new BasicNameValuePair("api_dev_key", PASTEBIN_DEVELOPER_KEY),
new BasicNameValuePair("api_option", "paste"),
new BasicNameValuePair("api_paste_code", content),
new BasicNameValuePair("api_paste_name", "Terasology Error Report"),
new BasicNameValuePair("api_paste_format", "apache"), // closest to a log file format
new BasicNameValuePair("api_paste_expire_date", "1M"));

HttpPost post = new HttpPost(apiUri);
post.setEntity(new UrlEncodedFormEntity(form, StandardCharsets.UTF_8));

// Socket timeouts only bound inactivity, so a trickling server could hold the request open;
// the watchdog aborts it at an overall deadline, or as soon as this thread is interrupted
// (UploadPanel cancels its Future on timeout) - blocking socket I/O ignores interrupts itself.
final Thread owner = Thread.currentThread();
final AtomicBoolean timedOut = new AtomicBoolean();
final ScheduledExecutorService watchdog = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "PasteBin upload watchdog");
thread.setDaemon(true);
return thread;
}
});
watchdog.schedule(new Runnable() {
@Override
public void run() {
timedOut.set(true);
post.abort();
}
}, timeoutMillis, TimeUnit.MILLISECONDS);
watchdog.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if (owner.isInterrupted()) {
post.abort();
}
}
}, INTERRUPT_POLL_MILLIS, INTERRUPT_POLL_MILLIS, TimeUnit.MILLISECONDS);

try (CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
CloseableHttpResponse response = client.execute(post)) {
String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8).trim();
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK || !body.startsWith("http")) {
throw new IOException(body); // PasteBin reports errors as "Bad API request, ..." text
}
return new URL(body);
} catch (IOException e) {
if (timedOut.get() && !(e instanceof SocketTimeoutException)) {
SocketTimeoutException timeout = new SocketTimeoutException("PasteBin upload exceeded " + timeoutMillis + " ms");
timeout.initCause(e);
throw timeout;
}
throw e;
} finally {
watchdog.shutdownNow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,8 @@ public URL getUploadedFileURL() {

/**
* Runs {@code callable} on its own thread and waits up to {@link #uploadTimeoutSeconds} for it
* to finish - {@code PastebinUploadRunnable} makes a real HTTP call with no timeout of its own,
* so without one here a slow or unreachable server leaves the button disabled and the status
* label reading "please wait" forever, with no way for the user to tell the difference between
* "still working" and "will never finish".
* to finish, so a slow or unreachable server can't leave the button disabled and the status
* label reading "please wait" forever.
*/
private void upload(final Callable<URL> callable) {
final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,14 @@ void issueFormFieldsIncludeVersionOsAndTheExceptionBlocks() {
assertTrue(fields.get("actual_behavior").contains("RuntimeException: boom"), fields.get("actual_behavior"));
}

@Test
void bodyAndFormDetailsStartWithTheCrashReporterDisclaimer() {
CrashSummary summary = CrashSummary.extract(new RuntimeException("boom"), LOG_TEXT, terasology());

assertTrue(summary.buildBody(null).startsWith(CrashSummary.DISCLAIMER), summary.buildBody(null));
assertTrue(summary.buildIssueFormFields(null).get("actual_behavior").startsWith(CrashSummary.DISCLAIMER));
}

@Test
void issueFormFieldsOmitVersionWhenNotFoundInsteadOfSayingUnknown() {
CrashSummary summary = CrashSummary.extract(new RuntimeException("boom"), "no relevant lines here", terasology());
Expand Down
Loading