1 package com.jsql.model.suspendable;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.model.bean.database.AbstractElementDatabase;
5 import com.jsql.model.bean.database.Table;
6 import com.jsql.model.bean.util.Interaction;
7 import com.jsql.model.bean.util.Request;
8 import com.jsql.model.exception.AbstractSlidingException;
9 import com.jsql.model.exception.InjectionFailureException;
10 import com.jsql.model.exception.LoopDetectedSlidingException;
11 import com.jsql.model.exception.StoppedByUserSlidingException;
12 import com.jsql.model.injection.strategy.AbstractStrategy;
13 import com.jsql.util.LogLevelUtil;
14 import com.jsql.util.StringUtil;
15 import org.apache.commons.lang3.StringUtils;
16 import org.apache.commons.text.StringEscapeUtils;
17 import org.apache.logging.log4j.LogManager;
18 import org.apache.logging.log4j.Logger;
19
20 import java.net.URLDecoder;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26 import java.util.regex.PatternSyntaxException;
27
28 import static com.jsql.model.accessible.DataAccess.*;
29 import static com.jsql.model.injection.vendor.model.VendorYaml.LIMIT;
30
31
32
33
34
35
36
37
38
39
40
41
42
43 public class SuspendableGetRows extends AbstractSuspendable {
44
45
46
47
48 private static final Logger LOGGER = LogManager.getRootLogger();
49
50 public SuspendableGetRows(InjectionModel injectionModel) {
51 super(injectionModel);
52 }
53
54 @Override
55 public String run(Object... args) throws AbstractSlidingException {
56
57 String initialSqlQuery = (String) args[0];
58 String[] sourcePage = (String[]) args[1];
59 boolean isMultipleRows = (Boolean) args[2];
60 int countRowsToFind = (Integer) args[3];
61 AbstractElementDatabase elementDatabase = (AbstractElementDatabase) args[4];
62 String metadataInjectionProcess = (String) args[5];
63
64 this.injectionModel.getMediatorUtils().getThreadUtil().put(elementDatabase, this);
65
66 AbstractStrategy strategy = this.injectionModel.getMediatorStrategy().getStrategy();
67
68
69 if (strategy == null) {
70 return StringUtils.EMPTY;
71 }
72
73
74 var slidingWindowAllRows = new StringBuilder();
75 var slidingWindowCurrentRow = new StringBuilder();
76
77 String previousChunk = StringUtils.EMPTY;
78 var countAllRows = 0;
79 var charPositionInCurrentRow = 1;
80 var countInfiniteLoop = 0;
81
82 String queryGetRows = this.getQuery(initialSqlQuery, countAllRows);
83
84 while (true) {
85 this.checkSuspend(strategy, slidingWindowAllRows, slidingWindowCurrentRow);
86
87 sourcePage[0] = strategy.inject(queryGetRows, Integer.toString(charPositionInCurrentRow), this, metadataInjectionProcess);
88
89 Matcher regexLeadFound = this.parseLeadFound(sourcePage[0], strategy.getPerformanceLength());
90 Matcher regexTrailOnlyFound = this.parseTrailOnlyFound(sourcePage[0]);
91
92 if (
93 (!regexLeadFound.find() || regexTrailOnlyFound.find())
94 && isMultipleRows
95 && StringUtils.isNotEmpty(slidingWindowAllRows.toString())
96 ) {
97 this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
98 break;
99 }
100
101
102
103
104 try {
105 String currentChunk = regexLeadFound.group(1);
106 currentChunk = this.decodeUnicode(currentChunk, initialSqlQuery);
107 currentChunk = this.decodeUrl(currentChunk);
108
109 countInfiniteLoop = this.checkInfinite(countInfiniteLoop, previousChunk, currentChunk, slidingWindowCurrentRow, slidingWindowAllRows);
110
111 previousChunk = currentChunk;
112 slidingWindowCurrentRow.append(currentChunk);
113 this.sendChunk(currentChunk);
114 } catch (IllegalArgumentException | IllegalStateException | OutOfMemoryError e) {
115 this.endInjection(elementDatabase, e);
116 }
117
118
119 int countChunkRows = this.getCountRows(slidingWindowCurrentRow);
120 this.sendProgress(countRowsToFind, countAllRows + countChunkRows, elementDatabase);
121
122
123
124 if (
125 countChunkRows > 0
126 || slidingWindowCurrentRow.toString().matches("(?s).*"+ TRAIL_RGX +".*")
127 ) {
128 this.scrapeTrailJunk(slidingWindowCurrentRow);
129 slidingWindowAllRows.append(slidingWindowCurrentRow);
130
131 if (isMultipleRows) {
132 this.scrap(slidingWindowAllRows);
133 this.scrap(slidingWindowCurrentRow);
134 this.appendRowFixed(slidingWindowAllRows, slidingWindowCurrentRow);
135
136 countAllRows = this.getCountRows(slidingWindowAllRows);
137 this.sendProgress(countRowsToFind, countAllRows, elementDatabase);
138
139
140 if (countAllRows == countRowsToFind) {
141 break;
142 }
143
144
145 queryGetRows = this.getQuery(initialSqlQuery, countAllRows);
146 slidingWindowCurrentRow.setLength(0);
147 } else {
148 this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
149 break;
150 }
151 }
152 charPositionInCurrentRow = slidingWindowCurrentRow.length() + 1;
153 }
154 this.injectionModel.getMediatorUtils().getThreadUtil().remove(elementDatabase);
155 return slidingWindowAllRows.toString();
156 }
157
158 private String decodeUrl(String currentChunk) {
159 if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlDecodeDisabled()) {
160 try {
161 return URLDecoder.decode(currentChunk, StandardCharsets.UTF_8);
162 } catch (IllegalArgumentException e) {
163 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Decoding fails on UT8, keeping raw result");
164 }
165 }
166 return currentChunk;
167 }
168
169 private String decodeUnicode(String currentChunk, String initialSqlQuery) {
170 if (
171 !this.injectionModel.getMediatorUtils().getPreferencesUtil().isUnicodeDecodeDisabled()
172 && !"select@@plugin_dir".equals(initialSqlQuery)
173 && initialSqlQuery != null && !initialSqlQuery.matches("(?si).*select.*sys_eval\\('.*'\\).*")
174 ) {
175 return StringEscapeUtils.unescapeJava(
176 currentChunk
177 .replaceAll("\\\\u.{0,3}$", StringUtils.EMPTY)
178 .replaceAll("\\\\(\\d{4})", "\\\\u$1")
179 );
180 }
181 return currentChunk;
182 }
183
184 private String getQuery(String initialSqlQuery, int countAllRows) {
185 return initialSqlQuery.replace(LIMIT, this.injectionModel.getMediatorVendor().getVendor().instance().sqlLimit(countAllRows));
186 }
187
188 private void appendRowFixed(StringBuilder slidingWindowAllRows, StringBuilder slidingWindowCurrentRow) {
189
190 var regexAtLeastOneRow = Pattern.compile(
191 String.format(
192 "%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]%s%s%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$",
193 MODE,
194 ENCLOSE_VALUE_RGX,
195 SEPARATOR_CELL_RGX,
196 ENCLOSE_VALUE_RGX
197 )
198 )
199 .matcher(slidingWindowCurrentRow);
200
201 var regexRowIncomplete = Pattern.compile(
202 MODE
203 + ENCLOSE_VALUE_RGX
204 + "[^\\x01-\\x03\\x05-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$"
205 )
206 .matcher(slidingWindowCurrentRow);
207
208
209
210 if (regexAtLeastOneRow.find()) {
211 var allLine = slidingWindowAllRows.toString();
212 slidingWindowAllRows.setLength(0);
213 slidingWindowAllRows.append(
214 Pattern.compile(
215 MODE
216 + ENCLOSE_VALUE_RGX
217 + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$"
218 )
219 .matcher(allLine)
220 .replaceAll(StringUtils.EMPTY)
221 );
222 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Chunk unreliable, reloading row part...");
223 } else if (regexRowIncomplete.find()) {
224 slidingWindowAllRows.append(StringUtil.hexstr("05")).append("1").append(StringUtil.hexstr("0804"));
225 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Chunk unreliable, keeping row parts only");
226 }
227 }
228
229 private void scrapeTrailJunk(StringBuilder slidingWindowCurrentRow) {
230
231
232 var currentRow = slidingWindowCurrentRow.toString();
233 slidingWindowCurrentRow.setLength(0);
234 slidingWindowCurrentRow.append(
235 Pattern.compile(MODE + TRAIL_RGX +".*")
236 .matcher(currentRow)
237 .replaceAll(StringUtils.EMPTY)
238 );
239 }
240
241 private int getCountRows(StringBuilder slidingWindowCurrentRow) {
242 var regexAtLeastOneRow = Pattern.compile(
243 String.format(
244 "%s(%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?\\x08?%s)",
245 MODE,
246 ENCLOSE_VALUE_RGX,
247 SEPARATOR_QTE_RGX,
248 ENCLOSE_VALUE_RGX
249 )
250 )
251 .matcher(slidingWindowCurrentRow);
252 var nbCompleteLine = 0;
253 while (regexAtLeastOneRow.find()) {
254 nbCompleteLine++;
255 }
256 return nbCompleteLine;
257 }
258
259 private void endInjection(AbstractElementDatabase searchName, Throwable e) throws InjectionFailureException {
260
261
262 if (searchName != null) {
263 var request = new Request();
264 request.setMessage(Interaction.END_PROGRESS);
265 request.setParameters(searchName);
266 this.injectionModel.sendToViews(request);
267 }
268 var messageError = new StringBuilder("Fetching fails: no data to parse");
269 if (searchName != null) {
270 messageError.append(" for ").append(StringUtil.detectUtf8(searchName.toString()));
271 }
272 if (searchName instanceof Table && searchName.getChildCount() > 0) {
273 messageError.append(", check Network tab for logs");
274 }
275 throw new InjectionFailureException(messageError.toString(), e);
276 }
277
278 private void sendChunk(String currentChunk) {
279 var request = new Request();
280 request.setMessage(Interaction.MESSAGE_CHUNK);
281 request.setParameters(
282 Pattern.compile(MODE + TRAIL_RGX +".*")
283 .matcher(currentChunk)
284 .replaceAll(StringUtils.EMPTY)
285 .replace("\\n", "\\\\\\n")
286 .replace("\\r", "\\\\\\r")
287 .replace("\\t", "\\\\\\t")
288 );
289 this.injectionModel.sendToViews(request);
290 }
291
292
293
294 private int checkInfinite(
295 int loop,
296 String previousChunk,
297 String currentChunk,
298 StringBuilder slidingWindowCurrentRow,
299 StringBuilder slidingWindowAllRows
300 ) throws LoopDetectedSlidingException {
301 int infiniteLoop = loop;
302 if (previousChunk.equals(currentChunk)) {
303 infiniteLoop++;
304 if (infiniteLoop >= 20) {
305 this.stop();
306 throw new LoopDetectedSlidingException(
307 slidingWindowAllRows.toString(),
308 slidingWindowCurrentRow.toString()
309 );
310 }
311 }
312 return infiniteLoop;
313 }
314
315 private Matcher parseTrailOnlyFound(String sourcePage) {
316 String sourcePageUnicodeDecoded = this.decodeUnicode(sourcePage, null);
317
318
319 return Pattern.compile(
320 String.format("(?s)%s(?i)%s", LEAD, TRAIL_RGX)
321 )
322 .matcher(sourcePageUnicodeDecoded);
323 }
324
325
326
327
328
329
330
331 private Matcher parseLeadFound(String sourcePage, String performanceLength) throws InjectionFailureException {
332 Matcher regexAtLeastOneRow;
333 try {
334 regexAtLeastOneRow = Pattern.compile(
335 String.format("(?s)%s(?i)(.{1,%s})", LEAD, performanceLength)
336 )
337 .matcher(sourcePage);
338 } catch (PatternSyntaxException e) {
339
340 throw new InjectionFailureException("Row parsing failed using capacity", e);
341 }
342 return regexAtLeastOneRow;
343 }
344
345 private void checkSuspend(
346 AbstractStrategy strategy,
347 StringBuilder slidingWindowAllRows,
348 StringBuilder slidingWindowCurrentRow
349 ) throws StoppedByUserSlidingException, InjectionFailureException {
350 if (this.isSuspended()) {
351 throw new StoppedByUserSlidingException(
352 slidingWindowAllRows.toString(),
353 slidingWindowCurrentRow.toString()
354 );
355 } else if (strategy == null) {
356
357 throw new InjectionFailureException("Undefined strategy");
358 }
359 }
360
361 private void scrap(StringBuilder slidingWindowAllRows) {
362
363
364
365 var allRowsLimit = slidingWindowAllRows.toString();
366 slidingWindowAllRows.setLength(0);
367 slidingWindowAllRows.append(
368 Pattern.compile(
369 String.format(
370 "%s(%s%s|%s\\d*)$",
371 MODE,
372 SEPARATOR_CELL_RGX,
373 ENCLOSE_VALUE_RGX,
374 SEPARATOR_QTE_RGX
375 )
376 )
377 .matcher(allRowsLimit)
378 .replaceAll(StringUtils.EMPTY)
379 );
380 }
381
382 private void sendProgress(int numberToFind, int countProgress, AbstractElementDatabase searchName) {
383 if (numberToFind > 0 && searchName != null) {
384 var request = new Request();
385 request.setMessage(Interaction.UPDATE_PROGRESS);
386 request.setParameters(searchName, countProgress);
387 this.injectionModel.sendToViews(request);
388 }
389 }
390
391 public static List<List<String>> parse(String rows) throws InjectionFailureException {
392
393 var regexSearch = Pattern.compile(
394 String.format(
395 "%s%s([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)%s([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)(\\x08)?%s",
396 MODE,
397 ENCLOSE_VALUE_RGX,
398 SEPARATOR_QTE_RGX,
399 ENCLOSE_VALUE_RGX
400 )
401 )
402 .matcher(rows);
403 if (!regexSearch.find()) {
404 throw new InjectionFailureException();
405 }
406 regexSearch.reset();
407 var rowsFound = 0;
408 List<List<String>> listValues = new ArrayList<>();
409
410
411
412 while (regexSearch.find()) {
413 String values = regexSearch.group(1);
414 var instances = Integer.parseInt(regexSearch.group(2));
415
416 listValues.add(new ArrayList<>());
417 listValues.get(rowsFound).add(Integer.toString(rowsFound + 1));
418 listValues.get(rowsFound).add("x"+ instances);
419 for (String cellValue: values.split("\\x7F", -1)) {
420 listValues.get(rowsFound).add(cellValue);
421 }
422 rowsFound++;
423 }
424 return listValues;
425 }
426 }