SuspendableGetRows.java

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
 * Get data as chunks by performance query from SQL request.
33
 * 
34
 * <pre>
35
 * Single row format: \4[0-9A-F]*\5[0-9A-F]*c?\4
36
 * Row separator: \6
37
 * Tape example: \4xxRow#Xxx\5x\4\6\4xxRow#X+1xx\5x\4\6...\4\1\3\3\7</pre>
38
 * 
39
 * MID and LIMIT move two sliding windows in a 2D array tape in that order.
40
 * MID skips characters when collected, then LIMIT skips lines when collected.
41
 * The process can be interrupted by the user (stop/pause).
42
 */
43
public class SuspendableGetRows extends AbstractSuspendable {
44
45
    private static final Logger LOGGER = LogManager.getRootLogger();
46
47
    public SuspendableGetRows(InjectionModel injectionModel) {
48
        super(injectionModel);
49
    }
50
51
    @Override
52
    public String run(Object... args) throws AbstractSlidingException {
53
        // TODO Map class
54
        String initialSqlQuery = (String) args[0];
55
        String[] sourcePage = (String[]) args[1];
56
        boolean isMultipleRows = (Boolean) args[2];
57
        int countRowsToFind = (Integer) args[3];
58
        AbstractElementDatabase elementDatabase = (AbstractElementDatabase) args[4];
59
        String metadataInjectionProcess = (String) args[5];
60
        
61 1 1. run : removed call to com/jsql/util/ThreadUtil::put → NO_COVERAGE
        this.injectionModel.getMediatorUtils().getThreadUtil().put(elementDatabase, this);
62
63
        AbstractStrategy strategy = this.injectionModel.getMediatorStrategy().getStrategy();
64
        
65
        // Fix #14417
66 1 1. run : negated conditional → NO_COVERAGE
        if (strategy == null) {
67
            return StringUtils.EMPTY;
68
        }
69
        
70
        // Stop injection if all rows are found, skip rows and characters collected
71
        var slidingWindowAllRows = new StringBuilder();
72
        var slidingWindowCurrentRow = new StringBuilder();
73
        
74
        String previousChunk = StringUtils.EMPTY;
75
        var countAllRows = 0;
76
        var charPositionInCurrentRow = 1;
77
        var countInfiniteLoop = 0;
78
        
79
        String queryGetRows = this.getQuery(initialSqlQuery, countAllRows);
80
        
81
        while (true) {
82 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::checkSuspend → NO_COVERAGE
            this.checkSuspend(strategy, slidingWindowAllRows, slidingWindowCurrentRow);
83
            
84
            sourcePage[0] = strategy.inject(queryGetRows, Integer.toString(charPositionInCurrentRow), this, metadataInjectionProcess);
85
            // Parse all the data we have retrieved
86
            Matcher regexLeadFound = this.parseLeadFound(sourcePage[0], strategy.getPerformanceLength());
87
            Matcher regexTrailOnlyFound = this.parseTrailOnlyFound(sourcePage[0]);
88
            
89
            if (
90 3 1. run : negated conditional → NO_COVERAGE
2. run : negated conditional → NO_COVERAGE
3. run : negated conditional → NO_COVERAGE
                (!regexLeadFound.find() || regexTrailOnlyFound.find())
91
                && isMultipleRows
92 1 1. run : negated conditional → NO_COVERAGE
                && StringUtils.isNotEmpty(slidingWindowAllRows.toString())
93
            ) {
94 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
95
                break;
96
            }
97
98
            // Add the result to the data already found.
99
            // Fix #40947: OutOfMemoryError on append()
100
            // Fix #95382: IllegalArgumentException on URLDecoder.decode()
101
            try {
102
                String currentChunk = regexLeadFound.group(1);
103
                currentChunk = this.decodeUnicode(currentChunk, initialSqlQuery);
104
                currentChunk = this.decodeUrl(currentChunk);
105
106
                countInfiniteLoop = this.checkInfinite(countInfiniteLoop, previousChunk, currentChunk, slidingWindowCurrentRow, slidingWindowAllRows);
107
                
108
                previousChunk = currentChunk;
109
                slidingWindowCurrentRow.append(currentChunk);
110 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendChunk → NO_COVERAGE
                this.sendChunk(currentChunk);
111
            } catch (IllegalArgumentException | IllegalStateException | OutOfMemoryError e) {
112 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::endInjection → NO_COVERAGE
                this.endInjection(elementDatabase, e);
113
            }
114
115
            // Check how many rows we have collected from the beginning of that chunk
116
            int countChunkRows = this.getCountRows(slidingWindowCurrentRow);
117 2 1. run : Replaced integer addition with subtraction → NO_COVERAGE
2. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
            this.sendProgress(countRowsToFind, countAllRows + countChunkRows, elementDatabase);
118
119
            // End of rows detected: \1\3\3\7
120
            // => \4xxxxxxxx\500\4\6\4...\4\1\3\3\7
121 2 1. run : negated conditional → NO_COVERAGE
2. run : changed conditional boundary → NO_COVERAGE
            if (
122
                countChunkRows > 0
123 1 1. run : negated conditional → NO_COVERAGE
                || slidingWindowCurrentRow.toString().matches("(?s).*"+ TRAIL_RGX +".*")
124
            ) {
125 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrapeTrailJunk → NO_COVERAGE
                this.scrapeTrailJunk(slidingWindowCurrentRow);
126
                slidingWindowAllRows.append(slidingWindowCurrentRow);
127
                
128 1 1. run : negated conditional → NO_COVERAGE
                if (isMultipleRows) {
129 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE
                    this.scrap(slidingWindowAllRows);
130 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE
                    this.scrap(slidingWindowCurrentRow);
131 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::appendRowFixed → NO_COVERAGE
                    this.appendRowFixed(slidingWindowAllRows, slidingWindowCurrentRow);
132
133
                    countAllRows = this.getCountRows(slidingWindowAllRows);
134 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                    this.sendProgress(countRowsToFind, countAllRows, elementDatabase);
135
136
                    // Ending condition: every expected rows have been retrieved.
137 1 1. run : negated conditional → NO_COVERAGE
                    if (countAllRows == countRowsToFind) {
138
                        break;
139
                    }
140
                    // Add the LIMIT statement to the next SQL query and reset variables.
141
                    // Put the character cursor to the beginning of the line, and reset the result of the current query
142
                    queryGetRows = this.getQuery(initialSqlQuery, countAllRows);
143 1 1. run : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
                    slidingWindowCurrentRow.setLength(0);
144
                } else {
145 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                    this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
146
                    break;
147
                }
148
            }
149 1 1. run : Replaced integer addition with subtraction → NO_COVERAGE
            charPositionInCurrentRow = slidingWindowCurrentRow.length() + 1;
150
        }
151 1 1. run : removed call to com/jsql/util/ThreadUtil::remove → NO_COVERAGE
        this.injectionModel.getMediatorUtils().getThreadUtil().remove(elementDatabase);
152 1 1. run : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::run → NO_COVERAGE
        return slidingWindowAllRows.toString();
153
    }
154
155
    private String decodeUrl(String currentChunk) {
156 1 1. decodeUrl : negated conditional → NO_COVERAGE
        if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlDecodeDisabled()) {
157
            try {
158 1 1. decodeUrl : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUrl → NO_COVERAGE
                return URLDecoder.decode(currentChunk, StandardCharsets.UTF_8);  // Transform %00 entities to text
159
            } catch (IllegalArgumentException e) {
160
                LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Decoding fails on UT8, keeping raw result");
161
            }
162
        }
163 1 1. decodeUrl : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUrl → NO_COVERAGE
        return currentChunk;
164
    }
