001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.bugreport; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.io.IOException; 009import java.io.InputStream; 010import java.net.URL; 011import java.net.URLEncoder; 012import java.nio.ByteBuffer; 013import java.nio.CharBuffer; 014import java.nio.charset.Charset; 015import java.nio.charset.StandardCharsets; 016 017import javax.swing.JOptionPane; 018import javax.swing.JPanel; 019import javax.swing.SwingUtilities; 020import javax.xml.parsers.DocumentBuilder; 021import javax.xml.parsers.DocumentBuilderFactory; 022import javax.xml.parsers.ParserConfigurationException; 023import javax.xml.xpath.XPath; 024import javax.xml.xpath.XPathConstants; 025import javax.xml.xpath.XPathExpressionException; 026import javax.xml.xpath.XPathFactory; 027 028import org.openstreetmap.josm.Main; 029import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 030import org.openstreetmap.josm.gui.widgets.UrlLabel; 031import org.openstreetmap.josm.tools.Base64; 032import org.openstreetmap.josm.tools.GBC; 033import org.openstreetmap.josm.tools.HttpClient; 034import org.openstreetmap.josm.tools.HttpClient.Response; 035import org.openstreetmap.josm.tools.OpenBrowser; 036import org.openstreetmap.josm.tools.Utils; 037import org.w3c.dom.Document; 038import org.xml.sax.SAXException; 039 040/** 041 * This class handles sending the bug report to JOSM website. 042 * <p> 043 * Currently, we try to open a browser window for the user that displays the bug report. 044 * 045 * @author Michael Zangl 046 * @since 10055 047 */ 048public class BugReportSender extends Thread { 049 050 private final String statusText; 051 private String errorMessage; 052 053 /** 054 * Creates a new sender. 055 * @param statusText The status text to send. 056 */ 057 protected BugReportSender(String statusText) { 058 super("Bug report sender"); 059 this.statusText = statusText; 060 } 061 062 @Override 063 public void run() { 064 try { 065 // first, send the debug text using post. 066 String debugTextPasteId = pasteDebugText(); 067 068 // then open a browser to display the pasted text. 069 String openBrowserError = OpenBrowser.displayUrl(getJOSMTicketURL() + "?pdata_stored=" + debugTextPasteId); 070 if (openBrowserError != null) { 071 Main.warn(openBrowserError); 072 failed(openBrowserError); 073 } 074 } catch (BugReportSenderException e) { 075 Main.warn(e); 076 failed(e.getMessage()); 077 } 078 } 079 080 /** 081 * Sends the debug text to the server. 082 * @return The token which was returned by the server. We need to pass this on to the ticket system. 083 * @throws BugReportSenderException if sending the report failed. 084 */ 085 private String pasteDebugText() throws BugReportSenderException { 086 try { 087 String text = Utils.strip(statusText); 088 ByteBuffer buffer = Charset.forName("UTF-8").encode(CharBuffer.wrap(text)); 089 String pdata = Base64.encode(buffer, false); 090 String postQuery = "pdata=" + URLEncoder.encode(pdata, "UTF-8"); 091 HttpClient client = HttpClient.create(new URL(getJOSMTicketURL()), "POST") 092 .setHeader("Content-Type", "application/x-www-form-urlencoded") 093 .setRequestBody(postQuery.getBytes(StandardCharsets.UTF_8)); 094 095 Response connection = client.connect(); 096 097 if (connection.getResponseCode() >= 500) { 098 throw new BugReportSenderException("Internal server error."); 099 } 100 101 try (InputStream in = connection.getContent()) { 102 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 103 Document document = builder.parse(in); 104 return retrieveDebugToken(document); 105 } 106 } catch (IOException | SAXException | ParserConfigurationException | XPathExpressionException t) { 107 throw new BugReportSenderException(t); 108 } 109 } 110 111 private static String getJOSMTicketURL() { 112 return Main.getJOSMWebsite() + "/josmticket"; 113 } 114 115 private static String retrieveDebugToken(Document document) throws XPathExpressionException, BugReportSenderException { 116 XPathFactory factory = XPathFactory.newInstance(); 117 XPath xpath = factory.newXPath(); 118 String status = (String) xpath.compile("/josmticket/@status").evaluate(document, XPathConstants.STRING); 119 if (!"ok".equals(status)) { 120 String message = (String) xpath.compile("/josmticket/error/text()").evaluate(document, 121 XPathConstants.STRING); 122 if (message.isEmpty()) { 123 message = "Error in server response but server did not tell us what happened."; 124 } 125 throw new BugReportSenderException(message); 126 } 127 128 String token = (String) xpath.compile("/josmticket/preparedid/text()") 129 .evaluate(document, XPathConstants.STRING); 130 if (token.isEmpty()) { 131 throw new BugReportSenderException("Server did not respond with a prepared id."); 132 } 133 return token; 134 } 135 136 private void failed(String string) { 137 errorMessage = string; 138 SwingUtilities.invokeLater(new Runnable() { 139 @Override 140 public void run() { 141 JPanel errorPanel = new JPanel(new GridBagLayout()); 142 errorPanel.add(new JMultilineLabel( 143 tr("Opening the bug report failed. Please report manually using this website:")), 144 GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 145 errorPanel.add(new UrlLabel(Main.getJOSMWebsite() + "/newticket", 2), GBC.eop().insets(8, 0, 0, 0)); 146 errorPanel.add(new DebugTextDisplay(statusText)); 147 148 JOptionPane.showMessageDialog(Main.parent, errorPanel, tr("You have encountered a bug in JOSM"), 149 JOptionPane.ERROR_MESSAGE); 150 } 151 }); 152 } 153 154 /** 155 * Returns the error message that could have occured during bug sending. 156 * @return the error message, or {@code null} if successful 157 */ 158 public final String getErrorMessage() { 159 return errorMessage; 160 } 161 162 private static class BugReportSenderException extends Exception { 163 BugReportSenderException(String message) { 164 super(message); 165 } 166 167 BugReportSenderException(Throwable cause) { 168 super(cause); 169 } 170 } 171 172 /** 173 * Opens the bug report window on the JOSM server. 174 * @param statusText The status text to send along to the server. 175 * @return bug report sender started thread 176 */ 177 public static BugReportSender reportBug(String statusText) { 178 BugReportSender sender = new BugReportSender(statusText); 179 sender.start(); 180 return sender; 181 } 182}