| @@ -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 | { |