Apr 022013
 

Imagine that you want to write something like this:

std::unordered_map<std::string,int> list;
list["foo"] = 2;
list["bar"] = 5;

This works perfectly but is very inefficient. Every time you use the [] operator, a std::string is created, some memory is allocated on the heap, and the content of the C string is copied into this buffer.

Instead we would like to directly read from the const char*. You can’t just write std::unordered_map<const char*,int> because it would compare pointers instead of their content.

I quickly wrote this CStringUnorderedMap which does this:

#include <unordered_map>

namespace CStringUnorderedMapImpl_ {
	struct Hash {
		size_t operator()(const char* ptr) {
			size_t hash = 0;
			for (; *ptr; ++ptr)
				hash = (hash << 2) + *ptr;
			return hash;
		}
	};

	struct Comparaison {
		bool operator()(const char* str1, const char* str2) {
			for (; *str1 && *str1 == *str2; ++str1, ++str2) {}
			return (*str1 == *str2);
		}
	};
}

template<typename T>
using CStringUnorderedMap = std::unordered_map<const char*, T, CStringUnorderedMapImpl_::Hash, CStringUnorderedMapImpl_::Comparaison>;

#endif

If your compiler doesn't support the new using syntax (Visual C++ for example), you can replace the last line by this one:

template<typename T>
struct CStringUnorderedMap : public std::unordered_map<const char*, T, CStringUnorderedMapImpl_::Hash, CStringUnorderedMapImpl_::Comparaison> {};
 Posted by at 15:15  Tagged with:
Mar 222013
 

If you are using the DirectX library, you have probably understood that you can’t use regular smart pointers with DirectX objects because of the way Microsoft designed their API.
Here is a small smart pointer class I created to handle this problem.

#include <stdexcept>

/**
 * Pointer to any IUnknown* object
 * T must be a derivate of IUnknown
 */
template<typename T>
struct DXObjectPointer {
	DXObjectPointer() : pointer(nullptr) {}
	DXObjectPointer(const DXObjectPointer& ptr) : pointer(ptr.pointer) { pointer->AddRef(); }
	DXObjectPointer(DXObjectPointer&& ptr) : pointer(nullptr) { std::swap(pointer, ptr.pointer); }
	~DXObjectPointer() { reset(); }

	DXObjectPointer&	operator=(const DXObjectPointer& ptr)		{ reset(); pointer = ptr.pointer; pointer->AddRef(); return *this; }
	DXObjectPointer&	operator=(DXObjectPointer&& ptr)		{ std::swap(pointer, ptr.pointer); return *this; }

	T**		operator&()				{ if (pointer) throw std::logic_error("Trying to assign into a non-empty DXObjectPointer"); return &pointer; }
	T*		operator->() const			{ return pointer; }
	T&		operator*() const			{ return *pointer; }

	T*		get() const				{ return pointer; }

	void		reset()					{ if (pointer) pointer->Release(); pointer = nullptr; }
		

private:
	T*		pointer;
};

Example usage: (without error handling)

DXObjectPointer<IDXGIFactory> factory;
CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&factory));

{
	DXObjectPointer<IDXGIFactory> foo = factory;     // calls IDXGIFactory->AddRef();

	DXObjectPointer<IDXGIAdapter> adapter;
	foo->EnumAdapters(0, &adapter);
    	
	// use get() to obtain a raw IDXGIAdapter* that you can pass to functions
	D3D10CreateDeviceAndSwapChain(adapter.get(), D3D10_DRIVER_TYPE_HARDWARE, NULL, flags, D3D10_SDK_VERSION, &swapChainDesc, &swapChain, &device);

}  // calls IDXGIFactory->Release() when the foo variable is destroyed
Dec 032012
 

If you don’t know AngularJS, it is a very good Javascript library that allows you to easily build web applications. Here are some random useful tricks that I found for AngularJS users.

Ternary operator in expressions

When you write an expression, like {{ id }}, you can’t use the ternary operator ?:. But you can use the && and || operators. These two lines are identical provided that b always equals true:

a ? b : c
a && b || c

For example:

<p>This feature is {{ feature.isActivated && "activated" || "not activated" }}</p>

This reason why it works is that "a && b" is treated like "if (a) return b; return a;" and "a || b" is treated like "if (!a) return b; return a;".

Using arrays or objects inside expressions

You can use arrays or objects directly from inside AngularJS expressions. For example, you can write the list of days of the week like this:

<ul>
  <li ng-repeat="day in [1,2,3,4,5,6,7]">Name of the day: {{ ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][day-1] }}</li>
</ul>

This will output:

<ul>
  <li>Name of the day: Mon</li>
  <li>Name of the day: Tue</li>
  <li>Name of the day: Wed</li>
  <li>Name of the day: Thu</li>
  <li>Name of the day: Fri</li>
  <li>Name of the day: Sat</li>
  <li>Name of the day: Sun</li>
</ul>

Watching two variables are once

AngularJS allows you to watch a variable and will call a callback whenever its content is modified. But what if you need to watch two variables at once? You can write this:

function callback() {
  ...
}

$scope.$watch('a', callback);
$scope.$watch('b', callback);

But you can also write this, which is more elegant:

$scope.$watch('a + b', function() {
  ...
});

The value of "a + b" will change whenever either a or b changes. Therefore you watch both variable at once.

