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
    /**
46
     * Log4j logger sent to view.
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
        // TODO Map class
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 1 1. run : removed call to com/jsql/util/ThreadUtil::put → NO_COVERAGE
        this.injectionModel.getMediatorUtils().getThreadUtil().put(elementDatabase, this);
65
66
        AbstractStrategy strategy = this.injectionModel.getMediatorStrategy().getStrategy();
67
        
68
        // Fix #14417
69 1 1. run : negated conditional → NO_COVERAGE
        if (strategy == null) {
70
            return StringUtils.EMPTY;
71
        }
72
        
73
        // Stop injection if all rows are found, skip rows and characters collected
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 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::checkSuspend → NO_COVERAGE
            this.checkSuspend(strategy, slidingWindowAllRows, slidingWindowCurrentRow);
86
            
87
            sourcePage[0] = strategy.inject(queryGetRows, Integer.toString(charPositionInCurrentRow), this, metadataInjectionProcess);
88
            // Parse all the data we have retrieved
89
            Matcher regexLeadFound = this.parseLeadFound(sourcePage[0], strategy.getPerformanceLength());
90
            Matcher regexTrailOnlyFound = this.parseTrailOnlyFound(sourcePage[0]);
91
            
92
            if (
93 3 1. run : negated conditional → NO_COVERAGE
2. run : negated conditional → NO_COVERAGE
3. run : negated conditional → NO_COVERAGE
                (!regexLeadFound.find() || regexTrailOnlyFound.find())
94
                && isMultipleRows
95 1 1. run : negated conditional → NO_COVERAGE
                && StringUtils.isNotEmpty(slidingWindowAllRows.toString())
96
            ) {
97 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
98
                break;
99
            }
100
101
            // Add the result to the data already found.
102
            // Fix #40947: OutOfMemoryError on append()
103
            // Fix #95382: IllegalArgumentException on URLDecoder.decode()
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 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendChunk → NO_COVERAGE
                this.sendChunk(currentChunk);
114
            } catch (IllegalArgumentException | IllegalStateException | OutOfMemoryError e) {
115 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::endInjection → NO_COVERAGE
                this.endInjection(elementDatabase, e);
116
            }
117
118
            // Check how many rows we have collected from the beginning of that chunk
119
            int countChunkRows = this.getCountRows(slidingWindowCurrentRow);
120 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);
121
122
            // End of rows detected: \1\3\3\7
123
            // => \4xxxxxxxx\500\4\6\4...\4\1\3\3\7
124 2 1. run : negated conditional → NO_COVERAGE
2. run : changed conditional boundary → NO_COVERAGE
            if (
125
                countChunkRows > 0
126 1 1. run : negated conditional → NO_COVERAGE
                || slidingWindowCurrentRow.toString().matches("(?s).*"+ TRAIL_RGX +".*")
127
            ) {
128 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrapeTrailJunk → NO_COVERAGE
                this.scrapeTrailJunk(slidingWindowCurrentRow);
129
                slidingWindowAllRows.append(slidingWindowCurrentRow);
130
                
131 1 1. run : negated conditional → NO_COVERAGE
                if (isMultipleRows) {
132 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE
                    this.scrap(slidingWindowAllRows);
133 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::scrap → NO_COVERAGE
                    this.scrap(slidingWindowCurrentRow);
134 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::appendRowFixed → NO_COVERAGE
                    this.appendRowFixed(slidingWindowAllRows, slidingWindowCurrentRow);
135
136
                    countAllRows = this.getCountRows(slidingWindowAllRows);
137 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                    this.sendProgress(countRowsToFind, countAllRows, elementDatabase);
138
139
                    // Ending condition: every expected rows have been retrieved.
140 1 1. run : negated conditional → NO_COVERAGE
                    if (countAllRows == countRowsToFind) {
141
                        break;
142
                    }
143
                    // Add the LIMIT statement to the next SQL query and reset variables.
144
                    // Put the character cursor to the beginning of the line, and reset the result of the current query
145
                    queryGetRows = this.getQuery(initialSqlQuery, countAllRows);
146 1 1. run : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
                    slidingWindowCurrentRow.setLength(0);
147
                } else {
148 1 1. run : removed call to com/jsql/model/suspendable/SuspendableGetRows::sendProgress → NO_COVERAGE
                    this.sendProgress(countRowsToFind, countRowsToFind, elementDatabase);
149
                    break;
150
                }
151
            }
152 1 1. run : Replaced integer addition with subtraction → NO_COVERAGE
            charPositionInCurrentRow = slidingWindowCurrentRow.length() + 1;
153
        }
154 1 1. run : removed call to com/jsql/util/ThreadUtil::remove → NO_COVERAGE
        this.injectionModel.getMediatorUtils().getThreadUtil().remove(elementDatabase);
155 1 1. run : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::run → NO_COVERAGE
        return slidingWindowAllRows.toString();
156
    }
157
158
    private String decodeUrl(String currentChunk) {
159 1 1. decodeUrl : negated conditional → NO_COVERAGE
        if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlDecodeDisabled()) {
160
            try {
161 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
162
            } catch (IllegalArgumentException e) {
163
                LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Decoding fails on UT8, keeping raw result");
164
            }
165
        }
166 1 1. decodeUrl : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUrl → NO_COVERAGE
        return currentChunk;
167
    }
168
169
    private String decodeUnicode(String currentChunk, String initialSqlQuery) {
170
        if (
171 1 1. decodeUnicode : negated conditional → NO_COVERAGE
            !this.injectionModel.getMediatorUtils().getPreferencesUtil().isUnicodeDecodeDisabled()
172 2 1. decodeUnicode : negated conditional → NO_COVERAGE
2. decodeUnicode : negated conditional → NO_COVERAGE
            && !"select@@plugin_dir".equals(initialSqlQuery)  // can give C:\path\
173 1 1. decodeUnicode : negated conditional → NO_COVERAGE
            && initialSqlQuery != null && !initialSqlQuery.matches("(?si).*select.*sys_eval\\('.*'\\).*")
174
        ) {
175 1 1. decodeUnicode : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE
            return StringEscapeUtils.unescapeJava(  // transform \u0000 entities to text
176
                currentChunk
177
                .replaceAll("\\\\u.{0,3}$", StringUtils.EMPTY)  // remove incorrect entities
178
                .replaceAll("\\\\(\\d{4})", "\\\\u$1")  // transform PDO Error 10.11.3-MariaDB-1 \0000 entities
179
            );
180
        }
181 1 1. decodeUnicode : replaced return value with "" for com/jsql/model/suspendable/SuspendableGetRows::decodeUnicode → NO_COVERAGE
        return currentChunk;
182
    }
183
184
    private String getQuery(String initialSqlQuery, int countAllRows) {
185 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));
186
    }
187
188
    private void appendRowFixed(StringBuilder slidingWindowAllRows, StringBuilder slidingWindowCurrentRow) {
189
        // Check either if there is more than 1 row and if there is less than 1 complete row
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
        // If there is more than 1 row, delete the last incomplete one in order to restart properly from it at the next loop,
209
        // else if there is 1 row but incomplete, mark it as cut with the letter c
210 1 1. appendRowFixed : negated conditional → NO_COVERAGE
        if (regexAtLeastOneRow.find()) {
211
            var allLine = slidingWindowAllRows.toString();
212 1 1. appendRowFixed : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
            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 1 1. appendRowFixed : negated conditional → NO_COVERAGE
        } 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
        // Remove everything after chunk
231
        // => \4xxxxxxxx\500\4\6\4...\4 => \1\3\3\7junk
232
        var currentRow = slidingWindowCurrentRow.toString();
233 1 1. scrapeTrailJunk : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
        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 1 1. getCountRows : negated conditional → NO_COVERAGE
        while (regexAtLeastOneRow.find()) {
254 1 1. getCountRows : Changed increment from 1 to -1 → NO_COVERAGE
            nbCompleteLine++;
255
        }
256 1 1. getCountRows : replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::getCountRows → NO_COVERAGE
        return nbCompleteLine;
257
    }
258
259
    private void endInjection(AbstractElementDatabase searchName, Throwable e) throws InjectionFailureException {
260
        // Premature end of results
261
        // if it's not the root (empty tree)
262 1 1. endInjection : negated conditional → NO_COVERAGE
        if (searchName != null) {
263
            var request = new Request();
264 1 1. endInjection : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
            request.setMessage(Interaction.END_PROGRESS);
265 1 1. endInjection : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
            request.setParameters(searchName);
266 1 1. endInjection : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(request);
267
        }
268
        var messageError = new StringBuilder("Fetching fails: no data to parse");
269 1 1. endInjection : negated conditional → NO_COVERAGE
        if (searchName != null) {
270
            messageError.append(" for ").append(StringUtil.detectUtf8(searchName.toString()));
271
        }
272 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) {
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 1 1. sendChunk : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
        request.setMessage(Interaction.MESSAGE_CHUNK);
281 1 1. sendChunk : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
        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 1 1. sendChunk : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
        this.injectionModel.sendToViews(request);
290
    }
291
292
    // TODO pb for same char string like aaaaaaaaaaaaa...aaaaaaaaaaaaa
293
    // detected as infinite
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 1 1. checkInfinite : negated conditional → NO_COVERAGE
        if (previousChunk.equals(currentChunk)) {
303 1 1. checkInfinite : Changed increment from 1 to -1 → NO_COVERAGE
            infiniteLoop++;
304 2 1. checkInfinite : changed conditional boundary → NO_COVERAGE
2. checkInfinite : negated conditional → NO_COVERAGE
            if (infiniteLoop >= 20) {
305 1 1. checkInfinite : removed call to com/jsql/model/suspendable/SuspendableGetRows::stop → NO_COVERAGE
                this.stop();
306
                throw new LoopDetectedSlidingException(
307
                    slidingWindowAllRows.toString(),
308
                    slidingWindowCurrentRow.toString()
309
                );
310
            }
311
        }
312 1 1. checkInfinite : replaced int return with 0 for com/jsql/model/suspendable/SuspendableGetRows::checkInfinite → NO_COVERAGE
        return infiniteLoop;
313
    }
314
315
    private Matcher parseTrailOnlyFound(String sourcePage) {
316
        String sourcePageUnicodeDecoded = this.decodeUnicode(sourcePage, null);
317
        // TODO: prevent to find the last line directly: MODE + LEAD + .* + TRAIL_RGX
318
        // It creates extra query which can be endless if not nullified
319 1 1. parseTrailOnlyFound : replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseTrailOnlyFound → NO_COVERAGE
        return Pattern.compile(
320
            String.format("(?s)%s(?i)%s", LEAD, TRAIL_RGX)
321
        )
322
        .matcher(sourcePageUnicodeDecoded);
323
    }
324
325
    /**
326
     * After ${lead} tag, gets characters between 1 and maxPerf
327
     * performanceQuery() gets 65536 characters or fewer
328
     * ${lead}blahblah1337      ] : end or limit+1
329
     * ${lead}blahblah      blah] : continue substr()
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
            // Fix #35382 : PatternSyntaxException null on SQLi(.{1,null})
340
            throw new InjectionFailureException("Row parsing failed using capacity", e);
341
        }
342 1 1. parseLeadFound : replaced return value with null for com/jsql/model/suspendable/SuspendableGetRows::parseLeadFound → NO_COVERAGE
        return regexAtLeastOneRow;
343
    }
344
345
    private void checkSuspend(
346
        AbstractStrategy strategy,
347
        StringBuilder slidingWindowAllRows,
348
        StringBuilder slidingWindowCurrentRow
349
    ) throws StoppedByUserSlidingException, InjectionFailureException {
350 1 1. checkSuspend : negated conditional → NO_COVERAGE
        if (this.isSuspended()) {
351
            throw new StoppedByUserSlidingException(
352
                slidingWindowAllRows.toString(),
353
                slidingWindowCurrentRow.toString()
354
            );
355 1 1. checkSuspend : negated conditional → NO_COVERAGE
        } else if (strategy == null) {
356
            // Fix #1905 : NullPointerException on injectionStrategy.inject()
357
            throw new InjectionFailureException("Undefined strategy");
358
        }
359
    }
360
361
    private void scrap(StringBuilder slidingWindowAllRows) {
362
        // Remove everything not properly attached to the last row:
363
        // 1. very start of a new row: XXXXX\4[\6\4]$
364
        // 2. very end of the last row: XXXXX[\500]$
365
        var allRowsLimit = slidingWindowAllRows.toString();
366 1 1. scrap : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE
        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 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) {
384
            var request = new Request();
385 1 1. sendProgress : removed call to com/jsql/model/bean/util/Request::setMessage → NO_COVERAGE
            request.setMessage(Interaction.UPDATE_PROGRESS);
386 1 1. sendProgress : removed call to com/jsql/model/bean/util/Request::setParameters → NO_COVERAGE
            request.setParameters(searchName, countProgress);
387 1 1. sendProgress : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE
            this.injectionModel.sendToViews(request);
388
        }
389
    }
390
    
391
    public static List<List<String>> parse(String rows) throws InjectionFailureException {
392
        // Parse all the data we have retrieved
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 1 1. parse : negated conditional → NO_COVERAGE
        if (!regexSearch.find()) {
404
            throw new InjectionFailureException();
405
        }
406
        regexSearch.reset();
407
        var rowsFound = 0;
408
        List<List<String>> listValues = new ArrayList<>();
409
410
        // Build a 2D array of strings from the data we have parsed
411
        // => row number, occurrence, value1, value2...
412 1 1. parse : negated conditional → NO_COVERAGE
        while (regexSearch.find()) {
413
            String values = regexSearch.group(1);
414
            var instances = Integer.parseInt(regexSearch.group(2));
415
416
            listValues.add(new ArrayList<>());
417 1 1. parse : Replaced integer addition with subtraction → NO_COVERAGE
            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 1 1. parse : Changed increment from 1 to -1 → NO_COVERAGE
            rowsFound++;
423
        }
424 1 1. parse : replaced return value with Collections.emptyList for com/jsql/model/suspendable/SuspendableGetRows::parse → NO_COVERAGE
        return listValues;
425
    }
426
}

Mutations

64

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

69

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

85

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

93

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

95

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

97

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

113

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

115

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

120

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

124

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

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

126

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

128

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

131

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

132

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

133

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

134

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

137

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

140

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

146

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

148

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

152

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

154

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

155

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

159

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

161

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

166

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

171

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

172

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

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

173

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

175

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

181

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

185

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

210

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

212

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

223

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

233

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

253

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

254

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

256

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

262

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

264

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

265

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

266

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

269

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

272

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

280

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

281

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

289

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

302

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

303

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

304

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

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

305

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

312

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

319

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

342

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

350

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

355

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

366

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

383

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

385

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

386

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

387

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

403

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

412

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

417

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

422

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

424

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