165
166
    private String decodeUnicode(String currentChunk, String initialSqlQuery) {
167
        if (
168 1 1. decodeUnicode : negated conditional → NO_COVERAGE
            !this.injectionModel.getMediatorUtils().getPreferencesUtil().isUnicodeDecodeDisabled()
169 2 1. decodeUnicode : negated conditional → NO_COVERAGE
2. decodeUnicode : negated conditional → NO_COVERAGE
            && !"select@@plugin_dir".equals(initialSqlQuery)  // can give C:\path\
170 1 1. decodeUnicode : negated conditional → NO_COVERAGE
            && initialSqlQuery != null && !initialSqlQuery.matches("(?si).*select.*sys_eval\\('.*'\\).*")
171
        ) {
172 1 1. decodeUnicode : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE
            return StringEscapeUtils.unescapeJava(  // transform \u0000 entities to text
173
                currentChunk
174
                .replaceAll("\\\\u.{0,3}$", StringUtils.EMPTY)  // remove incorrect entities
175
                .replaceAll("\\\\(\\d{4})", "\\\\u$1")  // transform PDO Error 10.11.3-MariaDB-1 \0000 entities
176
            );
177
        }
178 1 1. decodeUnicode : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE
        return currentChunk;
179
    }
180
181
    private String getQuery(String initialSqlQuery, int countAllRows) {
182 1 1. getQuery : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::getQuery → NO_COVERAGE
        return initialSqlQuery.replace(LIMIT, this.injectionModel.getMediatorVendor().getVendor().instance().sqlLimit(countAllRows));
183
    }