Syntax of directives

The tutorial of AngularJS writes directives like this: "ng-model". But you can also write: "x-ng-model", "ng:model", "ng_model", "data-ng-model". They are all equivalent and work the same. I like to use the syntax with "data-" because it is standard-compliant.

Adding a loading screen on AJAX request

This module will add a loading screen over the whole page whenever an AJAX request is made using AngularJS's $http or $resource. This will prevent the user from clicking anywhere, which is a good solution if you don't want to handle the consequences of clicks during requests. Just don't use this if you load several megabytes of data.

angular
    .module('loadingOnAJAX', [])
    .config(function($httpProvider) {
        var numLoadings = 0;
        var loadingScreen = $('<div style="position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;background-color:gray;background-color:rgba(70,70,70,0.2);"><img style="position:absolute;top:50%;left:50%;" alt="" src="data:image/gif;base64,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" /></div>')
            .appendTo($('body')).hide();
        $httpProvider.responseInterceptors.push(function() {
            return function(promise) {
                numLoadings++;
                loadingScreen.show();
                var hide = function(r) { if (!(--numLoadings)) loadingScreen.hide(); return r; };
                return promise.then(hide, hide);
            };
        });
    });

Global errors handler

In web applications you usually handle AJAX errors globally. If the server returns an error, an error message will be displayed, and it's always at the same location in the application no matter the location of the error. This module allows you to define the location where they will be displayed.

angular
    .module('globalErrors', [])
    .config(function($provide, $httpProvider, $compileProvider) {
        var elementsList = $();

        var showMessage = function(content, cl, time) {
            $('<div/>')
                .addClass('message')
                .addClass(cl)
                .hide()
                .fadeIn('fast')
                .delay(time)
                .fadeOut('fast', function() { $(this).remove(); })
                .appendTo(elementsList)
                .text(content);
        };
        
        $httpProvider.responseInterceptors.push(function($timeout, $q) {
            return function(promise) {
                return promise.then(function(successResponse) {
                    if (successResponse.config.method.toUpperCase() != 'GET')
                        showMessage('Success', 'successMessage', 5000);
                    return successResponse;

                }, function(errorResponse) {
                    switch (errorResponse.status) {
                        case 401:
                            showMessage('Wrong usename or password', 'errorMessage', 20000);
                            break;
                        case 403:
                            showMessage('You don\'t have the right to do this', 'errorMessage', 20000);
                            break;
                        case 500:
                            showMessage('Server internal error: ' + errorResponse.data, 'errorMessage', 20000);
                            break;
                        default:
                            showMessage('Error ' + errorResponse.status + ': ' + errorResponse.data, 'errorMessage', 20000);
                    }
                    return $q.reject(errorResponse);
                });
            };
        });

        $compileProvider.directive('appMessages', function() {
            var directiveDefinitionObject = {
                link: function(scope, element, attrs) { elementsList.push($(element)); }
            };
            return directiveDefinitionObject;
        });
    });

Usage :

<div class="messagesList" app-messages></div>

Whenever an HTTP request (made using AngularJS's $http or $resource object) returns an error code, or when a non-GET request succeeds, a message will be displayed inside this container for a few seconds. Thanks to this, you almost don't need to worry about errors anymore.

Using HTTP basic auth

Some web applications use HTTP basic authentication (through HTTPS). You can also use this with AngularJS by setting the value of the "Authorization" header for the $http service.

$http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode($scope.login.login + ':' + $scope.login.password);

Note that Base64.encode comes from this website. IE9 and below don't support atob and bota.

Here is an example code for a login form:

var LoginController = function($scope, $http) {
    $scope.login = {};
    $scope.login.user = null;

    $scope.login.connect = function() {
        $http.get('/users/me').success(function(data, status) {
            if (status < 200 || status >= 300)
                return;
            $scope.login.user = data;
        });
    };
    
    $scope.login.disconnect = function() {
        $scope.login.user = null;
    };

    $scope.$watch('login.login + login.password', function() {
        $http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode($scope.login.login + ':' + $scope.login.password);
    });
};
<div ng-controller="LoginController">
  <div ng-hide="login.user">
    <form action="" ng-submit="login.connect()">
      <fieldset>
        <legend>Login</legend>
        <p><input ng-model="login.login" name="email" type="text" placeholder="Login" required /></p>
        <p><input ng-model="login.password" name="password" type="password" placeholder="Password" required /></p>
        <p><button type="submit">Login</button></p>
      </fieldset>
    </form>
  </div>

  <div ng-show="login.user">
    <p>Welcome, {{login.user.userName}}!</p>
    <p><button ng-click="login.disconnect()">Logout</button></p>
  </div>
</div>

What if the user enters a wrong username or password? The global errors handler (see above) will show an error message.

Note: this is out of topic, but if your server returns a 401 code, the web browser will display a "login dialog", asking the login/password of the user. You don't have any control over this dialog. The solution to prevent this is not to return 401 but another status code (418 I'm a Teapot is a good candidate). A clean way to do so is to add a header to tell the server, like this:

$http.defaults.headers.common['X-StatusOnLoginFail'] = '418';

Two-way binding for HTML

If you want to write some HTML that you loaded using AJAX, you can use ng-bind-html-unsafe. But this is a one-directional binding. If the content of the element is modified (thanks to contenteditable for example), the content of the variable is not updated.

