Workaround to XHR request and download prohibition - javascript

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().
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);
};
?>

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.

Related

Zip File - Download - Then Unzip

I'm getting really confused here. So here's whats going on in my project. The user clicks download then select directory to save the file, the file will be a zip file. After that I want to extract that zip file in the same directory the users chooses. Is this even possible in one script?
Here is my php code
<?php
require_once('connect.php');
// Get real path for our folder
$rootPath = realpath($_GET['uniq']);
// Name of the zip derive from the database
$zipname = $_GET['name'] . '.zip';
// Initialize archive object
$zip = new ZipArchive();
$zip->open($zipname, ZipArchive::CREATE | ZipArchive::OVERWRITE);
// Create recursive directory iterator
/** #var SplFileInfo[] $files */
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootPath),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file)
{
// Skip directories (they would be added automatically)
if (!$file->isDir())
{
// Get real and relative path for current file
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($rootPath) + 1);
// Add current file to archive
$zip->addFile($filePath, $relativePath);
}
}
// Zip archive will be created only after closing object
$zip->close();
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename='.$zipname);
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
?>
If its not possible another question. Is it possible to let user choose a zip file then after that it will be extracted at some client directory ie. C://xamp/extracthere/ using javascript.
Once the user has downloaded the file to their machine, it's beyond your control. You can't force them to unzip it, or do anything else with it for that matter.
Imagine if you could control the files on a user's local disk, it would be a hacker's dream. This is impossible using both PHP and JavaScript, for similar reasons in each case.

write a file on local disk from web app [duplicate]

I am trying to create and save a file to the root directory of my site, but I don't know where its creating the file as I cannot see any. And, I need the file to be overwritten every time, if possible.
Here is my code:
$content = "some text here";
$fp = fopen("myText.txt","wb");
fwrite($fp,$content);
fclose($fp);
How can I set it to save on the root?
It's creating the file in the same directory as your script. Try this instead.
$content = "some text here";
$fp = fopen($_SERVER['DOCUMENT_ROOT'] . "/myText.txt","wb");
fwrite($fp,$content);
fclose($fp);
If you are running PHP on Apache then you can use the enviroment variable called DOCUMENT_ROOT. This means that the path is dynamic, and can be moved between servers without messing about with the code.
<?php
$fileLocation = getenv("DOCUMENT_ROOT") . "/myfile.txt";
$file = fopen($fileLocation,"w");
$content = "Your text here";
fwrite($file,$content);
fclose($file);
?>
This question has been asked years ago but here is a modern approach using PHP5 or newer versions.
$filename = 'myfile.txt'
if(!file_put_contents($filename, 'Some text here')){
// overwriting the file failed (permission problem maybe), debug or log here
}
If the file doesn't exist in that directory it will be created, otherwise it will be overwritten unless FILE_APPEND flag is set.
file_put_contents is a built in function that has been available since PHP5.
Documentation for file_put_contents
fopen() will open a resource in the same directory as the file executing the command. In other words, if you're just running the file ~/test.php, your script will create ~/myText.txt.
This can get a little confusing if you're using any URL rewriting (such as in an MVC framework) as it will likely create the new file in whatever the directory contains the root index.php file.
Also, you must have correct permissions set and may want to test before writing to the file. The following would help you debug:
$fp = fopen("myText.txt","wb");
if( $fp == false ){
//do debugging or logging here
}else{
fwrite($fp,$content);
fclose($fp);
}

Passing variables in a URL of a php page to JavaScript and to another PHP

