PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Zend/Gdata/MediaMimeStream.php

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