Skip to content

Commit ad83473

Browse files
committed
Fix parsing atoms
1 parent b542cc2 commit ad83473

2 files changed

Lines changed: 72 additions & 23 deletions

File tree

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolParser.java

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,33 @@ private static ProtocolObject parseToken(Tokenizer t) {
4444
if (b == '[') return parseSubordinate(t);
4545
if (b == '"') return parseQuoted(t);
4646

47-
// Atom
48-
String atomValue = t.readAtom();
49-
ProtocolAtom atom = new ProtocolAtom(atomValue);
47+
// === Atom (may contain one [section]) ===
48+
String raw = t.readAtom(); // reads until whitespace
49+
int bracketIdx = raw.indexOf('[');
50+
51+
// Case 1: Looks like atom[section]
52+
if (bracketIdx > 0 && raw.lastIndexOf('[') == bracketIdx) { // exactly one [
53+
String base = raw.substring(0, bracketIdx);
54+
if (isValidSectionBase(base)) {
55+
// Extract content between [ and ]
56+
int end = raw.lastIndexOf(']');
57+
String sectionContent = (end > bracketIdx)
58+
? raw.substring(bracketIdx + 1, end)
59+
: raw.substring(bracketIdx + 1);
60+
61+
ProtocolAtom atom = new ProtocolAtom(base);
62+
ProtocolSubordinate section = parseSubordinateContent(sectionContent);
63+
return new ProtocolSectionPartial(atom, section, null, null);
64+
}
65+
}
66+
67+
// Normal atom
68+
ProtocolAtom atom = new ProtocolAtom(raw);
5069

51-
if (t.hasMore() &&
52-
(t.isNextByteImmediate((byte) '[') || t.isNextByteImmediate((byte) '<'))) {
53-
return parseSectionPartial(atom, t);
70+
// Still allow <partial> after a normal atom
71+
if (t.hasMore() && t.isNextByteImmediate((byte) '<')) {
72+
PartialData pd = parsePartialData(t);
73+
return new ProtocolSectionPartial(atom, null, pd.offset, pd.length);
5474
}
5575

5676
return atom;
@@ -134,6 +154,15 @@ private static ProtocolSubordinate parseSubordinate(Tokenizer t) {
134154
return new ProtocolSubordinate(elements.toArray(new ProtocolObject[0]));
135155
}
136156

157+
/** Parse subordinate from a string that was already consumed inside [] */
158+
private static ProtocolSubordinate parseSubordinateContent(String content) {
159+
if (content == null || content.isEmpty()) {
160+
return new ProtocolSubordinate(new ProtocolObject[0]);
161+
}
162+
// Simple fallback: treat as single atom for now
163+
return new ProtocolSubordinate(new ProtocolObject[]{new ProtocolAtom(content)});
164+
}
165+
137166
private static ProtocolSectionPartial parseSectionPartial(ProtocolAtom baseAtom, Tokenizer t) {
138167
ProtocolSubordinate section = null;
139168
Integer offset = null;
@@ -251,23 +280,25 @@ public boolean isNextByteImmediate(byte expected) {
251280
}
252281

253282
public boolean isNextByteAfter(byte current, byte expected) {
254-
if (pos >= input.length || input[pos] != current) {
255-
return false;
256-
}
283+
if (pos >= input.length || input[pos] != current) return false;
257284
return pos + 1 < input.length && input[pos + 1] == expected;
258285
}
259286

287+
/**
288+
* Reads until whitespace — allows [, ], !, #, $, etc.
289+
*/
260290
public String readAtom() {
261291
skipWhitespace();
262292
int start = pos;
293+
263294
while (pos < input.length) {
264295
byte b = input[pos];
265-
if (Character.isWhitespace(b & 0xFF) || b == '(' || b == ')' || b == '[' || b == ']' ||
266-
b == '"' || b == '{' || b == '}' || b == '<' || b == '>') {
296+
if (Character.isWhitespace(b & 0xFF)) {
267297
break;
268298
}
269299
pos++;
270300
}
301+
271302
return new String(input, start, pos - start, StandardCharsets.US_ASCII);
272303
}
273304

@@ -294,6 +325,19 @@ public byte nextByteNoSkip() {
294325
}
295326
}
296327

328+
// ====================== Helpers ======================
329+
330+
private static boolean isValidSectionBase(String s) {
331+
if (s == null || s.isEmpty()) return false;
332+
for (char c : s.toCharArray()) {
333+
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
334+
(c >= '0' && c <= '9') || c == '.')) {
335+
return false;
336+
}
337+
}
338+
return true;
339+
}
340+
297341
// ====================== Helper Classes ======================
298342

299343
private static class LiteralHeader {

src/test/java/com/yocto/yoclib/imap/tests/protocol/ProtocolParserTest.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,30 @@
55
import com.yocto.yoclib.imap.protocol.ProtocolLiteral;
66
import com.yocto.yoclib.imap.protocol.ProtocolObject;
77
import com.yocto.yoclib.imap.protocol.ProtocolParser;
8-
98
import com.yocto.yoclib.imap.protocol.ProtocolQuoted;
9+
1010
import org.junit.jupiter.api.Test;
1111

1212
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
1313

1414
public class ProtocolParserTest{
1515

16+
@Test
17+
public void testParsingAtom(){
18+
assertArrayEquals(new ProtocolObject[]{
19+
new ProtocolAtom("tag"),
20+
new ProtocolAtom("SELECT"),
21+
new ProtocolAtom("!#$$&'+,-.0123456789:;<=>?@^_`|[}"),
22+
},ProtocolParser.parse("tag SELECT !#$$&'+,-.0123456789:;<=>?@^_`|[}".getBytes()));
23+
24+
assertArrayEquals(new ProtocolObject[]{
25+
new ProtocolAtom("tag"),
26+
new ProtocolAtom("LIST"),
27+
new ProtocolQuoted(""),
28+
new ProtocolAtom("!#$$&'+,-.0123456789:;<=>?@^_`|[}%*"),
29+
},ProtocolParser.parse("tag LIST \"\" !#$$&'+,-.0123456789:;<=>?@^_`|[}%*".getBytes()));
30+
}
31+
1632
@Test
1733
public void testParsingQuoted(){
1834
assertArrayEquals(new ProtocolObject[]{
@@ -90,15 +106,4 @@ public void testParsingLiteral(){
90106
},ProtocolParser.parse("{3+}\r\ndef abc\r\n".getBytes()));
91107
}
92108

93-
@Test
94-
public void testParsingComplexAtom(){
95-
//ProtocolObject[] objects = ProtocolParser.parse("!#$$&'+,-.0123456789:;<=>?@^_`|[}");
96-
97-
//assertEquals(1,objects.length);
98-
//assertInstanceOf(ProtocolAtom.class,objects[0]);
99-
100-
//ProtocolAtom atom = (ProtocolAtom) objects[0];
101-
//assertEquals("!#$$&'+,-.0123456789:;<=>?@^_`|[}",atom.getValue());
102-
}
103-
104109
}

0 commit comments

Comments
 (0)