1<?php
2/**
3 * Copyright 2017 Facebook, Inc.
4 *
5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
6 * use, copy, modify, and distribute this software in source code or binary
7 * form for use in connection with the web services and APIs provided by
8 * Facebook.
9 *
10 * As with any software that integrates with the Facebook platform, your use
11 * of this software is subject to the Facebook Developer Principles and
12 * Policies [http://developers.facebook.com/policy/]. This copyright notice
13 * shall be included in all copies or substantial portions of the software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24namespace Facebook\Http;
25
26use Facebook\FileUpload\FacebookFile;
27
28/**
29 * Class RequestBodyMultipartt
30 *
31 * Some things copied from Guzzle
32 *
33 * @package Facebook
34 *
35 * @see https://github.com/guzzle/guzzle/blob/master/src/Post/MultipartBody.php
36 */
37class RequestBodyMultipart implements RequestBodyInterface
38{
39    /**
40     * @var string The boundary.
41     */
42    private $boundary;
43
44    /**
45     * @var array The parameters to send with this request.
46     */
47    private $params;
48
49    /**
50     * @var array The files to send with this request.
51     */
52    private $files = [];
53
54    /**
55     * @param array  $params   The parameters to send with this request.
56     * @param array  $files    The files to send with this request.
57     * @param string $boundary Provide a specific boundary.
58     */
59    public function __construct(array $params = [], array $files = [], $boundary = null)
60    {
61        $this->params = $params;
62        $this->files = $files;
63        $this->boundary = $boundary ?: uniqid();
64    }
65
66    /**
67     * @inheritdoc
68     */
69    public function getBody()
70    {
71        $body = '';
72
73        // Compile normal params
74        $params = $this->getNestedParams($this->params);
75        foreach ($params as $k => $v) {
76            $body .= $this->getParamString($k, $v);
77        }
78
79        // Compile files
80        foreach ($this->files as $k => $v) {
81            $body .= $this->getFileString($k, $v);
82        }
83
84        // Peace out
85        $body .= "--{$this->boundary}--\r\n";
86
87        return $body;
88    }
89
90    /**
91     * Get the boundary
92     *
93     * @return string
94     */
95    public function getBoundary()
96    {
97        return $this->boundary;
98    }
99
100    /**
101     * Get the string needed to transfer a file.
102     *
103     * @param string       $name
104     * @param FacebookFile $file
105     *
106     * @return string
107     */
108    private function getFileString($name, FacebookFile $file)
109    {
110        return sprintf(
111            "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s\r\n\r\n%s\r\n",
112            $this->boundary,
113            $name,
114            $file->getFileName(),
115            $this->getFileHeaders($file),
116            $file->getContents()
117        );
118    }
119
120    /**
121     * Get the string needed to transfer a POST field.
122     *
123     * @param string $name
124     * @param string $value
125     *
126     * @return string
127     */
128    private function getParamString($name, $value)
129    {
130        return sprintf(
131            "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n",
132            $this->boundary,
133            $name,
134            $value
135        );
136    }
137
138    /**
139     * Returns the params as an array of nested params.
140     *
141     * @param array $params
142     *
143     * @return array
144     */
145    private function getNestedParams(array $params)
146    {
147        $query = http_build_query($params, null, '&');
148        $params = explode('&', $query);
149        $result = [];
150
151        foreach ($params as $param) {
152            list($key, $value) = explode('=', $param, 2);
153            $result[urldecode($key)] = urldecode($value);
154        }
155
156        return $result;
157    }
158
159    /**
160     * Get the headers needed before transferring the content of a POST file.
161     *
162     * @param FacebookFile $file
163     *
164     * @return string
165     */
166    protected function getFileHeaders(FacebookFile $file)
167    {
168        return "\r\nContent-Type: {$file->getMimetype()}";
169    }
170}
171