Posts in Category: PHP

Google ClientLogin PHP Examples

The latest version of Shashin (v2.6), which is my plugin for using Picasa with WordPress, adds support for unlisted Picasa albums. Supporting unlisted albums is possible because 1. Picasa does not actually require any authentication to view the photos in unlisted albums if you know their URLs (they simply don’t show you links to view unlisted albums at Picasa, or view their RSS feeds, unless you’ve been authorized), and 2. the Google ClientLogin API is available.

The ClientLogin API documentation has really good prose, but precious little code. There is a freely available PHP library (fron Zend) for interacting with the Google APIs, but I didn’t want to bloat Shashin by several megabytes just so I could use the library’s authentication method (and there are enough interdependencies in the library that I couldn’t easily extract the authentication components).

Shashin uses ClientLogin instead of AuthSub because Shashin has an option for automatic scheduled synchronizing of Picasa albums, so the username and password needs to be saved. I can’t expect Shashin users to type in their Picasa username and password every time Shashin syncs its album data with Picasa.

Below I’ll explain how authenticate to Google services using ClientLogin, and then show a couple examples of interactions with Google services.

Making an HTML Table Using Arrays of Column Data

Most of the time, if you need to create an HTML table to display data pulled from a database, it’s a fairly straightforward task: the data is organized in rows, and since HTML tables are generated in rows, it’s easy to loop through the rows of data and display them in succession. But sometimes you can have a situation where you want to display the data as columns, which is not something you can easily do in HTML. Here’s an example of how to approach this challenge.

Displayed below is an array of data from a project I’ve been working on. I’ll explain the color coding in a minute. The first element is a sub array of laboratory sample IDs, and the subsequent elements are subarrays indicating which laboratory tests should be run on which samples. (I’ll spare you an explanation of the database structure behind all this, but suffice it to say that pulling out the data with this organization was the most efficient solution).


Array[4] (
    0 => Array[6] (
        0 => 'Sample ID',
        1 => 193,
        2 => 194,
        3 => 195,
        4 => 196,
        5 => 197,
    ),
    1 => Array[4] (
        0 => 'Phospholipids',
        1 => 'Y',
        3 => 'Y',
        5 => 'Y',
    ),
    2 => Array[6] (
        0 => 'Apolipoprotein A-I',
        1 => 'Y',
        2 => 'Y',
        3 => 'Y',
        4 => 'Y',
        5 => 'Y',
    ),
    3 => Array[4] (
        0 => 'Gastrin',
        2 => 'Y',
        3 => 'Y',
        4 => 'Y',
    ),
)

How I ultimately what to display the data is like this:

Sample ID Phospholipids Apolipoprotein A-I Gastrin
193 Y Y  
194   Y Y
195 Y Y Y
196   Y Y
197 Y Y  

To get from here to there, you need to visualize how you want to arrange the data elements. If you use the color coding I assigned to the array indexes above, you can picture the data in rows, like this:

0,0    1,0    2,0    3,0

0,1    1,1    2,1    3,1

etc.

To generate a display of the data in this manner, you need to loop through the arrays in an inside-out manner. You loop through the subarrays to set the rows, and then have a nested loop through the parent array to set the cells. Here’s the PHP code for it:


$content = "<table>\n";

// This loop controls the table rows
// $cols is the name of the parent array
for ($inner=0; $inner < count($cols[0]); $inner++) {
    $content .= "<tr>";

    // This loop generates the table cells
    for ($outer=0; $outer < count($cols); $outer++) {
        // this assumes you want the first row to be a header row
        $tag = ($inner == 0) ? "th" : "td";  
        $content .= "<$tag>{$cols[$outer][$inner]}</$tag>";  
    }

    $content .= "</tr>\n";
}

$content .= "</table>\n";

// then display or return $content...

The first subarray contains the Sample IDs, so I used it to set the maximum count for the outer loop that controls the HTML table rows (because I can be confident there will never be data beyond the last sample). If you don’t have an analogous subarray, you’ll need to check to see which subarray is longest, and use that to control the count.

Generating HTML Tables with a Variable Number of Columns and Rows

