This is my code:
<?php
$pass = $_GET["pass"];
$user = $_GET["user"];
$next = false;
$file = fopen("info.txt", "r") or die("Something went wrong.");
// Output one line until end-of-file
while(!feof($file)) {
if ($next == true and $pass === fgets($file)){
echo "true";
$next = false;
} else {
echo "false" . "<br>";
$next = false;
}
if (fgets($file) == $user) {
$next = true;
}
}
fclose($file);
?>
and this is info.txt
ch1ck3n
kodero1029
note that this is just a made up password
for example, we go to my website containing this code, https://ch1ck3n.com/login/auth/auth.php?user=ch1ck3n&pass=kodero1029
and it prints false TWICE.
I am making a login system using php and a simple txt document.
the php code reads the txt file lines one by one, and if a line matches the username, that means that the password is on the next line. but if you see, go to the website and it will print false twice.
the $next variable is to indicate that the next line is the password.
What's wrong?
Your issue is because:
if (fgets($file) == $user) {
$next = true;
}
Why is this wrong?
This is the only way that $next is true, but you have $user = $_GET["user"] //ch1ck3n but if you View the Manual it states:
fgets:
Returns a string of up to length - 1 bytes read from the file pointed to by handle. If there is no more data to read in the file pointer, then FALSE is returned.
And see also:
Reading ends when length - 1 bytes have been read, or a newline (which is included in the return value)
(my emphesis)
So you're return value from fgets is a string which includes the line ending, and you're comparing this to a text string ch1ck3n without a line ending. Therefore $next will never be true, so for each line of the file, the if/else statement will return the false option.
Solution:
Strip line endings from your strings:
if (trim(fgets($file)) === $user) {
$next = true;
}
The same will need to be done for all occassions you use fgets.
Security Note:
As mentioned in comments, your method of approach for this issue re: security is absolutely the incorrect way of doing things. You should be using POST requsts or Database references or SESSION cookies to safely associate credentials data with the script.
Don't send sensitive information in URL as this is insecure with or without https: Link
Comment by Felippe Duarte
You are reading two lines per iteration of the loop. Since you only have two lines (maybe three?) that means in a single iteration you are done. Do this:
<?php
$pass = $_GET["pass"];
$user = $_GET["user"];
$next = false;
$file = fopen("info.txt", "r") or die("Something went wrong.");
// Output one line until end-of-file
while(!feof($file)) {
$line = rtrim(fgets($file)); //Newline is included in the fgets result
if ($next == true && $pass === $line){
echo "true";
$next = false;
} else {
echo "false" . "<br>";
$next = false;
}
if ($line === $user) {
$next = true;
}
}
fclose($file);
The reason that you get false twice is (probably) because you may have an empty line at the end of the file.
Related
I have a file which is basically a list of names in that format:
name.lastname
name.lastname
name.lastname
myname.mylastname
name.lastname
and I want to check whether a string I have is equal to one of the names (with the dot).
Thats what I tried already:
$username = "myname.mylastname";
$boolean = False;
$handle = fopen("thelist.txt","r");
while (($line = fgets($handle)) !== false) {
if ($line === $username){
$liste = True;
}
}
after that list keeps being False and I dont know why.
Thanks in advance :)
There are a few potential issues I see.
First $boolean = False; while $liste = True;, so you may have a potential typo in your output variable.
Second issue is that thelist.txt is not an absolute path. So the file resource may have failed to be loaded. It is highly recommended that you use an absolute path to file resources such as /full/path/to/file or __DIR__ . '/path/to/file'.
The main issue is that fgets will include the \r and \n at the end of each line in the file, that does not exist in the compared string variable. You can use trim to remove extraneous line endings and white spaces to compare with the string variable.
Example: https://3v4l.org/4VG4D
$username = "myname.mylastname";
$liste = false;
$handle = fopen("thelist.txt", 'rb');
while (false !== ($line = fgets($handle))) {
if (trim($line) === $username){
$liste = true;
break; //stop at first match
}
}
fclose($handle);
var_dump($liste); //true
Using PHP 7.3, I'm trying to achieve "tail -f" functionality: open a file, waiting for some other process to write to it, then read those new lines.
Unfortunately, it seems that fgets() caches the EOF condition. Even when there's new data available (filemtime changes), fgets() returns a blank line.
The important part: I cannot simply close, reopen, then seek, because the file size is tens of gigs in size, well above the 32 bit limit. The file must stay open in order to be able to read new data from the correct position.
I've attached some code to demonstrate the problem. If you append data to the input file, filemtime() detects the change, but fgets() reads nothing new.
fread() does seem to work, picking up the new data but I'd rather not have to come up with a roll-your-own "read a line" solution.
Does anyone know how I might be able to poke fgets() into realising that it's not the EOF?
$fn = $argv[1];
$fp = fopen($fn, "r");
fseek($fp, -1000, SEEK_END);
$filemtime = 0;
while (1) {
if (feof($fp)) {
echo "got EOF\n";
sleep(1);
clearstatcache();
$tmp = filemtime($fn);
if ($tmp != $filemtime) {
echo "time $filemtime -> $tmp\n";
$filemtime = $tmp;
}
}
$l = trim(fgets($fp, 8192));
echo "l=$l\n";
}
Update: I tried excluding the call to feof (thinking that may be where the state becomes cached) but the behaviour doesn't change; once fgets reaches the original file pointer position, any further fgets reads will return false, even if more data is subsequently appended.
Update 2: I ended up rolling my own function that will continue returning new data after the first EOF is reached (in fact, it has no concept of EOF, just data available / data not available). Code not heavily tested, so use at your own risk. Hope this helps someone else.
*** NOTE this code was updated 20th June 2021 to fix an off-by-one error. The comment "includes line separator" was incorrect up to this point.
define('FGETS_TAIL_CHUNK_SIZE', 4096);
define('FGETS_TAIL_SANITY', 65536);
define('FGETS_TAIL_LINE_SEPARATOR', 10);
function fgets_tail($fp) {
// Get complete line from open file which may have additional data written to it.
// Returns string (including line separator) or FALSE if there is no line available (buffer does not have complete line, or is empty because of EOF)
global $fgets_tail_buf;
if (!isset($fgets_tail_buf)) $fgets_tail_buf = "";
if (strlen($fgets_tail_buf) < FGETS_TAIL_CHUNK_SIZE) { // buffer not full, attempt to append data to it
$t = fread($fp, FGETS_TAIL_CHUNK_SIZE);
if ($t != false) $fgets_tail_buf .= $t;
}
$ptr = strpos($fgets_tail_buf, chr(FGETS_TAIL_LINE_SEPARATOR));
if ($ptr !== false) {
$rv = substr($fgets_tail_buf, 0, $ptr + 1); // includes line separator
$fgets_tail_buf = substr($fgets_tail_buf, $ptr + 1); // may reduce buffer to empty
return($rv);
} else {
if (strlen($fgets_tail_buf) < FGETS_TAIL_SANITY) { // line separator not found, try to append some more data
$t = fread($fp, FGETS_TAIL_CHUNK_SIZE);
if ($t != false) $fgets_tail_buf .= $t;
}
}
return(false);
}
The author found the solution himself how to create PHP tail viewer for gians log files 4+ Gb in size.
To mark this question as replied, I summary the solution:
define('FGETS_TAIL_CHUNK_SIZE', 4096);
define('FGETS_TAIL_SANITY', 65536);
define('FGETS_TAIL_LINE_SEPARATOR', 10);
function fgets_tail($fp) {
// Get complete line from open file which may have additional data written to it.
// Returns string (including line separator) or FALSE if there is no line available (buffer does not have complete line, or is empty because of EOF)
global $fgets_tail_buf;
if (!isset($fgets_tail_buf)) $fgets_tail_buf = "";
if (strlen($fgets_tail_buf) < FGETS_TAIL_CHUNK_SIZE) { // buffer not full, attempt to append data to it
$t = fread($fp, FGETS_TAIL_CHUNK_SIZE);
if ($t != false) $fgets_tail_buf .= $t;
}
$ptr = strpos($fgets_tail_buf, chr(FGETS_TAIL_LINE_SEPARATOR));
if ($ptr !== false) {
$rv = substr($fgets_tail_buf, 0, $ptr + 1); // includes line separator
$fgets_tail_buf = substr($fgets_tail_buf, $ptr + 1); // may reduce buffer to empty
return($rv);
} else {
if (strlen($fgets_tail_buf) < FGETS_TAIL_SANITY) { // line separator not found, try to append some more data
$t = fread($fp, FGETS_TAIL_CHUNK_SIZE);
if ($t != false) $fgets_tail_buf .= $t;
}
}
return(false);
}
I'm really new to php. I decided to make a counter based off a script I've seen. I've made changes to it. I'm trying to figure out how to reset the counter.
$userCount = file_get_contents("count.txt");
$userCount = trim($userCount);
$userCount = $userCount + 1;
$countReset = $userCount - $userCount;
$file = fopen("count.txt","w+");
fwrite($file,$userCount);
fclose($file);
print "The number of visitors is: $userCount";
if ($userCount < 20){
echo 'not yet';
}
else {
echo 'done!';
}
if ($userCount > 40){
fwrite($file,$countReset);
fclose($file);
}
I tried subtracting the counter from itself to get it back to 0.
$countReset = $userCount - $userCount;
However, it doesn't seem to work. the counter itself works, but I am unable to get it to reset back to 0.
This is just an impression like script I'm doing as a way to learn php. Also, sorry about the format, struggling with this post editor.
Any help with the script?
You've closed the file before trying to write to it again. Before your second fwrite, add:
$file = fopen("count.txt","w+");
Try to simply set value 0 to the var:
$countReset = 0;
Couldn't you just edit the count.txt file?
Doing it in PHP, you could do
fwrite($file,'0');
EDIT: Like CanSpice said, you shouldn't close the file before you're done with it. Remove the first fclose, and it should work.
I wouldn't mix fopen() and file_get_content() functions in this context, either use fopen(), fread() and fwrite() or use file_get_contents() and file_put_contents().
If you need just reset counter and you don't need previous value, than use:
file_put_contents('count.txt', '0');
If you need update value you may use either:
$count = file_get_contents( 'count.txt');
$count++;
// Reset?
if( $reset){
$count = 0;
}
file_put_contents( 'count.txt', "$count");
Or rather:
$fp = fopen( 'count.txt', 'r+') or die( 'Cannot use counter');
$count = trim( fread( $fp, 1024));
$count++;
// Reset?
if( $reset){
$count = 0;
}
ftruncate( $fp, 0);
fseek( 0, SEEK_SET)
fwrite( $fp, "$count");
fclose( $fp);
Here are the manual pages for ftruncate() and fseek() + you should probably study flock() so two scripts wouldn't overwrite the content at the same time.
/****************************************************************************
* read the current count from the counter file, increment
* it by 1, and re-save the new value off to the file
***************************************************************************/
function getAndIncrementCount(){
// attempt to open the file
// a+ means keep the contents so we can read it at least once,
// but allow us to overwrite the value once we increment it
if (($fHandle = fopen('count.txt','a+')) !== FALSE){
// read in the count (also cast to an int so if there is
// an invalid (or absent) value it will default to a 0
$count = (int) fread($fHandle, 100);
// increase the counter
$count++;
// go back to the beginning of the file
fseek($fHandle, 0);
// re-write the new count back to the file
fwrite($fHandle, $count);
// cose the file now that we're done with it
fclose($fHandle);
// return back the count
return $count;
}
// we couldn't get to the file, so return an error flag
return FALSE;
}
/****************************************************************************
* write the specified value to the counter file
***************************************************************************/
function setCount($count = 0){
// attempt to open the file with over-write permissions
// w+ will open the file and clear it
if (($fHandle = fopen('count.txt','w+')){
// write the counter to the file
fwrite($fHandle, $count);
// close the file now that we're done
fclose($fHandle);
// return the newly saved count
return $count;
}
// we couldn't get to the file, so return an error flag
return FALSE;
}
And applied in practice:
$userCount = getAndIncrementCount();
echo "The number of visitors is: {$userCount}";
if ($userCount < 20){
echo "Not Yet";
}else{
echo "Done!";
}
if ($userCount > 40){
setCount(0);
}
It's because you are not rewriting the file contents but adding to them when you use the fwrite second time, so $countReset gets appended to the content already in the file. Try this:
$userCount = file_get_contents("count.txt");
$userCount = $userCount + 1;
$countReset = $userCount - $userCount;
file_put_contents("count.txt", $userCount);
print "The number of visitors is: $userCount\n";
if ($userCount < 20){
echo 'not yet';
}
else {
echo 'done!';
}
if ($userCount > 40){
file_put_contents("count.txt", $countReset);
}
When my script writes to the file, it doesn't break the added content onto a new line.
Instead of:
user1:password1
user2:password2
It writes:
user1:password1user2:password2
Originally, my fwrite looked like this fwrite($fh, $data); and from searching other questions, I changed my code to this:
fwrite($fh, $data . "\n");
This does not seem to work though.
Here is my code
<?php
if (isset($_POST['submit']))
{
$username = $_POST['user'];
$password = $_POST['password'];
$confirmpw = $_POST['confirmpw'];
$username = strtolower($username);
//Check if passwords match
if ($password != $confirmpw){
print "Passwords do not match, please try again.";
}
else{
//the data
$data = "$username:$password\n";
//open the file and choose the mode
$fh = fopen("passwd.txt", "a+");
// Cycle through the array
$match_found = false;
while (($buffer = fgets($fh, 4096)) !== false)
{
// Parse the line
list($usercheck, $passwordcheck) = explode(':', $buffer);
if (trim($usercheck) == $username)
{
print "The username is already in our system. Please use another one.";
$match_found = true;
break;
}
}
if(!$match_found)
{
fwrite($fh, $data . "\n");
// Set cookies for an hour
$hour = time() + 3600;
setcookie("username", $username, $hour);
//Redirect to home page
header("location: index.php");
}
}
//close the file
fclose($fh);
}
?>
What you need to use is \r\n.
For me only worked PHP_EOL (xampp on windows 7 php 5.3.8)
Use PHP_EOL for the platform dependent newline character. However, \n actually represents the newline character in the *NIX-world, but some windows editors denies to show them as newline (thats what happens in your case). You should consider using an other IDE and always use \n for compatibility (if the file should be usable on other platforms).
"\r\n" use this instead will work like a charm
I have a file named $dir and a string named $line, I know that this string is a complete line of that file but I don't know its line number and I want to remove it from file, what should I do?
Is it possible to use awk?
$contents = file_get_contents($dir);
$contents = str_replace($line, '', $contents);
file_put_contents($dir, $contents);
Read the lines one by one, and write all but the matching line to another file. Then replace the original file.
this will just look over every line and if it not what you want to delete, it gets pushed to an array that will get written back to the file. see this
$DELETE = "the_line_you_want_to_delete";
$data = file("./foo.txt");
$out = array();
foreach($data as $line) {
if(trim($line) != $DELETE) {
$out[] = $line;
}
}
$fp = fopen("./foo.txt", "w+");
flock($fp, LOCK_EX);
foreach($out as $line) {
fwrite($fp, $line);
}
flock($fp, LOCK_UN);
fclose($fp);
It can be solved without the use of awk:
function remove_line($file, $remove) {
$lines = file($file, FILE_IGNORE_NEW_LINES);
foreach($lines as $key => $line) {
if($line === $remove) unset($lines[$key]);
}
$data = implode(PHP_EOL, $lines);
file_put_contents($file, $data);
}
Another approach is to read the file line by line until you find a match, then truncate the file to that point, and then append the rest of the lines.
This is also good if you're looking for a substring (ID) in a line and want to replace the old line with the a new line.
Code:
$contents = file_get_contents($dir);
$new_contents = "";
if (strpos($contents, $id) !== false) { // if file contains ID
$contents_array = explode(PHP_EOL, $contents);
foreach ($contents_array as &$record) { // for each line
if (strpos($record, $id) !== false) { // if we have found the correct line
continue; // we've found the line to delete - so don't add it to the new contents.
} else {
$new_contents .= $record . "\r"; // not the correct line, so we keep it
}
}
file_put_contents($dir, $new_contents); // save the records to the file
echo json_encode("Successfully updated record!");
}
else {
echo json_encode("failed - user ID ". $id ." doesn't exist!");
}
Example:
input: "123,student"
Old file:
ID,occupation
123,student
124,brick layer
Running the code will change file to:
New file:
ID,occupation
124,brick layer
All answeres here have in common, that they load the complete file into the memory. Here is an implementation of removing one (or more) line(s) without coyping the files content into a variable.
The idea is to iterate over the files lines. If a line should be removed, the lines length is added to the $byte_offset. The next line is then moved $byte_offset bytes "upwards". This is done with all following lines. If all lines are processed, the files last $byte_offset bytes are removed.
I guess that this is faster for bigger files because nothing is copied. And I guess that at some file size the other answers do not work at all while this one should. But I didn't test it.
Usage:
$file = fopen("path/to/file", "a+");
// remove lines 1 and 2 and the line containing only "line"
fremove_line($file, 1, 2, "line");
fclose($file);
The code of the fremove_line() function:
/**
* Remove the `$lines` by either their line number (as an int) or their content
* (without trailing new-lines).
*
* Example:
* ```php
* $file = fopen("path/to/file", "a+"); // must be opened writable
* // remove lines 1 and 2 and the line containing only "line"
* fremove_line($file, 1, 2, "line");
* fclose($file);
* ```
*
* #param resource $file The file resource opened by `fopen()`
* #param int|string ...$lines The one-based line number(s) or the full line
* string(s) to remove, if the line does not exist, it is ignored
*
* #return boolean True on success, false on failure
*/
function fremove_line($file, ..$lines): bool{
// set the pointer to the start of the file
if(!rewind($file)){
return false;
}
// get the stat for the full size to truncate the file later on
$stat = fstat($file);
if(!$stat){
return false;
}
$current_line = 1; // change to 0 for zero-based $lines
$byte_offset = 0;
while(($line = fgets($file)) !== false){
// the bytes of the lines ("number of ASCII chars")
$line_bytes = strlen($line);
if($byte_offset > 0){
// move lines upwards
// go back the `$byte_offset`
fseek($file, -1 * ($byte_offset + $line_bytes), SEEK_CUR);
// move the line upwards, until the `$byte_offset` is reached
if(!fwrite($file, $line)){
return false;
}
// set the file pointer to the current line again, `fwrite()` added `$line_bytes`
// already
fseek($file, $byte_offset, SEEK_CUR);
}
// remove trailing line endings for comparing
$line_content = preg_replace("~[\n\r]+$~", "", $line);
if(in_array($current_line, $lines, true) || in_array($line_content, $lines, true)){
// the `$current_line` should be removed so save to skip the number of bytes
$byte_offset += $line_bytes;
}
// keep track of the current line
$current_line++;
}
// remove the end of the file
return ftruncate($file, $stat["size"] - $byte_offset);
}
Convert text to array, remove first line and reconvert to text
$line=explode("\r\n",$text);
unset($line[0]);
$text=implode("\r\n",$line);
I think the best way to work with files is to act them like strings:
/**
* Removes the first found line inside the given file.
*
* #param string $line The line content to be searched.
* #param string $filePath Path of the file to be edited.
* #param bool $removeOnlyFirstMatch Whether to remove only the first match or
* the whole matches.
* #return bool If any matches found (and removed) or not.
*
* #throw \RuntimeException If the file is empty.
* #throw \RuntimeException When the file cannot be updated.
*/
function removeLineFromFile(
string $line,
string $filePath,
bool $removeOnlyFirstMatch = true
): bool {
// You can wrap it inside a try-catch block
$file = new \SplFileObject($filePath, "r");
// Checks whether the file size is not zero
$fileSize = $file->getSize();
if ($fileSize !== 0) {
// Read the whole file
$fileContent = $file->fread($fileSize);
} else {
// File is empty
throw new \RuntimeException("File '$filePath' is empty");
}
// Free file resources
$file = null;
// Divide file content into its lines
$fileLineByLine = explode(PHP_EOL, $fileContent);
$found = false;
foreach ($fileLineByLine as $lineNumber => $thisLine) {
if ($thisLine === $line) {
$found = true;
unset($fileLineByLine[$lineNumber]);
if ($removeOnlyFirstMatch) {
break;
}
}
}
// We don't need to update file either if the line not found
if (!$found) {
return false;
}
// Join lines together
$newFileContent = implode(PHP_EOL, $fileLineByLine);
// Finally, update the file
$file = new \SplFileObject($filePath, "w");
if ($file->fwrite($newFileContent) !== strlen($newFileContent)) {
throw new \RuntimeException("Could not update the file '$filePath'");
}
return true;
}
Here is a brief description of what is being done: Get the whole file content, split the content into its lines (i.e. as an array), find the match(es) and remove them, join all lines together, and save the result back to the file (only if any changes happened).
Let's now use it:
// $dir is your filename, as you mentioned
removeLineFromFile($line, $dir);
Notes:
You can use fopen() family functions instead of SplFileObject, but I do recommend the object form, as it's exception-based, more robust and more efficient (in this case at least).
It's safe to unset() an element of an array being iterated using foreach (There's a comment here showing it can lead unexpected results, but it's totally wrong: As you can see in the example code, $value is copied (i.e. it's not a reference), and removing an array element does not affect it).
$line should not have new line characters like \n, otherwise, you may perform lots of redundant searches.
Don't use
$fileLineByLine[$lineNumber] = "";
// Or even
$fileLineByLine[$lineNumber] = null;
instead of
unset($fileLineByLine[$key]);
The reason is, the first case doesn't remove the line, it just clears the line (and an unwanted empty line will remain).
Hope it helps.
Like this:
file_put_contents($filename, str_replace($line . "\r\n", "", file_get_contents($filename)));