Based on the code in this link http://webaudiodemos.appspot.com/AudioRecorder/index.html, I am making changes to send the recorded audio file to server by passing a sessionId via URL. The php page is http://xxxxx/abc.php?sessionId=Sam. PHP versions: PHP 5.4 PHP 5.5.22. I am using the 2nd method from this link:How to pass variables and data from PHP to JavaScript?. The abc.php page has reference to a few JS codes as with the index.html from the link above. abc.php page process the URL values correctly with the following code:
<div id="bottom">
<?php
$faid = $_GET["sessionId"];
echo htmlspecialchars($faid); // tested working
?>
</div>
On the recorder.js JavaScript,I have a function that tries to pass the URL values to another PHP while uploading the audio file to server - The fname is not being passed on ... it seems .. can the xhr.send(blob) will still send the fname?
Recorder.setupDownload = function(blob){
var div = document.getElementById("bottom");
var fname = div.textContent;
var xhr = new XMLHttpRequest();
xhr.open('POST', "./uploads.php?" + fname, true);
xhr.onload = function(e) {};
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(blob);
The uploads.php in the server has the following script to receive the value and to create an audio file - but it is not creating the audio file - however, if I fix the file name (eg: "filename123") it writes the audio file - so the issue is in passing the variable name - I am a newbie and I wonder what am I missing?:
<?php
ini_set("display_errors", true);
error_reporting(E_ALL);
if(isset($_GET['fileId']) && !empty($_GET['fileId'])){
$id = $_GET["fileId"];
}
$fp = fopen( $id, 'wb' ); // writes the audio file
fwrite( $fp, $GLOBALS['HTTP_RAW_POST_DATA'] );
fclose( $fp );
?>
Update: It is working!
You didn't give your value a name, so you're passing a value that will appear as DIFFERENT key in every page.
e.g. each of your users will have something like
http://example.com?foo
http://example.com?bar
leading to $_GET['foo'] and $_GET['bar'] in PHP. But since foo/bar are some randomish value representing your session ID, you have no idea what that value will be. So... give it a name:
http://example.com?key=foo
Now you just do $key = $_GET['key'] and can always access your session value, no matter what value it really as - it'll always be assigned to the key.

Reading directory contents from a client's computer - PHP

I want to recursively read contents of a folder chosen by client on my site.
I have used opendir() and scandir() but they are unable to read directory contents from client's computer.
Is there any way that I can read the file names from visitor's directory.
function ListIn($dir, $prefix = '') {
$dir = rtrim($dir, '\\/');
$result = array();
$directory = opendir($dir);
foreach (scandir($directory) as $f) {
if ($f !== '.' and $f !== '..') {
if (is_dir("$dir/$f")) {
$result = array_merge($result, ListIn("$dir/$f", "$prefix$f/"));
} else {
$result[] = $prefix.$f;
}
}
}
return $result;
}
I need to implement this in either php or javascript.
This is possible with the storage apis provided by javascript nowadays.
http://www.html5rocks.com/en/tutorials/file/dndfiles/
However if you need raw read/write access, I suggest you read up on the chrome platform apis.
You cannot do this with PHP or any other server side technology.
You might be able to do this with a browser plugin or a flash app.
Ask yourself why you want to do this?

web services and phonegap : best practices

Hi I am using phonegap for crossed plateform development (I use angularJS as JS framework).
I want to use a web service to access to a list of positions from my database (mysql) on my website.
The problem is that the solution I found is not secure at all:
Javascript
var xhr;
if (window.XMLHttpRequest)
xhr = new XMLHttpRequest();
else
xhr = ActiveXObject("Microsoft.XMLHTTP");
xhr.open("GET", "http://localhost:8888/MAMP_Site/0/test.php", true);
xhr.send(null);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
console.log("Ready State4: Json Textual Data retrieved");
handleData(xhr.responseText); // Json Textual Data
}
};
function handleData(data)
{
var jsonData;
console.log("ReceivedData from WebService:"+data);
jsonData = eval('(' + data + ')');
$scope.lastUpdate = jsonData[0];
$scope.jsonData = jsonData[1];
$scope.$apply();
}
PHP (used as "web service")
<?php
header('Access-Control-Allow-Origin: *');
header("Content-Type: text/plain");
class UserInfo {
public $id = "";
public $name = "";
public $username = "";
public $timestamp = "";
public function __construct($_id, $_name, $_username, $_timestamp) {
$this->id = $_id;
$this->name = $_name;
$this->username = $_username;
$this->timestamp = $_timestamp;
}
}
$db = mysql_connect('localhost:8889', 'root', 'root');
mysql_select_db('myDbName',$db);
$sql = 'SELECT id,name,username,timestamp FROM positions_test';
$req = mysql_query($sql) or die('Erreur SQL !<br>'.$sql.'<br>'.mysql_error());
$dataArray = array();
while($data = mysql_fetch_assoc($req)) {
$dataArray[]= new UserInfo($data['id'],$data['name'],$data['username'],$data['timestamp']);
}
//Last Modified Time
$sql = "SELECT UPDATE_TIME FROM information_schema.tables WHERE TABLE_SCHEMA = 'myDbName'AND TABLE_NAME = 'positions_test'";
$req = mysql_query($sql) or die('Erreur SQL !<br>'.$sql.'<br>'.mysql_error());
$data = mysql_fetch_assoc($req)["UPDATE_TIME"];
$jsonDataArray = array($data, $dataArray);
echo json_encode($jsonDataArray);
mysql_close();
?>
Basically the PHP return a JSON (as text), and I get it (as text) in my JS. Then I evaluate it as a JSON.
The question
Security concern
As the application is made with cordova, all JS and Html source code can be viewed and so the URL of my php "web service". It means that anybody who have the adress can access to the Json File. Even if this data is public (in my case) I want it to be only accessible from my app (this way I can for instance avoid a bot to store all of this data and spam).
Token or user-agent
As there is no authentification for users is there any way for my webservice to know where the request come from?
I thought using a token to ensure that the request come from my app but once again as the source code can be viewed, anybody could see the token or the code to generate it.
Maybe using user-agent to know if it is accessed from a mobile device?
Other port than 80
Maybe it would be judicious to choose another port than 80 to connect to my web service, but how can I select my connexion port?
Best practice
The main point would actually be, what are the best practice for web services on phonegap (cordova) ?
Should I use SSL, Https?
Should I use a real web service instead of a simple php page and XMLHTTPRequest? If yes, which one?
And of course how building properly and securely my web service ?
I know this is a long post, but I searched the web a for while and I found a lot of interesting stuff but nothing really concret on the best practices to build your web services for a phonegap application (with no user authentification)
You could try to obfuscate it, or a a lot of other things, but in the end you have to receive it in the client side and therefore there is nothing you can do to fully prevent him from reading your data, seeing your client side code or spamming your service.
The best you can do to make sure that the service is safe is: make sure the connection to the db does not allow writes, all the software involved is updated regularly and that the queries sent to your service have the syntax and content that you are expecting.

Categories

Resources