184
185
    private void appendRowFixed(StringBuilder slidingWindowAllRows, StringBuilder slidingWindowCurrentRow) {
186
        // Check either if there is more than 1 row and if there is less than 1 complete row
187
        var regexAtLeastOneRow = Pattern.compile(
188
            String.format(
189
                "%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]%s%s%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$",
190
                MODE,
191
                ENCLOSE_VALUE_RGX,
192
                SEPARATOR_CELL_RGX,
193
                ENCLOSE_VALUE_RGX
194
            )
195
        )
196
        .matcher(slidingWindowCurrentRow);
197
        
198
        var regexRowIncomplete = Pattern.compile(
199
            MODE
200
            + ENCLOSE_VALUE_RGX
201
            + "[^\\x01-\\x03\\x05-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$"
202
        )
203
        .matcher(slidingWindowCurrentRow);
204
205
        // If there is more than 1 row, delete the last incomplete one in order to restart properly from it at the next loop,
206
        // else if there is 1 row but incomplete, mark it as cut with the letter c
207 1 1. appendRowFixed : negated conditional → NO_COVERAGE
        if (regexAtLeastOneRow.find()) {
208
            var allLine = slidingWindowAllRows.toString();
209 1 1. appendRowFixed : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
            slidingWindowAllRows.setLength(0);
210
            slidingWindowAllRows.append(
211
                Pattern.compile(
212
                    MODE
213
                    + ENCLOSE_VALUE_RGX
214
                    + "[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]+?$"
215
                )
216
                .matcher(allLine)
217
                .replaceAll(StringUtils.EMPTY)
218
            );
219
            LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Chunk unreliable, reloading row part...");
220 1 1. appendRowFixed : negated conditional → NO_COVERAGE
        } else if (regexRowIncomplete.find()) {
221
            slidingWindowAllRows.append(StringUtil.hexstr("05")).append("1").append(StringUtil.hexstr("0804"));
222
            LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Chunk unreliable, keeping row parts only");
223
        }
224
    }
225
226
    private void scrapeTrailJunk(StringBuilder slidingWindowCurrentRow) {
227
        // Remove everything after chunk
228
        // => \4xxxxxxxx\500\4\6\4...\4 => \1\3\3\7junk
229
        var currentRow = slidingWindowCurrentRow.toString();
230 1 1. scrapeTrailJunk : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
        slidingWindowCurrentRow.setLength(0);
231
        slidingWindowCurrentRow.append(
232
            Pattern.compile(MODE + TRAIL_RGX +".*")
233
            .matcher(currentRow)
234
            .replaceAll(StringUtils.EMPTY)
235
        );
236
    }
