body) || !$this->isAssociative($request->body)) { throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["content-type"]); } $boundary = "---------------------" . md5(mt_rand() . microtime()); $contentTypeHeader = $request->headers["content-type"]; $request->headers["content-type"] = "{$contentTypeHeader}; boundary={$boundary}"; $value_params = []; $file_params = []; $disallow = ["\0", "\"", "\r", "\n"]; $body = []; foreach ($request->body as $k => $v) { $k = str_replace($disallow, "_", $k); if (is_resource($v)) { $file_params[] = $this->prepareFilePart($k, $v, $boundary); } else if ($v instanceof FormPart) { $value_params[] = $this->prepareFormPart($k, $v, $boundary); } else { $value_params[] = $this->prepareFormField($k, $v, $boundary); } } $body = array_merge($value_params, $file_params); // add boundary for each parameters array_walk($body, function (&$part) use ($boundary) { $part = "--{$boundary}" . self::LINEFEED . "{$part}"; }); // add final boundary $body[] = "--{$boundary}--"; $body[] = ""; return implode(self::LINEFEED, $body); } public function decode($data) { throw new \Exception("Multipart does not support deserialization"); } private function isAssociative(array $array) { return array_values($array) !== $array; } private function prepareFormField($partName, $value, $boundary) { return implode(self::LINEFEED, [ "Content-Disposition: form-data; name=\"{$partName}\"", "", filter_var($value), ]); } private function prepareFilePart($partName, $file, $boundary) { $fileInfo = new finfo(FILEINFO_MIME_TYPE); $filePath = stream_get_meta_data($file)['uri']; $data = file_get_contents($filePath); $mimeType = $fileInfo->buffer($data); $splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath); $filePath = end($splitFilePath); $disallow = ["\0", "\"", "\r", "\n"]; $filePath = str_replace($disallow, "_", $filePath); return implode(self::LINEFEED, [ "Content-Disposition: form-data; name=\"{$partName}\"; filename=\"{$filePath}\"", "Content-Type: {$mimeType}", "", $data, ]); } private function prepareFormPart($partName, $formPart, $boundary) { $contentDisposition = "Content-Disposition: form-data; name=\"{$partName}\""; $partHeaders = $formPart->getHeaders(); $formattedheaders = array_change_key_case($partHeaders); if (array_key_exists("content-type", $formattedheaders)) { if ($formattedheaders["content-type"] === "application/json") { $contentDisposition .= "; filename=\"{$partName}.json\""; } $tempRequest = new HttpRequest('/', 'POST'); $tempRequest->headers = $formattedheaders; $tempRequest->body = $formPart->getValue(); $encoder = new Encoder(); $partValue = $encoder->serializeRequest($tempRequest); } else { $partValue = $formPart->getValue(); } $finalPartHeaders = []; foreach ($partHeaders as $k => $v) { $finalPartHeaders[] = "{$k}: {$v}"; } $body = array_merge([$contentDisposition], $finalPartHeaders, [""], [$partValue]); return implode(self::LINEFEED, $body); } }