PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 All 197 releases
← All changes | app/Services/Migrator/Classes/BaseMigrator.php +63 -2 6.2.13 → 6.2.15 View file →
@@ -1715,9 +1715,13 @@
1715 1715 $urls = [$urls];
1716 1716 }
1717 1717 $values = [];
1718 1718 foreach ($urls as $url) {
1719 - $file_name = 'ff-' . wp_basename($url);
1719 + $source = $this->resolveMigrationSource($url);
1720 + if (!$source) {
1721 + continue;
1722 + }
1723 +
1720 1724 $basDir = wp_upload_dir()['basedir'] . '/fluentform/';
1721 1725 $baseurl = wp_upload_dir()['baseurl'] . '/fluentform/';
1722 1726
1723 1727 if (!file_exists($basDir) || (file_exists($basDir) && !is_dir($basDir))) {
@@ -1723,17 +1727,74 @@
1723 1727 if (!file_exists($basDir) || (file_exists($basDir) && !is_dir($basDir))) {
1724 1728 wp_mkdir_p($basDir);
1725 1729 }
1726 1730
1731 + // Unique name so two sources that sanitise alike do not overwrite.
1732 + $file_name = wp_unique_filename($basDir, $source['name']);
1727 1733 $destination = $basDir . $file_name;
1728 1734 require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
1729 1735 require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php');
1730 1736 $fileSystemDirect = new \WP_Filesystem_Direct(false);
1731 - if ($fileSystemDirect->copy($url, $destination, true)) {
1737 + if ($fileSystemDirect->copy($source['path'], $destination, true)) {
1732 1738 $values[] = $baseurl . $file_name;
1733 1739 }
1740 + @unlink($source['path']);
1734 1741 }
1735 1742 return $values;
1743 + }
1744 +
1745 + // Entry values come from the source plugin's export. PHP copy() would also read local
1746 + // server paths, so only an upload-allowed type fetched over http(s) reaches the
1747 + // web-reachable destination; download_url() refuses internal addresses on its own.
1748 + protected function resolveMigrationSource($url)
1749 + {
1750 + $name = $this->safeMigrationFileName($url);
1751 + if (!$name) {
1752 + return null;
1753 + }
1754 +
1755 + if (!function_exists('download_url')) {
1756 + require_once ABSPATH . 'wp-admin/includes/file.php';
1757 + }
1758 + $tmp = download_url((string) $url);
1759 + if (is_wp_error($tmp)) {
1760 + return null;
1761 + }
1762 +
1763 + return ['path' => $tmp, 'name' => $name];
1764 + }
1765 +
1766 + /**
1767 + * Build the destination name from the URL PATH, not the raw URL: the raw basename
1768 + * keeps the query string (scan.pdf?/shell.php -> shell.php). Inner dots are collapsed
1769 + * so exactly one gate-approved extension survives.
1770 + *
1771 + * @param string $url
1772 + * @return string|null ff-<name>.<ext>, or null when the type/scheme is not allowed
1773 + */
1774 + protected function safeMigrationFileName($url)
1775 + {
1776 + $url = (string) $url;
1777 + if (!preg_match('#^https?://#i', $url)) {
1778 + return null;
1779 + }
1780 +
1781 + $base = wp_basename((string) wp_parse_url($url, PHP_URL_PATH));
1782 + // The upload list grows with unfiltered_html and upload_mimes; HTML, script and SVG would run on the site's origin.
1783 + $mimes = array_filter(get_allowed_mime_types(), function ($mime) {
1784 + return !preg_match('#html|javascript|xml$#i', $mime);
1785 + });
1786 + $ext = wp_check_filetype($base, $mimes)['ext'];
1787 + if (!$ext) {
1788 + return null;
1789 + }
1790 +
1791 + $stem = str_replace('.', '_', sanitize_file_name(pathinfo($base, PATHINFO_FILENAME)));
1792 + if ('' === $stem) {
1793 + $stem = 'file';
1794 + }
1795 +
1796 + return 'ff-' . $stem . '.' . strtolower($ext);
1736 1797 }
1737 1798
1738 1799 protected function getResolveOperator($key)
1739 1800 {