README: Fix typo.
[yourls-gitweb.git] / plugin.php
1 <?php\r
2 /*\r
3 Plugin Name: Gitweb\r
4 Plugin URI: http://octo.it/yourls-gitweb/\r
5 Description: Automatically redirect to a Gitweb installation if an appropriate Git object exists.\r
6 Version: 1.0\r
7 Author: Florian "octo" Forster\r
8 Author URI: http://octo.it/\r
9 */\r
10 \r
11 /**\r
12  * yourls -- Gitweb plugin\r
13  * Copyright (C) 2011  Florian Forster\r
14  *\r
15  * Permission is hereby granted, free of charge, to any person obtaining a\r
16  * copy of this software and associated documentation files (the "Software"),\r
17  * to deal in the Software without restriction, including without limitation\r
18  * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
19  * and/or sell copies of the Software, and to permit persons to whom the\r
20  * Software is furnished to do so, subject to the following conditions:\r
21  *\r
22  * The above copyright notice and this permission notice shall be included in\r
23  * all copies or substantial portions of the Software.\r
24  *\r
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
26  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
27  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
28  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
29  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
30  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r
31  * DEALINGS IN THE SOFTWARE.\r
32  *\r
33  * Authors:\r
34  *   Florian Forster <ff at octo.it>\r
35  **/\r
36 \r
37 function gitweb_check_repository ($obj, $repo, $dir, $base_url) /* {{{ */\r
38 {\r
39   $output = array ();\r
40   $retval = 0;\r
41 \r
42   $cmd = 'git --git-dir=' . escapeshellarg ($dir)\r
43     . ' rev-parse --verify ' . escapeshellarg ($obj)\r
44     . ' 2>/dev/null';\r
45   $obj_name = trim (shell_exec ($cmd));\r
46   if (!$obj_name)\r
47     return (false);\r
48 \r
49   if (!preg_match ('/^[0-9a-fA-F]{40}$/', $obj_name))\r
50   {\r
51     error_log ("git-rev-parse(1) returned unexpected object name: $obj_name");\r
52     return (false);\r
53   }\r
54 \r
55   $cmd = 'git --git-dir=' . escapeshellarg ($dir)\r
56     . ' cat-file -t ' . escapeshellarg ($obj_name)\r
57     . ' 2>/dev/null';\r
58   $obj_type = trim (shell_exec ($cmd));\r
59   if (!$obj_type)\r
60   {\r
61     error_log ("gitweb_check_repository: git-cat-file(1) failed.");\r
62     return (false);\r
63   }\r
64 \r
65   if ($obj_type == 'commit')\r
66   {\r
67     $to_url = "$base_url?p=" . urlencode ($repo) . ';a=commitdiff;h=' . urlencode ($obj_name);\r
68     yourls_redirect ($to_url, /* status = */ 301);\r
69     return (true);\r
70   }\r
71   elseif ($obj_type == 'tag')\r
72   {\r
73     $to_url = "$base_url?p=" . urlencode ($repo) . ';a=tag;h=' . urlencode ($obj_name);\r
74     yourls_redirect ($to_url, /* status = */ 301);\r
75     return (true);\r
76 \r
77   }\r
78   elseif ($obj_type == 'tree')\r
79   {\r
80     $to_url = "$base_url?p=" . urlencode ($repo) . ";a=tree;h=" . urlencode ($obj_name);\r
81     yourls_redirect ($to_url, /* status = */ 301);\r
82     return (true);\r
83   }\r
84   elseif ($obj_type == 'blob')\r
85   {\r
86     $to_url = "$base_url?p=" . urlencode ($repo) . ";a=blob;h=" . urlencode ($obj_name);\r
87     yourls_redirect ($to_url, /* status = */ 301);\r
88     return (true);\r
89   }\r
90   else\r
91   {\r
92     error_log ("Gitweb plugin: Object \"$obj_name\" in repository \"$repo\" has unknown type \"$obj_type\".");\r
93     return (false);\r
94   }\r
95 } /* }}} function gitweb_check_repository */\r
96 \r
97 /* This callback function is called when the given keyword was not found in the \r
98   * database. I'll see if this looks like an object identifier in a Git \r
99   * repository and, if so, try to locate the object using the local \r
100   * repositories. */\r
101 function gitweb_redirect_keyword_not_found ($args) /* {{{ */\r
102 {\r
103   $keyword = $args[0];\r
104 \r
105   if (!preg_match ('/^[0-9a-fA-F]{6,40}$/', $keyword))\r
106     return;\r
107 \r
108   $base_directory = yourls_get_option ('gitweb_base_directory');\r
109   if (!$base_directory)\r
110     return;\r
111 \r
112   $base_url = yourls_get_option ('gitweb_base_url');\r
113   if (!$base_url)\r
114     return;\r
115 \r
116   $dh = opendir ($base_directory);\r
117   if (!$dh)\r
118     return;\r
119 \r
120   while (($subdir = readdir ($dh)) !== false)\r
121   {\r
122     /* Ignore all files and directories starting with a dot, including the \r
123      * special directories "." and "..". */\r
124     if (substr ($subdir, 0, 1) == '.')\r
125       continue;\r
126 \r
127     $absdir = "$base_directory/$subdir";\r
128     if (!is_dir ($absdir))\r
129       continue;\r
130 \r
131     /* Ignore repositories which are private (i.e. not exported by the \r
132      * git-daemon(1). We might leak information if we don't. */\r
133     if (!file_exists ("$absdir/git-daemon-export-ok"))\r
134       continue;\r
135 \r
136     if (gitweb_check_repository ($keyword, $subdir, $absdir, $base_url))\r
137       break;\r
138   }\r
139 \r
140   closedir ($dh);\r
141 } /* }}} function gitweb_redirect_keyword_not_found */\r
142 \r
143 function gitweb_set_base_directory ($dir) /* {{{ */\r
144 {\r
145   /* Remove trailing slashes. */\r
146   $dir = preg_replace ('/\/+$/', '', $dir);\r
147 \r
148   if (!preg_match ('/^\//', $dir))\r
149   {\r
150     print ("<p class=\"error\">Not an absolute path: "\r
151       . htmlspecialchars ($dir)\r
152       . "</p>\n");\r
153     return (false);\r
154   }\r
155 \r
156   if (!is_dir ($dir))\r
157   {\r
158     print ("<p class=\"error\">Not a directory: "\r
159       . htmlspecialchars ($dir)\r
160       . "</p>\n");\r
161     return (false);\r
162   }\r
163 \r
164   /* Open the directory only to check its permissions. */\r
165   $dh = opendir ($dir);\r
166   if (!$dh)\r
167   {\r
168     print ("<p class=\"error\">Unable to open directory.</p>\n");\r
169     return (false);\r
170   }\r
171   closedir ($dh);\r
172 \r
173   yourls_update_option ('gitweb_base_directory', $dir);\r
174   return (true);\r
175 } /* }}} function gitweb_set_base_directory */\r
176 \r
177 function gitweb_set_base_url ($url) /* {{{ */\r
178 {\r
179   if (!preg_match ('/https?:\/\//i', $url))\r
180   {\r
181     print ("<p class=\"error\">This does not look like a valid URL: "\r
182       . htmlspecialchars ($url)\r
183       . "</p>\n");\r
184     return (false);\r
185   }\r
186 \r
187   $url = preg_replace ('/\?.*/', '', $url);\r
188 \r
189   yourls_update_option ('gitweb_base_url', $url);\r
190   return (true);\r
191 } /* }}} function gitweb_set_base_directory */\r
192 \r
193 function gitweb_show_plugin_page () /* {{{ */\r
194 {\r
195   echo <<<HTML\r
196     <h2>Gitweb Plugin Administration Page</h2>\r
197     <p>This plugin redirects to a Gitweb installation if a keyword wasn't\r
198       found in the database, looks like a Git object ID and is found in a\r
199       local Git repository.</p>\r
200 HTML;\r
201 \r
202   if (isset ($_POST['base_directory']))\r
203     gitweb_set_base_directory ($_POST['base_directory']);\r
204 \r
205   if (isset ($_POST['base_url']))\r
206     gitweb_set_base_url ($_POST['base_url']);\r
207 \r
208   $base_directory = yourls_get_option ('gitweb_base_directory');\r
209   if ($base_directory)\r
210     $base_directory = htmlspecialchars ($base_directory);\r
211 \r
212   $base_url = yourls_get_option ('gitweb_base_url');\r
213   if ($base_url)\r
214     $base_url = htmlspecialchars ($base_url);\r
215 \r
216   echo <<<HTML\r
217     <form method="post">\r
218       <table style="background-color: #cdcdcd; border-spacing: 1px;">\r
219         <tr>\r
220           <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_directory">Base directory</label></th>\r
221           <td style="background-color: white; padding: 4px;"><input type="text" id="base_directory" name="base_directory" value="$base_directory" /></td>\r
222         </tr>\r
223         <tr>\r
224           <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_url">Gitweb URL</label></th>\r
225           <td style="background-color: white; padding: 4px;"><input type="text" id="base_url" name="base_url" value="$base_url" /></td>\r
226         </tr>\r
227         <tr>\r
228           <td colspan="2" style="border: 1px solid white; background-color: #E3F3FF; text-align: right; padding: 4px;"><input type="submit" value="Update" class="button primary" /></td>\r
229         </tr>\r
230       </table>\r
231     </form>\r
232 HTML;\r
233 } /* }}} function gitweb_show_plugin_page */\r
234 \r
235 function gitweb_register_plugin_page () /* {{{ */\r
236 {\r
237   yourls_register_plugin_page ('gitweb_page', 'Gitweb',\r
238     'gitweb_show_plugin_page');\r
239 } /* }}} function gitweb_register_plugin_page */\r
240 \r
241 yourls_add_action ('plugins_loaded', 'gitweb_register_plugin_page');\r
242 yourls_add_action ('redirect_keyword_not_found', 'gitweb_redirect_keyword_not_found');\r
243 \r
244 /* vim: set sw=2 sts=2 et fdm=marker : */\r
245 ?>\r