Необычная отправка файлов на сервер и получение обратно

Поделюсь необычным способом пушить файлы на сервер.

Занимался разработкой проекта на laravel, технически ничего особенного. Сложность была в том, что сервер располагается на стороне клиента, подключаться к нему можно через промежуточный сервер, подключение к которому идет через vpn. Правки заливать можно через архивирование кода, загрузкой на яндекс.диск, отправкой ссылки, на сервер загружают этот архив и его нужно распаковать. Надо ли говорить, что в таком случае там вылезает масса ошибок с правами и путями к файлам. Стороннее ПО на сервер поставить нельзя, буфер обмена не работает, поэтому скопировать файлы не получается. Исходящие запросы с сервера так же заблокированы. Входящие ограничены размером около 300кб


Все что может делать сервер - принимать запрос и отвечать на него.


Исходя из такой ситуации был написан код, который делит файлы на части и отправляет файлы POST запросами. Это позволяет отправлять файлы на сервер напрямую.


Как работает

- fread читает бинарные данные из файла

- bin2hex переводит в строку

- str_split разбивает строку на части, по допустимому размеру запроса

- отправляется обычная форма на файл-приемник

- там каждая часть дописывается во временный файл

- когда приходит параметр last = 1 содержимое файла читается как строка и сохраняется как бинарные данные с помощью hex2bin



Код отправки

protected $signature = 'app:send-file {filepath}';

public function handle()
{
   $filePath = $this->argument('filepath');
   $path = base_path($filePath);
   $fileName = basename($path);

   $file = fopen($path, "rb");
   $bin = fread($file, filesize($path));
   $hex = bin2hex($bin);
   $chunks = str_split($hex, 1024 * 300);
   $salt = 'some_salt';
   echo 'File: ' . $fileName . PHP_EOL;
   echo 'Chunks: ' . sizeof($chunks) . PHP_EOL;
   foreach ($chunks as $index => $chunk) {
       echo 'Sending: ' . ($index + 1 . ' of ' . sizeof($chunks)) . PHP_EOL;
       $isLast = intval($index == count($chunks) - 1);
       $hash = md5($filePath . $chunk . $isLast . $salt);
       $opts = [
           'http' => [
               'method' => "POST",
               'header' => "Content-Type: application/x-www-form-urlencoded",
               'content' => http_build_query([
                   'hash'          => $hash,
                   'file_path'     => $filePath,
                   'chunk'         => $chunk,
                   'last'          => $isLast,
               ])
           ]
       ];
       $context = stream_context_create($opts);
       $result = file_get_contents('https://host/receiver.php?part=' . $index, false, $context);
       if (!$result) {
           echo 'Fail';
           exit;
       }
       sleep(1);
   }
   echo 'Sent' . PHP_EOL;
}


Отправка выглядит так

php artisan app:send-file some_dir/some-file-path.php



Код получения на стороне сервера. В папке received-files создается временный файл, по завершению приема данных файл переносится в путь some_dir/some-file-path.php

<?php
try{
   $salt = 'some_salt';
  
   $isLast = $_POST['last'] == '1';
   $chunk = $_POST['chunk'];
   $fileName = $_POST['file_name'];
   $filePath = $_POST['file_path'];
  
   $hash = md5($_POST['file_path'] . $_POST['chunk'] . (int)$isLast . $salt);
   if ($hash != $_POST['hash']) {
       echo 0; exit;
   }
  
   $path = __DIR__ . '/received-files/' . $filePath;
   @mkdir(dirname($path), 0755, true);
   file_put_contents($path, $chunk, FILE_APPEND);
   if ($isLast) {
       $string = file_get_contents($path);
       // Тут указать где корень проекта
       file_put_contents(__DIR__ . '/../../' . $filePath, hex2bin($string));
       @unlink($path);
   }
   echo 1;
}
catch(Throwadble $e){
   file_put_contents('error.log', $e->getMessage() . PHP_EOL . PHP_EOL, FILE_APPEND);
}


А теперь про получение файлов с сервера, т.к. иногда нужно было вытащить файл к себе. Команда отправляет путь до файла, отправитель получает этот путь и в ответ читает содержимое файла.


protected $signature = 'app:get-file {filepath} {filename}';

public function handle()
{
   $salt = 'some_salt';
   $name = $this->argument('filename');
   $path = $this->argument('filepath');
   $opts = [
       'http' => [
           'method' => "POST",
           'header' => "Content-Type: application/x-www-form-urlencoded",
           'content' => http_build_query([
               'path' => $path,
               'hash' => md5($path . $salt),
           ])
       ]
   ];
   $context = stream_context_create($opts);
   $result = file_get_contents('https://host/reader.php', false, $context);
   if (!empty($result)) {
       file_put_contents($name, $result);
   } else {
       echo 'Failed to read from the file';
   }
}


Код отправителя, читает файл в stdout

<?php

$salt = 'some_salt';
$path = $_POST["path"] ?? '';
$inputHash = $_POST['hash'] ?? '';
$hash = md5( $path . $salt);
if ($hash != $inputHash) {
   echo 0;
   exit;
}

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($path).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;


Скачать исходники


Оставить комментарий

Комментарий отправится на проверку

Отправка
Спасибо! Комментарий добавлен
Копировать можно с указанием активной ссылки на эту страницу.
Давайте уважать труд других.