php foreach in foreach looping - php

I want to extrect all usernames and passwords each from his file and output it nicely.
I wrote a code on my appserv 2.5.1 on my computer but only the last loop gave the username output.
Tested the code on other machines and it worked perfectly.
Dont know what is the problem ...
usernames.txt content :
user1
user2
user3
passwords.txt content :
pass1
pass2
pass3
script content :
$usernames = explode("\n", file_get_contents("usernames.txt"));
$passwords = explode("\n", file_get_contents("passwords.txt"));
foreach( $usernames as $username )
{
foreach( $passwords as $password )
{
echo $username.":".$password."\n";
}
}
output :
:pass1
:pass2
:pass3
:pass1
:pass2
:pass3
user3:pass1
user3:pass2
user3:pass3

for ($i=0;$i<count($usernames) && $i<count($password); $i++) {
echo $usernames[$i].':'.$passwords[$i];
}
But $password[x] must be related to $usernames[x]

There's always those that will say you don't need it (and you often don't) but I tend to use regular expressions whenever I'm parsing these kind of flat files - there's always some quirky character, extra line-break or difference that finds it's way into a text file - be it from transferring servers, restoring backups or simply user-interference. You could also make use of array_combine in this situation if you'd prefer to carrying on using a foreach loop - I know some folks prefer it for readability.
preg_match_all('/\w+/m', file_get_contents('usernames.txt'), $usernames);
preg_match_all('/\w+/m', file_get_contents('passwords.txt'), $passwords);
if(count($usernames[0]) !== count($passwords[0]))
die('Computer says: mismatch!'); // some resemblance of error handling...
$result = array_combine($usernames[0], $passwords[0]);
foreach($result as $name => $pass)
echo "{$name}:{$pass}\n";
demo

After debugging with the post author, I guessed that the problem was with the line return character. Using a \r\n fixed the problem:
$usernames = explode("\n\r", file_get_contents("usernames.txt"));
$passwords = explode("\n\r", file_get_contents("passwords.txt"));
For reference, please note that it is very important not to assume your input data is right. If you see that something is wrong and it points obviously to a mistake you made previously (in that case it is clearly not the foreach function that is buggy, but the array), then you need to swallow your pride and debug your own code. I have been programming PHP for 10 years, and I still have to remember that every single day.

Related

PHP fgets returns an empty string

So I'm making a webshop, well, trying to atleast for a course project using WAMP. But when trying to register new users and in the process checking their password against a list of common ones the use of fgets() returns an empty string.
if(empty(trim($_POST["password"]))){
...
} elseif (!checkPassword($_POST["password"])) {
$password_err = "Password to common.";
echo "<script>alert('Password to common.'); location.href='index.php';</script>";
}
The checkPassword() is where the fault lies.
function checkPassword($passwordtocheck) {
$passwordtocheck = strtolower($passwordtocheck);
$common_passwords = fopen("commonpasswords.txt", "r");
while(!feof($common_passwords)) {
$check_against = fgets($common_passwords);
echo "<script>alert('Checking $passwordtocheck against $check_against.'); location.href='index.php';</script>";
if($check_against == $passwordtocheck) {
fclose($common_passwords);
return false;
}
}
fclose($common_passwords);
return true;
}
Lets say that I input the password 12345678 when registering, then the scripted alert will say "Checking 12345678 against ." and send me back to index.php. So it looks like it doesn't succeed in reading the file at all. The commonpasswords.txt is in the same folder as the rest of the files and with a single password on each row.
And there is no problem opening the file to begin with either, if I do this instead:
$common_passwords = fopen("commonpasswords.txt", "a");
fwrite($common_passwords, "test");
'test' will appear at the bottom of the file under the existing words on its own row without a hitch. And this is where I'm at, would appreciate whatever input people can give!
EDIT; I do understand that this probably breaks a ton of good-practice 'rules' in general and regarding security. But the website is not really supposed to function or look good, it just need to barely work so that we can later try and use different methods of attacking it and the connected database.
If you insist on doing this yourself – which I do not recommend – you can simplify things a lot by using the file() function. This returns an array of every line in the file. Then use array_filter(); it runs a callback on each element of the array where you can check if there's a match with your password. If the callback returns false, the element is removed from the array. After that, if you have any elements left you know there was a match.
function checkPassword($pwd) {
$pwd = strtolower($pwd);
$common = file("commonpasswords.txt", FILE_IGNORE_NEW_LINES);
$results = array_filter($common, function($i) use ($pwd) {return $i == $pwd;});
return count($results) === 0;
}
But really, there are dozens of libraries out there to check password strength. Use one of them.
Or, as pointed out in the comment, even simpler array_search:
function checkPassword($pwd) {
$pwd = strtolower($pwd);
$common = file("commonpasswords.txt", FILE_IGNORE_NEW_LINES);
return array_search($pwd, $common) === false;
}

PHP performant search a text for given usernames

I am currently dealing with a performance issue where I cannot find a way to fix it. I want to search a text for usernames mentioned with the # sign in front. The list of usernames is available as PHP array.
The problem is usernames may contain spaces or other special characters. There is no limitation for it. So I can't find a regex dealing with that.
Currently I am using a function which gets the whole line after the # and checks char by char which usernames could match for this mention, until there is just one username left which totally matches the mention. But for a long text with 5 mentions it takes several seconds (!!!) to finish. for more than 20 mentions the script runs endlessly.
I have some ideas, but I don't know if they may work.
Going through username list (could be >1.000 names or more) and search for all #Username without regex, just string search. I would say this would be far more inefficient.
Checking on writing the usernames with JavaScript if space or punctual sign is inside the username and then surround it with quotation marks. Like #"User Name". Don't like that idea, that looks dirty for the user.
Don't start with one character, but maybe 4. and if no match, go back. So same principle like on sorting algorithms. Divide and Conquer. Could be difficult to implement and will maybe lead to nothing.
How does Facebook or twitter and any other site do this? Are they parsing the text directly while typing and saving the mentioned usernames directly in the stored text of the message?
This is my current function:
$regular_expression_match = '#(?:^|\\s)#(.+?)(?:\n|$)#';
$matches = false;
$offset = 0;
while (preg_match($regular_expression_match, $post_text, $matches, PREG_OFFSET_CAPTURE, $offset))
{
$line = $matches[1][0];
$search_string = substr($line, 0, 1);
$filtered_usernames = array_keys($user_list);
$matched_username = false;
// Loop, make the search string one by one char longer and see if we have still usernames matching
while (count($filtered_usernames) > 1)
{
$filtered_usernames = array_filter($filtered_usernames, function ($username_clean) use ($search_string, &$matched_username) {
$search_string = utf8_clean_string($search_string);
if (strlen($username_clean) == strlen($search_string))
{
if ($username_clean == $search_string)
{
$matched_username = $username_clean;
}
return false;
}
return (substr($username_clean, 0, strlen($search_string)) == $search_string);
});
if ($search_string == $line)
{
// We have reached the end of the line, so stop
break;
}
$search_string = substr($line, 0, strlen($search_string) + 1);
}
// If there is still one in filter, we check if it is matching
$first_username = reset($filtered_usernames);
if (count($filtered_usernames) == 1 && utf8_clean_string(substr($line, 0, strlen($first_username))) == $first_username)
{
$matched_username = $first_username;
}
// We can assume that $matched_username is the longest matching username we have found due to iteration with growing search_string
// So we use it now as the only match (Even if there are maybe shorter usernames matching too. But this is nothing we can solve here,
// This needs to be handled by the user, honestly. There is a autocomplete popup which tells the other, longer fitting name if the user is still typing,
// and if he continues to enter the full name, I think it is okay to choose the longer name as the chosen one.)
if ($matched_username)
{
$startpos = $matches[1][1];
// We need to get the endpos, cause the username is cleaned and the real string might be longer
$full_username = substr($post_text, $startpos, strlen($matched_username));
while (utf8_clean_string($full_username) != $matched_username)
{
$full_username = substr($post_text, $startpos, strlen($full_username) + 1);
}
$length = strlen($full_username);
$user_data = $user_list[$matched_username];
$mentioned[] = array_merge($user_data, array(
'type' => self::MENTION_AT,
'start' => $startpos,
'length' => $length,
));
}
$offset = $matches[0][1] + strlen($search_string);
}
Which way would you go? The problem is the text will be displayed often and parsing it every time will consume a lot of time, but I don't want to heavily modify what the user had entered as text.
I can't find out what's the best way, and even why my function is so time consuming.
A sample text would be:
Okay, #Firstname Lastname, I mention you!
Listen #[TEAM] John, you are a team member.
#Test is a normal name, but #Thât♥ should be tracked too.
And see #Wolfs garden! I just mean the Wolf.
Usernames in that text would be
Firstname Lastname
[TEAM] John
Test
Thât♥
Wolf
So yes, there is clearly nothing I know where a name may end. Only thing is the newline.
I think the main problem is, that you can't distinguish usernames from text and it's a bad idea, to lookup maybe thousands of usernames in a text, also this can lead to further problems, that John is part of [TEAM] John‌ or JohnFoo...
It's needed to separate the usernames from other text. Assuming that you're using UTF-8, could put the usernames inside invisible zero-w space \xE2\x80\x8B and non-joiner \xE2\x80\x8C.
The usernames can now be extracted fast and with little effort and if needed still verified in db.
$txt = "
Okay, #\xE2\x80\x8BFirstname Lastname\xE2\x80\x8C, I mention you!
Listen #\xE2\x80\x8B[TEAM] John\xE2\x80\x8C, you are a team member.
#\xE2\x80\x8BTest\xE2\x80\x8C is a normal name, but
#\xE2\x80\x8BThât?\xE2\x80\x8C should be tracked too.
And see #\xE2\x80\x8BWolfs\xE2\x80\x8C garden! I just mean the Wolf.";
// extract usernames
if(preg_match_all('~#\xE2\x80\x8B\K.*?(?=\xE2\x80\x8C)~s', $txt, $out)){
print_r($out[0]);
}
Array
(
[0] => Firstname Lastname
1 => [TEAM] John
2 => Test
3 => Thât♥
4 => Wolfs
)
echo $txt;
Okay, #​Firstname Lastname, I mention you!
Listen #​[TEAM] John‌, you are a team member.
#​Test‌ is a normal name, but
#​Thât♥‌ should be tracked too.
And see #​Wolfs‌ garden! I just mean the Wolf.
Could use any characters you like and that possibly don't occur elsewhere for separation.
Regex FAQ, Test at eval.in (link will expire soon)

if statement is not working when looping through file

I am trying to loop through a file of blocked IP address.
I seem to be unable to do this even when they should match.
Here is the code that checks.
$blocked_ips = file("/home/block_list.txt");
for ( $i = 0; $i < count($blocked_ips); $i++) {
if ( $_SERVER["REMOTE_ADDR"] == $blocked_ips[$i] ) {
exit;
}
}
And here is the code I use to add the ip address to the file.
if ( $_SESSION["LogInAttempts"] >= "10" ) {
$block_ip = $_SERVER["REMOTE_ADDR"];
$block_ip = $block_ip . "\r\n";
file_put_contents("/home/block_list.txt", $block_ip, FILE_APPEND);
}
And here is a list of ip addresses in the file. (example only)
169.254.51.183
192.168.0.1
192.168.10.84
I'm not to sure what I am doing wrong.
Make sure to account for whitespace when reading from files or receiving user input. trim() is your friend for such cases, though, as Jack noted in the comments, using FILE_IGNORE_NEW_LINES in file() will solve that for you. As the documentation says:
FILE_IGNORE_NEW_LINES
   Do not add newline at the end of each array element
Furthermore, you might find foreach a little more comfortable than your for loop, but that's a question of personal taste, ultimately. Here's a variant with the additional parameter in file() and with foreach:
$blocked_ips = file("/home/block_list.txt", FILE_IGNORE_NEW_LINES);
foreach($blocked_ips as $ip) {
if ($_SERVER["REMOTE_ADDR"] == $ip) {
exit;
}
}
Note also that you shouldn't have quotes around that 10 in your code below, though with PHP's type juggling feature your code will work, of course. However, it's always a good idea to be as precise as possible. If for nothing else, for clarity: it will make the intention of your code easier to understand – also for you when you'll be looking at your code after a long time.
You can also append a string to REMOTE_ADDR without a problem (again, a question of personal taste). Finally, note that getting the IP address of a user is not as straightforward as simply calling $_SERVER['REMOTE_ADDR'] if we want to be very correct, but for most purposes it will suffice. Nevertheless, there's a very interesting discussion about getting the IP address of a user in PHP on StackOverflow I recommend everyone to read.
if ($_SESSION["LogInAttempts"] >= 10) {
$block_ip = $_SERVER["REMOTE_ADDR"] . "\r\n";
file_put_contents("/home/block_list.txt", $block_ip, FILE_APPEND);
}
You don't need a loop at all. You can do that with in_array().
<?php
// Read in blocked ips list.
$blockedIps = file('/home/block_list.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// Check if ip is blocked.
if(in_array($_SERVER['REMOTE_ADDR'], $blockedIps)) {
exit;
}

active users script, user count not working properly

i have written a script to output active users on my site....
part of this is counting unique ips in the log, as the array i use to split the lines / data unloads active users from the array list after 5 minutes.....
however the "3 online users now" count is not working properly.....
it kinda works.... when someone views a page, it says there is 1 user
lets say i view a page.... 1 visitor
then user 2 views a page .... 2 visitors
but if i then view another page, it displays 3 users.....
even though i use the same ip for both page requests....
here is my code
$data = file_get_contents('active-log.txt');
$break = "\r\n";
$lines = explode($break, $data);
foreach ($lines as $key => $value) {
$active_ip[] = $lines[$key][1];
}
$active_ip_count = array_unique($active_ip);
$active_users = (count($active_ip_count));
$active_users is the variable i use to display how many unique visitors are online at one time
thanks in advance for anyone that can help me thanks
....
EDIT
.....
here is a sample of the log saved....
1328469393|157.55.39.84|g-book
1328469398|157.55.39.84|downloads
1328469400|157.55.39.84|badger
1328469404|157.55.39.84|home
1328469408|157.55.39.84|boneyard-dogs
the first part is timestamp (to remove the line from array, if timestamp is older than 5 minutes... this works fine)
the second part is ip
third part is page viewed and the new line is created with \r\n
$lines[$key][1] is the variable for each ip in each line....
as im not exacly a php expert, when writing scripts, i test them heavily while developing, and each time i add a new line of script , i echo the data, to check its what i hope, to make sure i make no mistakes......
here is a section of code that i didnt paste as i didnt think it was necessary....
foreach($lines as $k=>$v) {
$lines[$k] = explode("|", $v); }
// echo $lines[0][0]; // now this is first array of first line .... line 2 / url would be - $lines[1][2]
this is in my code, straight after the line "$lines = explode($break, $data);" in my code
Have you looked at the output of var_dump($active_ip) after the foreach loop ends? With this setup, I'm pretty sure $lines[$key][1] is simply the first character of the line you're dealing with, so that's not going to work well for a number of reasons. What does active-log.txt look like? Does it only contain IP addresses or user names, too? If it only contains IP addresses, consider using something like this:
<?php
$data = file('active-log.txt');
$no_duplicate_ips = array_unique($data);
$active_users = (count($no_duplicate_ips));
?>
Edit:
Right, that makes sense then. Try this:
$data = file_get_contents('active-log.txt');
$break = "\r\n"; //Note that it's generally a good idea to use PHP_EOL throughout your code, for greater cross-platform compatibility
$lines = explode($break, $data);
$exploded_data = array();
$active_ips = array();
foreach($lines as $v) {
$exploded_data = explode("|", $v);
//Now check whether the timestamp is not > 5 min
if(TIMESTAMP CHECK HERE) {
//OK, this one is not too old
$active_ips[] = $exploded_data[1];
}
}
$active_ip_count = array_unique($active_ip);
$active_users = (count($active_ip_count));

validating a string in php if only substring is true

How to validate a substring is true in PHP for example if user1 is in the string it should be true?
textfile:
user1 : pass1
user2 : pass2
user3 : pass3
if(in_array($_SERVER['user1'] . "\r\n", $textfile)){ //not the way want this to be true
printf("Ok user1 is in this row somewhere");
}
I would advice against this kind of authentication system as is prone to errors or abuse. Use other system like ACL or database user/password hash check.
As those above have said, this is not a good approach as far as user authentication goes. If you want something basic, look at using HTTP Authentication or something at least.
That said, you can do what you have asked using PHP's file function, e.g.
function validUser($file, $user, $pass) {
// Check file exists
if (!is_file($file)) {
return FALSE;
}
// Read file
$lines = file($file);
if ($lines === FALSE) {
return FALSE;
}
// Go over the lines and check each one
foreach ($lines as $line) {
list($fuser, $fpass) = explode(':', trim($line));
if ($user == $fuser && $pass == $fpass) {
return TRUE;
}
}
// No user found
return FALSE;
}
if (validUser('passwords.txt', 'foo', 'bar')) {
echo 'The user was found';
}
Note that this assumes each line is of the form "username:password" with nothing else; you may need to adjust exactly how you match your lines depending on your format. An example file which would be validated by this would have a line such as
foo:bar
If you are using this for authentication; for the sake of your users consider a different (more secure) approach. By the way the reply to the OP is correct that just about nothing in that PHP code would work as appears to be intended.
However, if the idea is to use the value of an array by key $arr['key'] to look up configuration settings that need not be protected (for the world to see, basically) you can use the parse_ini_file() and friends. Again: this is not a good idea for truly sensitive data.
EDIT: Also, it is probably a good idea to use the PHP_EOL constant for end-of-line characters rather than assuming "\r\n".

Categories