Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/aphront/multipartparser/AphrontMultipartParser.php
12241 views
1
<?php
2
3
final class AphrontMultipartParser extends Phobject {
4
5
private $contentType;
6
private $boundary;
7
8
private $buffer;
9
private $body;
10
private $state;
11
12
private $part;
13
private $parts;
14
15
public function setContentType($content_type) {
16
$this->contentType = $content_type;
17
return $this;
18
}
19
20
public function getContentType() {
21
return $this->contentType;
22
}
23
24
public function beginParse() {
25
$content_type = $this->getContentType();
26
if ($content_type === null) {
27
throw new PhutilInvalidStateException('setContentType');
28
}
29
30
if (!preg_match('(^multipart/form-data)', $content_type)) {
31
throw new Exception(
32
pht(
33
'Expected "multipart/form-data" content type when executing a '.
34
'multipart body read.'));
35
}
36
37
$type_parts = preg_split('(\s*;\s*)', $content_type);
38
$boundary = null;
39
foreach ($type_parts as $type_part) {
40
$matches = null;
41
if (preg_match('(^boundary=(.*))', $type_part, $matches)) {
42
$boundary = $matches[1];
43
break;
44
}
45
}
46
47
if ($boundary === null) {
48
throw new Exception(
49
pht('Received "multipart/form-data" request with no "boundary".'));
50
}
51
52
$this->parts = array();
53
$this->part = null;
54
55
$this->buffer = '';
56
$this->boundary = $boundary;
57
58
// We're looking for a (usually empty) body before the first boundary.
59
$this->state = 'bodynewline';
60
}
61
62
public function continueParse($bytes) {
63
$this->buffer .= $bytes;
64
65
$continue = true;
66
while ($continue) {
67
switch ($this->state) {
68
case 'endboundary':
69
// We've just parsed a boundary. Next, we expect either "--" (which
70
// indicates we've reached the end of the parts) or "\r\n" (which
71
// indicates we should read the headers for the next part).
72
73
if (strlen($this->buffer) < 2) {
74
// We don't have enough bytes yet, so wait for more.
75
$continue = false;
76
break;
77
}
78
79
if (!strncmp($this->buffer, '--', 2)) {
80
// This is "--" after a boundary, so we're done. We'll read the
81
// rest of the body (the "epilogue") and discard it.
82
$this->buffer = substr($this->buffer, 2);
83
$this->state = 'epilogue';
84
85
$this->part = null;
86
break;
87
}
88
89
if (!strncmp($this->buffer, "\r\n", 2)) {
90
// This is "\r\n" after a boundary, so we're going to going to
91
// read the headers for a part.
92
$this->buffer = substr($this->buffer, 2);
93
$this->state = 'header';
94
95
// Create the object to hold the part we're about to read.
96
$part = new AphrontMultipartPart();
97
$this->parts[] = $part;
98
$this->part = $part;
99
break;
100
}
101
102
throw new Exception(
103
pht('Expected "\r\n" or "--" after multipart data boundary.'));
104
case 'header':
105
// We've just parsed a boundary, followed by "\r\n". We are going
106
// to read the headers for this part. They are in the form of HTTP
107
// headers and terminated by "\r\n". The section is terminated by
108
// a line with no header on it.
109
110
if (strlen($this->buffer) < 2) {
111
// We don't have enough data to find a "\r\n", so wait for more.
112
$continue = false;
113
break;
114
}
115
116
if (!strncmp("\r\n", $this->buffer, 2)) {
117
// This line immediately began "\r\n", so we're done with parsing
118
// headers. Start parsing the body.
119
$this->buffer = substr($this->buffer, 2);
120
$this->state = 'body';
121
break;
122
}
123
124
// This is an actual header, so look for the end of it.
125
$header_len = strpos($this->buffer, "\r\n");
126
if ($header_len === false) {
127
// We don't have a full header yet, so wait for more data.
128
$continue = false;
129
break;
130
}
131
132
$header_buf = substr($this->buffer, 0, $header_len);
133
$this->part->appendRawHeader($header_buf);
134
135
$this->buffer = substr($this->buffer, $header_len + 2);
136
break;
137
case 'body':
138
// We've parsed a boundary and headers, and are parsing the data for
139
// this part. The data is terminated by "\r\n--", then the boundary.
140
141
// We'll look for "\r\n", then switch to the "bodynewline" state if
142
// we find it.
143
144
$marker = "\r";
145
$marker_pos = strpos($this->buffer, $marker);
146
147
if ($marker_pos === false) {
148
// There's no "\r" anywhere in the buffer, so we can just read it
149
// as provided. Then, since we read all the data, we're done until
150
// we get more.
151
152
// Note that if we're in the preamble, we won't have a "part"
153
// object and will just discard the data.
154
if ($this->part) {
155
$this->part->appendData($this->buffer);
156
}
157
$this->buffer = '';
158
$continue = false;
159
break;
160
}
161
162
if ($marker_pos > 0) {
163
// If there are bytes before the "\r",
164
if ($this->part) {
165
$this->part->appendData(substr($this->buffer, 0, $marker_pos));
166
}
167
$this->buffer = substr($this->buffer, $marker_pos);
168
}
169
170
$expect = "\r\n";
171
$expect_len = strlen($expect);
172
if (strlen($this->buffer) < $expect_len) {
173
// We don't have enough bytes yet to know if this is "\r\n"
174
// or not.
175
$continue = false;
176
break;
177
}
178
179
if (strncmp($this->buffer, $expect, $expect_len)) {
180
// The next two bytes aren't "\r\n", so eat them and go looking
181
// for more newlines.
182
if ($this->part) {
183
$this->part->appendData(substr($this->buffer, 0, $expect_len));
184
}
185
$this->buffer = substr($this->buffer, $expect_len);
186
break;
187
}
188
189
// Eat the "\r\n".
190
$this->buffer = substr($this->buffer, $expect_len);
191
$this->state = 'bodynewline';
192
break;
193
case 'bodynewline':
194
// We've parsed a newline in a body, or we just started parsing the
195
// request. In either case, we're looking for "--", then the boundary.
196
// If we find it, this section is done. If we don't, we consume the
197
// bytes and move on.
198
199
$expect = '--'.$this->boundary;
200
$expect_len = strlen($expect);
201
202
if (strlen($this->buffer) < $expect_len) {
203
// We don't have enough bytes yet, so wait for more.
204
$continue = false;
205
break;
206
}
207
208
if (strncmp($this->buffer, $expect, $expect_len)) {
209
// This wasn't the boundary, so return to the "body" state and
210
// consume it. (But first, we need to append the "\r\n" which we
211
// ate earlier.)
212
if ($this->part) {
213
$this->part->appendData("\r\n");
214
}
215
$this->state = 'body';
216
break;
217
}
218
219
// This is the boundary, so toss it and move on.
220
$this->buffer = substr($this->buffer, $expect_len);
221
$this->state = 'endboundary';
222
break;
223
case 'epilogue':
224
// We just discard any epilogue.
225
$this->buffer = '';
226
$continue = false;
227
break;
228
default:
229
throw new Exception(
230
pht(
231
'Unknown parser state "%s".\n',
232
$this->state));
233
}
234
}
235
}
236
237
public function endParse() {
238
if ($this->state !== 'epilogue') {
239
throw new Exception(
240
pht(
241
'Expected "multipart/form-data" parse to end '.
242
'in state "epilogue".'));
243
}
244
245
return $this->parts;
246
}
247
248
249
}
250
251