View Javadoc
1   package com.jsql.view.swing.panel.consoles;
2   
3   import com.jsql.model.bean.util.HttpHeader;
4   import com.jsql.model.injection.vendor.model.VendorYaml;
5   import com.jsql.util.I18nUtil;
6   import com.jsql.util.LogLevelUtil;
7   import com.jsql.util.StringUtil;
8   import com.jsql.view.swing.panel.util.HTMLEditorKitTextPaneWrap;
9   import com.jsql.view.swing.tab.TabbedPaneWheeled;
10  import com.jsql.view.swing.text.JPopupTextComponent;
11  import com.jsql.view.swing.text.JTextPanePlaceholder;
12  import com.jsql.view.swing.text.SyntaxTextArea;
13  import com.jsql.view.swing.util.I18nViewUtil;
14  import com.jsql.view.swing.util.MediatorHelper;
15  import com.jsql.view.swing.util.UiUtil;
16  import org.apache.commons.lang3.StringUtils;
17  import org.apache.logging.log4j.LogManager;
18  import org.apache.logging.log4j.Logger;
19  import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
20  import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
21  import org.fife.ui.rtextarea.RTextScrollPane;
22  import org.jsoup.Jsoup;
23  import org.jsoup.safety.Safelist;
24  
25  import javax.swing.*;
26  import javax.swing.text.DefaultCaret;
27  import java.awt.*;
28  import java.awt.event.FocusAdapter;
29  import java.awt.event.FocusEvent;
30  import java.util.AbstractMap.SimpleEntry;
31  import java.util.Arrays;
32  import java.util.Map;
33  import java.util.stream.Stream;
34  
35  public class TabbedPaneNetworkTab extends TabbedPaneWheeled {
36      
37      private static final Logger LOGGER = LogManager.getRootLogger();
38  
39      private final RSyntaxTextArea textAreaUrl = new SyntaxTextArea(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_URL"));
40      private final RSyntaxTextArea textAreaResponse = new SyntaxTextArea(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_RESPONSE"));
41      private final RSyntaxTextArea textAreaSource = new SyntaxTextArea(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_SOURCE"));
42      private final JTextPane textPanePreview = new JPopupTextComponent<>(new JTextPanePlaceholder(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_PREVIEW")){
43          @Override
44          public boolean isEditable() {
45              return false;
46          }
47      }).getProxy();
48      private final RSyntaxTextArea textAreaHeader = new SyntaxTextArea(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_HEADERS"));
49      private final RSyntaxTextArea textAreaRequest = new SyntaxTextArea(I18nUtil.valueByKey("NETWORK_LINE_PLACEHOLDER_REQUEST"));
50      private final JCheckBox checkBoxDecode = new JCheckBox("Decode", MediatorHelper.model().getMediatorUtils().getPreferencesUtil().isUrlDecodeNetworkTab());
51  
52      public TabbedPaneNetworkTab() {
53          this.setName("tabNetwork");
54          var panelDecode = new JPanel(new BorderLayout());
55          panelDecode.add(this.checkBoxDecode, BorderLayout.EAST);  // reduce to minimum size as checkbox expands by the label
56          this.putClientProperty("JTabbedPane.trailingComponent", panelDecode);
57          this.checkBoxDecode.setHorizontalTextPosition(SwingConstants.TRAILING);
58  
59          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_URL", this.textAreaUrl);
60          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_RESPONSE", this.textAreaResponse);
61          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_SOURCE", this.textAreaSource);
62          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_PREVIEW", this.textPanePreview);
63          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_HEADERS", this.textAreaHeader);
64          I18nViewUtil.addComponentForKey("NETWORK_LINE_PLACEHOLDER_REQUEST", this.textAreaRequest);
65          Stream.of(
66              new SimpleEntry<>("NETWORK_TAB_URL_LABEL", this.textAreaUrl),
67              new SimpleEntry<>("NETWORK_TAB_HEADERS_LABEL", this.textAreaHeader),
68              new SimpleEntry<>("NETWORK_TAB_PARAMS_LABEL", this.textAreaRequest),
69              new SimpleEntry<>("NETWORK_TAB_RESPONSE_LABEL", this.textAreaResponse),
70              new SimpleEntry<>("NETWORK_TAB_SOURCE_LABEL", this.textAreaSource),
71              new SimpleEntry<>("NETWORK_TAB_PREVIEW_LABEL", this.textPanePreview)
72          )
73          .forEach(entry -> {
74              this.addTab(
75                  I18nUtil.valueByKey(entry.getKey()),
76                  entry.getValue() == this.textAreaSource
77                  ? new RTextScrollPane(entry.getValue(), false)
78                  : new JScrollPane(entry.getValue())
79              );
80              var label = new JLabel(I18nUtil.valueByKey(entry.getKey()));
81              label.setName("label"+ entry.getKey());
82              this.setTabComponentAt(
83                  this.indexOfTab(I18nUtil.valueByKey(entry.getKey())),
84                  label
85              );
86              I18nViewUtil.addComponentForKey(entry.getKey(), label);
87  
88              entry.getValue().setName("text"+ entry.getKey());
89  
90              DefaultCaret caret = (DefaultCaret) entry.getValue().getCaret();
91              caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
92          });
93  
94          Arrays.asList(this.textAreaUrl, this.textAreaHeader, this.textAreaRequest, this.textAreaResponse, this.textAreaSource).forEach(entry -> {
95              entry.setEditable(false);
96              entry.setLineWrap(true);
97          });
98          this.textAreaResponse.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_HTML);
99          this.textAreaSource.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_HTML);
100         this.textAreaSource.setHighlightSecondaryLanguages(true);
101         this.applyTheme();
102         
103         this.textPanePreview.setEditorKit(new HTMLEditorKitTextPaneWrap());
104         this.textPanePreview.setContentType("text/html");
105         this.textPanePreview.setEditable(false);
106         this.textPanePreview.getCaret().setBlinkRate(0);
107         this.textPanePreview.addFocusListener(new FocusAdapter() {
108             @Override
109             public void focusGained(FocusEvent focusEvent) {
110                 TabbedPaneNetworkTab.this.textPanePreview.getCaret().setVisible(true);
111                 TabbedPaneNetworkTab.this.textPanePreview.getCaret().setSelectionVisible(true);
112             }
113         });
114     }
115     
116     public void changeTextNetwork(HttpHeader networkData) {
117         this.textAreaRequest.setText(this.getDecodedValue(this.checkBoxDecode.isSelected(), networkData.getPost()));
118         this.textAreaUrl.setText(this.getDecodedValue(this.checkBoxDecode.isSelected(), networkData.getUrl()));
119         this.updateTextArea(this.textAreaHeader, networkData.getHeader());
120         this.updateTextArea(this.textAreaResponse, networkData.getResponse());
121 
122         // Fix #53736: ArrayIndexOutOfBoundsException on setText()
123         // Fix #54573: NullPointerException on setText()
124         try {
125             this.textAreaSource.setText(
126                 StringUtil.detectUtf8(networkData.getSource())
127                 .replaceAll(VendorYaml.CALIBRATOR_SQL +"{5,}", VendorYaml.CALIBRATOR_SQL +"*")
128                 .trim()
129             );
130         } catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
131             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
132         }
133         
134         this.textPanePreview.getEditorKit().createDefaultDocument();  // Reset EditorKit to disable previous document effect
135         // Proxy is used by jsoup to display <img> tags
136         // Previous test for 2xx Success and 3xx Redirection was Header only, now get the HTML content
137         // Fix #35352: EmptyStackException on setText()
138         // Fix #39841: RuntimeException on setText()
139         // Fix #42523: ExceptionInInitializerError on clean()
140         try {
141             this.textPanePreview.setText(
142                 Jsoup.clean(
143                     String.format(
144                         "<html>%s</html>",
145                         StringUtil.detectUtf8(networkData.getSource())
146                     )
147                     .replaceAll("<img[^>]*>",StringUtils.EMPTY)  // avoid loading external resources
148                     .replaceAll("<input[^>]*type=\"?hidden\"?.*>", StringUtils.EMPTY)
149                     .replaceAll(
150                         "<input[^>]*type=\"?(submit|button)\"?.*>",
151                         "<div style=\"background-color:#eeeeee;text-align:center;border:1px solid black;width:100px;\">button</div>"
152                     )
153                     .replaceAll(
154                         "<input[^>]*>",
155                         "<div style=\"text-align:center;border:1px solid black;width:100px;\">input</div>"
156                     ),
157                     Safelist.relaxed().addTags("center", "div", "span").addAttributes(":all", "style")
158                 )
159             );
160         } catch (Exception | ExceptionInInitializerError e) {
161             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
162         }
163     }
164 
165     private void updateTextArea(JTextArea textArea, Map<String, String> httpData) {
166         textArea.setText(StringUtils.EMPTY);
167         if (httpData != null) {
168             httpData.forEach((key, value) -> {
169                 String decodedValue = this.getDecodedValue(this.checkBoxDecode.isSelected(), value);
170                 textArea.append(key + ": " + decodedValue + "\n");
171             });
172         }
173     }
174 
175     private String getDecodedValue(boolean isSelected, String value) {
176         // Fix #96095: IllegalArgumentException on URLDecoder.decode()
177         try {
178             return isSelected ? StringUtil.fromUrl(value) : value;
179         } catch (IllegalArgumentException e) {
180             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Decoding failure: {}", e.getMessage());
181             return value;
182         }
183     }
184 
185     public void reset() {
186         this.textAreaUrl.setText(StringUtils.EMPTY);
187         this.textAreaHeader.setText(StringUtils.EMPTY);
188         this.textAreaRequest.setText(StringUtils.EMPTY);
189         this.textAreaResponse.setText(StringUtils.EMPTY);
190         
191         // Fix #54572: NullPointerException on setText()
192         try {
193             this.textAreaSource.setText(StringUtils.EMPTY);
194         } catch (NullPointerException e) {
195             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
196         }
197         
198         // Fix #41879: ArrayIndexOutOfBoundsException on setText()
199         try {
200             this.textPanePreview.setText(StringUtils.EMPTY);
201         } catch (ArrayIndexOutOfBoundsException e) {
202             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
203         }
204     }
205 
206     public void applyTheme() {
207         Arrays.asList(
208             this.textAreaUrl, this.textAreaHeader, this.textAreaRequest, this.textAreaResponse, this.textAreaSource
209         ).forEach(UiUtil::applySyntaxTheme);
210     }
211 
212     public JCheckBox getCheckBoxDecode() {
213         return this.checkBoxDecode;
214     }
215 }