Often when you’re generating an HTML table on the fly, you need to be able to display an arbitrary number of columns and rows. This typically comes up when displaying data from a database, where you can get any number of records (rows) back. Fortunately handling a variable number of rows is easy in HTML, since all you have to do is keep adding table row tags until you run out of records. The harder, and less common, case is handling a variable number of columns. An example of where this situation comes up is my Shashin plugin, as Shashin users can decide how many columns they want in the display of their photos. Another example is a form where you need multiple rows and columns for neatly laying out a variable number of checkboxes.

I’ve seen a number of strange and tortured solutions to this problem over the years, so I thought I’d share my solution, which is really simple (I’m sure many others have figured this out as well, but I haven’t seen it posted anywhere). What’s often missing in other solutions I’ve seen is properly closing the final table row when the number of cells in it is less than the preceding rows. Here’s some generic PHP code for it:

// desired number of columns - this can be any number
$cols = 3;

$output = "<table>\n";

$cell_count = 1;

for ($i = 0; $i < count($your_array); $i++) {
    if ($cell_count == 1) {
        $output .= "<tr>\n";
    }

    $output .= "<td>your cell content</td>\n";
    $cell_count++;

    // end the row if we've generated the expected number of columns
    // or if we're at the end of the array
    if ($cell_count > $cols || $i == (count($your_array) - 1)) {
        $output .= "</tr>\n";
        $cell_count = 1;
    }
}

$output .= "</table>\n";

And just for fun here’s the same thing in Smarty:

<table>
<{assign var="cell_count" value=1}>
<{foreach key=key item=item from=$your_array name=yourloop}>
    <{if $cell_count == 1}>
        <tr>
    <{/if}>
    <td>your cell content</td>
    <{math assign="cell_count" equation="$cell_count + 1"}>

    <{if $cell_count > 3 || $smarty.foreach.yourloop.last}>
        </tr>
        <{assign var="cell_count" value=1}>
    <{/if}>
<{/foreach}>
</table>

Managing Objects and Database Connections in PHP Sessions

If you’re not familiar with PHP sessions and how to use session variables, getting up to speed isn’t easy. What makes it difficult to learn is that it’s hard to make sense of the online resources. That includes php.net, which has a ton of little pink and yellow boxes on its pages about sessions, with caveats about important changes between PHP versions. The “right way” to use sessions has changed with successive releases of PHP, and many of the old ways are now either more trouble than they’re worth, or simply may not work at all anymore. Throw in the changes with PHP classes (if you want to store objects in sessions), and the multiple possible ways your server can be configured for handling sessions, and it gets even more confusing. So any tutorial you read that’s more than a couple years old may lead you astray.

I’m not going to try covering all the possible variations, but here’s what works for PHP 4.3.9 with a default session configuration (see the Runtime Configuration section of that page if you want to see the details), using MySQL and Apache. I’m fairly certain this all works in PHP 5 as well, but I haven’t tested it.

  • session_start: you need to call session_start() on every page (you can save yourself from repetitive coding by putting this and other page startup code in an include file). Note that by default it stays with the session id that’s automatically set when the user first starts his session – you don’t need to pass in the session id (the php.net documentation could be clearer on that point).
  • Requiring your class files: for any objects stored in session variables that you use in a page, you will need to call require_once() on their class files before your call to session_start(). This is necessary for PHP to know how to map the data in the session variable to the class (another good candidate for reuse in an include file).
  • session_register: you don’t need to use session_register() anymore. Just use the $_SESSION array to store your variables. I found a lot of online discussions from a few years ago about session_register being essential when putting objects in sessions – that doesn’t apply anymore.
  • serialize: if you just want to store your objects in sessions (not in files or database tables), you don’t need to serialize() them yourself, and you don’t have to worry about losing the object type or access to its methods.
  • mysql_connect: at first I tried putting a call to mysql_connect() in a startSession method of a database class I created, thinking I’d only need to call it once for the user’s session. That doesn’t work: the connection is lost after the http response for the page is complete. Trying to store the connection in a session variable does not magically persist it for the user. mysql_pconnect() is not the answer either, for reasons outlined in the php.net Persistent Databaase Connection page. The answer is to simply make connections as needed for each page – old connections will be reused if they’re available, so this doesn’t necessarily lead to an unnecessary proliferation of connections. You can even call mysql_connect() repeatedly on a page and it will by default re-use the connection that was initially opened on the page. This is nice if, like me, you’ve written a database class and you have a generic query method in it: you can call mysql_connect() in your query method, and not worry about how many times it’s being called by a particular page.

