80
|
1 |
// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
2 |
// All rights reserved.
|
|
3 |
// This component and the accompanying materials are made available
|
|
4 |
// under the terms of "Eclipse Public License v1.0"
|
|
5 |
// which accompanies this distribution, and is available
|
|
6 |
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
|
|
7 |
//
|
|
8 |
// Initial Contributors:
|
|
9 |
// Nokia Corporation - initial contribution.
|
|
10 |
//
|
|
11 |
// Contributors:
|
|
12 |
//
|
|
13 |
// Description:
|
|
14 |
//
|
|
15 |
|
|
16 |
#include "cimapbodystructurebuilder.h"
|
|
17 |
|
|
18 |
#include "cimapatom.h"
|
|
19 |
#include "cimapatomwalker.h"
|
|
20 |
#include "cimapatomparser.h"
|
|
21 |
#include "cimapbodystructure.h"
|
|
22 |
#include "cimapfetchresponse.h"
|
|
23 |
#include "cimapsessionconsts.h"
|
|
24 |
#include "cimapcommand.h"
|
|
25 |
#include "imappaniccodes.h"
|
|
26 |
|
|
27 |
|
|
28 |
CImapBodyStructureBuilder* CImapBodyStructureBuilder::NewL(CImapFetchResponse& aFetchResponse, TInt aLogId)
|
|
29 |
// static method
|
|
30 |
{
|
|
31 |
CImapBodyStructureBuilder* self = new(ELeave)CImapBodyStructureBuilder(aFetchResponse, aLogId);
|
|
32 |
CleanupStack::PushL(self);
|
|
33 |
self->ConstructL();
|
|
34 |
CleanupStack::Pop(self);
|
|
35 |
return self;
|
|
36 |
}
|
|
37 |
CImapBodyStructureBuilder::CImapBodyStructureBuilder(CImapFetchResponse& aFetchResponse, TInt aLogId)
|
|
38 |
: iFetchResponse(aFetchResponse)
|
|
39 |
, iBodyStructureOwned(ETrue)
|
|
40 |
, iProcessBlockState(EWaitLine)
|
|
41 |
, iLogId(aLogId)
|
|
42 |
{}
|
|
43 |
|
|
44 |
void CImapBodyStructureBuilder::ConstructL()
|
|
45 |
{
|
|
46 |
iAtomParser = CImapAtomParser::NewL(EFalse, iLogId);
|
|
47 |
iAtomWalker = CImapAtomWalker::NewL(iLogId);
|
|
48 |
}
|
|
49 |
|
|
50 |
CImapBodyStructureBuilder::~CImapBodyStructureBuilder()
|
|
51 |
{
|
|
52 |
delete iAtomWalker;
|
|
53 |
delete iAtomParser;
|
|
54 |
|
|
55 |
// NOTE
|
|
56 |
//
|
|
57 |
// CImapBodyStructure is a tree data strucutre, where any CImapBodyStructure owns and
|
|
58 |
// is responssible for destroying its children.
|
|
59 |
//
|
|
60 |
// iBodyStructureStack[0] is the root bodystructure
|
|
61 |
// ownership of the which is usually passed to CImapFetchResponse object before we get here
|
|
62 |
// So destroying iBodyStructureStack[0] will cause all its children to be destroyed too.
|
|
63 |
//
|
|
64 |
// With the exception of the root bodystructure, iBodyStructureStack does not own any
|
|
65 |
// of the objects it points to, and MUST NOT destroy them.
|
|
66 |
|
|
67 |
if (iBodyStructureOwned)
|
|
68 |
{
|
|
69 |
if (iBodyStructureStack.Count() > 0)
|
|
70 |
{
|
|
71 |
// delete the root bodystructure.
|
|
72 |
delete iBodyStructureStack[0];
|
|
73 |
}
|
|
74 |
}
|
|
75 |
iBodyStructureStack.Close(); // And DO NOT destroy the data that is pointed to.
|
|
76 |
}
|
|
77 |
|
|
78 |
/**
|
|
79 |
Parses a block of incoming data from the session.
|
|
80 |
ProcessBlockL() should be called repeatedly with more data until it returns EFalse to
|
|
81 |
indicate that enough data has been received.
|
|
82 |
This method parses the incoming data into an atom tree as the data is received.
|
|
83 |
When the last block of data is received, the method will then parse the complete atom tree,
|
|
84 |
populating iFetchResponse with a fully initialised CImapBodyStructure tree.
|
|
85 |
@param aData either a line or literal block of data.
|
|
86 |
@return Whether ProcessBlockL() expects to be called again with the next block of data from the session.
|
|
87 |
*/
|
|
88 |
TBool CImapBodyStructureBuilder::ProcessBlockL(const TDesC8& aData)
|
|
89 |
{
|
|
90 |
TBool wantMore = ETrue;
|
|
91 |
switch (iProcessBlockState)
|
|
92 |
{
|
|
93 |
case EWaitLine:
|
|
94 |
{
|
|
95 |
wantMore = iAtomParser->ProcessLineL(aData);
|
|
96 |
|
|
97 |
if (!wantMore)
|
|
98 |
{
|
|
99 |
iAtomWalker->SetRootL(iAtomParser->RootAtom());
|
|
100 |
|
|
101 |
// Get to the first "("
|
|
102 |
__ASSERT_ALWAYS(iAtomWalker->CurrentDes(EFalse).Length()==0, CImapCommand::CorruptDataL(iLogId));
|
|
103 |
__ASSERT_ALWAYS(iAtomWalker->PeekAcross() == NULL, CImapCommand::CorruptDataL(iLogId));
|
|
104 |
|
|
105 |
iAtomWalker->WalkDownL();
|
|
106 |
__ASSERT_ALWAYS(iAtomWalker->CurrentMatch(KImapTxtOpenBracket()), CImapCommand::CorruptDataL(iLogId));
|
|
107 |
|
|
108 |
iProcessBlockState = EParsing;
|
|
109 |
ParseLoopL();
|
|
110 |
|
|
111 |
TransferBufferOwnershipToFetchResponseL();
|
|
112 |
}
|
|
113 |
else
|
|
114 |
{
|
|
115 |
iProcessBlockState = EWaitLiteral;
|
|
116 |
}
|
|
117 |
}
|
|
118 |
break;
|
|
119 |
case EWaitLiteral:
|
|
120 |
{
|
|
121 |
iAtomParser->ProcessLiteralBlockL(aData);
|
|
122 |
iProcessBlockState = EWaitLine;
|
|
123 |
}
|
|
124 |
break;
|
|
125 |
default:
|
|
126 |
{
|
|
127 |
// This is an internal programming error.
|
|
128 |
__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderInvalidProcessBlockState));
|
|
129 |
wantMore = EFalse;
|
|
130 |
}
|
|
131 |
break;
|
|
132 |
}
|
|
133 |
|
|
134 |
return wantMore;
|
|
135 |
}
|
|
136 |
|
|
137 |
/**
|
|
138 |
Assigns the root bodystructure object, and its associated data to iFetchResponse.
|
|
139 |
iFetchResponse takes ownership of the bodystructure and its data.
|
|
140 |
*/
|
|
141 |
void CImapBodyStructureBuilder::TransferBufferOwnershipToFetchResponseL()
|
|
142 |
{
|
|
143 |
// Check for internal programming errors.
|
|
144 |
__ASSERT_DEBUG(iBodyStructureOwned, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderRootNotOwned));
|
|
145 |
__ASSERT_DEBUG(iBodyStructureStack.Count() == 1, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderExpectedRootAtomOnlyOnStack));
|
|
146 |
__ASSERT_DEBUG(iBodyStructureStack[0] == iBodyStructure, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderCurrentAtomIsNotRoot));
|
|
147 |
|
|
148 |
// Prepare to transfer ownership of bodyStructureData from iAtomParser to iFetchResponse
|
|
149 |
// bodyStructureData will no longer be owned by iAtomParser
|
|
150 |
HBufC8* bodyStructureData = iAtomParser->DetachBuffer();
|
|
151 |
|
|
152 |
// Transfer ownership of iBodyStructure and bodyStructureData to iFetchResponse
|
|
153 |
iFetchResponse.SetBodyStructure(iBodyStructure, bodyStructureData);
|
|
154 |
|
|
155 |
// iBodyStructure is no longer owned by "this" CImapBodyStructureBuilder object.
|
|
156 |
iBodyStructureOwned = EFalse;
|
|
157 |
}
|
|
158 |
|
|
159 |
/**
|
|
160 |
Returns any data that was not parsed by ParseBlockL()
|
|
161 |
- i.e any data that follows the top level BODYSTRUCTURE.
|
|
162 |
This will be a null string until ParseBlockL() has returned EFalse
|
|
163 |
to indicate that it has finished parsing.
|
|
164 |
When non-null, the returned pointer descriptor points into a section of the
|
|
165 |
aData descriptor that was passed into ParseBlockL(). Consequently, it is
|
|
166 |
only valid while the aData descriptor it points into is valid.
|
|
167 |
@return a descriptor pointing to unparsed data.
|
|
168 |
*/
|
|
169 |
TPtrC8 CImapBodyStructureBuilder::UnparsedData()
|
|
170 |
{
|
|
171 |
return iAtomParser->UnparsedData();
|
|
172 |
}
|
|
173 |
|
|
174 |
/**
|
|
175 |
The main loop for parsing the bodystructure.
|
|
176 |
The loop uses a state machine and bodystructure stack in order to handle
|
|
177 |
embedded bodystructures without needing to use recursion.
|
|
178 |
*/
|
|
179 |
void CImapBodyStructureBuilder::ParseLoopL()
|
|
180 |
{
|
|
181 |
// Check for internal programming error
|
|
182 |
__ASSERT_DEBUG(iBodyStructureStack.Count() == 0, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderStackNotEmpty));
|
|
183 |
|
|
184 |
TParseStep parseStep = EParseNewBodyStructure;
|
|
185 |
|
|
186 |
while (parseStep != EParseComplete)
|
|
187 |
{
|
|
188 |
switch (parseStep)
|
|
189 |
{
|
|
190 |
case EParseNewBodyStructure:
|
|
191 |
{
|
|
192 |
// Either at the start of the root body structure,
|
|
193 |
// Or at the start of an embedded body structure.
|
|
194 |
PushNewBodyStructureL();
|
|
195 |
parseStep = ParseBodyStructureTypeL();
|
|
196 |
}
|
|
197 |
break;
|
|
198 |
case EParseBasic:
|
|
199 |
{
|
|
200 |
// Found a "basic" bodystructure - i.e. not text, rfc822 or multipart.
|
|
201 |
ParseBodyTypeBasicL();
|
|
202 |
ParseBodyExt1PartL();
|
|
203 |
parseStep = EParseSubStructureComplete;
|
|
204 |
}
|
|
205 |
break;
|
|
206 |
case EParseText:
|
|
207 |
{
|
|
208 |
// Found a "TEXT" body structure
|
|
209 |
ParseBodyTypeTextL();
|
|
210 |
ParseBodyExt1PartL();
|
|
211 |
parseStep = EParseSubStructureComplete;
|
|
212 |
}
|
|
213 |
break;
|
|
214 |
case EParseBodyTypeMessageRfc822:
|
|
215 |
{
|
|
216 |
// Found a "MESSAGE/RFC822" body structure.
|
|
217 |
// This contains an embedded bodystructure, so parse up to the structure,
|
|
218 |
// and then allow the loop to parse the embedded structure.
|
|
219 |
TRAPD(err, ParseBodyTypeMessageRfc822L());
|
|
220 |
if(err == KErrImapCorrupt)
|
|
221 |
{
|
|
222 |
parseStep =EParseRemainderMessageRfc822;
|
|
223 |
}
|
|
224 |
else
|
|
225 |
{
|
|
226 |
parseStep = EParseNewBodyStructure;
|
|
227 |
}
|
|
228 |
|
|
229 |
}
|
|
230 |
break;
|
|
231 |
case EParseRemainderMessageRfc822:
|
|
232 |
{
|
|
233 |
// Just finished parsing the embedded bodystructure of a "MESSAGE/RFC822".
|
|
234 |
// Complete parsing the parent MESSAGE/RFC822 structure here.
|
|
235 |
ParseRemainderMessageRfc822L();
|
|
236 |
ParseBodyExt1PartL();
|
|
237 |
parseStep = EParseSubStructureComplete;
|
|
238 |
}
|
|
239 |
break;
|
|
240 |
case EParseRemainderMultipart:
|
|
241 |
{
|
|
242 |
// Just finished parsing the final embedded bodystructure of a MULTIPART structure.
|
|
243 |
// Complete parsing the parent MULTIPART structure here
|
|
244 |
ParseRemainderMultipartL();
|
|
245 |
parseStep = EParseSubStructureComplete;
|
|
246 |
}
|
|
247 |
break;
|
|
248 |
case EParseSubStructureComplete:
|
|
249 |
{
|
|
250 |
// Just finished parsing a bodystructure.
|
|
251 |
// If it is the root bodystructure then we are complete.
|
|
252 |
// Otherwise, let ParseSubStructureCompleteL() will find out whether it is
|
|
253 |
// * embedded in a MESSAGE/RFC822 structure - requiring the remainder to be parsed next: EParseRemainderMessageRfc822
|
|
254 |
// * embedded in a MULTIPART structure in which case
|
|
255 |
// > either there is another embedded structure next: EParseNewBodyStructure
|
|
256 |
// > or this is the last embedded structure, so we need to parse the multipart remainder: EParseRemainderMultipart
|
|
257 |
|
|
258 |
if (PopBodyStructureL())
|
|
259 |
{
|
|
260 |
// we were actually in the root, so we are fully complete.
|
|
261 |
parseStep = EParseComplete;
|
|
262 |
}
|
|
263 |
else
|
|
264 |
{
|
|
265 |
// we were in a genuine substructure, so need to walk up and decide what to do.
|
|
266 |
parseStep = ParseSubStructureCompleteL();
|
|
267 |
}
|
|
268 |
}
|
|
269 |
break;
|
|
270 |
default:
|
|
271 |
{
|
|
272 |
// This is an internal programming error.
|
|
273 |
__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderInvalidParseStep));
|
|
274 |
User::Leave(KErrGeneral); // avoid an infinite loop.
|
|
275 |
}
|
|
276 |
}
|
|
277 |
}
|
|
278 |
}
|
|
279 |
|
|
280 |
/**
|
|
281 |
When the parse loop comes across a new root or embedded bodystructure, it will use this method to...
|
|
282 |
> Create a new CImapBodyStructure object to represent the bodystructure
|
|
283 |
> Push the object onto the stack and make it "current"
|
|
284 |
> Associate an embedded bodystructure with its parent.
|
|
285 |
|
|
286 |
*/
|
|
287 |
void CImapBodyStructureBuilder::PushNewBodyStructureL()
|
|
288 |
{
|
|
289 |
// Going to create a new body structure.
|
|
290 |
// Need to be sure that something (either the stack root or its tree)
|
|
291 |
// is going to own and ultimatelty destroy it.
|
|
292 |
__ASSERT_DEBUG(iBodyStructureOwned, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderRootNotOwned));
|
|
293 |
|
|
294 |
CImapBodyStructure* bodyStructure = CImapBodyStructure::NewL();
|
|
295 |
CleanupStack::PushL(bodyStructure);
|
|
296 |
|
|
297 |
// root bodystructure is iBodyStructureStack[0]
|
|
298 |
if (iBodyStructure == NULL)
|
|
299 |
{
|
|
300 |
// Check for internal programming error
|
|
301 |
__ASSERT_DEBUG(iBodyStructureStack.Count() == 0, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderStackNotEmpty));
|
|
302 |
|
|
303 |
iBodyStructureStack.AppendL(bodyStructure);
|
|
304 |
|
|
305 |
// ownership is now transferred to the root bodystructure tree
|
|
306 |
CleanupStack::Pop(bodyStructure);
|
|
307 |
}
|
|
308 |
else
|
|
309 |
{
|
|
310 |
// Check for internal programming error
|
|
311 |
__ASSERT_DEBUG(iBodyStructureStack.Count() > 0, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderStackIsEmpty));
|
|
312 |
|
|
313 |
iBodyStructure->AppendEmbeddedBodyStructureL(*bodyStructure);
|
|
314 |
|
|
315 |
// ownership is now transferred to the root bodystructure tree
|
|
316 |
CleanupStack::Pop(bodyStructure);
|
|
317 |
|
|
318 |
iBodyStructureStack.AppendL(bodyStructure);
|
|
319 |
}
|
|
320 |
|
|
321 |
// This is now the bodystructure that we are parsing.
|
|
322 |
iBodyStructure = bodyStructure;
|
|
323 |
}
|
|
324 |
|
|
325 |
/**
|
|
326 |
Pops a bodystructure from the stack - except the root, which will not be popped.
|
|
327 |
@return whether we were in the root already.
|
|
328 |
*/
|
|
329 |
TBool CImapBodyStructureBuilder::PopBodyStructureL()
|
|
330 |
{
|
|
331 |
TBool bRootAlready = EFalse;
|
|
332 |
TInt stackCount = iBodyStructureStack.Count();
|
|
333 |
|
|
334 |
if (stackCount > 1)
|
|
335 |
{
|
|
336 |
// Pop the bodystructure stack
|
|
337 |
--stackCount;
|
|
338 |
CImapBodyStructure* poppedBs = iBodyStructureStack[stackCount];
|
|
339 |
iBodyStructureStack.Remove(stackCount); // No need to destroy the bodystructure, as it is now owned by iBodyStructureStack[stackCount-1]
|
|
340 |
|
|
341 |
iBodyStructure = iBodyStructureStack[stackCount-1];
|
|
342 |
}
|
|
343 |
else
|
|
344 |
{
|
|
345 |
// Check for internal programming error: Not expecting a stack count of 0 or less
|
|
346 |
__ASSERT_DEBUG(stackCount == 1, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderExpectedRootAtomOnlyOnStack));
|
|
347 |
bRootAlready = ETrue;
|
|
348 |
}
|
|
349 |
|
|
350 |
return bRootAlready;
|
|
351 |
}
|
|
352 |
|
|
353 |
/**
|
|
354 |
Found the closing bracket of our substructure.
|
|
355 |
Pop the stack, walk up and decide what to do next.
|
|
356 |
If we've reached the root level, then parsing is complete.
|
|
357 |
If our parent is a multipart, then check to see if we have a sibling.
|
|
358 |
If not then we have come to the end of our parent's structure too - handle this in a separate loop.
|
|
359 |
If our parent is a Rfc822, then we need to parse the remainder of the Rfc822 structure.
|
|
360 |
If not then we have come to the end of our parent's structure too - handle this in a separate loop.
|
|
361 |
@return
|
|
362 |
*/
|
|
363 |
CImapBodyStructureBuilder::TParseStep CImapBodyStructureBuilder::ParseSubStructureCompleteL()
|
|
364 |
{
|
|
365 |
TParseStep nextStep = EParseComplete;
|
|
366 |
|
|
367 |
iAtomWalker->WalkUpL();
|
|
368 |
|
|
369 |
if (iBodyStructure->BodyStructureType() == CImapBodyStructure::ETypeMultipart)
|
|
370 |
{
|
|
371 |
// Expecting either an open bracket for another bodystructure
|
|
372 |
// or the subtype.
|
|
373 |
CImapAtom* peekAcross = iAtomWalker->PeekAcross(); // peekAcross does not need to be destroyed as no ownership is transferred.
|
|
374 |
if (peekAcross == NULL)
|
|
375 |
{
|
|
376 |
// But not expecting "nothing".
|
|
377 |
CImapCommand::CorruptDataL(iLogId);
|
|
378 |
}
|
|
379 |
|
|
380 |
if (peekAcross->Match(KImapTxtOpenBracket()))
|
|
381 |
{
|
|
382 |
// position the atom walker on the open bracket, ready for ParseBodyStructureTypeL
|
|
383 |
// to walk down into it.
|
|
384 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
385 |
nextStep = EParseNewBodyStructure;
|
|
386 |
}
|
|
387 |
else
|
|
388 |
{
|
|
389 |
// stay where we are, so that ParseRemainderMultipartL()
|
|
390 |
// can walk accross to the subtype, as any other Parse method would do
|
|
391 |
nextStep = EParseRemainderMultipart;
|
|
392 |
}
|
|
393 |
}
|
|
394 |
else if (iBodyStructure->BodyStructureType() == CImapBodyStructure::ETypeMessageRfc822)
|
|
395 |
{
|
|
396 |
// stay where we are, so that ParseRemainderMessageRfc822L()
|
|
397 |
// can walk accross to the body-fld-lines, as any other Parse method would do
|
|
398 |
nextStep = EParseRemainderMessageRfc822;
|
|
399 |
}
|
|
400 |
else
|
|
401 |
{
|
|
402 |
// No other bodystruct type has substructures.
|
|
403 |
// So getting here is an internal programming error.
|
|
404 |
__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EBodyStructureBuilderInvalidBodyStructureType));
|
|
405 |
User::Leave(KErrGeneral);
|
|
406 |
}
|
|
407 |
|
|
408 |
return nextStep;
|
|
409 |
}
|
|
410 |
|
|
411 |
/**
|
|
412 |
body = "(" (body-type-1part / body-type-mpart) ")"
|
|
413 |
|
|
414 |
This method expects iAtomWalker to be positioned at the opening bracket of a body structure.
|
|
415 |
The method works out the type of the bodystructure, and returns the appropriate next parse
|
|
416 |
step to the parse loop.
|
|
417 |
For multipart structures, iAtomWalker is left positioned at the opening bracket of the embedded structure.
|
|
418 |
Foa all other structures, iAtomWalker is left positioned at the subtype field.
|
|
419 |
|
|
420 |
@return The next step for the parse loop to take. This is one of
|
|
421 |
> EParseNewBodyStructure for MULTIPART structures
|
|
422 |
> EParseText for TEXT structures
|
|
423 |
> EParseBodyTypeMessageRfc822 for MESSAGE/RFC822
|
|
424 |
> EParseBasic for all other structures.
|
|
425 |
*/
|
|
426 |
CImapBodyStructureBuilder::TParseStep CImapBodyStructureBuilder::ParseBodyStructureTypeL()
|
|
427 |
{
|
|
428 |
// Start at the opening bracket
|
|
429 |
__ASSERT_ALWAYS(iAtomWalker->CurrentMatch(KImapTxtOpenBracket()), CImapCommand::CorruptDataL(iLogId));
|
|
430 |
iAtomWalker->WalkDownL();
|
|
431 |
|
|
432 |
// What kind of body type does this represent?
|
|
433 |
// Assume Basic, unless we find otherwise.
|
|
434 |
TParseStep nextStep = EParseBasic;
|
|
435 |
|
|
436 |
// Is it body-type-mpart? - check for opening bracket
|
|
437 |
// body-type-mpart = 1*body SP media-subtype [SP body-ext-mpart]
|
|
438 |
if (iAtomWalker->CurrentMatch(KImapTxtOpenBracket()))
|
|
439 |
{
|
|
440 |
// According to section 6.4.5 of RFC3501, "MULTIPART" is the correct Type string for
|
|
441 |
// multipart messages. This is an implicit value not directly available from the
|
|
442 |
// bodystructure input string. So we point the bodystructure object at a constant string instead.
|
|
443 |
iBodyStructure->SetType(KImapTxtMultipart());
|
|
444 |
|
|
445 |
iBodyStructure->SetBodyStructureType(CImapBodyStructure::ETypeMultipart);
|
|
446 |
nextStep = EParseNewBodyStructure;
|
|
447 |
}
|
|
448 |
else
|
|
449 |
{
|
|
450 |
// body-type-1part = (body-type-basic / body-type-msg / body-type-text) [SP body-ext-1part]
|
|
451 |
iBodyStructure->SetType(iAtomWalker->CurrentDes(EFalse)); // media-basic and variants is a string, not an nstring
|
|
452 |
__ASSERT_ALWAYS(iAtomWalker->PeekDown() == NULL, CImapCommand::CorruptDataL(iLogId));
|
|
453 |
|
|
454 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
455 |
iBodyStructure->SetSubType(iAtomWalker->CurrentDes(EFalse)); // media-subtype and variants is a string, not an nstring
|
|
456 |
|
|
457 |
if (iBodyStructure->Type().CompareF(KImapTxtText())==0)
|
|
458 |
// body-type-text = media-text SP body-fields SP body-fld-lines
|
|
459 |
//
|
|
460 |
// media-text = DQUOTE "TEXT" DQUOTE SP media-subtype
|
|
461 |
// media-subtype = string
|
|
462 |
{
|
|
463 |
// we have media-text...
|
|
464 |
// ... so this is body-type-text
|
|
465 |
iBodyStructure->SetBodyStructureType(CImapBodyStructure::ETypeText);
|
|
466 |
nextStep = EParseText;
|
|
467 |
}
|
|
468 |
else if (iBodyStructure->Type().CompareF(KImapTxtMessage())==0 && iBodyStructure->SubType().CompareF(KImapTxtRfc822())==0)
|
|
469 |
// body-type-msg = media-message SP body-fields SP envelope SP body SP body-fld-lines
|
|
470 |
//
|
|
471 |
// media-message = DQUOTE "MESSAGE" DQUOTE SP DQUOTE "RFC822" DQUOTE
|
|
472 |
{
|
|
473 |
// we have media-message...
|
|
474 |
// ... so this is body-type-msg
|
|
475 |
iBodyStructure->SetBodyStructureType(CImapBodyStructure::ETypeMessageRfc822);
|
|
476 |
nextStep = EParseBodyTypeMessageRfc822;
|
|
477 |
}
|
|
478 |
else
|
|
479 |
{
|
|
480 |
// Not multipart, text or rfc822, so must be basic.
|
|
481 |
iBodyStructure->SetBodyStructureType(CImapBodyStructure::ETypeBasic);
|
|
482 |
}
|
|
483 |
}
|
|
484 |
|
|
485 |
return nextStep;
|
|
486 |
}
|
|
487 |
|
|
488 |
/**
|
|
489 |
body-type-basic = media-basic SP body-fields
|
|
490 |
|
|
491 |
This method expects media-basict to have been parsed already, as part of ParseBodyStructureTypeL()
|
|
492 |
It expects the atom walker to be positioned at the last atom of media-basic
|
|
493 |
*/
|
|
494 |
void CImapBodyStructureBuilder::ParseBodyTypeBasicL()
|
|
495 |
{
|
|
496 |
// body-fields
|
|
497 |
ParseBodyFieldsL();
|
|
498 |
}
|
|
499 |
|
|
500 |
/**
|
|
501 |
body-type-text = media-text SP body-fields SP body-fld-lines
|
|
502 |
|
|
503 |
This method expects media-text to have been parsed already, as part of ParseBodyStructureTypeL()
|
|
504 |
It expects the atom walker to be positioned at the last atom of media-text
|
|
505 |
*/
|
|
506 |
void CImapBodyStructureBuilder::ParseBodyTypeTextL()
|
|
507 |
{
|
|
508 |
// body-fields
|
|
509 |
ParseBodyFieldsL();
|
|
510 |
|
|
511 |
// body-fld-lines = number
|
|
512 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
513 |
iBodyStructure->SetBodyLines(iAtomWalker->CurrentDes(EFalse));
|
|
514 |
}
|
|
515 |
|
|
516 |
/**
|
|
517 |
body-type-msg = media-message SP body-fields SP envelope SP body SP body-fld-lines
|
|
518 |
|
|
519 |
This method parses up to and including envelope.
|
|
520 |
It then returns, allowing the parse loop to parse the nested "body" that is next.
|
|
521 |
Upon completion of the nested "body", ParseRemainderMessageRfc822L() will be called to finish parsing
|
|
522 |
the message body type
|
|
523 |
|
|
524 |
This method expects media-message to have been parsed already, as part of ParseBodyStructureTypeL()
|
|
525 |
It expects the atom walker to be positioned at the last atom of media-text
|
|
526 |
*/
|
|
527 |
void CImapBodyStructureBuilder::ParseBodyTypeMessageRfc822L()
|
|
528 |
{
|
|
529 |
// body-fields SP SP body SP body-fld-lines
|
|
530 |
ParseBodyFieldsL();
|
|
531 |
//Sometime RFCb22 message has empty evvelopel, in that case we are traping this and processed.
|
|
532 |
TRAPD(err, ParseEnvelopeL());
|
|
533 |
|
|
534 |
// Expect a body substructure next.
|
|
535 |
// Position iAtomWalker at the opening bracket, ready for ParseBodyStructureTypeL
|
|
536 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
537 |
if(err == KErrImapCorrupt)
|
|
538 |
CImapCommand::CorruptDataL(iLogId);
|
|
539 |
}
|
|
540 |
|
|
541 |
/**
|
|
542 |
body-type-msg = media-message SP body-fields SP envelope SP body SP body-fld-lines
|
|
543 |
|
|
544 |
ParseBodyTypeMessageRfc822L processes up to and including body-fields.
|
|
545 |
The ParseLoop processes the nested "body"
|
|
546 |
This method processes the remainder - i.e. body-fld-lines
|
|
547 |
It expects the atom walker to be positioned at the field just prior to body-fld-lines
|
|
548 |
*/
|
|
549 |
void CImapBodyStructureBuilder::ParseRemainderMessageRfc822L()
|
|
550 |
{
|
|
551 |
// body-fld-lines = number
|
|
552 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
553 |
iBodyStructure->SetBodyLines(iAtomWalker->CurrentDes(EFalse));
|
|
554 |
}
|
|
555 |
|
|
556 |
/**
|
|
557 |
body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP body-fld-enc SP body-fld-octets
|
|
558 |
|
|
559 |
This method expects iAtomWalker to be positioned just prior to body-fld-param
|
|
560 |
*/
|
|
561 |
void CImapBodyStructureBuilder::ParseBodyFieldsL()
|
|
562 |
{
|
|
563 |
ParseBodyFieldParamsL(EFalse);
|
|
564 |
|
|
565 |
// body-fld-id = nstring
|
|
566 |
iAtomWalker->WalkAcrossL(EFalse);
|
|
567 |
iBodyStructure->SetBodyId(iAtomWalker->CurrentDes(ETrue));
|
|
568 |
|
|
569 |
// body-fld-desc = nstring
|
|
570 |
iAtomWalker->WalkAcrossL(EFalse);
|
|
571 |
iBodyStructure->SetBodyDescription(iAtomWalker->CurrentDes(ETrue));
|
|
572 |
|
|
573 |
// body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ "QUOTED-PRINTABLE") DQUOTE) / string
|
|
574 |
// i.e. it's a string that might be in quotes
|
|
575 |
iAtomWalker->WalkAcrossL(EFalse);
|
|
576 |
iBodyStructure->SetBodyEncoding(iAtomWalker->CurrentDes(EFalse));
|
|
577 |
|
|
578 |
// body-fld-octets = number
|
|
579 |
iAtomWalker->WalkAcrossL(EFalse);
|
|
580 |
iBodyStructure->SetBodySizeOctets(iAtomWalker->CurrentDes(EFalse));
|
|
581 |
}
|
|
582 |
|
|
583 |
/**
|
|
584 |
body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
|
|
585 |
|
|
586 |
This method expects iAtomWalker to be positioned just prior to the "(" or "NIL" atom.
|
|
587 |
*/
|
|
588 |
void CImapBodyStructureBuilder::ParseBodyFieldParamsL(TBool aStoreAsDisposition)
|
|
589 |
|
|
590 |
{
|
|
591 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomNil)
|
|
592 |
{
|
|
593 |
// there are no params
|
|
594 |
return;
|
|
595 |
}
|
|
596 |
|
|
597 |
// Consume the bracket
|
|
598 |
iAtomWalker->WalkDownL();
|
|
599 |
|
|
600 |
do
|
|
601 |
{
|
|
602 |
CImapBodyStructure::TAttributeValuePair pair;
|
|
603 |
pair.iAttribute.Set(iAtomWalker->CurrentDes(EFalse));
|
|
604 |
|
|
605 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
606 |
pair.iValue.Set(iAtomWalker->CurrentDes(EFalse));
|
|
607 |
|
|
608 |
if (aStoreAsDisposition)
|
|
609 |
{
|
|
610 |
iBodyStructure->AppendExtDispositionParameterListL(pair);
|
|
611 |
}
|
|
612 |
else
|
|
613 |
{
|
|
614 |
iBodyStructure->AppendParameterListL(pair);
|
|
615 |
}
|
|
616 |
}
|
|
617 |
while (iAtomWalker->WalkAcrossL(EFalse));
|
|
618 |
|
|
619 |
iAtomWalker->WalkUpL();
|
|
620 |
}
|
|
621 |
|
|
622 |
/**
|
|
623 |
body-fld-dsp = "(" string SP body-fld-param ")" / nil
|
|
624 |
|
|
625 |
This method expects iAtomWalker to be positioned just prior to the "(" or "NIL" atom.
|
|
626 |
*/
|
|
627 |
void CImapBodyStructureBuilder::ParseBodyFieldDispL()
|
|
628 |
{
|
|
629 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomNil)
|
|
630 |
{
|
|
631 |
// there are no params
|
|
632 |
return;
|
|
633 |
}
|
|
634 |
|
|
635 |
iAtomWalker->WalkDownL();
|
|
636 |
|
|
637 |
// string
|
|
638 |
iBodyStructure->SetExtDispositionName(iAtomWalker->CurrentDes(EFalse));
|
|
639 |
|
|
640 |
// body-fld-param
|
|
641 |
ParseBodyFieldParamsL(ETrue);
|
|
642 |
|
|
643 |
iAtomWalker->WalkUpL();
|
|
644 |
}
|
|
645 |
|
|
646 |
/**
|
|
647 |
body-fld-lang = nstring / "(" string *(SP string) ")"
|
|
648 |
|
|
649 |
This method should only be called if body-fld-lang is expected.
|
|
650 |
The caller should check first, using iAtomWalker->PeekAcross()
|
|
651 |
*/
|
|
652 |
void CImapBodyStructureBuilder::ParseBodyFieldLangL()
|
|
653 |
{
|
|
654 |
// Consume the bracket
|
|
655 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
656 |
|
|
657 |
if (iAtomWalker->CurrentMatch(KImapTxtOpenBracket()))
|
|
658 |
{
|
|
659 |
// we have many strings
|
|
660 |
// "(" string *(SP string) ")"
|
|
661 |
|
|
662 |
iAtomWalker->WalkDownL();
|
|
663 |
|
|
664 |
do
|
|
665 |
{
|
|
666 |
iBodyStructure->AppendExtLanguageListL(iAtomWalker->CurrentDes(EFalse));
|
|
667 |
|
|
668 |
} while (iAtomWalker->WalkAcrossL(EFalse));
|
|
669 |
|
|
670 |
iAtomWalker->WalkUpL();
|
|
671 |
}
|
|
672 |
else
|
|
673 |
{
|
|
674 |
// we have a single nstring - only add it if it is non-empty
|
|
675 |
const TDesC8& language = iAtomWalker->CurrentDes(ETrue);
|
|
676 |
if (language.Length() > 0)
|
|
677 |
{
|
|
678 |
iBodyStructure->AppendExtLanguageListL(language);
|
|
679 |
}
|
|
680 |
}
|
|
681 |
}
|
|
682 |
|
|
683 |
/**
|
|
684 |
body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
685 |
|
|
686 |
This only appears in
|
|
687 |
body-type-1part = (body-type-basic / body-type-msg / body-type-text) [SP body-ext-1part]
|
|
688 |
So, this method treats the body-fld-md5 field as optional
|
|
689 |
*/
|
|
690 |
void CImapBodyStructureBuilder::ParseBodyExt1PartL()
|
|
691 |
{
|
|
692 |
// Return as soon as a field is not found.
|
|
693 |
if (iAtomWalker->WalkAcrossL(EFalse))
|
|
694 |
{
|
|
695 |
// body-fld-md5 = nstring
|
|
696 |
iBodyStructure->SetExtMD5(iAtomWalker->CurrentDes(ETrue));
|
|
697 |
|
|
698 |
// [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
699 |
ParseCommonOptionalExtensionsL();
|
|
700 |
}
|
|
701 |
}
|
|
702 |
|
|
703 |
/**
|
|
704 |
body-type-mpart = 1*body SP media-subtype [SP body-ext-mpart]
|
|
705 |
This method deals with media-subtype [SP body-ext-mpart]
|
|
706 |
|
|
707 |
body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
708 |
|
|
709 |
It expects iAtomWalker to be positioned just prior to media-subtype.
|
|
710 |
*/
|
|
711 |
void CImapBodyStructureBuilder::ParseRemainderMultipartL()
|
|
712 |
{
|
|
713 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
714 |
|
|
715 |
// media-subtype = string
|
|
716 |
iBodyStructure->SetSubType(iAtomWalker->CurrentDes(EFalse));
|
|
717 |
|
|
718 |
// The remainder of items are optional. Return as soon as one is not found.
|
|
719 |
|
|
720 |
// body-fld-param
|
|
721 |
if (iAtomWalker->PeekAcross())
|
|
722 |
{
|
|
723 |
ParseBodyFieldParamsL(EFalse);
|
|
724 |
|
|
725 |
// [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
726 |
ParseCommonOptionalExtensionsL();
|
|
727 |
}
|
|
728 |
}
|
|
729 |
|
|
730 |
/**
|
|
731 |
body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
732 |
body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]]
|
|
733 |
Apart from the first parameter, body extensions for 1 and multi part are the same.
|
|
734 |
So this method parses them in one place.
|
|
735 |
*/
|
|
736 |
void CImapBodyStructureBuilder::ParseCommonOptionalExtensionsL()
|
|
737 |
{
|
|
738 |
// Return as soon as a field is not found.
|
|
739 |
|
|
740 |
// body-fld-dsp
|
|
741 |
if (iAtomWalker->PeekAcross())
|
|
742 |
{
|
|
743 |
ParseBodyFieldDispL();
|
|
744 |
|
|
745 |
// body-fld-lang
|
|
746 |
if (iAtomWalker->PeekAcross())
|
|
747 |
{
|
|
748 |
ParseBodyFieldLangL();
|
|
749 |
|
|
750 |
// body-fld-loc = nstring
|
|
751 |
if (iAtomWalker->WalkAcrossL(EFalse))
|
|
752 |
{
|
|
753 |
iBodyStructure->SetExtLocation(iAtomWalker->CurrentDes(ETrue));
|
|
754 |
|
|
755 |
// *(SP body-extension)
|
|
756 |
if (iAtomWalker->PeekAcross())
|
|
757 |
{
|
|
758 |
ParseBodyExtensionL();
|
|
759 |
}
|
|
760 |
}
|
|
761 |
}
|
|
762 |
}
|
|
763 |
}
|
|
764 |
|
|
765 |
/**
|
|
766 |
body-extension = nstring / number / "(" body-extension *(SP body-extension) ")"
|
|
767 |
RFC3501 says: "Future expansion. Client implementations
|
|
768 |
MUST accept body-extension fields. Server
|
|
769 |
implementations MUST NOT generate
|
|
770 |
body-extension fields except as defined by
|
|
771 |
future standard or standards-track
|
|
772 |
revisions of this specification.
|
|
773 |
As body-extension is always at the end of a (sub)bodystructure, it is safe to ignore.
|
|
774 |
This method provides a placeholder for extracting any body-extension data that we might
|
|
775 |
be interested in, in the future.
|
|
776 |
*/
|
|
777 |
void CImapBodyStructureBuilder::ParseBodyExtensionL()
|
|
778 |
{}
|
|
779 |
|
|
780 |
/**
|
|
781 |
envelope = "(" env-date SP env-subject SP env-from SP env-sender SP env-reply-to SP env-to SP env-cc SP env-bcc SP env-in-reply-to SP env-message-id ")"
|
|
782 |
|
|
783 |
This method expects iAtomWalker to be positioned at the opening bracket.
|
|
784 |
*/
|
|
785 |
void CImapBodyStructureBuilder::ParseEnvelopeL()
|
|
786 |
{
|
|
787 |
// Always expect an open bracket here, so allow atom walker to leave if there is one
|
|
788 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
789 |
iAtomWalker->WalkDownL();
|
|
790 |
|
|
791 |
CImapEnvelope& envelope = iBodyStructure->GetRfc822EnvelopeStructureL();
|
|
792 |
|
|
793 |
// env-date = nstring
|
|
794 |
envelope.SetEnvDate(iAtomWalker->CurrentDes(ETrue));
|
|
795 |
|
|
796 |
// env-subject = nstring
|
|
797 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
798 |
envelope.SetEnvSubject(iAtomWalker->CurrentDes(ETrue));
|
|
799 |
|
|
800 |
// This single address structure will be *copied* into various envelope address arrays
|
|
801 |
CImapEnvelope::TAddress address; // this will copied into many en
|
|
802 |
|
|
803 |
// env-from = "(" 1*address ")" / nil
|
|
804 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
805 |
{
|
|
806 |
iAtomWalker->WalkDownL();
|
|
807 |
do
|
|
808 |
{
|
|
809 |
ParseAddressL(address);
|
|
810 |
envelope.AppendEnvFromL(address);
|
|
811 |
|
|
812 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
813 |
|
|
814 |
iAtomWalker->WalkUpL();
|
|
815 |
}
|
|
816 |
|
|
817 |
// env-sender = "(" 1*address ")" / nil
|
|
818 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
819 |
{
|
|
820 |
iAtomWalker->WalkDownL();
|
|
821 |
do
|
|
822 |
{
|
|
823 |
ParseAddressL(address);
|
|
824 |
envelope.AppendEnvSenderL(address);
|
|
825 |
|
|
826 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
827 |
|
|
828 |
iAtomWalker->WalkUpL();
|
|
829 |
}
|
|
830 |
// env-reply-to = "(" 1*address ")" / nil
|
|
831 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
832 |
{
|
|
833 |
iAtomWalker->WalkDownL();
|
|
834 |
do
|
|
835 |
{
|
|
836 |
ParseAddressL(address);
|
|
837 |
envelope.AppendEnvReplyToL(address);
|
|
838 |
|
|
839 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
840 |
|
|
841 |
iAtomWalker->WalkUpL();
|
|
842 |
}
|
|
843 |
|
|
844 |
// env-to = "(" 1*address ")" / nil
|
|
845 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
846 |
{
|
|
847 |
iAtomWalker->WalkDownL();
|
|
848 |
do
|
|
849 |
{
|
|
850 |
ParseAddressL(address);
|
|
851 |
envelope.AppendEnvToL(address);
|
|
852 |
|
|
853 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
854 |
|
|
855 |
iAtomWalker->WalkUpL();
|
|
856 |
}
|
|
857 |
|
|
858 |
// env-cc = "(" 1*address ")" / nil
|
|
859 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
860 |
{
|
|
861 |
iAtomWalker->WalkDownL();
|
|
862 |
do
|
|
863 |
{
|
|
864 |
ParseAddressL(address);
|
|
865 |
envelope.AppendEnvCcL(address);
|
|
866 |
|
|
867 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
868 |
|
|
869 |
iAtomWalker->WalkUpL();
|
|
870 |
}
|
|
871 |
|
|
872 |
// env-bcc = "(" 1*address ")" / nil
|
|
873 |
if (iAtomWalker->WalkAcrossToNilOrOpenL() == CImapAtomWalker::EAtomOpen)
|
|
874 |
{
|
|
875 |
iAtomWalker->WalkDownL();
|
|
876 |
do
|
|
877 |
{
|
|
878 |
ParseAddressL(address);
|
|
879 |
envelope.AppendEnvBccL(address);
|
|
880 |
|
|
881 |
} while(iAtomWalker->WalkAcrossL(EFalse));
|
|
882 |
|
|
883 |
iAtomWalker->WalkUpL();
|
|
884 |
}
|
|
885 |
|
|
886 |
// env-in-reply-to = nstring
|
|
887 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
888 |
envelope.SetEnvInReplyTo(iAtomWalker->CurrentDes(ETrue));
|
|
889 |
|
|
890 |
// env-message-id = nstring
|
|
891 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
892 |
envelope.SetEnvMessageId(iAtomWalker->CurrentDes(ETrue));
|
|
893 |
|
|
894 |
iAtomWalker->WalkUpL();
|
|
895 |
}
|
|
896 |
|
|
897 |
/**
|
|
898 |
address = "(" addr-name SP addr-adl SP addr-mailbox SP addr-host ")"
|
|
899 |
|
|
900 |
This method expects iAtomWalker to be positioned at the opening bracket.
|
|
901 |
*/
|
|
902 |
void CImapBodyStructureBuilder::ParseAddressL(CImapEnvelope::TAddress& aAddress)
|
|
903 |
{
|
|
904 |
// Always expect an open bracket here, so allow atom walker to leave if there is one
|
|
905 |
iAtomWalker->WalkDownL();
|
|
906 |
|
|
907 |
// addr-name = nstring
|
|
908 |
aAddress.SetName(iAtomWalker->CurrentDes(ETrue));
|
|
909 |
|
|
910 |
// addr-adl = nstring
|
|
911 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
912 |
aAddress.SetAdl(iAtomWalker->CurrentDes(ETrue));
|
|
913 |
|
|
914 |
// addr-mailbox = nstring
|
|
915 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
916 |
aAddress.SetMailbox(iAtomWalker->CurrentDes(ETrue));
|
|
917 |
|
|
918 |
// addr-host = nstring
|
|
919 |
iAtomWalker->WalkAcrossL(ETrue);
|
|
920 |
aAddress.SetHost(iAtomWalker->CurrentDes(ETrue));
|
|
921 |
|
|
922 |
iAtomWalker->WalkUpL();
|
|
923 |
}
|