Загрузка файлов на сервер из Wolfram Mathematica.
Опубликовано: 10 Декабрь 2013
Те, кто занимается ведением блога, не понаслышке знают, что после написания статьи, всегда стоит особая работа по ее оформлению и публикации на сайте. Что всегда сопряжено с ручной расстановкой html тегов, прописыванием атрибутов этих тегов и прочее... Можно, конечно, делать все это через визуальные редакторы типа TinyMCE или JCE и другие аналогичные встроеные в CMS - но, как показывает практика, с ними тоже надо уметь работать, чтобы на выходе не получить сильно замусоренный html-тегами контент статьи. В таких случаях все равно требуется ручная правка, что при объемной работе достаточно утомительно и печально:).
Поэтому блоггерам, ведущим каких-то рубрик на сайтах, модераторам форумов - иногда приходят к мысли о том, чтобы после написания статьи можно было нажать одну кнопочку и статья полностью оформленная и красивая появлялась на сайте, вместе со всеми прописанными атрибутами и настройками. И подобные мечты вовсе не являются утопией.
Подобная задача действительно вполне решаема, и на мой взгляд одним из лучших инструментов для этого может послужить система Wolfram Mathematica. К существенным преимуществам этой системы относится:
- компактность кода;
- широчайшие возможности в области обработки данных практически любого типа;
- структурированность ноутбуков (так называются документы создаваемые в этой системе).
Любая статья, помимо текста, содержит множество других элементов, в том числе картинки и ссылки для скачивания документов, например. Все эти элементы, чтобы быть доступными, должны располагаться либо на сервере где размещен сайт, либо на каком-то удаленном сервере. Конкретно в данной статье, мы пока не будем рассматривать процесс отправки текста статьи на сайт, а рассмотрим задачу именно отправки файлов (например, картинок статьи) на сервер для загрузки.
Общая структура системы загрузки файлов.
Программа Wolfram Mathematica должна быть установлена на компьютере автора статьи, и соответственно в ней он формирует все содержимое материала. После того как материал создан, запускается его обработка и полученные на выходе данные отправляются на сервер, в том числе файлы для загрузки. На сервере располагается php скрипт, который ловит присланные данные, проверяет их надежность и в случае успешной проверки записывает полученные данные в базу данных, и создает в указанном месте на сервере новые файлы. Назад возвращает статус выполнения этой операции.
Скрипт Wolfram.
Для работы с веб, в программе Wolfram Mathematica начиная с версии 9, предусмотрена функция:
URLFetch["URL запроса","Параметры и свойства запроса"]
данная функция позволяет на указанный в ней URL отправлять данные в соответствии с указанными параметрами и свойствами. Данные могут быть отправлены методами GET, POST и HEAD. Назад функция возвращает ответ сервера в том числе html-вывод, заголовки, cookies. Для передачи данных будем использовать метод POST.
В этой функции предусмотрен параметр, специально предназначенный для передачи содержимого произвольного файла в бинарном виде:
"MultipartData" ->{"имя файла", "mime/type", {"список байтов"}}
в общем структура параметра, думаю, ясна: передается имя файла, его mime-тип и его бинарное представление.
Для получения бинарного представления файла, воспользуемся также встроенной функцией
BinaryReadList["путь до файла","Byte"]
Таким образом, для передачи на сервер картинки с именем image.png, расположенной по адресу “G:image.png”, методом POST на скрипт upload.php, мы должны оформить код следующим образом:
URLFetch["http://vash_site.ru/upload.php", "Method" -> "POST", "MultipartData" -> {FileNameTake["G:\image.png"], "application/octet-stream", BinaryReadList["G:\image.png", "Byte"]} ]
Если же мы захотим передать несколько файлов, то поместив (любым удобным для себя способом) список путей до файлов на компьютере в переменную filesToUpload, и перебрав их с помощью функции Table получим базовый вариант скрипта Wolfram для отправки файлов на сервер:
URLFetch["http://vash_site.ru/upload.php", "Method" -> "POST", "MultipartData" -> Table[{FileNameTake[file], "application/octet-stream", BinaryReadList[file, "Byte"]},{file,filesToUpload}] ]
обратите внимание, что мы не стали заморачиваться с указанием mime-типа каждого отправлемого файла, а просто указали тип “application/octet-stream” - что хорошо подходит для передачи данных в бинарном виде без указания формата пересылаемого документа.
PHP скрипт на сервере.
Напрямую через протокол HTTP загрузить файл на сервер, к счастью - невозможно, иначе это была бы очень серьезная уязвимость для всех интернет ресурсов в сети. Поэтому требуется некий обработчик, который полученные данные обработает таким образом, чтобы на сервере в необходимых директориях появились требуемые нам файлы, или записал эти файлы в бинарном виде в базу данных сайта. Мы разберем первый случай - простое создание файла внутри файловой системы сайта. Обработчик будем разрабатывать на популярном серверном языке php.
Работа функции URLFetch устроена таким образом, что данные пересылаемые через параметр “MultipartData” после отправки на сервер, помещаются в массив $_POST. Структура массива в данном случае будет примено такой:
Array ( [image1_png] => содержимое файла image1.png в бинарном виде [image2_png] => содержимое файла image2.png в бинарном виде )
То есть, нам остается в цикле перебрать содержимое массива $_POST и в соответствии с ним на сервере создать необходимые файлы. Это может быть реализовано примерно в следующем виде:
chmod(__DIR__."/files", 0777); foreach($_POST as $name=>$content) { $fp = fopen(__DIR__."/files/".str_replace("_",".",$name),"w"); fwrite($fp,$content); fclose($fp); } chmod(__DIR__."/files", 0755);
По сути, здесь выполняется очень простая операция. Все данные в массиве перебираются в цикле, и для каждого элемента массива создается одноименный файл в директории “files”, и в него в бинарном виде записывается переданное содержимое.
Безопасность.
Очевидно, что представленный выше php скрипт, открывает собой большую узвимость - через него можно на сервер в папку “files” загрузить любой файл, в том числе зараженный вирусом или любой другой потенциально опасный скрипт, способный нанести вред как CMS, на которой работает сайт, так и клиентам сайта. Чтобы этого избежать, есть смысл задуматься о безопасности подобных транзакций.
Во-первых, можно ограничить список форматов загружаемых файлов, во-вторых - нужно убедиться, что данные присланы из надежного источника.
Ограничение форматов загружаемых файлов на стороне сервера.
Для ограничения форматов загружаемых файлов, мы можем задать список разрешенных расширений (“png”,”zip”,”pdf”) и каждый раз перед записью нового файла проверять его по этому списку. Если результат проверки положительный - создаем и записываем файл, если отрицательный - этот процесс блокируется и переходим к следующему файлу в присланном списке.
$allow_ext = array("png","zip","pdf"); chmod(__DIR__."/files", 0777); foreach($_POST as $name=>$content) { $file_name = explode("_", $name); //извлекаем расширение присланного файла if (in_array(array_pop($file_name),$allow_ext)){// сравниваем со списком разрешенных расширений $fp = fopen(__DIR__."/files/".str_replace("_",".",$name),"w"); fwrite($fp,$content); fclose($fp); } else { echo "Файл ".str_replace("_",".",$name)." имеет недопустимое расширение. "; } } chmod(__DIR__."/files", 0755);
Таким образом не получится загрузить на сервер скрипты, которые можно было бы исполнить на стороне сервера интерпретаторами perl, php, python или какими-либо другими. Так же не получится загрузить файлы, которые могли бы быть исполнены в браузере на стороне посетителя сайта (например, javascript файлы).
Проверка надежности присланных данных (аналог электронной подписи).
Сами себе мы зараженные или опасные файлы слать не будем, этим будут заниматься люди поторонние (будут! можете не сомневаться). Поэтому было бы хорошо в систему добавить проверку надежности данных на стороне сервера и подпись данных на стороне Wolfram-скрипта. Подобную защиту можно реализовать следующим простым образом:
- вместе с пересылаемыми данными отправляется хэш-строка, полученная как результат работы хэш-функции. При этом хэшируется строка, структура которой известна только скрипту Wolfram и скрипту на сервере. Данная строка обязательно должна содержать пересылаемые данные и некий секретный ключ.
- скрипт на стороне сервера, получив данные, на основе них формирует входную строку такого же рода, что в скрипте Wolfram, и на основе нее вычиляет хэш, таким же способом. Далее скрипт извлекает присланный хэш, сравнивает с вычисленным у себя. Если они равны - считает, что данные присланы из надежного источника и можно выполнять дальнейшие операции с ними. Если не совпадает - попытка загрузки файлов блокируется.
В качестве хэширующей функции выберем алгоритм SHA-512, как наиболее надежный на сегодняшний день. Входную строку будем формировать следующим образом: “секретный ключ” плюс хэш-суммы содержимого пересылаемых файлов в порядке следования в списке.
hash = IntegerString[Hash["secret_key"<>"::"<>StringJoin[Riffle[IntegerString[FileHash[#,"SHA512"],16,128]&/@filesToUpload,"::"]],"SHA512"],16,128]
Здесь мы вычислили ключ безопасности, который припишем к отправляемым данным. Вместо “secret_key” в будущем следует использовать строку с каким-то сложным сочетанием символов - эта строка должна быть известна только Wolfram-скрипту и php-обработчику.
Дополнительно, на всякий случай, отправим и список имен файлов, в той последовательности, в которой их содержимое следует в исходной строке:
fileNamesList = StringJoin[Riffle[StringReplace[FileNameTake[#],"."->"_"]&/@filesToUpload,"::"]]
полученные данные мы отправим в заголовках запроса исходящего из Wolfram Mathematica:
"Headers" -> { "SecurityHash" -> hash, "FileNamesList" -> fileNamesList }
В итоге скрипт Wolfram примет приблизительно такой вид:
hash = IntegerString[Hash["secret_key"<>"::"<>StringJoin[Riffle[IntegerString[FileHash[#,"SHA512"],16,128]&/@filesToUpload,"::"]],"SHA512"],16,128] fileNamesList = StringJoin[Riffle[StringReplace[FileNameTake[#],"."->"_"]&/@filesToUpload,"::"]] URLFetch["http://vash_site.ru/upload.php", "Method" -> "POST", "Headers" -> { "SecurityHash" -> hash, "FileNamesList"->fileNamesList }, "MultipartData" -> Table[{FileNameTake[file], "application/octet-stream", BinaryReadList[file, "Byte"]},{file,filesToUpload}] ]
Итак, со стороны wolfram мы подготовили систему к безопасному обмену данными. Теперь необходимо адаптировать скрипт на сервере так, чтобы он смог корректно обработать присланную информацию.
$headers = apache_request_headers(); $file_names_list = explode("::",$headers["FileNamesList"]); foreach ($file_names_list as $file_name){ $file_contents[] = hash("SHA512",$_POST[$file_name], false); } if (isset($file_contents) and count($file_contents) != 0){ $contentHashString = implode("::",$file_contents); } $EvaluateHash = hash("SHA512",$secret."::".$contentHashString, false);
Здесь все заголовки присланного запроса помещаются в переменную $headers, далее строка в заголовке “FileNamesList” разбивается в массив по разделителю “::”, и в той последовательности в которой они находятся в массиве $file_names_list, извлекается их содержимое из массива $_POST и вычисляесят хэш-сумма, результат формируется в массив $file_contents, который затем объединяется в строку с резделителем “::”. И далее к этой строке дописывается секретный ключ $secret через разделитель “::” и из всей строки вычисляется новая хэш функция - $EvaluateHash. Осталось сравнить вычисленный и присланный хэши.
В итоге, дополненный php-скрипт может быть сформирован следующим образом:
$headers = apache_request_headers(); $file_names_list = explode("::",$headers["FileNamesList"]); foreach ($file_names_list as $file_name){ $file_contents[] = hash("SHA512",$_POST[$file_name], false); } if (isset($file_contents) and count($file_contents) != 0){ $contentHashString = implode("::",$file_contents); } $EvaluateHash = hash("SHA512",$secret."::".$contentHashString, false); if ($EvaluateHash == $headers["SecurityHash"]) { $allow_ext = array("png","zip","pdf"); chmod(__DIR__."/files", 0777); foreach($_POST as $name=>$content) { $file_name = explode("_", $name); if (in_array(array_pop($file_name),$allow_ext)){ $fp = fopen(__DIR__."/files/".str_replace("_",".",$name),"w"); fwrite($fp,$content); fclose($fp); } else { echo "Файл ".str_replace("_",".",$name)." имеет недопустимое расширение. "; } } chmod(__DIR__."/files", 0755); }
Теперь php-обработчик на сервере начнет загрузку файлов, только в том случае, если вычисленный и присланный хэш-суммы совпадут. Иначе, процесс загрузки не будет запущен.
Конечно, здесь представлена очень простая по структуре исходная строка. Вы можете ее значительно усложнить, добавив в нее ip-адрес компьютера с которого идет запрос, можно добавить имя User-Agent, которое будет иметь какой-то специальный вид. Можно реализовать генерацию секретного ключа (который дописывается к хэшируемой строке) при каждой транзакции, и так далее....на безопасности экономить нельзя.
Ссылки для скачивания.
Здесь Вы можете скачать сопроводительные материалы к статье:
wolfram-php-upload-files.zip (2Kb)
* * * * * * * * * * * *
Если информация этой статьи будет интересна и полезна Вашему кругу друзей и знакомых, то Вы можете опубликовать ссылку - тогда им проще будет ее найти. Они Вам будут благодарны:).