237
238
    private int getCountRows(StringBuilder slidingWindowCurrentRow) {
239
        var regexAtLeastOneRow = Pattern.compile(
240
            String.format(
241
                "%s(%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?%s[^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?\\x08?%s)",
242
                MODE,
243
                ENCLOSE_VALUE_RGX,
244
                SEPARATOR_QTE_RGX,
245
                ENCLOSE_VALUE_RGX
246
            )
247
        )
248
        .matcher(slidingWindowCurrentRow);
249
        var nbCompleteLine = 0;
250 1 1. getCountRows : negated conditional → NO_COVERAGE
        while (regexAtLeastOneRow.find()) {
251 1 1. getCountRows : Changed increment from 1 to -1 → NO_COVERAGE
            nbCompleteLine++;
252
        }
253 1 1. getCountRows : replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::getCountRows → NO_COVERAGE
        return nbCompleteLine;
254
    }
255
256
    private void endInjection(AbstractElementDatabase searchName, Throwable e) throws InjectionFailureException {
257
        // Premature end of results
258
        // if it's not the root (empty tree)
259 1 1. endInjection : negated conditional → NO_COVERAGE
        if (searchName != null) {
260
            var request = new Request();
261 1 1. endInjection : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
            request.setMessage(Interaction.END_PROGRESS);
262 1 1. endInjection : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
            request.setParameters(searchName);
263 1 1. endInjection : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(request);
264
        }
265
        var messageError = new StringBuilder("Fetching fails: no data to parse");
266 1 1. endInjection : negated conditional → NO_COVERAGE
        if (searchName != null) {
267
            messageError.append(" for ").append(StringUtil.detectUtf8(searchName.toString()));
268
        }
269 3 1. endInjection : negated conditional → NO_COVERAGE
2. endInjection : negated conditional → NO_COVERAGE
3. endInjection : changed conditional boundary → NO_COVERAGE
        if (searchName instanceof Table && searchName.getChildCount() > 0) {
270
            messageError.append(", check Network tab for logs");
271
        }
272
        throw new InjectionFailureException(messageError.toString(), e);
273
    }
274
275
    private void sendChunk(String currentChunk) {
276
        var request = new Request();
277 1 1. sendChunk : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
        request.setMessage(Interaction.MESSAGE_CHUNK);
278 1 1. sendChunk : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
        request.setParameters(
279
            Pattern.compile(MODE + TRAIL_RGX +".*")
280
            .matcher(currentChunk)
281
            .replaceAll(StringUtils.EMPTY)
282
            .replace("\\n", "\\\\\\n")
283
            .replace("\\r", "\\\\\\r")
284
            .replace("\\t", "\\\\\\t")
285
        );
286 1 1. sendChunk : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
        this.injectionModel.sendToViews(request);
287
    }
288
289
    // TODO pb for same char string like aaaaaaaaaaaaa...aaaaaaaaaaaaa
290
    // detected as infinite
291
    private int checkInfinite(
292
        int loop,
293
        String previousChunk,
294
        String currentChunk,
295
        StringBuilder slidingWindowCurrentRow,
296
        StringBuilder slidingWindowAllRows
297
    ) throws LoopDetectedSlidingException {
298
        int infiniteLoop = loop;
299 1 1. checkInfinite : negated conditional → NO_COVERAGE
        if (previousChunk.equals(currentChunk)) {
300 1 1. checkInfinite : Changed increment from 1 to -1 → NO_COVERAGE
            infiniteLoop++;
301 2 1. checkInfinite : changed conditional boundary → NO_COVERAGE
2. checkInfinite : negated conditional → NO_COVERAGE
            if (infiniteLoop >= 20) {
302 1 1. checkInfinite : removed call to com/jsql/model/suspendable/SuspendableGetRows::stop → NO_COVERAGE
                this.stop();
303
                throw new LoopDetectedSlidingException(
304
                    slidingWindowAllRows.toString(),
305
                    slidingWindowCurrentRow.toString()
306
                );
307
            }
308
        }
309 1 1. checkInfinite : replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::checkInfinite → NO_COVERAGE
        return infiniteLoop;
310
    }