I should point out all of the foregoing is for garden variety purposes: managing connections for high traffic sites, security, scalability, and dealing with users who don’t accept cookies, are all beyond the scope of this post.

So, in my code I have no calls to session_register() or serialize() (as they’re not needed for storing objects in session variables), and in my database class’s query method, I call mysql_connect(). The main things to remember are requiring your class files before calling session_start(), and doing so on every page.

Using PHP’s Program Execution Functions for SFTP

If you need to make use of an external program from within a PHP script, then this essay is for you. My example script is for managing an sftp connection (using OpenSSH), but the principals can be applied to any interaction that requires communication between your script and an external process.

One of the Penn Medical School’s business partners recently stopped allowing ftp connections to their servers for retrieving data files. They required us to switch to sftp (secure ftp). Those providing services for transferring sensitive data files over the internet have been steadily moving from ftp to sftp over the past couple of years, and from what I can see, the pace is accelerating. This poses a programming challenge if you have scripts that automate your ftp needs, as they’ll need to be re-written for sftp. This is not a trivial undertaking, especially if you’re programming in PHP. You can’t just swap out your PHP ftp function calls with sftp equivalents. Actually, you can, but you probably don’t want to, as you would have to upgrade to PHP 5 (adoption of which has been very slow across the PHP community) and you would have to install the PECL/ssh2 library, which – as noted on php.net – currently has no stable version.

So we had to roll our own sftp solution, which required using PHP’s program execution functions. The php.net documentation is good on this topic, but much of it is fully comprehensible only if you already know what you’re doing (this isn’t a criticism – it’s a documentation site after all, not a tutorial site). This annotated sample script will help you get started if you’re new to PHP’s program execution functions.

#!/usr/local/bin/php
<?php

$keyPath = 'path/to/your/ssh_key';
$login = 'your_username';
$server = 'your_sftp_server';
$connectionString = "Connecting to $server...\n";

$childPipes = array(
    0 => array("pipe", "r"), // stdin is a pipe that the child will read from
    1 => array("pipe", "w"), // stdout is a pipe that the child will write to
    2 => array("pipe", "w"), // stderr is a pipe that the child will write to
);

# turning off password authentication will avoid getting a password prompt if
# the key fails for any reason
$connection = proc_open(
    "sftp -oPasswordAuthentication=no -oIdentityFile={$keyPath} {$login}@{$server}",
    $childPipes, $parentPipes);

if ($connection === FALSE) {
    print "Cannot connect to $server.\n";
    exit;
}

PHP’s proc_open is a fork by another name. The $childPipes array is for setting up the communication channels from the child process perspective, and proc_open will set $parentPipes to a corresponding set of communication channels from the parent process perspective. Looking at the definition of $childPipes, the logic may seem backwards at first, but it’s not. For example, the parent process will write to the child’s stdin (element 0), which means the child process is reading that channel.

In the user contributed notes on the php.net proc_open page, most folks write out stderr to a file. But for our sftp script we need to see what’s coming through on stderr, so we’re not directing it to a file.

For establishing the connection, we turn off password authentication, which means we won’t get a password prompt if the key authentication fails. This is important, since the script cannot see or respond to such a prompt (the prompt goes directly to the terminal, so you can’t see it on stdin or stdout; you could see it if you want to do TTY buffering, but let’s not go there…).

# The "connecting..." message is written to stderr. Make sure there's nothing
# besides that in stderr before continuing.
$error = readError($parentPipes, TRUE);
sleep(3);
$error .= readError($parentPipes);

if ($error != $connectionString) {
    fclose($parentPipes[0]);
    fclose($parentPipes[1]);
    fclose($parentPipes[2]);
    $closeStatus = proc_close($connection);
    print $error;
    print "proc_close return value: $closeStatus\n";
    exit;
}

I don’t know if this is typical, but the sftp server we’re connecting to returns the “connecting…” welcome message on stderr (we’re reading stderr with a custom function named readError, which we’ll get to below). Having this message on stderr is problematic, since it’s not really an error message. An actual connection error, such as having a bad key, will come through on stderr after the “connecting…” message. This means we first look for the “connecting…” string (the TRUE argument to readError turns blocking on, so we’ll wait for it to appear – more on this below in the readError function), and then we have no choice but to sleep for a few seconds, to see if anything else comes through on stderr. And finally, to see if there was anything in stderr besides the “connecting…” message, we have no choice but to analyze the string 🙁 . This is an ugly solution, but dealing with stderr is difficult, since you never know when an error may or may not appear.

