0

I have a weird situation: I get data from a Postgres database, and from that data, I create a table in a website, using Grid.js. Each line has a "Download" button, that takes 2 arguments from that table entry and send them to a function. Originally, that function would make a XHR request to a php file, that gets files from another DB, creates a ZIP file, and should send it to the user, with readfile().

Table

I now discovered that this is not possible. XHR does not allow downloads for safety reasons. I could do something using window.location to call the PHP file, and get the download, but I am dealing with hundreds of files, so I cannot write hundreds of PHP files to get the data individually. I could, but it would be very hard to maintain and manage all those files, and it feels unprofessional.

Right now, I can:

  • Send the data from JS to PHP, using POST;
  • Using those two variables, fetch the data from another Postgres server;
  • Use those files and create a ZIP file (The ZIP files cannot be stored permanently in the server, because of storage restrictions. A cronjob in the server will clean the files eventually)

I need to:

  • Send the ZIP to to the user;
  • Maintain the simplest code possible, in a way that I can feed 2 variables and it just works, without needing a PHP file for each table line (if that makes sense)

The current code is:

  • Javascript
const getData = (schema, table) => {

    const xhr = new XMLHttpRequest();

    xhr.open('POST', 'php/get_data.php', true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

    let packg = {
        schema: schema,
        table: table
    };

    const packgJSON = JSON.stringify(packg);
    // Vanilla JS XHR requires this formatting to send the data
    const data = new URLSearchParams({'content': packgJSON});

    xhr.send(data);

};
  • PHP
<?php

// File with connection info
$config = include('config.php');

// Connection info
$host = $config['host'];
$port = $config['port'];
$database = $config['database'];
$username = $config['username'];
$password = $config['password'];

// POST reception
$packg = json_decode($_POST['content']);
$schema = $packg->schema;
$table = $packg->table;



// File info and paths
$filename = $table;
$rootDir = "tempData/";
$fileDir = $filename . "/";
$filePath = $rootDir . $fileDir;
$completeFilePath = $filePath . $filename;
$shpfile = $filename . ".shp";
$zipped = $filename . ".zip";
$zipFile = $completeFilePath . ".zip";



// Function to send the file (PROBLEM - THIS DOES NOT WORK WITH XHR)
function sendZip($zipFile) {

    $zipName = basename($zipFile);

    header("Content-Type: application/zip");
    header("Content-Disposition: attachment; filename=$zipName");
    header("Content-Length: " . filesize($zipFile));

    ob_flush();
    flush();

    readfile($zipFile);

};


// Send the zip if it's already available (NOT PROBLEMATIC)
if (file_exists($zipFile)) {
    
    sendZip($zipFile);

};


// Get shapefile, zip it and send it, if not available (NOT PROBLEMATIC)
if (!file_exists($zipFile)) {

    // Get files
    exec("mkdir -p -m 777 $filePath");
    exec("pgsql2shp -h $host -p $port -u $username -P $password -g geom -k -f $completeFilePath $database $schema.$table");

    // ZIP the files
    $zip = new ZipArchive;

    if ($zip -> open($zipFile, ZipArchive::CREATE) === TRUE) {

        $handlerDir = opendir($filePath);

        // Iterates all files inside folder
        while ($handlerFile = readdir($handlerDir)) {
            // If the files are indeed files, and not directories
            if (is_file($filePath . $handlerFile) && $handlerFile != "." && $handlerFile != "..") {
                // Zip them
                $zip -> addFile($filePath . $handlerFile, $fileDir . $handlerFile);
            };
        };

        // Close the file
        $zip -> close();

    };

    sendZip($zipFile);

};

?>
d_araujo
  • 25
  • 1
  • 8
  • 1
    How would making an http request be any different than making a GET request with a link? – epascarello Jul 19 '21 at 18:31
  • https://stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch – epascarello Jul 19 '21 at 18:32
  • I am actually ashamed that i did not consider a simple GET. I am really sorry. Sometime we get so deep in our work that we fail to see the bigger picture, and the simplest solution is the best one. It works. And because I'm using pgsql2shp, the data is not directly sent to the database, so I don't need to fear a SQL injection attack as much. That's why I'm always using POST. I will formulate an answer to close this. Thank you @epascarello – d_araujo Jul 19 '21 at 18:56

1 Answers1

0

As pointed out by @epascarello here, a simple GET request solves this.

Even though I was afraid of not using POST because of an SQL injection attack, the variables pass through a pgsql2shp program, and that only accepts a valid schema and table names, so no need to worry about that as much.

I am currently using this KISS code, and it works:

const getData = (schema, table) => {
    
    window.location='php/get_data.php?schema=' + schema + '&table=' + table;

};

In PHP, it's only needed a small change from the POST reception to a GET reception. The variables are already separated, no need to decode anything:

$schema = $_GET['schema'];
$table = $_GET['table'];

This goes to show that sometimes, we look so deep into a problem that the solution is right in front of us.

d_araujo
  • 25
  • 1
  • 8