I want to retrieve email from gmails' imap server but the problem is that the responses from the server are multiple lines long (as demonstrated here) and fgets only retrieves one line.
I've tried using fgets, fread, socket_read but none of them work so either i'm using the wrong method or using the methods incorrectly. I also tried this tutorial but it didn't work either. I would appreciate if someone could help me with this.
Thanks and i'm really sorry if this is an amateur question.
Code:
<?php
$stuff = fsockopen('ssl://imap.gmail.com',993);
$reply = fgets($stuff,4096);
echo 'connection: '.$reply.'<br/>';
$request = fputs($stuff,"a1 LOGIN MyUserName Password\r\n");
$receive = socket_read($stuff, 4096);
echo 'login: '.$receive.'<br/>';
$request = fputs($stuff,"a2 EXAMINE INBOX\r\n");
$reply = '';
while(!feof($stuff))
$reply .= fread($stuff, 4096);
echo $reply;
/*
$request = fputs($stuff,'a3 FETCH 1 BODY[]\r\n');
$reply = fgets($stuff);
echo $reply;
*/
?>
Max's answer below works. This is my implementation of it.
private function Response($instructionNumber)
{
$end_of_response = false;
while (!$end_of_response)
{
$line = fgets($this->connection,self::responseSize);
$response .= $line.'<br/>';
if(preg_match("/$instructionNumber (OK|NO|BAD)/", $response,$responseCode))
$end_of_response = true;
}
return array('code' => $responseCode[1],
'response'=>$response);
}
Generally, you know to stop reading when you get the OK/BAD/NO response for the tag you sent. If you send a1 LOGIN ... you stop when you get a1 OK/BAD/NO ....
It's been a while since I wrote PHP, and I don't know that much about IMAP, but if it's anything like NNTP, your code would look a bit like this (wrote it in the SO editor, might be bugged) :
$buffer = '';
function read_line($socket) {
global $buffer;
while (strpos($buffer, "\n") === false)
$buffer .= fread($socket, 1024);
$lineEnd = strpos($buffer, "\n");
$line = substr($buffer, 0, $lineEnd-1);
$buffer = substr($buffer, $lineEnd);
return $line;
}
function send_line($socket, $line) {
fwrite($socket, $line);
}
$socket = fsockopen('ssl://imap.gmail.com',993);
$welcome = read_line($socket);
send_line("a1 LOGIN MyUserName Password\r\n");
$reply = read_line($socket);
send_line("a2 EXAMINE INBOX\r\n");
while (($reply = trim(read_line($socket))) != '.') {
echo $reply.PHP_EOL;
}
echo "Done";
The basic concepts are :
Always buffer all incoming data. PHP doesn't handle lines very well, so do the splitting yourself.
Don't randomly read everything, but know what to expect. You expect one welcome line, LOGIN has one reply, and EXAMINE INBOX keeps outputting data until there's a single dot, so immediately stop reading once you see that.
You'll most likely want a simple function to take care of the reading. You could even write another function to make it easy:
function read_block($socket) {
$block = '';
while ('.' != trim($reply = read_line($socket)) {
$block .= $reply;
}
return $block;
}
Related
I need to search gmail messages (google apps for work) via imap/php. However, imap_search criterias are not enough to catch the messages in question.
The code I use looks like this:
$imap = imap_open("{imap.gmail.com:993/imap/ssl}Label1/label2", $user_email, $user_passwd);
$msg_list = imap_search($imap, 'TEXT "Delivered-To: username+label#gmail.com"');
imap_close($imap);
The imap_search call didn't return anything.
I did some research, it seems I can filter messages based on "Delivered-To" header field via the gmail search syntax X-GM-RAW, but I just couldn't achieve this, I tried all these calls (and more):
$msg_list = imap_search($imap, 'UID SEARCH X-GM-RAW "deliveredto:username+label#gmail.com"');
$msg_list = imap_search($imap, 'SEARCH X-GM-RAW "deliveredto:username+label#gmail.com"');
$msg_list = imap_search($imap, 'X-GM-RAW "deliveredto:username+label#gmail.com"');
But it didn't work, anyone knows what's wrong with my code?
OK, either I don't know how to ask questions, SOers are busy, or I'm asking difficult questions.
Anyway, now I know the imap_* functions built-into PHP don't handle direct IMAP commands, so I had to either use the zend framework (too heavy for my needs), or directly connect to imap via sockets.
I chose the second option, the code is as follow (code stolen from here and adapted for my own needs), in case someone needs it:
<?php
// Open a socket
if (!($fp = fsockopen('ssl://imap.gmail.com', 993, $errno, $errstr, 15))) die("Could not connect to host");
// Set timout to 1 second
if (!stream_set_timeout($fp, 1)) die("Could not set timeout");
// Fetch first line of response and echo it
echo fgets($fp);
// =========================================
fwrite($fp, "0001 LOGIN user.name#gmail.com YOUR_PASSWORD_HERE_WITHOUT_QUOTES\r\n");
// ie. fwrite($fp, "0001 LOGIN super.dude#gmail.com pass123\r\n");
// Keep fetching lines until response code is correct
while ($line = trim(fgets($fp)))
{
echo "Line = [$line]\n";
$line = preg_split('/\s+/', $line, 0, PREG_SPLIT_NO_EMPTY);
$code = $line[0];
if (strtoupper($code) == '0001') {
break;
}
}
// =========================================
fwrite($fp, "0002 SELECT Inbox\r\n");
// Keep fetching lines until response code is correct
while ($line = trim(fgets($fp)))
{
echo "Line = [$line]\n";
$line = preg_split('/\s+/', $line, 0, PREG_SPLIT_NO_EMPTY);
$code = $line[0];
if (strtoupper($code) == '0002') {
break;
}
}
// =========================================
fwrite($fp, "0003 SEARCH X-GM-RAW \"deliveredto:user.name+someLabel#gmail.com\"\r\n");
// Keep fetching lines until response code is correct
while ($line = fgets($fp))
{
echo "Line = [$line]\n";
$line = preg_split('/\s+/', $line, 0, PREG_SPLIT_NO_EMPTY);
$code = $line[0];
if (strtoupper($code) == '0003') {
break;
}
}
fclose($fp);
echo "I've finished!";
?>
Voila! Just copy and paste and now you have access to the gmail syntax right from PHP! (Hey vote if you like :p)
This what I'm trying to do:
$output = '';
$stream = popen("some-long-running-command 2>&1", 'r');
while (!feof($stream)) {
$meta = stream_get_meta_data($stream);
if ($meta['unread_bytes'] > 0) {
$line = fgets($stream);
$output .= $line;
}
echo ".";
}
$code = pclose($stream);
Looks like this code is not correct, since it gets stuck at the call to stream_get_meta_data(). What is the right way to check whether the stream has some data to read? The whole point here is to avoid locking at fgets().
The correct way to do this is with stream_select():
$stream = popen("some-long-running-command 2>&1", 'r');
while (!feof($stream)) {
$r = array($stream);
$w = $e = NULL;
if (stream_select($r, $w, $e, 1)) {
// there is data to be read
}
}
$code = pclose($stream);
One thing to note though (I'm not sure about this) is that it may be the feof() check that is "blocking" - it may be that the loop never ends because the child process does not close its STDOUT descriptor.
I am using sockets in PHP to create a simple command line based chat. It works ok, but there is one main issue that is making it almost unusable. When there are multiple people in the chat and one person is typing a message and the other sends a message the person typing the message gets the message received appended to what they are typing. Is there anyway around this? I'm using stdin and stream select. Here is a piece from the client:
$uin = fopen("php://stdin", "r");
while (true) {
$r = array($socket, $uin);
$w = NULL;
$e = NULL;
if (0 < stream_select($r, $w, $e, 0)) {
foreach ($r as $i => $fd) {
if ($fd == $uin) {
$text = (fgets($uin));
fwrite($socket, $text);
} else {
$text = fgets($socket);
print $text;
}
}
}
}
All help is appreciated! Thanks!
The code outputs a message to stdout everytime a full string is waiting in $socket.
The only way to get around that is to put the text to a variable ($outtext) in stead of printing it. Then you can display it whenever you are ready to read it, such as before writing to the outgoing socket...
$uin = fopen("php://stdin", "r");
while (true) {
$r = array($socket, $uin);
$w = NULL;
$e = NULL;
$outtext = '';
if (0 < stream_select($r, $w, $e, 0)) {
foreach ($r as $i => $fd) {
if ($fd == $uin) {
$text = (fgets($uin));
print $outtext;
$outtext = '';
fwrite($socket, $text);
} else {
$text = fgets($socket);
$outtext .= $text;
}
}
}
}
The downside being that it will only display incoming text when you press enter. The only way around that would be to use something other than fgets().
I'm assuming this is just an experiment - event driven programming with Node.js or similar would be much better for this type of thing.
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.
I need to retrieve a small amount of data from a very large remote XML file that I access via http. I only need a portion of the file at the beginning, but the files I am accessing can often be so large that downloading them all will cause a timeout. It seems like it should be possible with fsockopen to pull only as much as needed before closing the connection, but nothing I have tried has worked.
Below is a simplified version of what I have been trying. Can anyone tell me what I need to do differently?
<?php
$k = 0;
function socketopen($funcsite, $funcheader){
$fp = fsockopen ($funcsite, 80, $errno, $errstr, 5);
$buffer = NULL;
if ($fp) {
fwrite($fp, "GET " . $funcheader . " HTTP/1.0\r\nHost: " . $funcsite. "\r\n\r\n");
while (!feof($fp)) {
$buffer = fgets($fp, 4096);
echo $buffer;
if($k == 200){
break;
}
$k++;
}
fclose ($fp);
} else {
print "No Response:";
}
return ( html_entity_decode($buffer));
}
$site = "www.remotesite.com";
$header = "/bigdatafile.xml";
$data = socketopen($site, $header);
?>
This works fine, but always opens and downloads the entire remote file. (I actually use a different conditional than the if($k = x), but that shouldn't matter).
Any help greatly appreciated. -Jim
Any reason not to use file_get_contents() instead?
$buffer = html_entity_decode(file_get_contents('http://www.remotesite.com/bigdatafile.xml', 0, null, $offsetBytes, $maxlenBytes));
You just need to specify $offsetBytes and $maxlenBytes.
Try this:
set_time_limit(0);
echo $buffer = html_entity_decode(file_get_contents('http://www.remotesite.com/bigdatafile.xml', 0, null, 1024, 4096));
with this code you could download the entire rss
if (!$xml = simplexml_load_file("http://remotesite.com/bigrss.rss))
{
throw new RuntimeException('Unable to load or parse feed');
}
else
{
file_put_contents($xml,'mybigrss.rss');
}
but if you want to get just some parts then do the following;
$limit = 512000; // set here a limit
$sourceData = fread($s_handle,$limit);
// your code ect..
Or with eof
$source='';
while (!feof($s_handle))
$source.=fread($s_handle,1024); // set limit