If we detect an error, we close the pipes before closing the connection. This is important for avoiding the possibility of a deadlock.

# gets us past the first "sftp>" prompt
$output = readOut($parentPipes);

After logging in, we’ll get an “sftp>” prompt on stdout. We’ll read from stdout to get past this prompt, using the custom function readOut (which is defined below).

# Get the directory listing and print it
writeIn($parentPipes, "ls -l");
$output .= readOut($parentPipes);
$error = readError($parentPipes);

if (strlen($error)) {
    fclose($parentPipes[0]);
    fclose($parentPipes[1]);
    fclose($parentPipes[2]);
    $closeStatus = proc_close($connection);
    print $error;
    print "proc_close return value: $closeStatus\n";
    exit;
}

print $output;

# close the sftp connection
writeIn($parentPipes, "quit");
fclose($parentPipes[0]);
fclose($parentPipes[1]);
fclose($parentPipes[2]);
$closeStatus = proc_close($connection);

if ($closeStatus != 0) {
    print "proc_close return value: $closeStatus\n";
}

This code just demonstrates getting a directory listing (using the custom function writeIn), printing it, and then closing the connection. You can use this as a template for any sftp commands you want to run.

function readOut($pipes, $end = 'sftp> ', $length = 1024) {
    stream_set_blocking($pipes[1], FALSE);

    while (!feof($pipes[1])) {
        $buffer = fgets($pipes[1], $length);
        $returnValue .= $buffer;

        if (substr_count($buffer, $end) > 0) {
            $pipes[1] = "" ;
            break;
        }
    }

    return $returnValue;
}

readOut loops over the stdout pipe until it sees an “sftp>” prompt, which is how we know that the server has finished writing to stdout. Note that we’ve turned off stream_set_blocking. This lets us define our own controls for reading from the stdout stream. In this case, we want readOut to return when the server has finished responding to a command. The best marker for that is the appearance of the “sftp>” after it finishes processing a command, so we set the while loop to break when it sees the prompt.

function readError($pipes, $blocking = FALSE, $length = 1024) {
    stream_set_blocking($pipes[2], $blocking);

    while (!feof($pipes[2])) {
        $buffer = fgets($pipes[2], $length);
        $returnValue .= $buffer;

        if ((!strlen($buffer) && $blocking === FALSE)
          || ($blocking === TRUE && substr_count($buffer, "\n") > 0)) {
            $pipes[2] = "" ;
            break;
        }
    }

    return $returnValue;
}

function writeIn($pipes, $string) {
    fwrite($pipes[0], $string . "\n");
}
?>

Reading from stderr is more complicated than reading from stdout, because 1. there is no equivalent to the “sftp>” prompt to let us know when the server is done writing to stdout, and 2. at any given time, there may or may not be an error. For most places in the script, we solve this problem by:

  1. Calling readError after calling readOut. This is based on the supposition – which has proved reliable – that the server will finish writing to stderr by the time it has finished writing to stdout.
  2. Setting stream_set_blocking to false. If we set it to true, the script would wait indefinitely for something to appear on stderr, and most of the time there will be nothing there.

The one situation when this approach doesn’t work is when we first log in, since the server writes to stderr before writing to stdout (as described above, it sends that “connecting…” message on stderr). In this case we turn blocking on, since we know the message is coming.

So far this script has been used with only one sftp server, so you may need to make some adjustments to make it work with your server (particularly with how it reads stderr when logging in). Also, I’d be interested in hearing from anyone who has a more elegant solution to handling the initial connection.

Stepping back from the specifics of sftp, the key thing to take away from this is that you will need to acquire a detailed knowledge of the behaviors of your external process so that your script can interact with it reliably. In particular, you need to test your handling of all the different kinds of errors the external process might throw at your script.

On Newsstands Now…

My “Solving the Unicode Puzzle” article was just published in the May 2005 issue of php|architect. Although the magazine is subscription only, they chose my article as this month’s free sample. Aside from introducing a few typos, it looks like they didn’t do any editing, so the published version is almost exactly the same as my original.

Article for PHP Architect

