1<?php 2 3/* 4 * This file is part of SwiftMailer. 5 * (c) 2004-2009 Chris Corbyn 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * Allows reading and writing of bytes to and from a file. 13 * 14 * @author Chris Corbyn 15 */ 16class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_FileStream 17{ 18 /** The internal pointer offset */ 19 private $_offset = 0; 20 21 /** The path to the file */ 22 private $_path; 23 24 /** The mode this file is opened in for writing */ 25 private $_mode; 26 27 /** A lazy-loaded resource handle for reading the file */ 28 private $_reader; 29 30 /** A lazy-loaded resource handle for writing the file */ 31 private $_writer; 32 33 /** If magic_quotes_runtime is on, this will be true */ 34 private $_quotes = false; 35 36 /** If stream is seekable true/false, or null if not known */ 37 private $_seekable = null; 38 39 /** 40 * Create a new FileByteStream for $path. 41 * 42 * @param string $path 43 * @param bool $writable if true 44 */ 45 public function __construct($path, $writable = false) 46 { 47 if (empty($path)) { 48 throw new Swift_IoException('The path cannot be empty'); 49 } 50 $this->_path = $path; 51 $this->_mode = $writable ? 'w+b' : 'rb'; 52 53 if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) { 54 $this->_quotes = true; 55 } 56 } 57 58 /** 59 * Get the complete path to the file. 60 * 61 * @return string 62 */ 63 public function getPath() 64 { 65 return $this->_path; 66 } 67 68 /** 69 * Reads $length bytes from the stream into a string and moves the pointer 70 * through the stream by $length. 71 * 72 * If less bytes exist than are requested the 73 * remaining bytes are given instead. If no bytes are remaining at all, boolean 74 * false is returned. 75 * 76 * @param int $length 77 * 78 * @throws Swift_IoException 79 * 80 * @return string|bool 81 */ 82 public function read($length) 83 { 84 $fp = $this->_getReadHandle(); 85 if (!feof($fp)) { 86 if ($this->_quotes) { 87 ini_set('magic_quotes_runtime', 0); 88 } 89 $bytes = fread($fp, $length); 90 if ($this->_quotes) { 91 ini_set('magic_quotes_runtime', 1); 92 } 93 $this->_offset = ftell($fp); 94 95 // If we read one byte after reaching the end of the file 96 // feof() will return false and an empty string is returned 97 if ($bytes === '' && feof($fp)) { 98 $this->_resetReadHandle(); 99 100 return false; 101 } 102 103 return $bytes; 104 } 105 106 $this->_resetReadHandle(); 107 108 return false; 109 } 110 111 /** 112 * Move the internal read pointer to $byteOffset in the stream. 113 * 114 * @param int $byteOffset 115 * 116 * @return bool 117 */ 118 public function setReadPointer($byteOffset) 119 { 120 if (isset($this->_reader)) { 121 $this->_seekReadStreamToPosition($byteOffset); 122 } 123 $this->_offset = $byteOffset; 124 } 125 126 /** Just write the bytes to the file */ 127 protected function _commit($bytes) 128 { 129 fwrite($this->_getWriteHandle(), $bytes); 130 $this->_resetReadHandle(); 131 } 132 133 /** Not used */ 134 protected function _flush() 135 { 136 } 137 138 /** Get the resource for reading */ 139 private function _getReadHandle() 140 { 141 if (!isset($this->_reader)) { 142 $pointer = @fopen($this->_path, 'rb'); 143 if (!$pointer) { 144 throw new Swift_IoException( 145 'Unable to open file for reading ['.$this->_path.']' 146 ); 147 } 148 $this->_reader = $pointer; 149 if ($this->_offset != 0) { 150 $this->_getReadStreamSeekableStatus(); 151 $this->_seekReadStreamToPosition($this->_offset); 152 } 153 } 154 155 return $this->_reader; 156 } 157 158 /** Get the resource for writing */ 159 private function _getWriteHandle() 160 { 161 if (!isset($this->_writer)) { 162 if (!$this->_writer = fopen($this->_path, $this->_mode)) { 163 throw new Swift_IoException( 164 'Unable to open file for writing ['.$this->_path.']' 165 ); 166 } 167 } 168 169 return $this->_writer; 170 } 171 172 /** Force a reload of the resource for reading */ 173 private function _resetReadHandle() 174 { 175 if (isset($this->_reader)) { 176 fclose($this->_reader); 177 $this->_reader = null; 178 } 179 } 180 181 /** Check if ReadOnly Stream is seekable */ 182 private function _getReadStreamSeekableStatus() 183 { 184 $metas = stream_get_meta_data($this->_reader); 185 $this->_seekable = $metas['seekable']; 186 } 187 188 /** Streams in a readOnly stream ensuring copy if needed */ 189 private function _seekReadStreamToPosition($offset) 190 { 191 if ($this->_seekable === null) { 192 $this->_getReadStreamSeekableStatus(); 193 } 194 if ($this->_seekable === false) { 195 $currentPos = ftell($this->_reader); 196 if ($currentPos < $offset) { 197 $toDiscard = $offset - $currentPos; 198 fread($this->_reader, $toDiscard); 199 200 return; 201 } 202 $this->_copyReadStream(); 203 } 204 fseek($this->_reader, $offset, SEEK_SET); 205 } 206 207 /** Copy a readOnly Stream to ensure seekability */ 208 private function _copyReadStream() 209 { 210 if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) { 211 /* We have opened a php:// Stream Should work without problem */ 212 } elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) { 213 /* We have opened a tmpfile */ 214 } else { 215 throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available'); 216 } 217 $currentPos = ftell($this->_reader); 218 fclose($this->_reader); 219 $source = fopen($this->_path, 'rb'); 220 if (!$source) { 221 throw new Swift_IoException('Unable to open file for copying ['.$this->_path.']'); 222 } 223 fseek($tmpFile, 0, SEEK_SET); 224 while (!feof($source)) { 225 fwrite($tmpFile, fread($source, 4096)); 226 } 227 fseek($tmpFile, $currentPos, SEEK_SET); 228 fclose($source); 229 $this->_reader = $tmpFile; 230 } 231} 232