Skip to content

Commit ba97c90

Browse files
Fix DataReader.findNewLine with multiple lone EOL character (helidon-io#9362)
When there is multiple lone CR within a buffer, the virtual index is accumulated each time with the node index. The virtual index may be incorrectly seen as >= max, which ignores CRLN within max. - Fix virtual index accumulation - Re-use the branch that changes the buffer - Renamed indexWithinNode -> fromIndexNode - Renamed crIndex -> crIndexNode - Renamed lfIndex -> lfIndexNode - Add unit tests
1 parent 4e2d6de commit ba97c90

2 files changed

Lines changed: 48 additions & 26 deletions

File tree

common/buffers/src/main/java/io/helidon/common/buffers/DataReader.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -395,69 +395,62 @@ public String debugDataHex() {
395395
*/
396396
public int findNewLine(int max) throws IncorrectNewLineException {
397397
ensureAvailable();
398-
int idx = 0;
399398
Node n = head;
400-
int indexWithinNode = n.position;
399+
int idx = 0;
400+
int fromIndexNode = n.position;
401401

402402
while (true) {
403403
byte[] barr = n.bytes;
404-
int maxLength = Math.min(max - idx, barr.length - indexWithinNode);
405-
int crIndex = Bytes.firstIndexOf(barr, indexWithinNode, indexWithinNode + maxLength, Bytes.CR_BYTE);
404+
int maxLength = Math.min(max - idx, barr.length - fromIndexNode);
405+
int crIndexNode = Bytes.firstIndexOf(barr, fromIndexNode, fromIndexNode + maxLength, Bytes.CR_BYTE);
406406

407-
if (crIndex == -1) {
408-
int lfIndex = Bytes.firstIndexOf(barr, indexWithinNode, indexWithinNode + maxLength, Bytes.LF_BYTE);
409-
if (lfIndex != -1) {
407+
if (crIndexNode == -1) {
408+
int lfIndexNode = Bytes.firstIndexOf(barr, fromIndexNode, fromIndexNode + maxLength, Bytes.LF_BYTE);
409+
if (lfIndexNode != -1) {
410410
if (!ignoreLoneEol) {
411-
throw new IncorrectNewLineException("Found LF (" + (idx + lfIndex - n.position)
411+
throw new IncorrectNewLineException("Found LF (" + (idx + lfIndexNode - n.position)
412412
+ ") without preceding CR. :\n" + this.debugDataHex());
413413
}
414414
}
415-
// not found, continue with next buffer
416-
idx += maxLength;
417-
if (idx >= max) {
418-
// not found and reached the limit
419-
return max;
420-
}
421-
n = n.next();
422-
indexWithinNode = n.position;
423-
continue;
424415
} else {
425416
// found, next byte should be LF
426-
if (crIndex == barr.length - 1) {
417+
if (crIndexNode == barr.length - 1) {
427418
// found CR as the last byte of the current node, peek next node
428419
byte nextByte = n.next().peek();
429420
if (nextByte == Bytes.LF_BYTE) {
430-
return idx + crIndex - n.position;
421+
return idx + crIndexNode - fromIndexNode;
431422
}
432423
if (!ignoreLoneEol) {
433-
throw new IncorrectNewLineException("Found CR (" + (idx + crIndex - n.position)
424+
throw new IncorrectNewLineException("Found CR (" + (idx + crIndexNode - n.position)
434425
+ ") without following LF. :\n" + this.debugDataHex());
435426
}
436427
} else {
437428
// found CR within the current array
438-
byte nextByte = barr[crIndex + 1];
429+
byte nextByte = barr[crIndexNode + 1];
439430
if (nextByte == Bytes.LF_BYTE) {
440-
return idx + crIndex - n.position;
431+
return idx + crIndexNode - fromIndexNode;
441432
}
442433
if (!ignoreLoneEol) {
443434
throw new IncorrectNewLineException("Found CR (" + idx
444435
+ ") without following LF. :\n" + this.debugDataHex());
445436
}
446-
indexWithinNode = crIndex + 1;
447-
idx += indexWithinNode;
437+
idx += (crIndexNode - fromIndexNode + 1);
438+
fromIndexNode = crIndexNode + 1;
448439
if (idx >= max) {
449440
return max;
450441
}
451442
continue;
452443
}
453444
}
454445

446+
// not found, continue with next buffer
455447
idx += maxLength;
456448
if (idx >= max) {
449+
// not found and reached the limit
457450
return max;
458451
}
459452
n = n.next();
460-
indexWithinNode = n.position;
453+
fromIndexNode = n.position;
461454
}
462455
}
463456

common/buffers/src/test/java/io/helidon/common/buffers/DataReaderTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.helidon.common.buffers;
1818

19+
import java.nio.charset.StandardCharsets;
1920
import java.util.concurrent.atomic.AtomicReference;
2021

2122
import org.junit.jupiter.api.Test;
@@ -30,7 +31,7 @@ void testFindNewLineWithLoneCR() {
3031
// reading N bytes at a time until a new line is found
3132
// with data containing a lone CR
3233

33-
byte[] data = new byte[] {0, 0, (byte) '\r', 0, (byte) '\r', (byte) '\n'};
34+
byte[] data = "00\r0\r\n".getBytes(StandardCharsets.US_ASCII);
3435
AtomicReference<byte[]> ref = new AtomicReference<>(data);
3536
DataReader dataReader = new DataReader(() -> ref.getAndSet(null), true);
3637

@@ -41,4 +42,32 @@ void testFindNewLineWithLoneCR() {
4142
dataReader.skip(n);
4243
assertThat(dataReader.findNewLine(n), is(0));
4344
}
45+
46+
@Test
47+
void testFindNewLineWithMultipleLoneCR() {
48+
// if the stream index is accumulated with the node index for each lone CR
49+
// it may exceed max and the new line is ignored
50+
51+
byte[] data = "00\r\r\r\n".getBytes(StandardCharsets.US_ASCII);
52+
AtomicReference<byte[]> ref = new AtomicReference<>(data);
53+
DataReader dataReader = new DataReader(() -> ref.getAndSet(null), true);
54+
55+
int n = 5;
56+
assertThat(dataReader.findNewLine(n), is(4));
57+
}
58+
59+
@Test
60+
void testFindNewLineWithMultipleLoneWithinMax() {
61+
// if the stream index is not updated for each lone CR
62+
// the computed search range is too big and a value greater than max is returned
63+
64+
byte[] data = "00\r00\r\n00".getBytes(StandardCharsets.US_ASCII);
65+
AtomicReference<byte[]> ref = new AtomicReference<>(data);
66+
DataReader dataReader = new DataReader(() -> ref.getAndSet(null), true);
67+
68+
int n = 4;
69+
assertThat(dataReader.findNewLine(n), is(n));
70+
dataReader.skip(n);
71+
assertThat(dataReader.findNewLine(n), is(1));
72+
}
4473
}

0 commit comments

Comments
 (0)