Remember that long post on UTF-8 from a few weeks ago – the one that gave you a sudden urge to take nap? I ran it by the editors of PHP Architect, and they’ve commissioned an article. I’m in the process of pumping it up to the required 4,000 words. I’ll be sending it off to them in a couple days. Commissioning the article only obliges them to pay me a small (very small) sum for it – it doesn’t mean they’ll necessarily publish it. But hopefully they will! I’ll let you know as soon as I find out – probably in a few weeks.

Converting Web Applications to UTF-8

UPDATE: I expanded this to a full length article, which was published in the May 2005 issue of php|architect. They had it available for several years as a free download, but it’s no longer available there, so you can download it from me as a PDF. My apologies for not responding to earlier comments – I had a newborn baby at the time.

An Overview of UTF-8 in PHP, Smarty, Oracle, and Apache, with data exports to PDF, RTF, email, and text

Here at the Penn Med School we recently switched our web and database applications from Western/ISO encoding to Unicode/UTF-8. We did this so we can provide better support for international character sets (Greek, Japanese, etc.). As sometimes happens with projects that involve computers, it grew into a big, hairy beast that was way beyond anything we initially anticipated. I was partly responsible for managing the transition, and since I found no comprehensive guide to help us through it, I thought I’d write one now that we’re done. We’re using two-thirds of the open source PHP-Apache-MySQL trinity, with Oracle instead of MySQL. Even if you have a different mix of applications, the concepts I’ll describe are probably applicable to your situation, even if the semantics are different.

Getting Started

First, if you need some orientation in understanding character sets, start with The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!). It’s actually quite readable, even if you’re not a techie.

Second, you need to read the Oracle document An Overview on Globalizing Oracle PHP Applications. It’s an excellent starting point, but unfortunately it doesn’t always explain the reasons behind its recommendations, which means you’ll get stuck if things don’t happen to work after you follow their instructions. I’ll try to fill those gaps here.

Persuading Apache and Oracle to talk to each other in UTF-8

PHP web applications are run under the Apache web server, which itself is running in a user account (assuming you’re in a Unix environment). So the first step is to set the environment of that account correctly, so it will know how to “speak” UTF-8 to Oracle. You do this by setting the NLS_LANG environment variable in the Apache configuration. The Oracle Overview document says to set it to .AL32UTF8, but doesn’t explain why. So when this didn’t do the trick for me, I had to do some more research. I found the Oracle Character Set descriptions, and found that .AL32UTF8 corresponds to Unicode 3.1. After talking with our DBA I learned that our Oracle database is set to Unicode 3.0, which meant I needed to set NLS_LANG=.UTF8 (we ultimately switched to .AL32UTF8, since it is Oracle’s recommended standard). The key point here is that NLS_LANG must exactly match the character set you’re using in Oracle.

Serving your web pages to users in UTF-8

