PHP file stream data coming back in Binary - php

So I have a function that I CANNOT get to work. What's happening is it's coming back with data that's in binary and not the actual file. However, in the binary that I get back, the name of the particular file is included in the string. The data looks like this:
PK‹Mpt-BR/finalinport.html³)°S(ÉÈ,V¢‘ŒT…‚ü¢’ÒôÒÔâT…´Ì¼Ä ™” “I,QðT(ÏÌÉQ(-ÈÉOLQH„¨‡*Ê/B×]’X”žZ¢`£_`ÇPK.Ùô LePK‹M.Ùô Lept-BR/finalinport.htmlPKD
pt-BR is the directory and the 'finalinport.html' is the file that I am trying to have downloaded. If I replace the second parameter of fwrite to just a plain string, then everything works and I get the string that I wrote in a file inside of the zip. But not when I'm using Stream->getContents(), which leads me to believe that it is something going on with the stream. I cannot wrap my head around what can be happening. I've been on this for a week and a half now so any suggestions would be great.
public function downloadTranslations(Request $request, $id)
{
$target_locales = $request->input("target_locale");
$has_source = $request->input("source");
$client = new API(Auth::user()->access_token, ZKEnvHelper::env('API_URL', 'https://myaccount.com'));
$document = Document::find($id);
$job_document = JobDocument::where('document_id', $id)->first();
$job = Job::find($job_document->job_id);
$file = tempnam('tmp', 'zip');
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);
$name_and_extension = explode('.', $document->name);
if($target_locales == null){
$target_locales = [];
foreach ($job->target_languages as $target_language) {
$target_locales[] = $target_language['locale'];
}
}
foreach($target_locales as $target_locale){
$translation = $client->downloadDocument($document->document_id, $target_locale);
$filename = $name_and_extension[0] . ' (' . $target_locale . ').' . $name_and_extension[1];
if($translation->get('message') == 'true') {
//API brings back file in stream type
$stream = Stream::factory($translation->get('body'));
$newFile = tempnam(sys_get_temp_dir(), 'lingo');
$handle = fopen($newFile, 'w');
fwrite($handle, $stream->getContents());
$zip->addFile($newFile, 'eh.html');
fclose($handle);
}
else if($translation->get('message') == 'false'){
//API brings back file contents
$zip->addFromString($filename, $translation->get('body'));
}
}
$translation = $client->downloadDocument($document->document_id, null, null);
$filename = $name_and_extension[0]. ' (Source).'.$name_and_extension[1];
$zip->addFromString($filename, $translation->get('body'));
sleep(10);
$zip->close();
return response()->download($file, $name_and_extension[0].'.zip')->deleteFileAfterSend(true);
}
I'm unfamiliar with PHP streams and I don't have a debugger set up so I keep thinking it has something to do with how I am handling the stream. Because the other condition (the else if) is coming back as content of the file (string) and the if statement, the data is coming back as a stream resource, which I am unfamiliar with.

Stream::factory
is used in Guzzle 5, use json_encode for Guzzle 6.

Related

PHP. How to read a file, if it is writing without a problem with "a+", but is not readable with "r"?

I have two scripts: one of them writes the value of a variable to a file. In another script, I try to read it. It is written without problems, but it is not readable.
Here I write to a file:
$peer_id=2000000001;
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
$file = fopen($fileLocation,"a+");
fwrite($file, $peer_id);
fclose($file);
Here I read the file:
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
$file = fopen($fileLocation,"r");
if(file_exists($fileLocation)){
// Result is TRUE
}
if(is_readable ($file)){
// Result is FALSE
}
// an empty variables, because the file is not readable
$peer_id = fread($file);
$peer_id = fileread($file);
$peer_id = file_get_contents($file);
fclose($file);
The code runs on "sprinthost" hosting, if that makes a difference. There are suspicions that this is because of that hosting.
file_get_contents in short runs the fopen, fread, and fclose. You don't use a pointer with it. You should just use:
$peer_id = file_get_contents($fileLocation);
That is the same for is_readable:
if(is_readable($fileLocation)){
// Result is FALSE
}
So full code should be something like:
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
if(file_exists($fileLocation) && is_readable($fileLocation)) {
$peer_id = file_get_contents($fileLocation);
} else {
echo 'Error message about file being inaccessible here';
}
The file_get_contents has an inverse function for writing; https://www.php.net/manual/en/function.file-put-contents.php. Use that with the append constant and you should have the same functionality your first code block had:
file_put_contents($fileLocation, $peer_id, FILE_APPEND | LOCK_EX);

Rotate text files based on size

I am trying to record hits to my website to a text file and I want to limit the size of the text file to some value. Once the limit is crossed I want a new file to be created dynamically. With my current code, a new file does get created after the set limit is passed, but the rest of the data is only stored on that new file.
public function storeActivityHitCount(Request $request)
{
if($request->ajax() && isset($request->data)){
$clientIP = request()->ip(); $i= 1;
$date = date("Y-m-d h:i:sa");
$data = $clientIP.', '.$date.', '.$request->data;
$file = public_path().'/adminpanel/hits/activity/activity'.$i.'.txt';
if(!file_exists($file)){
fopen($file,"w+");
}
$filesize = filesize($file);
if($filesize >= 76){ $i++;
$file1 = public_path().'/adminpanel/hits/activity/activity'.$i.'.txt';
if(!file_exists($file1)){
fopen($file1,"w+");
}
$content = file_get_contents($file1);
$content .= $data. PHP_EOL;
$upload_success = file_put_contents($file1, $content);
}else{
$content = file_get_contents($file);
$content .= $data. PHP_EOL;
$upload_success = file_put_contents($file, $content);
}
if($upload_success){
return Response::json(['status' => 'success'], 200);
}
return Response::json(['status' => 'failed'], 400);
}
abort(403);
}
Your original code of course was only trying one other file and then writing to it regardless of its size. You want to put that logic into a repeating structure. Or in other words, you want to look for a different file while you keep finding full ones.
public function storeActivityHitCount(Request $request)
{
if ($request->ajax() && isset($request->data)) {
$clientIP = request()->ip();
$date = date('Y-m-d h:i:sa');
$data = $clientIP . ', ' . $date . ', ' . $request->data;
$i = 1;
$file = public_path() . '/adminpanel/hits/activity/activity' . $i . '.txt';
while (filesize($file) >= 76 && $i <= 20) {
$i++;
$file = public_path() . '/adminpanel/hits/activity/activity' . $i . '.txt';
}
if (filesize($file) >= 76) {
// the loop got away from us
// do something?
$upload_success = false;
} else {
$upload_success = file_put_contents($file, $content, \FILE_APPEND);
}
if ($upload_success) {
return Response::json(['status' => 'success'], 200);
}
return Response::json(['status' => 'failed'], 400);
}
abort(403);
}
I put an upper limit of 20 iterations on the loop; you usually don't want a while loop without some kind of escape mechanism.
file_put_contents will always create a file that doesn't exist, so you didn't need to use fopen (and, you weren't using fclose.) Furthermore, if you pass the FILE_APPEND flag, it will append to the existing file; no need for getting contents and appending stuff to it.
Try to keep your code consistent and readable. Multiple commands on one line, inconsistent whitespace around control structures and operators, inconsistent indentation: all these things end up making you work harder than you have to.
And, of course, this would all be better handled by standard system tools like logrotate which is probably running on your server.
I would isolate the action of getting the activity log file. Like using a function like this:
function getAvailableActivityLogFile(){
$looking=true;
$i=0;
while($looking){
$i++;
$file_path = public_path() . "/adminpanel/hits/activity/activity{$i}.txt";
// If file does not exist or file is less than 76bytes you have the right candiate
if(!file_exists($file_path) || filesize($file_path) < 76){
$looking=false;
return fopen($file_path, 'a+');
}
// Otherwise keep looking on next iteration. You can also write some logic to have a max of loop iterations or max files to look for also.
}
}
Then you can use this function to get the next available file and don't bother with too much logic about what file is available. In my version about you should use fwrite() to write to the file using the file pointer returned by the function.
With the a+ option, you get a pointer there that appends content to the file on every new fwrite to the pointer.
You can also write the function to retrieve a path instead of a pointer.

Batch download URLs in PHP?

So, I have a PHP script that is supposed to download images that the user inputs. However, if the user uploads a TXT file and it contains direct links to images, it should download the images from all the URLs in the file. My script seems to be working, although it seems that only the last file is downloaded while the others are stored as files containing no data.
Here's the portion of my script where it parses the TXT
$contents = file($file_tmp);
$parts = new SplFileObject($file_tmp);
foreach($parts as $line) {
$url = $line;
$dir = "{$save_loc}".basename($url);
$fp = fopen ($destination, 'w+');
$raw = file_get_contents($url);
file_put_contents($dir, $raw);
}
How do I make it download every URL from the TXT file?
When you iterate over an SplFileObject, you get the whole line, including whitespace. Your URL will thus be something like
http://example.com/_
(php seems to mangle the newline to an underscore) and thus you'll get an error for many URLs (some URLs will still work fine, since they contain the important information prior. For instance, Batch download URLs in PHP? works, but https://stackoverflow.com/_ does not). If an error occurs, file_get_contents will return false, and file_put_contents will interpret that like an empty string.
Also, the line $fp = fopen ($destination, 'w+'); is really strange. For one, since $destination is not defined, it would error anyways. Even if $destination is defined, you'll end up with lots of file handles and overwrite that poor file multiple times. You can just remove it.
To summarize, your code should look like
<?php
$file_tmp = "urls.txt";
$save_loc = "sav/";
$parts = new SplFileObject($file_tmp);
foreach($parts as $line) {
$url = trim($line);
if (!$url) {
continue;
}
$dir = "{$save_loc}".basename($url);
$raw = file_get_contents($url);
if ($raw === false) {
echo 'failed to donwload ' . $url . "\n";
continue;
}
file_put_contents($dir, $raw);
}
It looks like line
$parts = new SplFileObject($file_tmp);
isn't necessary as well as
$fp = fopen ($destination, 'w+');
file() function reads entire file into array. You just have call trim() on each array element to remove new line from characters. Following code should work properly:
<?php
$save_loc = './';
$urls = file('input.txt');
foreach($urls as $url) {
$url = trim($url);
$destination = $save_loc . basename($url);
$content = file_get_contents($url);
if ($content) {
file_put_contents($destination, $content);
}
}

PHP fwrite writing empty file

I'm trying to make this save a file and it creates the file, but it's always empty. This is the code for it:
<?php
$code = htmlentities($_POST['code']);
$i = 0;
$path = 'files/';
$file_name = '';
while(true) {
if (file_exists($path . strval($i) . '.txt')) {
$i++;
} else {
$name = strval($i);
$file_name = $path . $name . '.txt';
break;
}
}
fopen($file_name, 'w');
fwrite($file_name, $code);
fclose($file_name);
header("location: index.php?file=$i");
?>
I echoed out $code to make sure it wasn't empty, and it wasn't. I also tried replacing
fwrite($file_name, $code);
with this:
fwrite($file_name, 'Test');
and it was still empty. I have written to files a couple of times before in PHP, but I'm still really new to PHP and I have no idea whats wrong. Could someone tell me what I'm doing wrong or how to fix this? Thanks
Reading/Writing to/from a file or stream requires a resource handle:
$resource = fopen($file_name, 'w');
fwrite($resource, $code);
fclose($resource);
The resource handle $resource is essentially a pointer to the open file/stream resource. You interact with the created resource handle, not the string representation of the file name.
This concept also exists with cURL as well. This is a common practice in PHP, especially since PHP didn't have support for OOP when these methods came to be.
Take a look of the samples on php.net

Script to download and extract zip files returns errors

Hey everyone, I have written a script that downloads a zip file from a remote source, and then is supposed to extract the zip file to a directory. Below is the script:
<?php
$url = "http://example.com/some_file.zip";
download($url,'file.zip');
function download($url,$file_name = NULL){
if($file_name == NULL){ $file_name = basename($url);}
$url_stuff = parse_url($url);
$port = isset($url_stuff['port']) ? $url_stuff['port'] : 80;
$fp = fsockopen($url_stuff['host'], $port);
if(!$fp){ return false;}
$query = 'GET ' . $url_stuff['path'] . " HTTP/1.0\n";
$query .= 'Host: ' . $url_stuff['host'];
$query .= "\n\n";
fwrite($fp, $query);
while ($tmp = fread($fp, 8192)) {
$buffer .= $tmp;
}
preg_match('/Content-Length: ([0-9]+)/', $buffer, $parts);
$file_binary = substr($buffer, - $parts[1]);
if($file_name == NULL){
$temp = explode(".",$url);
$file_name = $temp[count($temp)-1];
}
if(!file_exists("packages")){ mkdir("packages", 0755);}
$file_open = fopen("packages/" . $file_name,'w');
if(!$file_open){ return false;}
fwrite($file_open,$file_binary);
$zip = zip_open(realpath("packages")."/".$file_name);
if ($zip) {
while ($zip_entry = zip_read($zip)) {
$fp = fopen("some_dir/".zip_entry_name($zip_entry), "w");
if(zip_entry_open($zip, $zip_entry, "r")) {
$buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
fwrite($fp,"$buf");
zip_entry_close($zip_entry);
fclose($fp);
}
}
zip_close($zip);
}
fclose($file_open);
return true;
}
?>
The issue that I have is that while the downloading of the remote file works flawlessly, I can't seem to extract it. The zip_read() and zip_close() return errors saying that it "expects parameter 1 to be resource, integer given...", which I have found means that the zip_open() was unable to extract and is returning an error code, which I have found to be "19" meaning "Zip File Function error: Not a zip archive". However, I know the file I am downloading is, in fact, a zip file. Can anyone explain this odd behavior and provide a fix? It would be much appreciated!
Quoting php.net: "zip_open() ... Returns a resource handle for later use with zip_read() and zip_close() or returns the number of error if filename does not exist or in case of other error."
This means you cannot test if ($zip) like that. Try
if ( is_resource($zip) ) {
// stuff
} else {
print "Zip_open() returned error $zip\n";
}
edit: Apart from that, you need to cut the response in 2 parts properly. You are relying heavily on the Content-Length parameter. You don't check if the preg_match actually matched. A lot of things can go wrong and you should check those things. Try splitting the content on the first empty line (explode on \r\n\r\n or something like that)
Besides the fread() loop should check for feof(), since you would stop reading now if for some reason you would encounter an empty read. Copy&paste from php.net:
while (!feof($handle)) {
$contents .= fread($handle, 8192);
}
But we can go on and on here. Three main points have to be made:
read the fantastic manual (php.net)
check return values
don't assume you know things you don't
those are related: you must lookup the manual to see what return values you might encounter.

Categories