311
312
    private Matcher parseTrailOnlyFound(String sourcePage) {
313
        String sourcePageUnicodeDecoded = this.decodeUnicode(sourcePage, null);
314
        // TODO: prevent to find the last line directly: MODE + LEAD + .* + TRAIL_RGX
315
        // It creates extra query which can be endless if not nullified
316 1 1. parseTrailOnlyFound : replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseTrailOnlyFound → NO_COVERAGE
        return Pattern.compile(
317
            String.format("(?s)%s(?i)%s", LEAD, TRAIL_RGX)
318
        )
319
        .matcher(sourcePageUnicodeDecoded);
320
    }
321
322
    /**
323
     * After ${lead} tag, gets characters between 1 and maxPerf
324
     * performanceQuery() gets 65536 characters or fewer
325
     * ${lead}blahblah1337      ] : end or limit+1
326
     * ${lead}blahblah      blah] : continue substr()
327
     */
328
    private Matcher parseLeadFound(String sourcePage, String performanceLength) throws InjectionFailureException {
329
        Matcher regexAtLeastOneRow;
330
        try {
331
            regexAtLeastOneRow = Pattern.compile(
332
                String.format("(?s)%s(?i)(.{1,%s})", LEAD, performanceLength)
333
            )
334
            .matcher(sourcePage);
335
        } catch (PatternSyntaxException e) {
336
            // Fix #35382 : PatternSyntaxException null on SQLi(.{1,null})
337
            throw new InjectionFailureException("Row parsing failed using capacity", e);
338
        }
339 1 1. parseLeadFound : replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseLeadFound → NO_COVERAGE
        return regexAtLeastOneRow;
340
    }
341
342
    private void checkSuspend(
343
        AbstractStrategy strategy,
344
        StringBuilder slidingWindowAllRows,
345
        StringBuilder slidingWindowCurrentRow
346
    ) throws StoppedByUserSlidingException, InjectionFailureException {
347 1 1. checkSuspend : negated conditional → NO_COVERAGE
        if (this.isSuspended()) {
348
            throw new StoppedByUserSlidingException(
349
                slidingWindowAllRows.toString(),
350
                slidingWindowCurrentRow.toString()
351
            );
352 1 1. checkSuspend : negated conditional → NO_COVERAGE
        } else if (strategy == null) {
353
            // Fix #1905 : NullPointerException on injectionStrategy.inject()
354
            throw new InjectionFailureException("Undefined strategy");
355
        }
356
    }
357
358
    private void scrap(StringBuilder slidingWindowAllRows) {
359
        // Remove everything not properly attached to the last row:
360
        // 1. very start of a new row: XXXXX\4[\6\4]$
361
        // 2. very end of the last row: XXXXX[\500]$
362
        var allRowsLimit = slidingWindowAllRows.toString();
363 1 1. scrap : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
        slidingWindowAllRows.setLength(0);
364
        slidingWindowAllRows.append(
365
            Pattern.compile(
366
                String.format(
367
                    "%s(%s%s|%s\\d*)$",
368
                    MODE,
369
                    SEPARATOR_CELL_RGX,
370
                    ENCLOSE_VALUE_RGX,
371
                    SEPARATOR_QTE_RGX
372
                )
373
            )
374
            .matcher(allRowsLimit)
375
            .replaceAll(StringUtils.EMPTY)
376
        );
377
    }