There are a few different aspects to this:

  1. If you want all the documents on your server to default to UTF-8, then set the AddDefaultCharset directive in the Apache configuration to UTF-8. You should do either #2 or #3 below in addition to this (see the Apache documentation for the reason).
  2. If you want all your PHP documents served in UTF-8, but not necessarily other document types, set default_charset=UTF-8 in your php.ini file. It’s OK if the PHP charset is different from the Apache charset: the PHP charset will apply to PHP files, and the Apache charset will apply to all other types (this goes for #3 below as well).
  3. If you only want certain PHP documents in UTF-8, specify UTF-8 in the Content-type header of those documents. It’s important to point out here that, if you haven’t done #1 or #2 above, then you must set this header with the PHP header() function. If you try to set it with an HTML Meta tag, the charset defined in Apache will override your Meta tag.

UTF-8 in form submissions

In Windows 95 and 98, Microsoft used the Windows ANSI character set. If you ever copy-and-pasted text from Microsoft Word into a web form under Windows 9x, chances are any upper ASCII characters, such as ©, turned into something like ä in the web form. This is because the web page was probably Western ISO8859-1 encoded, and that character set organizes the upper ASCII range differently from Windows ANSI. So the web page thought it was receiving a different character than what you intended. Windows NT, 2000, and XP use Unicode, so you won’t have this problem under the newer versions of Windows. Macs and most other modern OSs use either Western ISO 8859-1 or Unicode. The first 256 characters of Western ISO 8859-1 are the same in Unicode. So your Unicode encoded web form should correctly interpret upper ASCII text provided by anyone not using Windows 9x (or a completely foreign, non-Unicode character set).

Additional PHP and Oracle configurations

You will want to enable multi-byte character support in PHP. Compile PHP with the -enable-mbstring option, and set mbstring.internal_encoding=UTF-8 in your php.ini file. Also, you should definitely look over the PHP documentation for multi-byte string functions. Note that if you haven’t upgraded to PHP 5 yet, the html_entity_decode() function will fail hard if you pass it a UTF-8 string. This was the only UTF-8 incompatibility we found in PHP 4.3.

You may want to implement PHP’s function overloading. An example will illustrate why this is important: in UTF-8, a string that is 4 characters long could occupy anywhere from 4 to 12 bytes depending on the multi-byte characters in it. The mb_strlen() function will correctly tell you the number of characters in such a string, but the regular strlen() function won’t (it’ll tell you the number of bytes). Enabling function overloading will cause PHP to automatically assume it’s handling multi-byte strings, so, in this example, it will execute mb_strlen() when you call strlen(). If you’re making a wholesale conversion to UTF-8, and you don’t want to tweak all your existing code, implementing function overloading makes sense. But there is one exception: you may not want to do function overloading on mail() – I’ll get to that in a minute.

Related to this, in Oracle 9, you can set NLS_LENGTH_SEMANTICS to use either character length or byte length semantics for the tables you create. That is, you can use it to indicate whether, for example, a varchar(10) column is 10 characters, or 10 bytes.

Smarty

If you’re using Smarty with PHP, you’ll need to override the escape() function. It calls the PHP htmlentities() and htmlspecialchars() functions, but it doesn’t provide them with the necessary charset argument so they’ll work with UTF-8. Make a copy of the escape() modifier and tweak it to pass along a charset argument to PHP, and then use it to override the original.

Exporting to other formats

As you’ll see below, it may not always be wise to do data exports in UTF-8. Sometimes you need to change the character set before performing the export. Take a look at PHP’s utf8_decode() and iconv functions to learn about converting UTF-8 to single-byte encoding. Note that utf8_decode(), while easy to use, is limited to the Latin character set (see the user contributed notes on the PHP utf8_decode() page for tips on dealing with other character sets).

  • PDF: we use PDFlib on our web server to create PDF documents on the fly. For it to work with UTF-8 data, you need to use it with a UTF-8 compatible font. The standard Arial font supports Greek and Cyrillic in UTF-8, which is generally sufficient (don’t confuse standard Arial with Microsoft’s Arial Unicode MS font – while it can print just about any UTF-8 character, it’s 32MB, so you probably don’t want to load it on your web server!). Also, Gentium is a very nice UTF-8 compatible serif font that supports Greek and Cyrillic.
  • RTF: we are moving away from RTF, but we still have some applications that generate RTF files. RTF does not provide good UTF-8 support. Our solution is to do a utf8_decode() on our data before generating RTF files (we can get away with this since none of the data going into our RTF files contain non-Latin characters – hopefully we’ll get rid of RTF before non-Latin characters start showing up).
  • Text: we also do data exports to text files, mainly in .csv format for use in spreadsheets. Surprisingly, Microsoft Excel does not support importing UTF-8 encoded text files. Again, our solution is to perform a utf8_decode() before generating these text files.
  • Email: I recommend not doing function overloading on PHP mail(). The reason has to do with line breaks. In Unix, a line break is represented by a line feed (LF) character. On Macs, it’s represented by a carriage return (CR) character. And on Windows, by a CR+LF. For email to work between platforms, an email standard was agreed upon in the early days of the Internet, which is CR+LF. So, for example, on Unix, sendmail will add a CR as needed to each LF it finds in the body of an email message. But when an email is UTF-8, mailers don’t try to wade through the multi-byte encoding, and they don’t “fix” the line breaks. We found that the line breaks in UTF-8 emails (generated on Unix) were interpreted as desired in Mac and Unix mail readers, and by Microsoft Outlook on Windows, but not by Eudora 6.2 (and previous versions) on Windows. In Eudora, the messages displayed with no line breaks. You can’t say it’s a Eudora bug, since the line breaks weren’t meeting the standard. At this time, the emails we generate only contain basic Latin characters, so sticking with the standard mail() function meets our needs for now.