import
[web.mtrack] / web / avatar.php
1 <?php # vim:ts=2:sw=2:et:
2 include '../inc/common.php';
3
4 $username = $_GET['u'];
5 $size = $_GET['s'];
6 $data = MTrackAuth::getUserData($username);
7
8 $loc = MTrackConfig::get('core', 'httpcachedir');
9 if (!$loc) {
10   $loc = MTrackConfig::get('core', 'vardir') . '/httpcache';
11   if (!is_dir($loc)) {
12     mkdir($loc);
13   }
14 }
15
16 $cache_duration = 3600; // seconds
17
18 $source = null;
19 if (isset($data['avatar'])) {
20   $source = $data['avatar'];
21 } else if (isset($data['email'])) {
22   $source = "http://www.gravatar.com/avatar/" .
23     md5(strtolower($data['email'])) . "?s=$size&d=wavatar";
24 } else if (preg_match('/^https?:\/\//', $username)) {
25   // Let's try a favatar!
26
27   function extract_favatar_link($filename, $relurl)
28   {
29     $data = file_get_contents($filename);
30     // Just get the head
31     if (preg_match('@<head[^>]*>(.*)</head>@smi', $data, $M)) {
32       $data = "<html><head>" . $M[1] . "</head></html>";
33     }
34     $doc = new DomDocument;
35     if (!@$doc->loadHTML($data)) {
36       return null;
37     }
38     $xpath = new DomXPath($doc);
39     $links = $xpath->query(
40         '/html/head/link[@rel="shortcut icon" or @rel="icon"]');
41
42     if (substr($relurl, -1) != '/') {
43       $relurl .= '/';
44     }
45
46     foreach ($links as $link) {
47       $url = $link->getAttribute('href');
48       if ($url !== null) {
49         break;
50       }
51     }
52
53     if ($url === null) {
54       return $relurl . 'favicon.ico';
55     }
56
57     if (!preg_match('@^([a-zA-Z]+)://@', $url)) {
58       /* fixup relative links */
59       if ($url[0] == '/') {
60         $url = substr($url, 1);
61       }
62       foreach ($xpath->query('/html/head/base') as $base) {
63         $url = $base->getAttribute('href') . $url;
64       }
65       if (!preg_match('@^([a-zA-Z]+)://@', $url)) {
66         $url = $relurl . $url;
67       }
68     }
69     return $url;
70   }
71
72   list($head, $link) = cache_get_url_and_operate(
73     $username, 'extract_favatar_link', $username);
74
75   $source = $link;
76 }
77
78 function logit($msg)
79 {
80 #  echo "$msg<br>";
81 //  error_log($msg);
82 }
83
84 /**
85  * Fetches the contents of the URL $source using a cache.
86  * Optionally runs a callback specified by $funcname on the
87  * data while it is under a lock (to ensure a consistent view).
88  * $funcname is passed the local cache filename as its first parameter.
89  * Any additional parameters passed to this function will be passed
90  * to $funcname as parameters after the cache filename.
91  *
92  * returns an array(
93  *  0 => data from the url
94  *  1 => return value of optional funcname
95  * )
96  */
97 function cache_get_url_and_operate($source, $funcname = null /* args */)
98 {
99   global $loc;
100   global $cache_duration;
101
102   $args = func_get_args();
103   if (count($args) > 2) {
104     array_shift($args);
105     array_shift($args);
106   } else {
107     $args = array();
108   }
109   $cache = $loc . "/" . md5($source);
110   array_unshift($args, $cache);
111
112   // cache file population, avoiding thundering herd and maintaining
113   // consistency under concurrency.
114
115   $dat = null;
116   $tosend = null;
117
118   $tries = 20;
119   while ($tries-- > 0) {
120     logit("tries=$tries");
121     // Can we open the file for read?
122     $fp = @fopen($cache, 'r+b');
123     if (!$fp) {
124       $fp = @fopen($cache, 'x+');
125     }
126     if ($fp) {
127       // Yes; get a lock for consistency
128       flock($fp, LOCK_SH);
129       logit("got shared lock");
130       // What do we need to do?
131       $st = fstat($fp);
132       if ($st['size'] == 0) {
133         // No data in the file, let's see if we can do something about that
134         logit("zero size; getting ex lock");
135         flock($fp, LOCK_EX);
136         $st = fstat($fp);
137         if ($st['size'] == 0) {
138           // We get to fix it
139           logit("zero sized; we're fixing it, reading from $source");
140           $tosend = file_get_contents($source);
141           fwrite($fp, $tosend);
142
143           if ($funcname !== null) {
144             $dat = call_user_func_array($funcname, $args);
145           }
146           break;
147         }
148         // Someone else fixed it
149         logit("Someone else fixed it, size is now $st[size]");
150       } else if (time() - $st['mtime'] > $cache_duration) {
151         // Someone needs to re-fetch the data
152         logit("Past cache period, getting ex lock");
153         flock($fp, LOCK_EX);
154         $st = fstat($fp);
155         if (time() - $st['mtime'] > $cache_duration) {
156           // We get to fix it
157           logit("cache expired; reading from $source, truncating");
158           ftruncate($fp, 0);
159           rewind($fp);
160           $tosend = file_get_contents($source);
161           logit("read " . strlen($tosend) . " from $source");
162           $x = fwrite($fp, $tosend);
163           logit("wrote $x to local cache file");
164           if ($funcname !== null) {
165             $dat = call_user_func_array($funcname, $args);
166           }
167           break;
168         }
169         // Someone else fixed it
170         logit("Someone fixed it, mtime now $st[mtime]");
171       }
172       // Good to read through
173       logit("Reading through cache");
174       $tosend = stream_get_contents($fp);
175       if ($funcname !== null) {
176         $dat = call_user_func_array($funcname, $args);
177       }
178       break;
179     }
180     logit("Couldn't get data, sleeping and retrying");
181     usleep(100);
182   }
183   if ($fp) {
184     flock($fp, LOCK_UN);
185     fclose($fp);
186   }
187   return array($tosend, $dat);
188 }
189
190 if ($source) {
191   $hint = basename($source);
192   list($tosend, $mime) = cache_get_url_and_operate(
193     $source, 'mtrack_mime_detect', $hint);
194   if ($mime) {
195     logit("All is good, sending data");
196   } else {
197     logit("Unable to get data");
198   }
199   if ($mime) {
200     header("Content-Type: $mime");
201     header("Content-Disposition: inline; filename=\"$hint\"");
202     echo $tosend;
203     exit;
204   }
205 }
206
207 $cache = dirname(__FILE__) . "/images/default_avatar.png";
208 $mime = mtrack_mime_detect($cache, $cache);
209 header("Content-Type: $mime");
210 header("Content-Disposition: inline");
211 readfile($cache);
212 exit;
213