378
379
    private void sendProgress(int numberToFind, int countProgress, AbstractElementDatabase searchName) {
380 3 1. sendProgress : negated conditional → NO_COVERAGE
2. sendProgress : negated conditional → NO_COVERAGE
3. sendProgress : changed conditional boundary → NO_COVERAGE
        if (numberToFind > 0 && searchName != null) {
381
            var request = new Request();
382 1 1. sendProgress : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
            request.setMessage(Interaction.UPDATE_PROGRESS);
383 1 1. sendProgress : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
            request.setParameters(searchName, countProgress);
384 1 1. sendProgress : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(request);
385
        }
386
    }
387
    
388
    public static List<List<String>> parse(String rows) throws InjectionFailureException {
389
        // Parse all the data we have retrieved
390
        var regexSearch = Pattern.compile(
391
                String.format(
392
                    "%s%s([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)%s([^\\x01-\\x09\\x0B-\\x0C\\x0E-\\x1F]*?)(\\x08)?%s",
393
                    MODE,
394
                    ENCLOSE_VALUE_RGX,
395
                    SEPARATOR_QTE_RGX,
396
                    ENCLOSE_VALUE_RGX
397
                )
398
            )
399
            .matcher(rows);
400 1 1. parse : negated conditional → NO_COVERAGE
        if (!regexSearch.find()) {
401
            throw new InjectionFailureException();
402
        }
403
        regexSearch.reset();
404
        var rowsFound = 0;
405
        List<List<String>> listValues = new ArrayList<>();
406
407
        // Build a 2D array of strings from the data we have parsed
408
        // => row number, occurrence, value1, value2...
409 1 1. parse : negated conditional → NO_COVERAGE
        while (regexSearch.find()) {
410
            String values = regexSearch.group(1);
411
            var instances = Integer.parseInt(regexSearch.group(2));
412
413
            listValues.add(new ArrayList<>());
414 1 1. parse : Replaced integer addition with subtraction → NO_COVERAGE
            listValues.get(rowsFound).add(Integer.toString(rowsFound + 1));
415
            listValues.get(rowsFound).add("x"+ instances);
416
            for (String cellValue: values.split("\\x7F", -1)) {
417
                listValues.get(rowsFound).add(cellValue);
418
            }
419 1 1. parse : Changed increment from 1 to -1 → NO_COVERAGE
            rowsFound++;
420
        }
421 1 1. parse : replaced return value with Collections.emptyList for com/jsql/model/suspendable/SuspendableGetRows::parse → NO_COVERAGE
        return listValues;
422
    }
423
}

Mutations

61

1.1
Location : run
Killed by : none
removed call to com/jsql/util/ThreadUtil::put → NO_COVERAGE

66

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

82

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::checkSuspend → NO_COVERAGE

90

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : run
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : run
Killed by : none
negated conditional → NO_COVERAGE

92

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

94

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE

110

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::sendChunk → NO_COVERAGE

112

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::endInjection → NO_COVERAGE

117

1.1
Location : run
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

2.2
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE

121

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : run
Killed by : none
changed conditional boundary → NO_COVERAGE

123

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

125

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::scrapeTrailJunk → NO_COVERAGE

128

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

129

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE

130

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE

131

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::appendRowFixed → NO_COVERAGE

134

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE

137

1.1
Location : run
Killed by : none
negated conditional → NO_COVERAGE

143

1.1
Location : run
Killed by : none
removed call to java/lang/StringBuilder::setLength → NO_COVERAGE

145

1.1
Location : run
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE

149

1.1
Location : run
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

151

1.1
Location : run
Killed by : none
removed call to com/jsql/util/ThreadUtil::remove → NO_COVERAGE

152

1.1
Location : run
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::run → NO_COVERAGE

156

1.1
Location : decodeUrl
Killed by : none
negated conditional → NO_COVERAGE

158

1.1
Location : decodeUrl
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUrl → NO_COVERAGE

163