This module allows two-way HTML binding between a variable and an element.

angular
	.module('twoWayHTMLBinding', [])
	.directive('adminBindHtml', function($timeout) {
		return {
			link: function compile(scope, tElement, tAttrs) {
				var refresh = function() {
					scope.$apply(tAttrs.adminBindHtml + ' = "' + tElement.html().replace(/"/g, '\\"') + '"');
					$timeout(refresh, 200);
				};
				scope.$watch(tAttrs.adminBindHtml, function(val, oldVal) {
					if (val != oldVal && tElement.html() != val)
						tElement.html(scope.$eval(tAttrs.adminBindHtml));
				});
				$timeout(refresh, 200);
			}
		};
	});

Example:

<div ng-init="htmlData = '&lt;p&gt;Hello!&lt;/p&gt;'">
  <div admin-bind-html="htmlData" contenteditable="true"></div>
</div>

Note: this may seem obvious, but don't allow non-admins to modify HTML code that can be displayed to anyone, or if you do you must add a server-side filter.

Dec 022012
 

It’s not a very well-known feature of C++11, but you are now able to get a pointer to the exception currently being processed.

void foo() {
	try {
		throw anything;
	} catch(...) {
		auto exceptionPtr = std::current_exception();
	}
}

In this example, exceptionPtr is of type std::exception_ptr. This type is kind of a smart pointer, which means that as long as the exceptionPtr variable or one of its copies exists, the thrown object still exists too.

So what can you do with a std::exception_ptr ? Nothing, except rethrow it later.

void foo() {
	std::exception_ptr exceptionPtr;
	try {
		throw std::exception("Error!");
	} catch(...) {
		exceptionPtr = std::current_exception();
	}

	... code here ...

	try {
		std::rethrow_exception(exceptionPtr);
	} catch(const std::exception& e) {
		std::cout << e.what();     // will print "Error!"
	}
}

Note that there exist a std::make_exception_ptr function that can build a std::exception_ptr without having to go through a try-catch block. The code just above can be rewritten like this:

void foo() {
	auto exceptionPtr = std::make_exception_ptr(std::exception("Error!"));

	... code here ...

	try {
		std::rethrow_exception(exceptionPtr);
	} catch(const std::exception& e) {
		std::cout << e.what();     // will print "Error!"
	}
}

These features are mainly here to support the new thread and future functions. But you can also use them to log exceptions:

void logException(std::exception_ptr exception) {
	try {
		std::rethrow_exception(exception);

	} catch(const std::system_error& e) {
		log("std::system_error with error code: " + e.code().message());
		log("Message: " + std::string(e.what()));

	} catch(const std::exception& e) {
		log("std::exception of type " + std::string(typeid(e).name()));
		log("Message: " + std::string(e.what()));

	} catch(...) {
		log("Exception of unknown type");
	}
}

int main() {
	try {
		... anything ...
		return 0;
	} catch(...) {
		logException(std::current_exception());
		return 1;
	}
}

These features are supported in both GCC 4.4 and Visual C++ 2012.

 Posted by at 18:30  Tagged with:
Sep 272012
 

Here are two small functions to convert between a GUID and a string.

#ifdef _MSC_VER
#	define snprintf	_snprintf
#endif

std::string guidToString(GUID guid) {
	std::array<char,40> output;
	snprintf(output.data(), output.size(), "{%08X-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
	return std::string(output.data());
}

GUID stringToGUID(const std::string& guid) {
	GUID output;
	const auto ret = sscanf(guid.c_str(), "{%8X-%4hX-%4hX-%2hX%2hX-%2hX%2hX%2hX%2hX%2hX%2hX}", &output.Data1, &output.Data2, &output.Data3, &output.Data4[0], &output.Data4[1], &output.Data4[2], &output.Data4[3], &output.Data4[4], &output.Data4[5], &output.Data4[6], &output.Data4[7]);
	if (ret != 11)
		throw std::logic_error("Unvalid GUID, format should be {00000000-0000-0000-0000-000000000000}");
	return output;
}
Aug 072012
 

If you are developing an AJAX application, here is a small jQuery code to add a loading screen to your page whenever an AJAX request is made. No external dependency, should work everywhere.

$(function() {
    $('<div style="position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;background-color:gray;background-color:rgba(70,70,70,0.2);"><img style="position:absolute;top:50%;left:50%;" alt="" src="data:image/gif;base64,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" /></div>')
        .appendTo($('body')).hide()
        .ajaxStart(function() { $(this).show(); }).ajaxStop(function() { $(this).hide(); });
});
Jul 172012
 

In PHP, when you want to write some XML data, the “correct” way is to use XMLWriter. However it can really be annoying to use for large documents.

Take this simple XHTML page for example:

<?xml version="1.0" encoding="UTF-8"?>
<html lang="fr" xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>Test</title>
 </head>
 <body>
  <p>Hello world</p>
 </body>
</html>

If you want to write this using XMLWriter, you get this:

$xmlWriter = new XMLWriter();
$xmlWriter->openURI('php://output');
$xmlWriter->setIndentString(' ');
$xmlWriter->setIndent(true);
$xmlWriter->startDocument('1.0', 'utf-8');

$xmlWriter->startElementNS(null, 'html', 'http://www.w3.org/1999/xhtml');
$xmlWriter->writeAttribute('lang', 'fr');

$xmlWriter->startElement('head');
$xmlWriter->writeElement('title', 'Test');
$xmlWriter->endElement();

$xmlWriter->startElement('body');
$xmlWriter->writeElement('p', 'Hello world');
$xmlWriter->endElement();

$xmlWriter->endElement();
$xmlWriter->endDocument();
$xmlWriter->flush();

You can easily understand why it’s annoying to use! And for larger documents, keeping track of startElement/endElement is also difficult.

So I coded this little wrapper, which you can use if you wish:

function writeXML($xml) {
	$writeNode = function($xmlWriter, $node) use (&$writeNode) {
		if (!isset($node[0]))
			return;

		if (is_array($node[0])) {
			foreach ($node as $elem)
				$writeNode($xmlWriter, $elem);

		} else if (is_string($node[0])) {
			if ($node[0] == '#comment') {
				$xmlWriter->writeComment($node[1]);

			} else if ($node[0] == '#cdata') {
				$xmlWriter->writeCData($node[1]);

			} else {
				$xmlWriter->startElement($node[0]);
				foreach ($node as $key => $value) {
					if (!is_string($key))
						continue;
					if (strlen($key) > 0)
						$xmlWriter->writeAttribute($key, $value);
				}
				foreach ($node as $key => $value) {
					if ($key === 0)
						continue;

					if (is_array($value)) {
						$writeNode($xmlWriter, $value);
					} else if (!is_string($key)) {
						$xmlWriter->text($value);
					}
				}
				$xmlWriter->endElement();
			}
		}
	};

	$writer = new XMLWriter();
	$writer->openMemory();
	$writer->setIndentString(' ');
	$writer->setIndent(true);
	$writer->startDocument('1.0', 'utf-8');
	$writeNode($writer, $xml);
	$writer->endDocument();
	return $writer->outputMemory();
}

Writing this same document is now much more intuitive:

echo writeXML(array('html',
	'lang' => 'fr',
	'xmlns' => 'http://www.w3.org/1999/xhtml',

	array('head',
		array('title', 'Test')
	),

	array('body',
		array('p', 'Hello world'),
		'test'
	)
));

And with PHP 5.4, you can even write this:

echo writeXML(['html',
	'lang' => 'fr',
	'xmlns' => 'http://www.w3.org/1999/xhtml',

	['head',
		['title', 'Test']
	],

	['body',
		['p', 'Hello world'],
		'test'
	]
]);

Even better, you can split your document writing code:

$head = ['head', 'title' => ['Test']];
$body = ['body', 'p' => ['Hello world']];

echo writeXML(['html',
	'lang' => 'fr',
	'xmlns' => 'http://www.w3.org/1999/xhtml',

	$head,
	$body
]);

For me, XML has now become a lot easier to write!

May 022012
 

There is a lot of documentation about how to set up a web server on Debian, but not so much about an email server. Let’s say you want all your registered users to get an email @yourdomain.tld. Let’s see how to do it.

Postfix

First, let’s install postfix, which is the most popular mail server.

apt-get install postfix postfix-mysql

Linux natively provides a simple mail system. Just enter mail root, type your message and end with Ctrl+D. The email will be stored in root’s home directory. The root user can then read its messages by typing mail.
Postfix is an SMTP server and will allow your computer to send or receive emails from the Internet. For example, after setting up postfix, you can write from anywhere an email to john@yourserverip, and it will be stored in john’s home directory on your server.

However we don’t want to create a UNIX user for each email account. We want to have a list of email accounts in a database so we can easily manage them. This is done by using postfix’s virtual hosting system.

Here is how it works. When postfix receives an email, it will first check whether the @domain of the destination address is in its configuration file, under "mydestination". If so, it will deliver the email to the UNIX user, if the user exists. Then it will look whether the @domain is in its configuration file, under "virtual_mailbox_domains". If so, it will search for the user in our database and store the email at the path specified in the database. With this second approach, we don’t have to create a UNIX user for each email account.

Now we’ll configure postfix. Open /etc/postfix/main.cf with vim or whatever text editor you like.

Let’s start by setting “myorigin” to “yourdomain.tld”. If the “john” user sends an email, the source address will be “john@yourdomain.tld”. For “mydestination” just write “localhost”, and for “virtual_mailbox_domains” write “yourdomain.tld”. You can put multiple domains here (separated by comas) or even look for a domains list from your database (same method as we’ll see below). Do not put “yourdomain.tld” in “mydestination”, or it will conflict.

Virtual mailboxes

Whenever the server receives an email targeted at @yourdomain.tld (or one of your virtual domains), it will first look for an alias in virtual_alias_maps. An alias is an alternative name, for example “webmaster@yourdomain” can be set as an alternative name for “john@yourdomain”. This mechanics allows you to easily create redirections, including redirections to multiple addresses (the email will be duplicated) and to external domains.

Since we want to look for these redirections from our database, lets write virtual_alias_maps = mysql:/etc/postfix/virtual_forward.cf. This tells postfix that we want to use the MySQL driver, and the configuration provided in /etc/postfix/virtual_forward.cf. This configuration file has a syntax like this:

hosts = localhost
user = 
password = 
dbname = 
query = 

I recommend creating a MySQL user named “email” which will only have access to the tables accessible by your email system. Just write the appropriate username, password, database name, and SQL query in the configuration file. The SQL query simply needs to return rows with one field containing the email address to redirect the email to. In the query, ‘%s’ is replaced by the source email address, ‘%u’ by the ‘user’ part of ‘user@domain’ and ‘%d’ by the ‘domain’ part of ‘user@domain’. If you have a simple table with a “source” column and a “destination” column, your query would be “SELECT destination FROM table WHERE source = ‘%s’”.

If it doesn’t find any alias, postfix will look for a proper mailbox where to store the destination email. This is done again by looking into our database, this time by reading “virtual_mailbox_maps“. Like above, this parameter should be like “mysql:/etc/postfix/virtual_accounts.cf”, and virtual_accounts.cf a file containing the MySQL host, username, password and query. The query should return one row of one column, whose value is the path where to store the email. Note that the value of “virtual_mailbox_base” is prepended to this result. If the value is a file (ie. doesn’t end with ‘/’) then the email is stored in this file, in a standard way. If the result is a directory (ie. ends with ‘/’) then a maildir is created and the email added to it. The latter solution is preferred, since it allows emails to be received and read at the same time.

You should add a “virtual_mailbox_base” directive, with a value like “/var/mail/vhosts”. This is the base directory where all virtual emails will be stored. Personally, I use the email address as a directory name by writing a query like "SELECT CONCAT(email, '/') FROM email_accounts WHERE email = '%s'". Therefore, emails for ‘john@yourdomain.tld’ will be stored into ‘/var/mail/vhosts/john@yourdomain.tld/’.

Finally, you should add virtual_uid_maps = static:5000 and virtual_gid_maps = static:5000. This defines the UNIX user used to write the emails to the directory.

Note that if an alias exists, it will overwrite any proper mailbox. For example if I have a mailbox named “john@mydomain” and a redirection from “john@mydomain” to “somethingelse@otherdomain”, then the mailbox won’t receive anything. You need to add a redirection from “john@mydomain” to “john@mydomain” in your aliases list. The easiest way to do so is to add a union in your aliases’ SQL query.

Conclusion

You need to reload postfix by calling /etc/init.d/postfix reload after modifying one the .cf files, but you don’t need to reload after modifying the database. The database is read each time an email is received, so updates are instant.

To test whether it works, simply call ‘mail someuser@yourdomain.com‘ where ‘someuser@yourdomain.com’ is in the database. The ‘/var/mail/vhosts/someuser@yourdomain.com/new/’ directory should contain the email you just sent. If it doesn’t work, you can find informations in /var/log/mail.err and /var/log/mail.log. The next step to be sure it works is to send an email from an external computer and see if it arrives in the directory.

You may see access errors in your logs. In this case, edit /etc/postfix/master.cf and write ‘n’ at the fifth column for any program that generates an error (for me it was smtp, qmgr, cleanup, rewrite, local and virtual). This will disable the chroot mode. Remember to reload postfix afterwards.

Don’t forget to set the owner of your virtual_accounts.cf and virtual_forward.cf files to postfix, and to forbid reading for anybody else (chmod 700 virtual_*). After all, these files contain your database’s password.

Courier

Now you can send emails to any user whose name is in your database, and they will be stored on the disk. But your users can’t read their emails since they are virtual users. To do so, we’ll set up courier, a POP3/IMAP server.

apt-get install courier-base courier-authlib-mysql courier-imap courier-pop

Let’s start by modifying /etc/courier/authdaemonrc. Change “authmodulelist” to “authmysql”. In /etc/courier/authmysql, put your MySQL server’s host, username and password (remember that you should have a MySQL account dedicated to emails). Don’t bother modifying MYSQL_USER_TABLE or anything below. Instead, just uncomment MYSQL_SELECT_CLAUSE. The query specified here should return some fields (see the text right above in the file). For example, my query is something like “SELECT email, NULL, password, 5000, 5000, '', CONCAT('/var/mail/vhosts/', email), NULL, '' FROM email_accounts WHERE login = CONCAT('$(local_part)', '@', '$(domain)')“.

Whenever a user connects to your POP3/IMAP server, this query will be called to determine the possible accounts. The maildir where the email are stored is returned in the SQL query.

Important: if the maildir doesn’t exist, courier will send an error to your user (contrary to postfix which will simply create the directory). A simple fix is to send a welcome email to the user whenever you create a new account. A more complicated fix is to add a crontab task which will read the database every 5 minutes or so and create the maildirs that don’t exist. Here is an example shell script:

#!/bin/bash

accounts="`mysql -s -N -e "SELECT email FROM table.email_accounts"`"

for element in $accounts
do
        if [ ! -d "/var/mail/vhosts/$element" ]
        then
                echo "Creating $element"
                maildirmake /var/mail/vhosts/$element
        fi
done

Simply add it to crontab by calling crontab -e -u root and adding:

*/15 * * * * /path/to/script/create_maildirs.sh >>/var/log/create_maildirs_log

Note that a more elaborated script should also destroy any directory whose name is not in the database.

Autoreply

A very common and useful feature is an autoreply system. When one of your users is on vacations and an email is sent to him, an automatic response is sent to inform the sender. The trick (described here) is to create a redirection from the email address to a fake email address. For example, let’s say john is on vacation. We’ll redirect john@yourdomain.tld to john@autoreply.yourdomain.tld. When postfix receives an email for john@yourdomain.tld, it will do the redirection. But we’ll also tell postfix that emails for autoreply.yourdomain.tld should be processed by another program named YAA. And this program will send the automatic response.

First, download YAA and put it somewhere. For the rest of this article, I’ll put it in /usr/local/yaa-0.3.1. You will also need perl to execute this program.

Now create a MySQL table containing our automatic replies. For example, one column for the email, one column for the message, one column for the start date and one column for the ending date. Then edit /etc/postfix/virtual_forward.cf. We’ll add to the SQL request a union: SELECT destination FROM email_forward WHERE source = '%s' UNION SELECT REPLACE(email, '@', '@autoreply.') FROM email_autoreply WHERE email = '%s'. When postfix receives an email for john@yourdomain and john@yourdomain is in the email_autoreply table, then a redirection to john@autoreply.yourdomain will be added.

Don’t forget that aliases overwrite mailboxes. So when setting up an autoreply, the destinator won’t receive anything. To solve this, we’ll add another union to the query: SELECT destination FROM email_forward WHERE source = '%s' UNION SELECT REPLACE(email, '@', '@autoreply.') FROM email_autoreply WHERE email = '%s' UNION SELECT email FROM email_accounts WHERE email = '%s'

Now we’ll tell postfix that emails to autoreply.yourdomain should be handled by YAA. Edit /etc/postfix/main.cf and add “transport_maps = hash:/etc/postfix/transport”. Edit /etc/postfix/transport and add “autoreply.yourdomain yaa”. Note that you can also put a MySQL query here instead of the hash, the same way as we did above. The ‘yaa’ alias must now be added to master.cf. Edit master.cf and add “yaa unix - n n - - pipe flags= user=mail argv=/usr/local/yaa-0.3.1/bin/yaa.pl“. Don’t forget to reload postfix after this.

Rename /usr/local/yaa-0.3.1/conf/yaa.conf.sample to yaa.conf and edit this file. The main section of this configuration file is $lookup_map_query_order. Each element in the table tells YAA where to find the information.

$lookup_map_query_order = {
        active => [ 'my_sql_map:active' ],
        subject => [ 'my_sql_map:subject' ],
        message => [ 'my_sql_map:message' ],
        charset => [ 'utf-8' ],
        forward => [ '' ]
}

The ‘my_sql_map’ is defined above, in a commented section that you will need to uncomment.

'my_sql_map' => {
    'driver' => 'SQL',
    'sql_dsn' => 'dbi:mysql:database=xxx;host=localhost',
    'sql_username' => "",
    'sql_password' => "",
    'sql_select' => "SELECT 1 AS active, `text` AS message, 'Automatic response' AS subject FROM email_autoreply WHERE email = %m AND (start IS NULL OR start <= NOW()) AND (end IS NULL OR end >= NOW())",
},

Everything should work fine. If not, you can find some informations in /var/log/mail.log. Beware that in some cases if your SQL query is incorrect, no error message is generated (this happened to me) so check your query if it doesn’t work for no reason.

Apr 262012
 

You may already know about PHP’s output buffering feature. Whatever you write to stdout will instead be sent to a callback function, which will process it.

PHP proposes some handlers, like ob_gzhandler, ob_tidyhandler or ob_iconv_handler. It also provides ob_etaghandler, but this requires a PECL extension. If you don’t want to bother installing this, here is an alternative implementation I just wrote.

[php]/// \brief Returns true if the user sent a "If-None-Match" which matches the $etag
function shouldReturn304($etag) {
	if (!function_exists('apache_request_headers'))
		return false;
	$headers = apache_request_headers();
	if (!isset($headers['If-None-Match']))
		return false;

	return $headers['If-None-Match'] == $etag;
}

/// \brief Call ob_start('ob_handleEtag') and the etag will automatically be handled
function ob_handleEtag($content, $bits) {
	static $bufferContent = '';
	if (!($bits & PHP_OUTPUT_HANDLER_END)) {
		$bufferContent .= $content;
		return '';
	}

	$localCtnt = $bufferContent.$content;
	$bufferContent = '';

	$etag = md5($localCtnt).strlen($localCtnt);

	if (shouldReturn304($etag)) {
		header(':', true, 304);
		return '';
	}

	header('ETag:'.$etag);
	return $localCtnt;
}

To use it, just call ob_start('ob_handleEtag'); before your script, and an ETag will automatically be generated. A “304 Not Modified” answer will also automatically be sent if the HTTP request contains a “If-None-Match” header matching the ETag.

Mar 082012
 

I just managed to read a video file using libavcodec and display it on an OpenGL texture. It took me a whole day to manage to do so, because of the lack of documentation and tutorials available for this library. So let’s see how it works.

Note: builds of the library for Windows are available here. You need to download both “-dev” to get the headers and .lib files, and “-shared” in order to get the DLLs required to run your program.

Multimedia file containers

There is one important thing that needs to be understood when working with multimedia files: they are just containers. The AVI, MOV, MP4, etc. formats are only containers, just like tarballs for instance. They contain streams of video, audio, subtitles, or even random data, just like a tarball would contain different files.

So the first step when opening a video file is to parse its headers and get informations about what is in the file. To do so, we are going to use libavformat which is a library dedicated to this task.

But first, we need to initialize everything by calling “av_register_all”.

static std::once_flag initFlag;
std::call_once(initFlag, []() { av_register_all(); });

Now, let’s allocate an AVFormatContext structure. Since I’m coding in C++, I’m going to use a shared_ptr to make it exception-safe and so we don’t forget to release it later.

std::shared_ptr<AVFormatContext> avFormat(avformat_alloc_context(), &avformat_free_context);

Then, we ask it to open our file and parse its header.

auto avFormatPtr = avFormat.get();
if (avformat_open_input(&avFormatPtr, "C:\\path\\to\\video.avi", nullptr, nullptr) != 0)
	throw std::runtime_error("Error while calling avformat_open_input (probably invalid file format)");

if (avformat_find_stream_info(avFormat.get(), nullptr) < 0)
	throw std::runtime_error("Error while calling avformat_find_stream_info");

Note that it is also possible to open from an istream, you have to fill avFormat->pb before calling avformat_open_input and pass a fake filename to it.

The video, audio and subtitles data contained in a multimedia file are called "streams". Now that we loaded the file, the AVFormatContext structure contains members called "nb_streams" and "streams" which contain informations about the list of streams in the file.

for (unsigned int i = 0; i < avFormat->nb_streams; ++i) {
	auto stream = avFormat->streams[i];		// pointer to a structure describing the stream
	auto codecType = stream->codec->codec_type;	// the type of data in this stream, notable values are AVMEDIA_TYPE_VIDEO and AVMEDIA_TYPE_AUDIO
	auto codecID = stream->codec->codec_id;		// identifier for the codec
}

The video and audio data in the file are compressed in a specific format. For example for audio it can be MP3, WAV, Ogg vorbis, etc.. For video it can be MPEG, h.264, Ogg theora, etc. In order to decode (or encode) this data, we need the appropriate codec. libavformat gives us the identifier of the codec that we will need to use to decode the stream.

Decoding a video stream

Fortunately, libavcodec contains a lot of codecs. In order to really decode the video stream, we will need to use this library. Let's find a video stream that we want to decode using the same kind of code as above.

AVStream* videoStream = nullptr;
for (auto i = 0; i < avFormat->nb_streams; ++i) {
	if (avFormat->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
		// we've found a video stream!
		videoStream = avFormat->streams[i];
		break;
	}
}

if (!videoStream)
	throw std::runtime_error("Didn't find any video stream in the file (probably audio file)");

We need to initialize an AVCodecContext structure, which is where the library will store informations throughout the decoding process.

Update 2012-12-05: added this extradata thing, which is required for some codecs

// getting the required codec structure
const auto codec = avcodec_find_decoder(videoStream->codec->codec_id);
if (codec == nullptr)
	throw std::runtime_error("Codec required by video file not available");

// allocating a structure
std::shared_ptr<AVCodecContext> avVideoCodec(avcodec_alloc_context3(codec), [](AVCodecContext* c) { avcodec_close(c); av_free(c); });

// we need to make a copy of videoStream->codec->extradata and give it to the context
// make sure that this vector exists as long as the avVideoCodec exists
std::vector codecContextExtraData(stream->codec->extradata, stream->codec->extradata + stream->codec->extradata_size);
avVideoCodec->extradata = reinterpret_cast(codecContextExtraData.data());
avVideoCodec->extradata_size = codecContextExtraData.size();

// initializing the structure by opening the codec
if (avcodec_open2(avVideoCodec.get(), codec, nullptr) < 0)
	throw std::runtime_error("Could not open codec");

The decoding loop

If you have ever used the zlib library, it works exactly the same here. We are going to give some packet of data to libavcodec, which will in return send us the decoded video frames. Then we give another packet of data, and it will return another frame, etc. In order to get the input data, we need to ask libavformat.

// let's read some data
AVPacket packet;
// libavformat mallocs a buffer with the data and stores a pointer to it inside AVPacket
if (av_read_frame(avFormat.get(), &packet) < 0)
	return;		// we are at EOF

Update 2012-12-05. This code is not very safe, because the library allocates an AVPacket, and we need to call av_free_packet manually. If we kept the code like this, we'd have to take care that no exception is thrown while the packet is alive. Instead, we'll use RAII with our own Packet structure.

struct Packet {
	explicit Packet(AVFormatContext* ctxt = nullptr) {
		av_init_packet(&packet);
		packet.data = nullptr;
		if (ctxt) reset(ctxt);
	}

	Packet(Packet&& other) : packet(std::move(other.packet)) {
		other.packet.data = nullptr;
	}

	~Packet() {
		if (packet.data)
			av_free_packet(&packet);
	}

	void reset(AVFormatContext* ctxt) {
		if (packet.data)
			av_free_packet(&packet);
		if (av_read_frame(ctxt, &packet) < 0)
			packet.data = nullptr;
	}

	AVPacket packet;
};

Now we can rewrite the code above like this:

Packet packet(avFormat.get());
if (packet.packet.data == nullptr)
	return;

However, libavformat doesn't only read the data of our video stream, but also the data for other streams. Thus we have to check that the data we read belongs to our stream, and ignore it if it is not the case.

Packet packet;
do {
	packet.reset(avFormat.get());
	if (packet.packet.stream_index != videoStream->index)
		continue;	// the packet is not about the video stream we want, let's jump again the start of the loop
} while(0);

// decoding code here

Now that we have our input data, we send it data to our libavcodec context. We also need to give to it a pointer to an AVFrame which will eventually contain the decoded image.

// allocating an AVFrame
std::shared_ptr<AVFrame> avFrame(avcodec_alloc_frame(), &av_free);

// sending data to libavcodec
int isFrameAvailable = 0;
const auto processedLength = avcodec_decode_video2(avVideoCodec.get(), avFrame.get(), &isFrameAvailable, &packet.packet);
if (processedLength < 0)
	throw std::runtime_error("Error while processing the data");

The "isFrameAvailable" and "processedLength" variables are very important. The first one tells us whether an image has been written to avFrame. The second one tells us the number of bytes processed by libavcodec in the data. There are three possibilities here:

  • the library managed to read one frame from the data of the packet, so we process the frame, and to get the next frame we will read another packet
  • the library didn't have enough data to read one frame, isFrameAvailable is equal to 0, and we need to read another packet and send it
  • the library had too much data and didn't process the whole packet, so we process the frame, but next time we won't read a new packet, but use the same buffer, just pointing right after the data already processed

So let's rewrite the code above by taking this into account.

// allocating an AVFrame
std::shared_ptr<AVFrame> avFrame(avcodec_alloc_frame(), &av_free);

// the current packet of data
Packet packet;
// data in the packet of data already processed
size_t offsetInData = 0;

// the decoding loop, running until EOF
while (true) {
	// reading a packet using libavformat
	if (offsetInData >= packet.packet.size) {
		do {
			packet.reset(avFormat.get());
			if (packet.stream_index != videoStream->index)
				continue;
		} while(0);
	}

	// preparing the packet that we will send to libavcodec
	AVPacket packetToSend;
	packetToSend.data = packet.packet.data + offsetInData;
	packetToSend.size = packet.packet.size - offsetInData;
	
	// sending data to libavcodec
	int isFrameAvailable = 0;
	const auto processedLength = avcodec_decode_video2(avVideoCodec.get(), avFrame.get(), &isFrameAvailable, &packetToSend);
	if (processedLength < 0) {
		av_free_packet(&packet);
		throw std::runtime_error("Error while processing the data");
	}
	offsetInData += processedLength;

	// processing the image if available
	if (isFrameAvailable) {
		// display image on the screen

		// sleeping until next frame
		const auto msToWait = avVideoContext->ticks_per_frame * 1000 * avVideoContext->time_base.num / avVideoContext->time_base.den;
		std::this_thread::sleep(std::chrono::milliseconds(msToWait));
	}
}

Note that this code can greatly be improved by ensuring that av_free_packet is automatically called.

Processing the resulting image

The last missing piece of code is processing the image. The AVFrame structure contains the data of our image and some informations like its width and height. Unfortunately the format commonly used in a lot of videos is YUV, which is natively supported by neither OpenGL nor DirectX. That's why we going to convert it to RGB using libswscale, which is another "part" of libavcodec.

The code is pretty straight-forward: we create a SwsContext* with source and destination pixel formats set, and ask it to rescale our image to the size it was already.

AVPicture pic;
avpicture_alloc(&pic, PIX_FMT_RGB24, avFrame->width, avFrame->height);
auto ctxt = sws_getContext(avFrame->width, avFrame->height, static_cast<PixelFormat>(avFrame->format), avFrame->width, avFrame->height, PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr);
if (ctxt == nullptr)
	throw std::runtime_error("Error while calling sws_getContext");
sws_scale(ctxt, avFrame->data, avFrame->linesize, 0, avFrame->height, pic.data, pic.linesize);

// pic.data[0] now contains the image data in RGB format (3 bytes)
// and pic.linesize[0] is the pitch of the data (ie. size of a row in memory, which can be larger than width*sizeof(pixel))

// we can for example upload it to an OpenGL texture (note: untested code)
// glBindTexture(GL_TEXTURE_2D, myTex);
// for (int i = 0; i < avFrame->height; ++i) {
// 	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i, avFrame->width, 1, GL_RGB, GL_UNSIGNED_BYTE, avFrame->data[0] + (i * pic.linesize[0]));
// }

avpicture_free(&pic);

And here is the result, this OpenGL debugger shows us the texture (the black borders are part of the video)

The sound

Decoding a sound works exactly the same as decoding a video. You initialize an AVCodecContext, you read the data using libavformat, and you decode it. But this time, we'll use avcodec_decode_audio4 instead of avcodec_decode_video2.

Here are some useful informations in order to process the data:

unsigned int bitsPerSample = av_get_bytes_per_sample(stream.codecContext->sample_fmt) * CHAR_BIT;    // 16 or 32
unsigned int numberOfChannels = stream.codecContext->channels;     // 1 for mono, 2 for stereo, or more
unsigned int numberOfSamplesContainedInTheFrame = stream.frame->nb_samples * stream.codecContext->channels;
unsigned int numberOfSamplesToPlayPerSecond = stream.codecContext->sample_rate;   // usually 44100 or 48000
const void* data = stream.frame->data[0];