OSDN Git Service

BugTrack2/98: flock() seems not thorough (Pointed out by Cue)
[pukiwiki/pukiwiki.git] / lib / trackback.php
1 <?php
2 // $Id: trackback.php,v 1.21 2005/07/05 12:51:08 henoheno Exp $
3 // Copyright (C)
4 //   2003-2005 PukiWiki Developers Team
5 //   2003      Originally written by Katsumi Saito <katsumi@jo1upk.ymt.prug.or.jp>
6 // License: GPL v2 or (at your option) any later version
7 //
8 // PukiWiki/TrackBack
9
10 /*
11  * NOTE:
12  *     To get TrackBack ID correctly, specify URI clearly like:
13  *     http://localhost/pukiwiki/pukiwiki.php?FrontPage
14  *
15  * tb_get_id($page)        Get TrackBack ID from page name
16  * tb_id2page($tb_id)      Get page name from TrackBack ID
17  * tb_get_filename($page)  Get file name of TrackBack ping data
18  * tb_count($page)         Count the number of TrackBack pings included for the page
19  *                         // pukiwiki.skin.php
20  * tb_send($page, $data)   Send TrackBack ping(s) automatically // file.php
21  * tb_delete($page)        Remove TrackBack ping data // edit.inc.php
22  * tb_get($file, $key = 1) Import TrackBack ping data from file
23  * tb_get_rdf($page)       Get a RDF comment to bury TrackBack-ping-URI under HTML(XHTML) output
24  *                         // lib/pukiwiki.php
25  * tb_get_url($url)        HTTP-GET from $uri, and reveal the TrackBack Ping URL
26  * class TrackBack_XML     Parse and reveal the TrackBack Ping URL from RDF data
27  *
28  * == Referer related ==
29  * ref_save($page)         Save or update referer data // lib/pukiwiki.php
30  */
31
32 define('PLUGIN_TRACKBACK_VERSION', 'PukiWiki/TrackBack 0.3');
33
34 // Get TrackBack ID from page name
35 function tb_get_id($page)
36 {
37         return md5($page);
38 }
39
40 // Get page name from TrackBack ID
41 function tb_id2page($tb_id)
42 {
43         static $pages, $cache = array();
44
45         if (isset($cache[$tb_id])) return $cache[$tb_id];
46
47         if (! isset($pages)) $pages = get_existpages();
48         foreach ($pages as $page) {
49                 $_tb_id = tb_get_id($page);
50                 $cache[$_tb_id] = $page;
51                 unset($pages[$page]);
52                 if ($tb_id == $_tb_id) return $cache[$tb_id]; // Found
53         }
54
55         $cache[$tb_id] = FALSE;
56         return $cache[$tb_id]; // Not found
57 }
58
59 // Get file name of TrackBack ping data
60 function tb_get_filename($page, $ext = '.txt')
61 {
62         return TRACKBACK_DIR . encode($page) . $ext;
63 }
64
65 // Count the number of TrackBack pings included for the page
66 function tb_count($page, $ext = '.txt')
67 {
68         $filename = tb_get_filename($page, $ext);
69         return file_exists($filename) ? count(file($filename)) : 0;
70 }
71
72 // Send TrackBack ping(s) automatically
73 // $plus  = Newly added lines may include URLs
74 // $minus = Removed lines may include URLs
75 function tb_send($page, $plus, $minus = '')
76 {
77         global $page_title;
78
79         $script = get_script_uri();
80
81         // Disable 'max execution time' (php.ini: max_execution_time)
82         if (ini_get('safe_mode') == '0') set_time_limit(0);
83
84         // Get URLs from <a>(anchor) tag from convert_html()
85         $links = array();
86         $plus  = convert_html($plus); // WARNING: heavy and may cause side-effect
87         preg_match_all('#href="(https?://[^"]+)"#', $plus, $links, PREG_PATTERN_ORDER);
88         $links = array_unique($links[1]);
89
90         // Reject from minus list
91         if ($minus != '') {
92                 $links_m = array();
93                 $minus = convert_html($minus); // WARNING: heavy and may cause side-effect
94                 preg_match_all('#href="(https?://[^"]+)"#', $minus, $links_m, PREG_PATTERN_ORDER);
95                 $links_m = array_unique($links_m[1]);
96
97                 $links = array_diff($links, $links_m);
98         }
99
100         // Reject own URL (Pattern _NOT_ started with '$script' and '?')
101         $links = preg_grep('/^(?!' . preg_quote($script, '/') . '\?)./', $links);
102
103         // No link, END
104         if (! is_array($links) || empty($links)) return;
105
106         $r_page  = rawurlencode($page);
107         $excerpt = strip_htmltag(convert_html(get_source($page)));
108
109         // Sender's information
110         $putdata = array(
111                 'title'     => $page, // Title = It's page name
112                 'url'       => $script . '?' . $r_page, // will be rawurlencode() at send phase
113                 'excerpt'   => mb_strimwidth(preg_replace("/[\r\n]/", ' ', $excerpt), 0, 255, '...'),
114                 'blog_name' => $page_title . ' (' . PLUGIN_TRACKBACK_VERSION . ')',
115                 'charset'   => SOURCE_ENCODING // Ping text encoding (Not defined)
116         );
117
118         foreach ($links as $link) {
119                 $tb_id = tb_get_url($link);  // Get Trackback ID from the URL
120                 if (empty($tb_id)) continue; // Trackback is not supported
121
122                 $result = http_request($tb_id, 'POST', '', $putdata, 2, CONTENT_CHARSET);
123                 // FIXME: Create warning notification space at pukiwiki.skin!
124         }
125 }
126
127 // Remove TrackBack ping data
128 function tb_delete($page)
129 {
130         $filename = tb_get_filename($page);
131         if (file_exists($filename)) @unlink($filename);
132 }
133
134 // Import TrackBack ping data from file
135 function tb_get($file, $key = 1)
136 {
137         if (! file_exists($file)) return array();
138
139         $result = array();
140         $fp = @fopen($file, 'r');
141         set_file_buffer($fp, 0);
142         flock($fp, LOCK_EX);
143         rewind($fp);
144         while ($data = @fgetcsv($fp, 8192, ',')) {
145                 // $data[$key] = URL
146                 $result[rawurldecode($data[$key])] = $data;
147         }
148         flock($fp, LOCK_UN);
149         fclose ($fp);
150
151         return $result;
152 }
153
154 // Get a RDF comment to bury TrackBack-ping-URI under HTML(XHTML) output
155 function tb_get_rdf($page)
156 {
157         $_script = get_script_uri(); // Get absolute path
158         $r_page = rawurlencode($page);
159         $tb_id  = tb_get_id($page);
160         // $dcdate = substr_replace(get_date('Y-m-d\TH:i:sO', $time), ':', -2, 0);
161         // dc:date="$dcdate"
162
163         return <<<EOD
164 <!--
165 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
166   xmlns:dc="http://purl.org/dc/elements/1.1/"
167   xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
168  <rdf:Description
169    rdf:about="$_script?$r_page"
170    dc:identifier="$_script?$r_page"
171    dc:title="$page"
172    trackback:ping="$_script?tb_id=$tb_id" />
173 </rdf:RDF>
174 -->
175 EOD;
176 }
177
178 // HTTP-GET from $uri, and reveal the TrackBack Ping URL
179 function tb_get_url($url)
180 {
181         global $use_proxy, $no_proxy;
182
183         // Don't go across HTTP-proxy server
184         $parse_url = parse_url($url);
185         if (empty($parse_url['host']) ||
186            ($use_proxy && ! in_the_net($no_proxy, $parse_url['host'])))
187                 return '';
188
189         $data = http_request($url);
190         if ($data['rc'] !== 200) return '';
191
192         $matches = array();
193         if (! preg_match_all('#<rdf:RDF[^>]*xmlns:trackback=[^>]*>(.*?)</rdf:RDF>#si', $data['data'],
194             $matches, PREG_PATTERN_ORDER))
195                 return '';
196
197         $obj = new TrackBack_XML();
198         foreach ($matches[1] as $body) {
199                 $tb_url = $obj->parse($body, $url);
200                 if ($tb_url !== FALSE) return $tb_url;
201         }
202
203         return '';
204 }
205
206 // Parse and reveal the TrackBack Ping URL from RDF(XML) data
207 class TrackBack_XML
208 {
209         var $url;
210         var $tb_url;
211
212         function parse($buf, $url)
213         {
214                 // Init
215                 $this->url    = $url;
216                 $this->tb_url = FALSE;
217
218                 $xml_parser = xml_parser_create();
219                 if ($xml_parser === FALSE) return FALSE;
220
221                 xml_set_element_handler($xml_parser, array(& $this, 'start_element'),
222                         array(& $this, 'end_element'));
223
224                 if (! xml_parse($xml_parser, $buf, TRUE)) {
225 /*                      die(sprintf('XML error: %s at line %d in %s',
226                                 xml_error_string(xml_get_error_code($xml_parser)),
227                                 xml_get_current_line_number($xml_parser),
228                                 $buf));
229 */
230                         return FALSE;
231                 }
232
233                 return $this->tb_url;
234         }
235
236         function start_element($parser, $name, $attrs)
237         {
238                 if ($name !== 'RDF:DESCRIPTION') return;
239
240                 $about = $url = $tb_url = '';
241                 foreach ($attrs as $key=>$value) {
242                         switch ($key) {
243                         case 'RDF:ABOUT'     : $about  = $value; break;
244                         case 'DC:IDENTIFER'  : /*FALLTHROUGH*/
245                         case 'DC:IDENTIFIER' : $url    = $value; break;
246                         case 'TRACKBACK:PING': $tb_url = $value; break;
247                         }
248                 }
249                 if ($about == $this->url || $url == $this->url)
250                         $this->tb_url = $tb_url;
251         }
252
253         function end_element($parser, $name) {}
254 }
255
256 // Save or update referer data
257 function ref_save($page)
258 {
259         global $referer;
260
261         if (PKWK_READONLY || ! $referer || empty($_SERVER['HTTP_REFERER'])) return TRUE;
262
263         $url = $_SERVER['HTTP_REFERER'];
264
265         // Validate URI (Ignore own)
266         $parse_url = parse_url($url);
267         if (empty($parse_url['host']) || $parse_url['host'] == $_SERVER['HTTP_HOST'])
268                 return TRUE;
269
270         if (! is_dir(TRACKBACK_DIR))      die('No such directory: TRACKBACK_DIR');
271         if (! is_writable(TRACKBACK_DIR)) die('Permission denied to write: TRACKBACK_DIR');
272
273         // Update referer data
274         if (ereg("[,\"\n\r]", $url))
275                 $url = '"' . str_replace('"', '""', $url) . '"';
276
277         $filename = tb_get_filename($page, '.ref');
278         $data     = tb_get($filename, 3);
279         $d_url    = rawurldecode($url);
280         if (! isset($data[$d_url])) {
281                 $data[$d_url] = array(
282                         '',    // [0]: Last update date
283                         UTIME, // [1]: Creation date
284                         0,     // [2]: Reference counter
285                         $url,  // [3]: Referer header
286                         1      // [4]: Enable / Disable flag (1 = enable)
287                 );
288         }
289         $data[$d_url][0] = UTIME;
290         $data[$d_url][2]++;
291
292         $fp = fopen($filename, 'w');
293         if ($fp === FALSE) return FALSE;        
294         set_file_buffer($fp, 0);
295         flock($fp, LOCK_EX);
296         rewind($fp);
297         foreach ($data as $line)
298                 fwrite($fp, join(',', $line) . "\n");
299         flock($fp, LOCK_UN);
300         fclose($fp);
301
302         return TRUE;
303 }
304 ?>