lastAddedChild !== null) { $this->lastAddedChild->parseAll(); } return $this; } /** * Parses the next child of this part and adds it to the 'stack' of * children. */ protected function parseNextChild() : static { if ($this->allChildrenParsed) { return $this; } $this->parseContent(); $this->ensureLastChildParsed(); $next = $this->parser->parseNextChild($this); if ($next !== null) { $this->children[] = $next; $this->childrenStack[] = $next; $this->lastAddedChild = $next; } else { $this->allChildrenParsed = true; } return $this; } /** * Returns the next child part if one exists, popping it from the internal * 'stack' of children, attempting to parse a new one if the stack is empty, * and returning null if there are no more children. * * @return ?IMessagePart the child part. */ public function popNextChild() : ?IMessagePart { if (empty($this->childrenStack)) { $this->parseNextChild(); } $proxy = \array_shift($this->childrenStack); return ($proxy !== null) ? $proxy->getPart() : null; } /** * Parses all content and children for this part. */ public function parseAll() : static { $this->parseContent(); while (!$this->allChildrenParsed) { $this->parseNextChild(); } return $this; } /** * Returns a ParameterHeader representing the parsed Content-Type header for * this part. */ public function getContentType() : ?ParameterHeader { return $this->getHeaderContainer()->get(HeaderConsts::CONTENT_TYPE); } /** * Returns the parsed boundary parameter of the Content-Type header if set * for a multipart message part. * */ public function getMimeBoundary() : ?string { if ($this->mimeBoundaryQueried === false) { $this->mimeBoundaryQueried = true; $contentType = $this->getContentType(); if ($contentType !== null) { $this->mimeBoundary = $contentType->getValueFor('boundary'); } } return $this->mimeBoundary; } /** * Returns true if the passed $line of read input matches this part's mime * boundary, or any of its parent's mime boundaries for a multipart message. * * If the passed $line is the ending boundary for the current part, * $this->isEndBoundaryFound will return true after. */ public function setEndBoundaryFound(string $line) : bool { $boundary = $this->getMimeBoundary(); if ($this->getParent() !== null && $this->getParent()->setEndBoundaryFound($line)) { $this->parentBoundaryFound = true; return true; } elseif ($boundary !== null) { if ($line === "--$boundary--") { $this->endBoundaryFound = true; return true; } elseif ($line === "--$boundary") { return true; } } return false; } /** * Returns true if the parser passed an input line to setEndBoundary that * matches a parent's mime boundary, and the following input belongs to a * new part under its parent. * */ public function isParentBoundaryFound() : bool { return ($this->parentBoundaryFound); } /** * Returns true if an end boundary was found for this part. * */ public function isEndBoundaryFound() : bool { return ($this->endBoundaryFound); } /** * Called once EOF is reached while reading content. The method sets the * flag used by isParentBoundaryFound() to true on this part and all parent * parts. * */ public function setEof() : static { $this->parentBoundaryFound = true; if ($this->getParent() !== null) { $this->getParent()->setEof(); } return $this; } /** * Overridden to set a 0-length content length, and a stream end pos of -2 * if the passed end pos is before the start pos (can happen if a mime * end boundary doesn't have an empty line before the next parent start * boundary). */ public function setStreamPartAndContentEndPos(int $streamContentEndPos) : static { // check if we're expecting a boundary and didn't find one if (!$this->endBoundaryFound && !$this->parentBoundaryFound) { if (!empty($this->mimeBoundary) || ($this->getParent() !== null && !empty($this->getParent()->mimeBoundary))) { $this->addError('End boundary for part not found', LogLevel::WARNING); } } $start = $this->getStreamContentStartPos(); if ($streamContentEndPos - $start < 0) { parent::setStreamPartAndContentEndPos($start); $this->setStreamPartEndPos($streamContentEndPos); } else { parent::setStreamPartAndContentEndPos($streamContentEndPos); } return $this; } /** * Sets the length of the last line ending read by MimeParser (e.g. 2 for * '\r\n', or 1 for '\n'). * * The line ending may not belong specifically to this part, so * ParserMimePartProxy simply calls setLastLineEndingLength on its parent, * which must eventually reach a ParserMessageProxy which actually stores * the length. */ public function setLastLineEndingLength(int $length) : static { $this->getParent()->setLastLineEndingLength($length); return $this; } /** * Returns the length of the last line ending read by MimeParser (e.g. 2 for * '\r\n', or 1 for '\n'). * * The line ending may not belong specifically to this part, so * ParserMimePartProxy simply calls getLastLineEndingLength on its parent, * which must eventually reach a ParserMessageProxy which actually keeps * the length and returns it. * * @return int the length of the last line ending read */ public function getLastLineEndingLength() : int { return $this->getParent()->getLastLineEndingLength(); } /** * Returns the last part that was added. */ public function getLastAddedChild() : ?ParserPartProxy { return $this->lastAddedChild; } /** * Returns the added child at the provided index, useful for looking at * previously parsed children. */ public function getAddedChildAt(int $index) : ?ParserPartProxy { return $this->children[$index] ?? null; } }