Skip to content

Commit d8ce9e2

Browse files
committed
add tests + fix QuotedPrintable with ending spaces
1 parent d58287a commit d8ce9e2

File tree

8 files changed

+201
-2
lines changed

8 files changed

+201
-2
lines changed

net.lecousin.core/src/main/java/net/lecousin/framework/io/encoding/QuotedPrintable.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,21 @@
99

1010
/** Utility method to encode and decode quoted-printable as defined in RFC 2045. */
1111
public final class QuotedPrintable {
12+
13+
private QuotedPrintable() { /* no instance. */ }
1214

1315
/** Decode the given input. */
1416
public static ByteBuffer decode(ByteBuffer input) throws IOException {
1517
byte[] out = new byte[input.remaining()];
1618
int pos = 0;
1719
int endingSpaces = 0;
20+
int endingSpacesSkipped = 0;
1821
while (input.hasRemaining()) {
1922
byte b = input.get();
2023
if ((b >= 33 && b <= 60) || (b >= 62 && b <= 126)) {
2124
out[pos++] = b;
2225
endingSpaces = 0;
26+
endingSpacesSkipped = 0;
2327
continue;
2428
}
2529
if (b == '=') {
@@ -33,6 +37,7 @@ public static ByteBuffer decode(ByteBuffer input) throws IOException {
3337
if (c1 == '\r' && c2 == '\n') {
3438
// soft line break
3539
endingSpaces = 0;
40+
endingSpacesSkipped = 0;
3641
continue;
3742
}
3843
int i = StringUtil.decodeHexa(c1);
@@ -41,6 +46,7 @@ public static ByteBuffer decode(ByteBuffer input) throws IOException {
4146
if (j == -1) throw new IOException("Invalid hexadecimal value in quoted-printable");
4247
out[pos++] = (byte)((i << 4) | j);
4348
endingSpaces = 0;
49+
endingSpacesSkipped = 0;
4450
continue;
4551
}
4652
if (b == ' ' || b == '\t') {
@@ -50,17 +56,19 @@ public static ByteBuffer decode(ByteBuffer input) throws IOException {
5056
}
5157
if (b == '\r') {
5258
endingSpaces++;
59+
endingSpacesSkipped++;
5360
continue;
5461
}
5562
if (b == '\n') {
5663
// end of line
57-
pos -= endingSpaces;
64+
pos -= endingSpaces - endingSpacesSkipped;
5865
endingSpaces = 0;
66+
endingSpacesSkipped = 0;
5967
continue;
6068
}
6169
throw new IOException("Unexpected byte " + b + " in quoted-printable data");
6270
}
63-
pos -= endingSpaces;
71+
pos -= endingSpaces - endingSpacesSkipped;
6472
input.position(input.position() - endingSpaces);
6573
return ByteBuffer.wrap(out, 0, pos);
6674
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package net.lecousin.framework.core.test.io;
2+
3+
import java.io.File;
4+
import java.nio.charset.Charset;
5+
import java.nio.charset.StandardCharsets;
6+
7+
import net.lecousin.framework.concurrent.Task;
8+
import net.lecousin.framework.core.test.LCCoreAbstractTest;
9+
import net.lecousin.framework.io.FileIO;
10+
import net.lecousin.framework.io.IO;
11+
import net.lecousin.framework.io.IOUtil;
12+
import net.lecousin.framework.io.TemporaryFiles;
13+
import net.lecousin.framework.io.text.ICharacterStream;
14+
15+
import org.junit.Assert;
16+
import org.junit.Test;
17+
18+
public abstract class TestCharacterStreamWritable extends LCCoreAbstractTest {
19+
20+
protected abstract ICharacterStream.Writable open(IO.Writable out, Charset charset) throws Exception;
21+
22+
protected abstract void flush(ICharacterStream.Writable cs) throws Exception;
23+
24+
@Test(timeout=120000)
25+
public void test() throws Exception {
26+
testWrite("Hello", StandardCharsets.US_ASCII);
27+
testWrite("Bonjour vous-même", StandardCharsets.UTF_8);
28+
testWrite("Hello World !", StandardCharsets.UTF_16);
29+
}
30+
31+
@SuppressWarnings("resource")
32+
private void testWrite(String s, Charset charset) throws Exception {
33+
File tmp = TemporaryFiles.get().createFileSync("test", "writablecs");
34+
ICharacterStream.Writable cs = open(new FileIO.WriteOnly(tmp, Task.PRIORITY_NORMAL), charset);
35+
cs.writeSync(s);
36+
flush(cs);
37+
cs.close();
38+
Assert.assertEquals(s, IOUtil.readFullyAsStringSync(tmp, charset));
39+
tmp.delete();
40+
41+
tmp = TemporaryFiles.get().createFileSync("test", "writablecs");
42+
cs = open(new FileIO.WriteOnly(tmp, Task.PRIORITY_NORMAL), charset);
43+
cs.writeAsync(s).blockThrow(0);
44+
flush(cs);
45+
cs.close();
46+
Assert.assertEquals(s, IOUtil.readFullyAsStringSync(tmp, charset));
47+
tmp.delete();
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package net.lecousin.framework.core.test.io;
2+
3+
import java.io.File;
4+
import java.nio.charset.Charset;
5+
import java.nio.charset.StandardCharsets;
6+
7+
import net.lecousin.framework.concurrent.Task;
8+
import net.lecousin.framework.core.test.LCCoreAbstractTest;
9+
import net.lecousin.framework.io.FileIO;
10+
import net.lecousin.framework.io.IO;
11+
import net.lecousin.framework.io.IOUtil;
12+
import net.lecousin.framework.io.TemporaryFiles;
13+
import net.lecousin.framework.io.text.ICharacterStream;
14+
15+
import org.junit.Assert;
16+
import org.junit.Test;
17+
18+
public abstract class TestCharacterStreamWritableBuffered extends LCCoreAbstractTest {
19+
20+
protected abstract ICharacterStream.Writable.Buffered open(IO.Writable out, Charset charset) throws Exception;
21+
22+
protected abstract void flush(ICharacterStream.Writable cs) throws Exception;
23+
24+
@Test(timeout=120000)
25+
public void test() throws Exception {
26+
testWrite("Hello", StandardCharsets.US_ASCII);
27+
testWrite("Bonjour vous-même", StandardCharsets.UTF_8);
28+
testWrite("Hello World !", StandardCharsets.UTF_16);
29+
}
30+
31+
private void testWrite(String s, Charset charset) throws Exception {
32+
File tmp = TemporaryFiles.get().createFileSync("test", "writablecs");
33+
@SuppressWarnings("resource")
34+
ICharacterStream.Writable.Buffered cs = open(new FileIO.WriteOnly(tmp, Task.PRIORITY_NORMAL), charset);
35+
char[] chars = s.toCharArray();
36+
for (int i = 0; i < chars.length; ++i) {
37+
if ((i % 2) == 0)
38+
cs.writeSync(chars[i]);
39+
else
40+
cs.writeAsync(chars[i]).blockThrow(0);
41+
}
42+
flush(cs);
43+
cs.close();
44+
Assert.assertEquals(s, IOUtil.readFullyAsStringSync(tmp, charset));
45+
tmp.delete();
46+
}
47+
48+
}

net.lecousin.core/src/test/java/net/lecousin/framework/core/tests/io/TestIOUtil.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ public void test1() throws Exception {
8181
for (int i = 70; i < 70 + 50; ++i)
8282
Assert.assertEquals(data[10 + i - 70], buf[i]);
8383
io.close();
84+
85+
io = new ByteArrayIO(buf, "test");
86+
Assert.assertEquals(0, IOUtil.skipSyncByReading(io, -1));
87+
Assert.assertEquals(0, IOUtil.skipAsyncByReading(io, -1, null).blockResult(0).intValue());
88+
Assert.assertEquals(0, IOUtil.skipAsyncByReading(io, -1, (p) -> {}).blockResult(0).intValue());
89+
io.close();
8490
}
8591

8692
@Test(timeout=120000)
@@ -136,5 +142,15 @@ public void testCopy() throws Exception {
136142
IOUtil.copy(new FileIO.ReadOnly(tmp1, Task.PRIORITY_NORMAL), new FileIO.WriteOnly(tmp2, Task.PRIORITY_NORMAL), tmp1.length(), true, new FakeWorkProgress(), 100).blockThrow(0);
137143
Assert.assertEquals(string, IOUtil.readFullyAsStringSync(tmp2, StandardCharsets.UTF_16));
138144
tmp2.delete();
145+
146+
// big file
147+
tmp1 = TemporaryFiles.get().createFileSync("test", "ioutilcopy");
148+
out = new FileOutputStream(tmp1);
149+
for (int i = 0; i < 10; ++i)
150+
out.write(new byte[64 * 1024]);
151+
tmp2 = TemporaryFiles.get().createFileSync("test", "ioutilcopy");
152+
IOUtil.copy(new FileIO.ReadOnly(tmp1, Task.PRIORITY_NORMAL), new FileIO.WriteOnly(tmp2, Task.PRIORITY_NORMAL), tmp1.length(), true, new FakeWorkProgress(), 100).blockThrow(0);
153+
tmp1.delete();
154+
tmp2.delete();
139155
}
140156
}

net.lecousin.core/src/test/java/net/lecousin/framework/core/tests/io/encoding/TestQuotedPrintable.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ public void testEncodeAndDecode() throws Exception {
3232
for (int i = 0; i < 512; ++i)
3333
data[i] = (byte)(i * 3 - (i % 31) + i / 35);
3434
test(data);
35+
36+
data = "This is a test =C3=A9 This is a test =C3=A9 This is a test =C3=A9 This is a=\r\n test =C3=A9 This is a test =C3=A9 This is a test \t \r".getBytes(StandardCharsets.US_ASCII);
37+
ByteBuffer decoded = QuotedPrintable.decode(ByteBuffer.wrap(data));
38+
byte[] expected = "This is a test é This is a test é This is a test é This is a test é This is a test é This is a test".getBytes(StandardCharsets.UTF_8);
39+
for (int i = 0; i < expected.length; ++i)
40+
if (decoded.hasRemaining())
41+
Assert.assertEquals("At " + i, expected[i], decoded.get());
42+
else
43+
throw new AssertionError("Missing byte " + i);
44+
decoded = QuotedPrintable.decode(data);
45+
for (int i = 0; i < expected.length; ++i)
46+
Assert.assertEquals("At " + i, expected[i], decoded.get());
3547
}
3648

3749
private static void test(byte[] data) throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.lecousin.framework.core.tests.io.text;
2+
3+
import java.nio.charset.Charset;
4+
5+
import net.lecousin.framework.core.test.io.TestCharacterStreamWritable;
6+
import net.lecousin.framework.io.IO;
7+
import net.lecousin.framework.io.text.BufferedWritableCharacterStream;
8+
import net.lecousin.framework.io.text.ICharacterStream;
9+
10+
public class TestBufferedWritableCharacterStream extends TestCharacterStreamWritable {
11+
12+
@Override
13+
protected ICharacterStream.Writable open(IO.Writable out, Charset charset) {
14+
return new BufferedWritableCharacterStream(out, charset, 5);
15+
}
16+
17+
@Override
18+
protected void flush(ICharacterStream.Writable cs) throws Exception {
19+
((BufferedWritableCharacterStream)cs).flush().blockThrow(0);
20+
}
21+
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.lecousin.framework.core.tests.io.text;
2+
3+
import java.nio.charset.Charset;
4+
5+
import net.lecousin.framework.core.test.io.TestCharacterStreamWritableBuffered;
6+
import net.lecousin.framework.io.IO.Writable;
7+
import net.lecousin.framework.io.text.BufferedWritableCharacterStream;
8+
import net.lecousin.framework.io.text.ICharacterStream;
9+
10+
public class TestBufferedWritableCharacterStreamAsBuffered extends TestCharacterStreamWritableBuffered {
11+
12+
@Override
13+
protected ICharacterStream.Writable.Buffered open(Writable out, Charset charset) {
14+
return new BufferedWritableCharacterStream(out, charset, 5);
15+
}
16+
17+
@Override
18+
protected void flush(ICharacterStream.Writable cs) throws Exception {
19+
((BufferedWritableCharacterStream)cs).flush().blockThrow(0);
20+
}
21+
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package net.lecousin.framework.core.tests.io.text;
2+
3+
import java.nio.charset.Charset;
4+
5+
import net.lecousin.framework.core.test.io.TestCharacterStreamWritable;
6+
import net.lecousin.framework.io.IO;
7+
import net.lecousin.framework.io.text.ICharacterStream;
8+
import net.lecousin.framework.io.text.WritableCharacterStream;
9+
10+
public class TestWritableCharacterStream extends TestCharacterStreamWritable {
11+
12+
@Override
13+
protected ICharacterStream.Writable open(IO.Writable out, Charset charset) {
14+
return new WritableCharacterStream(out, charset);
15+
}
16+
17+
@Override
18+
protected void flush(ICharacterStream.Writable cs) {
19+
}
20+
21+
}

0 commit comments

Comments
 (0)