FormData.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  10. * @link https://cakephp.org CakePHP(tm) Project
  11. * @since 3.0.0
  12. * @license https://opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Http\Client;
  15. use Countable;
  16. use finfo;
  17. /**
  18. * Provides an interface for building
  19. * multipart/form-encoded message bodies.
  20. *
  21. * Used by Http\Client to upload POST/PUT data
  22. * and files.
  23. */
  24. class FormData implements Countable
  25. {
  26. /**
  27. * Boundary marker.
  28. *
  29. * @var string
  30. */
  31. protected $_boundary;
  32. /**
  33. * Whether or not this formdata object has attached files.
  34. *
  35. * @var bool
  36. */
  37. protected $_hasFile = false;
  38. /**
  39. * Whether or not this formdata object has a complex part.
  40. *
  41. * @var bool
  42. */
  43. protected $_hasComplexPart = false;
  44. /**
  45. * The parts in the form data.
  46. *
  47. * @var \Cake\Http\Client\FormDataPart[]
  48. */
  49. protected $_parts = [];
  50. /**
  51. * Get the boundary marker
  52. *
  53. * @return string
  54. */
  55. public function boundary()
  56. {
  57. if ($this->_boundary) {
  58. return $this->_boundary;
  59. }
  60. $this->_boundary = md5(uniqid(time()));
  61. return $this->_boundary;
  62. }
  63. /**
  64. * Method for creating new instances of Part
  65. *
  66. * @param string $name The name of the part.
  67. * @param string $value The value to add.
  68. * @return \Cake\Http\Client\FormDataPart
  69. */
  70. public function newPart($name, $value)
  71. {
  72. return new FormDataPart($name, $value);
  73. }
  74. /**
  75. * Add a new part to the data.
  76. *
  77. * The value for a part can be a string, array, int,
  78. * float, filehandle, or object implementing __toString()
  79. *
  80. * If the $value is an array, multiple parts will be added.
  81. * Files will be read from their current position and saved in memory.
  82. *
  83. * @param string|\Cake\Http\Client\FormData $name The name of the part to add,
  84. * or the part data object.
  85. * @param mixed $value The value for the part.
  86. * @return $this
  87. */
  88. public function add($name, $value = null)
  89. {
  90. if (is_array($value)) {
  91. $this->addRecursive($name, $value);
  92. } elseif (is_resource($value)) {
  93. $this->addFile($name, $value);
  94. } elseif (is_string($value) && strlen($value) && $value[0] === '@') {
  95. trigger_error(
  96. 'Using the @ syntax for file uploads is not safe and is deprecated. ' .
  97. 'Instead you should use file handles.',
  98. E_USER_DEPRECATED
  99. );
  100. $this->addFile($name, $value);
  101. } elseif ($name instanceof FormDataPart && $value === null) {
  102. $this->_hasComplexPart = true;
  103. $this->_parts[] = $name;
  104. } else {
  105. $this->_parts[] = $this->newPart($name, $value);
  106. }
  107. return $this;
  108. }
  109. /**
  110. * Add multiple parts at once.
  111. *
  112. * Iterates the parameter and adds all the key/values.
  113. *
  114. * @param array $data Array of data to add.
  115. * @return $this
  116. */
  117. public function addMany(array $data)
  118. {
  119. foreach ($data as $name => $value) {
  120. $this->add($name, $value);
  121. }
  122. return $this;
  123. }
  124. /**
  125. * Add either a file reference (string starting with @)
  126. * or a file handle.
  127. *
  128. * @param string $name The name to use.
  129. * @param mixed $value Either a string filename, or a filehandle.
  130. * @return \Cake\Http\Client\FormDataPart
  131. */
  132. public function addFile($name, $value)
  133. {
  134. $this->_hasFile = true;
  135. $filename = false;
  136. $contentType = 'application/octet-stream';
  137. if (is_resource($value)) {
  138. $content = stream_get_contents($value);
  139. if (stream_is_local($value)) {
  140. $finfo = new finfo(FILEINFO_MIME);
  141. $metadata = stream_get_meta_data($value);
  142. $contentType = $finfo->file($metadata['uri']);
  143. $filename = basename($metadata['uri']);
  144. }
  145. } else {
  146. $finfo = new finfo(FILEINFO_MIME);
  147. $value = substr($value, 1);
  148. $filename = basename($value);
  149. $content = file_get_contents($value);
  150. $contentType = $finfo->file($value);
  151. }
  152. $part = $this->newPart($name, $content);
  153. $part->type($contentType);
  154. if ($filename) {
  155. $part->filename($filename);
  156. }
  157. $this->add($part);
  158. return $part;
  159. }
  160. /**
  161. * Recursively add data.
  162. *
  163. * @param string $name The name to use.
  164. * @param mixed $value The value to add.
  165. * @return void
  166. */
  167. public function addRecursive($name, $value)
  168. {
  169. foreach ($value as $key => $value) {
  170. $key = $name . '[' . $key . ']';
  171. $this->add($key, $value);
  172. }
  173. }
  174. /**
  175. * Returns the count of parts inside this object.
  176. *
  177. * @return int
  178. */
  179. public function count()
  180. {
  181. return count($this->_parts);
  182. }
  183. /**
  184. * Check whether or not the current payload
  185. * has any files.
  186. *
  187. * @return bool Whether or not there is a file in this payload.
  188. */
  189. public function hasFile()
  190. {
  191. return $this->_hasFile;
  192. }
  193. /**
  194. * Check whether or not the current payload
  195. * is multipart.
  196. *
  197. * A payload will become multipart when you add files
  198. * or use add() with a Part instance.
  199. *
  200. * @return bool Whether or not the payload is multipart.
  201. */
  202. public function isMultipart()
  203. {
  204. return $this->hasFile() || $this->_hasComplexPart;
  205. }
  206. /**
  207. * Get the content type for this payload.
  208. *
  209. * If this object contains files, `multipart/form-data` will be used,
  210. * otherwise `application/x-www-form-urlencoded` will be used.
  211. *
  212. * @return string
  213. */
  214. public function contentType()
  215. {
  216. if (!$this->isMultipart()) {
  217. return 'application/x-www-form-urlencoded';
  218. }
  219. return 'multipart/form-data; boundary="' . $this->boundary() . '"';
  220. }
  221. /**
  222. * Converts the FormData and its parts into a string suitable
  223. * for use in an HTTP request.
  224. *
  225. * @return string
  226. */
  227. public function __toString()
  228. {
  229. if ($this->isMultipart()) {
  230. $boundary = $this->boundary();
  231. $out = '';
  232. foreach ($this->_parts as $part) {
  233. $out .= "--$boundary\r\n";
  234. $out .= (string)$part;
  235. $out .= "\r\n";
  236. }
  237. $out .= "--$boundary--\r\n\r\n";
  238. return $out;
  239. }
  240. $data = [];
  241. foreach ($this->_parts as $part) {
  242. $data[$part->name()] = $part->value();
  243. }
  244. return http_build_query($data);
  245. }
  246. }
  247. // @deprecated Add backwards compat alias.
  248. class_alias('Cake\Http\Client\FormData', 'Cake\Network\Http\FormData');