From a86477aad3bab8ad519626c56e3e253faea50518 Mon Sep 17 00:00:00 2001
From: Evan Prodromou <evan@prodromou.name>
Date: Fri, 13 Jun 2008 10:49:13 -0400
Subject: [PATCH] add content negotiation for media type

darcs-hash:20080613144913-84dde-3e970b4e6f19ea1e0db09d7ab133a6c148be7a75.gz
---
 doc/roadmap  | 12 +++----
 lib/util.php | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 99 insertions(+), 7 deletions(-)

diff --git a/doc/roadmap b/doc/roadmap
index a3e7258339..4caeb4e612 100644
--- a/doc/roadmap
+++ b/doc/roadmap
@@ -91,8 +91,8 @@ First public release (theoretically). Added distributed subscriptions,
 + log of consumers who ask for access
 + receive remote notice
 + send remote notice
-- receive remote profile update
-- send remote profile update
++ receive remote profile update
++ send remote profile update
 + subscribe form for not-logged-in users on showstream
 + pretty URLs
 + doc action
@@ -138,8 +138,8 @@ First public release (theoretically). Added distributed subscriptions,
 - graphic refresh on remotesubscribe
 + graphic refresh on shownotice
 + graphic refresh on showstream
-- graphic refresh on subscribed
-- graphic refresh on subscriptions
++ graphic refresh on subscribed
++ graphic refresh on subscriptions
 + graphic refresh on userauthorization
 - update default theme to use new, more semantic, HTML
 - subscribe/unsubscribe on subscriptions page
@@ -147,10 +147,12 @@ First public release (theoretically). Added distributed subscriptions,
 + correct use of views menu in settings
 + correct use of views menu in streams
 - INSTALL file
+- content negotiation for content type
 
 Release 0.4
 -----------
 
+- jQuery for as much as possible
 - microid for profile page
 - format times per user
 - timezone preferences in Profile settings
@@ -164,8 +166,6 @@ Release 0.4
 - email confirmation for registration
 - email options
 - change cookie handling for anon users to be more cache-friendly
-- jQuery for as much as possible
-- content negotiation for content type
 - content negotiation for encoding
 - If-Modified-Since support
 - Vary
diff --git a/lib/util.php b/lib/util.php
index d6c1a9f8b9..e1e5081765 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -123,10 +123,24 @@ function common_end_xml() {
 	$xw->flush();
 }
 
+define('PAGE_TYPE_PREFS', 'application/xhtml+xml,text/html;q=0.7,application/xml;q=0.3,text/xml;q=0.2');
+	   
 function common_show_header($pagetitle, $callable=NULL, $data=NULL, $headercall=NULL) {
 	global $config, $xw;
 
-	header('Content-Type: application/xhtml+xml');
+	$httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : NULL;
+
+	# XXX: allow content negotiation for RDF, RSS, or XRDS
+	
+	$type = common_negotiate_type(common_accept_to_prefs($httpaccept),
+								  common_accept_to_prefs(PAGE_TYPE_PREFS));
+
+	if (!$type) {
+		common_client_error(_t('This page is not available in a media type you accept'), 406);
+		exit(0);
+	}
+	
+	header('Content-Type: '.$type);
 
 	common_start_xml('html',
 					 '-//W3C//DTD XHTML 1.0 Strict//EN',
@@ -736,3 +750,81 @@ function common_pagination($have_before, $have_after, $page, $action, $args=NULL
 		common_element_end('div');
 	}
 }
+
+/* Following functions are copied from MediaWiki GlobalFunctions.php
+ * and written by Evan Prodromou. */
+
+function common_accept_to_prefs($accept, $def = '*/*') {
+	# No arg means accept anything (per HTTP spec)
+	if(!$accept) {
+		return array($def => 1);
+	}
+
+	$prefs = array();
+
+	$parts = explode(',', $accept);
+
+	foreach($parts as $part) {
+		# FIXME: doesn't deal with params like 'text/html; level=1'
+		@list($value, $qpart) = explode(';', $part);
+		$match = array();
+		if(!isset($qpart)) {
+			$prefs[$value] = 1;
+		} elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)) {
+			$prefs[$value] = $match[1];
+		}
+	}
+
+	return $prefs;
+}
+
+function common_mime_type_match($type, $avail) {
+	if(array_key_exists($type, $avail)) {
+		return $type;
+	} else {
+		$parts = explode('/', $type);
+		if(array_key_exists($parts[0] . '/*', $avail)) {
+			return $parts[0] . '/*';
+		} elseif(array_key_exists('*/*', $avail)) {
+			return '*/*';
+		} else {
+			return NULL;
+		}
+	}
+}
+
+function common_negotiate_type($cprefs, $sprefs) {
+	$combine = array();
+
+	foreach(array_keys($sprefs) as $type) {
+		$parts = explode('/', $type);
+		if($parts[1] != '*') {
+			$ckey = common_mime_type_match($type, $cprefs);
+			if($ckey) {
+				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
+			}
+		}
+	}
+
+	foreach(array_keys($cprefs) as $type) {
+		$parts = explode('/', $type);
+		if($parts[1] != '*' && !array_key_exists($type, $sprefs)) {
+			$skey = common_mime_type_match($type, $sprefs);
+			if($skey) {
+				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
+			}
+		}
+	}
+
+	$bestq = 0;
+	$besttype = NULL;
+
+	foreach(array_keys($combine) as $type) {
+		if($combine[$type] > $bestq) {
+			$besttype = $type;
+			$bestq = $combine[$type];
+		}
+	}
+
+	return $besttype;
+}