source: trunk/www.guidonia.net/wp/wp-content/plugins/webtv/Drivers/Zend/Gdata/MediaMimeStream.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 17.3 KB
Line 
1<?php
2
3/**
4 * Zend Framework
5 *
6 * LICENSE
7 *
8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
15 *
16 * @category Zend
17 * @package Zend_Gdata
18 * @subpackage Gdata
19 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
20 * @license http://framework.zend.com/license/new-bsd New BSD License
21 */
22
23/**
24 * A streaming Media MIME class that allows for buffered read operations.
25 *
26 * @category Zend
27 * @package Zend_Gdata
28 * @subpackage Gdata
29 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
30 * @license http://framework.zend.com/license/new-bsd New BSD License
31 */
32class Zend_Gdata_MediaMimeStream
33{
34
35 /**
36 * The Content-Type section that precedes the XML data in the message.
37 *
38 * @var string
39 */
40 // TODO (jhartmann) Add support for charset [ZF-5768]
41 const XML_HEADER = "Content-Type: application/atom+xml\r\n\r\n";
42
43 /**
44 * A constant indicating the xml string part of the message
45 *
46 * @var integer
47 */
48 const PART_XML_STRING = 0;
49
50 /**
51 * A constant indicating the file binary part of the message
52 *
53 * @var integer
54 */
55 const PART_FILE_BINARY = 1;
56
57 /**
58 * A constant indicating the closing boundary string of the message
59 *
60 * @var integer
61 */
62 const PART_CLOSING_XML_STRING = 2;
63
64 /**
65 * The maximum buffer size that can be used.
66 *
67 * @var integer
68 */
69 const MAX_BUFFER_SIZE = 8192;
70
71 /**
72 * A valid MIME boundary including a linefeed.
73 *
74 * @var string
75 */
76 protected $_boundaryLine = null;
77
78 /**
79 * A valid MIME boundary without a linefeed for use in the header.
80 *
81 * @var string
82 */
83 protected $_boundaryString = null;
84
85 /**
86 * A valid MIME closing boundary including a linefeed.
87 *
88 * @var string
89 */
90 protected $_closingBoundaryLine = null;
91
92 /**
93 * A handle to the file that is part of the message.
94 *
95 * @var resource
96 */
97 protected $_fileHandle = null;
98
99 /**
100 * The headers that preceed the file binary including linefeeds.
101 *
102 * @var string
103 */
104 protected $_fileHeaders = null;
105
106 /**
107 * The internet media type of the enclosed file.
108 *
109 * @var string
110 */
111 protected $_fileContentType = null;
112
113 /**
114 * The file size.
115 *
116 * @var integer
117 */
118 protected $_fileSize = null;
119
120 /**
121 * The total size of the message.
122 *
123 * @var integer
124 */
125 protected $_totalSize = null;
126
127 /**
128 * The XML string that typically represents the entry to be sent.
129 *
130 * @var string
131 */
132 protected $_xmlString = null;
133
134 /**
135 * The number of bytes that have been read so far.
136 *
137 * @var integer
138 */
139 protected $_bytesRead = 0;
140
141 /**
142 * Enumeration indicating the part of the message that is currently being
143 * read. Allowed values are: 0, 1 and 2, corresponding to the constants:
144 * PART_XML_STRING, PART_FILE_BINARY, PART_CLOSING_XML_STRING
145 *
146 * @var integer
147 */
148 protected $_currentPart = 0;
149
150 /**
151 * A nested array containing the message to be sent. Each element contains
152 * an array in the format:
153 *
154 * [integer (size of message)][string (message)]
155 *
156 * Note that the part corresponding to the file only contains a size.
157 *
158 * @var array
159 */
160 protected $_parts = null;
161
162 /**
163 * A boolean to be set immediately once we have finished reading.
164 *
165 * @var boolean
166 */
167 protected $_doneReading = false;
168
169 /**
170 * Create a new MimeMediaStream object.
171 *
172 * @param string $xmlString The string corresponding to the XML section
173 * of the message, typically an atom entry or feed.
174 * @param string $filePath The path to the file that constitutes the binary
175 * part of the message.
176 * @param string $fileContentType The valid internet media type of the file.
177 * @throws Zend_Gdata_App_IOException If the file cannot be read or does
178 * not exist. Also if mbstring.func_overload has been set > 1.
179 */
180 public function __construct($xmlString = null, $filePath = null,
181 $fileContentType = null)
182 {
183 $this->_xmlString = $xmlString;
184 $this->_filePath = $filePath;
185 $this->_fileContentType = $fileContentType;
186
187 if (!file_exists($filePath) || !is_readable($filePath)) {
188 require_once 'Zend/Gdata/App/IOException.php';
189 throw new Zend_Gdata_App_IOException('File to be uploaded at ' .
190 $filePath . ' does not exist or is not readable.');
191 }
192
193 $this->_fileHandle = fopen($filePath, 'rb', true);
194 $this->generateBoundaries();
195 $this->calculatePartSizes();
196 }
197
198 /**
199 * Generate the MIME message boundary strings.
200 *
201 * @return void
202 */
203 private function generateBoundaries()
204 {
205 $this->_boundaryString = '=_' . md5(microtime(1) . rand(1,20));
206 $this->_boundaryLine = "\r\n" . '--' . $this->_boundaryString . "\r\n";
207 $this->_closingBoundaryLine = "\r\n" . '--' . $this->_boundaryString .
208 '--';
209 }
210
211 /**
212 * Calculate the sizes of the MIME message sections.
213 *
214 * @return void
215 */
216 private function calculatePartSizes()
217 {
218 $this->_fileHeaders = 'Content-Type: ' . $this->_fileContentType .
219 "\r\n" . 'Content-Transfer-Encoding: binary' . "\r\n\r\n";
220 $this->_fileSize = filesize($this->_filePath);
221
222 $stringSection = $this->_boundaryLine . self::XML_HEADER .
223 $this->_xmlString . "\r\n" . $this->_boundaryLine .
224 $this->_fileHeaders;
225 $stringLen = strlen($stringSection);
226 $closingBoundaryLen = strlen($this->_closingBoundaryLine);
227
228 $this->_parts = array();
229 $this->_parts[] = array($stringLen, $stringSection);
230 $this->_parts[] = array($this->_fileSize);
231 $this->_parts[] = array($closingBoundaryLen,
232 $this->_closingBoundaryLine);
233
234 $this->_totalSize = $stringLen + $this->_fileSize + $closingBoundaryLen;
235 }
236
237 /**
238 * A wrapper around fread() that doesn't error when $length is 0.
239 *
240 * @param integer $length Number of bytes to read.
241 * @return string Results of byte operation.
242 */
243 private function smartfread($length)
244 {
245 if ($length < 1) {
246 return '';
247 } else {
248 return fread($this->_fileHandle, $length);
249 }
250 }
251
252 /**
253 * A non mbstring overloadable strlen-like function.
254 *
255 * @param string $string The string whose length we want to get.
256 * @return integer The length of the string.
257 */
258 private function strlen2($string)
259 {
260 return array_sum(char_count($string));
261 }
262
263 /**
264 * Read a specific chunk of the the MIME multipart message.
265 *
266 * This function works by examining the internal 'parts' array. It
267 * expects that array to consist of basically a string, a file handle
268 * and a closing string.
269 *
270 * An abbreviated version of what this function does is as follows:
271 *
272 * - throw exception if trying to read bigger than the allocated max buffer
273 * - If bufferSize bigger than the entire message: return it and exit.
274 *
275 * - Check how far to read by looking at how much has been read.
276 * - Figure out whether we are crossing sections in this read:
277 * i.e. -> reading past the xml_string and into the file ?
278 * - Determine whether we are crossing two sections in this read:
279 * i.e. xml_string, file and half of the closing string or
280 * possibly file, closing string and next (non-existant) section
281 * and handle each case.
282 * - If we are NOT crossing any sections: read either string and
283 * increment counter, or read file (no counter needed since fread()
284 * stores it's own counter.
285 * - If we are crossing 1 section, figure out how much remains in that
286 * section that we are currently reading and how far to read into
287 * the next section. If the section just read is xml_string, then
288 * immediately unset it from our 'parts' array. If it is the file,
289 * then close the handle.
290 *
291 * @param integer $bufferSize The size of the chunk that is to be read,
292 * must be lower than MAX_BUFFER_SIZE.
293 * @throws Zend_Gdata_App_InvalidArgumentException if buffer size too big.
294 * @return string A corresponding piece of the message. This could be
295 * binary or regular text.
296 */
297 public function read($bufferSize)
298 {
299 if ($bufferSize > self::MAX_BUFFER_SIZE) {
300 require_once 'Zend/Gdata/App/InvalidArgumentException.php';
301 throw new Zend_Gdata_App_InvalidArgumentException('Buffer size ' .
302 'is larger than the supported max of ' . self::MAX_BUFFER_SIZE);
303 }
304
305 // handle edge cases where bytesRead is negative
306 if ($this->_bytesRead < 0) {
307 $this->_bytesRead = 0;
308 }
309
310 $returnString = null;
311 // If entire message is smaller than the buffer, just return everything
312 if ($bufferSize > $this->_totalSize) {
313 $returnString = $this->_parts[self::PART_XML_STRING][1];
314 $returnString .= fread($this->_fileHandle, $bufferSize);
315 $returnString .= $this->_closingBoundaryLine;
316 $this->closeFileHandle();
317 $this->_doneReading = true;
318 return $returnString;
319 }
320
321 // increment internal counters
322 $readTo = $this->_bytesRead + $bufferSize;
323 $sizeOfCurrentPart = $this->_parts[$this->_currentPart][0];
324 $sizeOfNextPart = 0;
325
326 // if we are in a part past the current part, exit
327 if ($this->_currentPart > self::PART_CLOSING_XML_STRING) {
328 $this->_doneReading = true;
329 return;
330 }
331
332 // if bytes read is bigger than the current part and we are
333 // at the end, return
334 if (($this->_bytesRead > $sizeOfCurrentPart) &&
335 ($this->_currentPart == self::PART_CLOSING_XML_STRING)) {
336 $this->_doneReading = true;
337 return;
338 }
339
340 // check if we have a next part
341 if ($this->_currentPart != self::PART_CLOSING_XML_STRING) {
342 $nextPart = $this->_currentPart + 1;
343 $sizeOfNextPart = $this->_parts[$nextPart][0];
344 }
345
346 $readIntoNextPart = false;
347 $readFromRemainingPart = null;
348 $readFromNextPart = null;
349
350 // are we crossing into multiple sections of the message in
351 // this read?
352 if ($readTo > ($sizeOfCurrentPart + $sizeOfNextPart)) {
353 if ($this->_currentPart == self::PART_XML_STRING) {
354 // If we are in XML string and have crossed over the file
355 // return that and whatever we can from the closing boundary
356 // string.
357 $returnString = $this->_parts[self::PART_XML_STRING][1];
358 unset($this->_parts[self::PART_XML_STRING]);
359 $returnString .= fread($this->_fileHandle,
360 self::MAX_BUFFER_SIZE);
361 $this->closeFileHandle();
362
363 $readFromClosingString = $readTo -
364 ($sizeOfCurrentPart + $sizeOfNextPart);
365 $returnString .= substr(
366 $this->_parts[self::PART_CLOSING_XML_STRING][1], 0,
367 $readFromClosingString);
368 $this->_bytesRead = $readFromClosingString;
369 $this->_currentPart = self::PART_CLOSING_XML_STRING;
370 return $returnString;
371
372 } elseif ($this->_currentPart == self::PART_FILE_BINARY) {
373 // We have read past the entire message, so return it.
374 $returnString .= fread($this->_fileHandle,
375 self::MAX_BUFFER_SIZE);
376 $returnString .= $this->_closingBoundaryLine;
377 $this->closeFileHandle();
378 $this->_doneReading = true;
379 return $returnString;
380 }
381 // are we just crossing from one section into another?
382 } elseif ($readTo >= $sizeOfCurrentPart) {
383 $readIntoNextPart = true;
384 $readFromRemainingPart = $sizeOfCurrentPart - $this->_bytesRead;
385 $readFromNextPart = $readTo - $sizeOfCurrentPart;
386 }
387
388 if (!$readIntoNextPart) {
389 // we are not crossing any section so just return something
390 // from the current part
391 switch ($this->_currentPart) {
392 case self::PART_XML_STRING:
393 $returnString = $this->readFromStringPart(
394 $this->_currentPart, $this->_bytesRead, $bufferSize);
395 break;
396 case self::PART_FILE_BINARY:
397 $returnString = fread($this->_fileHandle, $bufferSize);
398 break;
399 case self::PART_CLOSING_XML_STRING:
400 $returnString = $this->readFromStringPart(
401 $this->_currentPart, $this->_bytesRead, $bufferSize);
402 break;
403 }
404 } else {
405 // we are crossing from one section to another, so figure out
406 // where we are coming from and going to
407 switch ($this->_currentPart) {
408 case self::PART_XML_STRING:
409 // crossing from string to file
410 $returnString = $this->readFromStringPart(
411 $this->_currentPart, $this->_bytesRead,
412 $readFromRemainingPart);
413 // free up string
414 unset($this->_parts[self::PART_XML_STRING]);
415 $returnString .= $this->smartfread($this->_fileHandle,
416 $readFromNextPart);
417 $this->_bytesRead = $readFromNextPart - 1;
418 break;
419 case self::PART_FILE_BINARY:
420 // skipping past file section
421 $returnString = $this->smartfread($this->_fileHandle,
422 $readFromRemainingPart);
423 $this->closeFileHandle();
424 // read closing boundary string
425 $returnString = $this->readFromStringPart(
426 self::PART_CLOSING_XML_STRING, 0, $readFromNextPart);
427 // have we read past the entire closing boundary string?
428 if ($readFromNextPart >=
429 $this->_parts[self::PART_CLOSING_XML_STRING][0]) {
430 $this->_doneReading = true;
431 return $returnString;
432 }
433
434 // Reset counter appropriately since we are now just
435 // counting how much of the final string is being read.
436 $this->_bytesRead = $readFromNextPart - 1;
437 break;
438 case self::PART_CLOSING_XML_STRING:
439 // reading past the end of the closing boundary
440 if ($readFromRemainingPart > 0) {
441 $returnString = $this->readFromStringPart(
442 $this->_currentPart, $this->_bytesRead,
443 $readFromRemainingPart);
444 $this->_doneReading = true;
445 }
446 return $returnString;
447 }
448 $this->_currentPart++;
449 }
450 $this->_bytesRead += $bufferSize;
451 return $returnString;
452 }
453
454 /**
455 * Convenience method to shorthand the reading of non-file parts of the
456 * message.
457 *
458 * @param integer $part The part from which to read (supports only 0 or 2).
459 * @param integer $start The point at which to read from.
460 * @param integer $length How many characters to read.
461 * @return string A string of characters corresponding to the requested
462 * section.
463 */
464 private function readFromStringPart($part, $start, $length)
465 {
466 return substr($this->_parts[$part][1], $start, $length);
467 }
468
469 /**
470 * Return the total size of the mime message.
471 *
472 * @return integer Total size of the message to be sent.
473 */
474 public function getTotalSize()
475 {
476 return $this->_totalSize;
477 }
478
479 /**
480 * Check whether we have data left to read.
481 *
482 * @return boolean True if there is data remaining in the mime message,
483 * false, otherwise.
484 */
485 public function hasData()
486 {
487 return !($this->_doneReading);
488 }
489
490 /**
491 * Close the internal file that we are streaming to the socket.
492 *
493 * @return void
494 */
495 protected function closeFileHandle()
496 {
497 if ($this->_fileHandle !== null) {
498 fclose($this->_fileHandle);
499 }
500 }
501
502 /**
503 * Return a Content-type header that includes the current boundary string.
504 *
505 * @return string A valid HTTP Content-Type header.
506 */
507 public function getContentType()
508 {
509 return 'multipart/related; boundary="' .
510 $this->_boundaryString . '"' . "\r\n";
511 }
512
513}
Note: See TracBrowser for help on using the repository browser.