Skip to content

Commit 5941cee

Browse files
committed
Boundary detection overhauled to support "related" and "alternative" #90
1 parent b2c749f commit 5941cee

File tree

3 files changed

+76
-42
lines changed

3 files changed

+76
-42
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
99
- Boundary detection simplified #90
1010
- Prevent potential body overwriting #90
1111
- CSV files are no longer regarded as plain body
12+
- Boundary detection overhauled to support "related" and "alternative" multipart messages #90
1213

1314
### Added
1415
- NaN
1516

1617
### Affected Classes
1718
- [Structure::class](src/Structure.php)
1819
- [Message::class](src/Message.php)
20+
- [Header::class](src/Header.php)
21+
- [Part::class](src/Part.php)
1922

2023
### Breaking changes
2124
- NaN

src/Header.php

+25
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,31 @@ public function find($pattern) {
167167
return null;
168168
}
169169

170+
/**
171+
* Try to find a boundary if possible
172+
*
173+
* @return string|null
174+
*/
175+
public function getBoundary(){
176+
$boundary = $this->find("/boundary\=(.*)/i");
177+
178+
if ($boundary === null) {
179+
return null;
180+
}
181+
182+
return $this->clearBoundaryString($boundary);
183+
}
184+
185+
/**
186+
* Remove all unwanted chars from a given boundary
187+
* @param string $str
188+
*
189+
* @return string
190+
*/
191+
private function clearBoundaryString($str) {
192+
return str_replace(['"', '\r', '\n', "\n", "\r", ";", "\s"], "", $str);
193+
}
194+
170195
/**
171196
* Parse the raw headers
172197
*

src/Structure.php

+48-42
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ protected function parse(){
8686
* Determine the message content type
8787
*/
8888
public function findContentType(){
89-
9089
$content_type = $this->header->get("content_type");
9190
$content_type = (is_array($content_type)) ? implode(' ', $content_type) : $content_type;
9291
if(stripos($content_type, 'multipart') === 0) {
@@ -97,28 +96,51 @@ public function findContentType(){
9796
}
9897

9998
/**
100-
* Determine the message content type
99+
* Find all available headers and return the left over body segment
100+
* @var string $context
101+
* @var integer $part_number
101102
*
102-
* @return string|null
103+
* @return Part[]
104+
* @throws InvalidMessageDateException
103105
*/
104-
public function getBoundary(){
105-
$boundary = $this->header->find("/boundary\=(.*)/i");
106-
107-
if ($boundary === null) {
108-
return null;
106+
private function parsePart($context, $part_number = 0){
107+
$body = $context;
108+
while (($pos = strpos($body, "\r\n")) > 0) {
109+
$body = substr($body, $pos + 2);
109110
}
111+
$headers = substr($context, 0, strlen($body) * -1);
112+
$body = substr($body, 0, -2);
110113

111-
return $this->clearBoundaryString($boundary);
114+
$headers = new Header($headers);
115+
if (($boundary = $headers->getBoundary()) !== null) {
116+
return $this->detectParts($boundary, $body, $part_number);
117+
}
118+
return [new Part($body, $headers, $part_number)];
112119
}
113120

114121
/**
115-
* Remove all unwanted chars from a given boundary
116-
* @param string $str
122+
* @param string $boundary
123+
* @param string $context
124+
* @param int $part_number
117125
*
118-
* @return string
126+
* @return array
127+
* @throws InvalidMessageDateException
119128
*/
120-
private function clearBoundaryString($str) {
121-
return str_replace(['"', '\r', '\n', "\n", "\r", ";", "\s"], "", $str);
129+
private function detectParts($boundary, $context, $part_number = 0){
130+
$base_parts = explode( $boundary, $context);
131+
$final_parts = [];
132+
foreach($base_parts as $ctx) {
133+
$ctx = substr($ctx, 2);
134+
if ($ctx !== "--" && $ctx != "") {
135+
$parts = $this->parsePart($ctx, $part_number);
136+
foreach ($parts as $part) {
137+
$final_parts[] = $part;
138+
$part_number = $part->part_number;
139+
}
140+
$part_number++;
141+
}
142+
}
143+
return $final_parts;
122144
}
123145

124146
/**
@@ -130,39 +152,23 @@ private function clearBoundaryString($str) {
130152
*/
131153
public function find_parts(){
132154
if($this->type === IMAP::MESSAGE_TYPE_MULTIPART) {
133-
if (($boundary = $this->getBoundary()) === null) {
155+
if (($boundary = $this->header->getBoundary()) === null) {
134156
throw new MessageContentFetchingException("no content found", 0);
135157
}
136158

137-
$boundaries = [
138-
$boundary
139-
];
140-
141-
if (preg_match("/boundary\=\"?(.*)\"?/", $this->raw, $match) == 1) {
142-
if(is_array($match[1])){
143-
foreach($match[1] as $matched){
144-
$boundaries[] = $this->clearBoundaryString($matched);
145-
}
146-
}else{
147-
if(!empty($match[1])) {
148-
$boundaries[] = $this->clearBoundaryString($match[1]);
149-
}
150-
}
151-
}
152-
153-
$raw_parts = explode( $boundaries[0], str_replace($boundaries, $boundaries[0], $this->raw) );
154-
$parts = [];
155-
$part_number = 0;
156-
foreach($raw_parts as $part) {
157-
$part = trim(rtrim($part));
158-
if ($part !== "--") {
159-
$parts[] = new Part($part, null, $part_number);
160-
$part_number++;
161-
}
162-
}
163-
return $parts;
159+
return $this->detectParts($boundary, $this->raw);
164160
}
165161

166162
return [new Part($this->raw, $this->header)];
167163
}
164+
165+
/**
166+
* Try to find a boundary if possible
167+
*
168+
* @return string|null
169+
* @Depricated since version 2.4.4
170+
*/
171+
public function getBoundary(){
172+
return $this->header->getBoundary();
173+
}
168174
}

0 commit comments

Comments
 (0)