Skip to content

Commit ab7038c

Browse files
committed
Fix parsing partials
1 parent c6d7c58 commit ab7038c

2 files changed

Lines changed: 50 additions & 46 deletions

File tree

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

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

47-
// === Read raw token until whitespace or ( or ) ===
47+
// Read full token until whitespace / ( / )
4848
String raw = t.readAtom();
49-
int bracketIdx = raw.indexOf('[');
5049

51-
// Case: atom[section] (exactly one '[')
52-
if (bracketIdx > 0 && raw.lastIndexOf('[') == bracketIdx) {
53-
String base = raw.substring(0, bracketIdx);
50+
// === Try to split atom[section]<partial> ===
51+
int partialStart = raw.indexOf('<');
52+
String beforePartial = (partialStart >= 0) ? raw.substring(0, partialStart) : raw;
53+
String partialStr = (partialStart >= 0) ? raw.substring(partialStart) : null;
54+
55+
int bracketIdx = beforePartial.indexOf('[');
56+
57+
if (bracketIdx > 0 && beforePartial.lastIndexOf('[') == bracketIdx) {
58+
// Has section
59+
String base = beforePartial.substring(0, bracketIdx);
5460
if (isValidSectionBase(base)) {
55-
int end = raw.lastIndexOf(']');
56-
String sectionContent = (end > bracketIdx)
57-
? raw.substring(bracketIdx + 1, end)
58-
: raw.substring(bracketIdx + 1);
61+
int close = beforePartial.lastIndexOf(']');
62+
String sectionContent = (close > bracketIdx)
63+
? beforePartial.substring(bracketIdx + 1, close)
64+
: beforePartial.substring(bracketIdx + 1);
5965

6066
ProtocolAtom atom = new ProtocolAtom(base);
6167
ProtocolSubordinate section = parseSubordinateContent(sectionContent);
6268

63-
// Check for <partial> right after the section
64-
Integer offset = null;
65-
Integer length = null;
66-
if (t.hasMore() && t.isNextByteImmediate((byte) '<')) {
67-
PartialData pd = parsePartialData(t);
68-
offset = pd.offset;
69-
length = pd.length;
69+
PartialData pd = null;
70+
if (partialStr != null) {
71+
pd = parsePartialFromString(partialStr);
7072
}
7173

72-
return new ProtocolSectionPartial(atom, section, offset, length);
74+
return new ProtocolSectionPartial(atom, section,
75+
pd != null ? pd.offset : null,
76+
pd != null ? pd.length : null);
7377
}
7478
}
7579

76-
// Normal atom (or atom with only <partial>)
77-
ProtocolAtom atom = new ProtocolAtom(raw);
78-
79-
// Partial <...> only allowed when base is A-Za-z0-9.
80-
if (t.hasMore() && t.isNextByteImmediate((byte) '<') && isValidSectionBase(raw)) {
81-
PartialData pd = parsePartialData(t);
80+
// No section → check for plain atom<partial>
81+
if (partialStr != null && isValidSectionBase(beforePartial)) {
82+
ProtocolAtom atom = new ProtocolAtom(beforePartial);
83+
PartialData pd = parsePartialFromString(partialStr);
8284
return new ProtocolSectionPartial(atom, null, pd.offset, pd.length);
8385
}
8486

85-
return atom;
87+
// Normal atom (including complex ones with [ ] ! # etc.)
88+
return new ProtocolAtom(raw);
8689
}
8790

8891
// ====================== Literal Parsers ======================
@@ -167,27 +170,20 @@ private static ProtocolSubordinate parseSubordinateContent(String content) {
167170
if (content == null || content.isEmpty()) {
168171
return new ProtocolSubordinate(new ProtocolObject[0]);
169172
}
170-
// For now treat the inside as a single atom (can be improved later)
171173
return new ProtocolSubordinate(new ProtocolObject[]{new ProtocolAtom(content)});
172174
}
173175

174-
private static PartialData parsePartialData(Tokenizer t) {
175-
t.consumeNoSkip((byte) '<');
176-
StringBuilder sb = new StringBuilder();
177-
178-
while (t.hasMore()) {
179-
byte b = t.nextByteNoSkip();
180-
if (b == '>') {
181-
t.consumeNoSkip((byte) '>');
182-
break;
183-
}
184-
sb.append((char) b);
176+
private static PartialData parsePartialFromString(String s) {
177+
if (s == null || !s.startsWith("<") || !s.endsWith(">")) {
178+
return new PartialData(0, null);
185179
}
180+
String inner = s.substring(1, s.length() - 1).trim();
181+
return parsePartialContent(inner);
182+
}
186183

187-
String content = sb.toString().trim();
184+
private static PartialData parsePartialContent(String content) {
188185
Integer offset = null;
189186
Integer length = null;
190-
191187
if (!content.isEmpty()) {
192188
try {
193189
if (content.contains(".")) {
@@ -201,9 +197,7 @@ private static PartialData parsePartialData(Tokenizer t) {
201197
offset = 0;
202198
}
203199
}
204-
if (offset == null) offset = 0;
205-
206-
return new PartialData(offset, length);
200+
return new PartialData(offset != null ? offset : 0, length);
207201
}
208202

209203
private static ProtocolQuoted parseQuoted(Tokenizer t) {
@@ -274,10 +268,7 @@ public boolean isNextByteAfter(byte current, byte expected) {
274268
return pos + 1 < input.length && input[pos + 1] == expected;
275269
}
276270

277-
/**
278-
* Reads until whitespace, '(' or ')'.
279-
* Atoms can NEVER contain ( or ).
280-
*/
271+
/** Reads full atom until whitespace, ( or ) — allows [ and < inside */
281272
public String readAtom() {
282273
skipWhitespace();
283274
int start = pos;
@@ -289,7 +280,6 @@ public String readAtom() {
289280
}
290281
pos++;
291282
}
292-
293283
return new String(input, start, pos - start, StandardCharsets.US_ASCII);
294284
}
295285

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.yocto.yoclib.imap.protocol.ProtocolParser;
99
import com.yocto.yoclib.imap.protocol.ProtocolQuoted;
1010

11+
import com.yocto.yoclib.imap.protocol.ProtocolSectionPartial;
12+
import com.yocto.yoclib.imap.protocol.ProtocolSubordinate;
1113
import org.junit.jupiter.api.Test;
1214

1315
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -107,6 +109,18 @@ public void testParsingLiteral(){
107109
},ProtocolParser.parse("{3+}\r\ndef abc\r\n".getBytes()));
108110
}
109111

112+
@Test
113+
public void testParsingSectionPartial(){
114+
assertArrayEquals(new ProtocolObject[]{
115+
new ProtocolAtom("tag"),
116+
new ProtocolAtom("FETCH"),
117+
new ProtocolAtom("2"),
118+
new ProtocolSectionPartial(new ProtocolAtom("body.peek"),new ProtocolSubordinate(new ProtocolObject[]{
119+
new ProtocolAtom("text"),
120+
}),0,3),
121+
},ProtocolParser.parse("tag FETCH 2 body.peek[text]<0.3>\r\n".getBytes()));
122+
}
123+
110124
@Test
111125
public void testParsingList(){
112126
assertArrayEquals(new ProtocolObject[]{

0 commit comments

Comments
 (0)