1.1
Location : decodeUrl
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUrl → NO_COVERAGE

168

1.1
Location : decodeUnicode
Killed by : none
negated conditional → NO_COVERAGE

169

1.1
Location : decodeUnicode
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : decodeUnicode
Killed by : none
negated conditional → NO_COVERAGE

170

1.1
Location : decodeUnicode
Killed by : none
negated conditional → NO_COVERAGE

172

1.1
Location : decodeUnicode
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE

178

1.1
Location : decodeUnicode
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE

182

1.1
Location : getQuery
Killed by : none
replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::getQuery → NO_COVERAGE

207

1.1
Location : appendRowFixed
Killed by : none
negated conditional → NO_COVERAGE

209

1.1
Location : appendRowFixed
Killed by : none
removed call to java/lang/StringBuilder::setLength → NO_COVERAGE

220

1.1
Location : appendRowFixed
Killed by : none
negated conditional → NO_COVERAGE

230

1.1
Location : scrapeTrailJunk
Killed by : none
removed call to java/lang/StringBuilder::setLength → NO_COVERAGE

250

1.1
Location : getCountRows
Killed by : none
negated conditional → NO_COVERAGE

251

1.1
Location : getCountRows
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

253

1.1
Location : getCountRows
Killed by : none
replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::getCountRows → NO_COVERAGE

259

1.1
Location : endInjection
Killed by : none
negated conditional → NO_COVERAGE

261

1.1
Location : endInjection
Killed by : none
removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE

262

1.1
Location : endInjection
Killed by : none
removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE

263

1.1
Location : endInjection
Killed by : none
removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE

266

1.1
Location : endInjection
Killed by : none
negated conditional → NO_COVERAGE

269

1.1
Location : endInjection
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : endInjection
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : endInjection
Killed by : none
changed conditional boundary → NO_COVERAGE

277

1.1
Location : sendChunk
Killed by : none
removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE

278

1.1
Location : sendChunk
Killed by : none
removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE

286

1.1
Location : sendChunk
Killed by : none
removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE

299

1.1
Location : checkInfinite
Killed by : none
negated conditional → NO_COVERAGE

300

1.1
Location : checkInfinite
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

301

1.1
Location : checkInfinite
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : checkInfinite
Killed by : none
negated conditional → NO_COVERAGE

302

1.1
Location : checkInfinite
Killed by : none
removed call to com/jsql/model/suspendable/SuspendableGetRows::stop → NO_COVERAGE

309

1.1
Location : checkInfinite
Killed by : none
replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::checkInfinite → NO_COVERAGE

316

1.1
Location : parseTrailOnlyFound
Killed by : none
replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseTrailOnlyFound → NO_COVERAGE

339

1.1
Location : parseLeadFound
Killed by : none
replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseLeadFound → NO_COVERAGE

347

1.1
Location : checkSuspend
Killed by : none
negated conditional → NO_COVERAGE

352

1.1
Location : checkSuspend
Killed by : none
negated conditional → NO_COVERAGE

363

1.1
Location : scrap
Killed by : none
removed call to java/lang/StringBuilder::setLength → NO_COVERAGE

380

1.1
Location : sendProgress
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : sendProgress
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : sendProgress
Killed by : none
changed conditional boundary → NO_COVERAGE

382

1.1
Location : sendProgress
Killed by : none
removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE

383

1.1
Location : sendProgress
Killed by : none
removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE

384

1.1
Location : sendProgress
Killed by : none
removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE

400

1.1
Location : parse
Killed by : none
negated conditional → NO_COVERAGE

409

1.1
Location : parse
Killed by : none
negated conditional → NO_COVERAGE

414

1.1
Location : parse
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

419

1.1
Location : parse
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

421

1.1
Location : parse
Killed by : none
replaced return value with Collections.emptyList for com/jsql/model/suspendable/SuspendableGetRows::parse → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1