Initial commit.
authorFlorian Forster <octo@verplant.org>
Sun, 16 Jan 2011 13:31:25 +0000 (14:31 +0100)
committerFlorian Forster <octo@verplant.org>
Sun, 16 Jan 2011 13:31:25 +0000 (14:31 +0100)
README [new file with mode: 0644]
plugin.php [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ddd41cf
--- /dev/null
+++ b/README
@@ -0,0 +1,58 @@
+Gitweb plugin for YOURLS
+========================
+<http://octo.it/yourls-gitweb/>
+
+This plugin can be used to automatically redirect to a Gitweb installation, if
+the provided keywors is not found in the database.
+
+For example, imagine a user opening this URL:
+
+  http://sho.rt/a06c1a52
+
+And further imagine that “a06c1a52” is not a short URL stored in the database.
+This plugin will intercept the “redirect_keyword_not_found” action and check if
+the keyword is a hexadecimal number with at least six (hexadecimal) digits.
+
+If so, the plugin will iterate over all Git repositories in a base directory
+and check if there's an object in one of them which matches the given id. If
+such an object is found, the user will be redirected to the appropriate Gitweb
+page. Otherwise, YOURLS will proceed as usual.
+
+Installation
+------------
+First, you need at least YOURLS, version 1.5. Versions before that do not
+provide a plugin infrastructure.
+
+ * Copy the plugin to “$yourls_dir/user/plugins/”.
+ * Open the administrative interface in the web-browser of your choice.
+ * Open the “Plugins” page and search for the “Gitweb” plugin in the table.
+ * Click “Activate” in the rightmost “Action” column.
+ * A new bulletin “Gitweb” should appear below the link to the “Plugins” page.
+ * Go to the “Gitweb” page.
+ * Insert the base directory and the Gitweb URL. The base directory should be
+   the same directory you specified using “$projectroot” in your Gitweb
+   configuration. You can pass any valid Gitweb URL into the second box –
+   superfluous arguments are automatically stripped.
+ * Click “Update” to store your changes.
+
+Caveats
+-------
+The plugin executes the “git” command line utility to check for objects in the
+configured Git repositories. Great care has been taken that every argument
+passed to the shell has been sanitized, but you may experience problems if
+PHP's “safe mode” is activated. The commands used are:
+
+ * git rev-parse
+   Check existence of an object and determine it's complete object id.
+ * git cat-file
+   Determine an object's type.
+
+License
+-------
+The “Gitweb” plugin is distributed under the terms of the MIT license. The
+complete licensing terms can be found at the beginning of the file
+“plugin.php”.
+
+Author
+------
+Florian “octo” Forster <ff at octo.it>
diff --git a/plugin.php b/plugin.php
new file mode 100644 (file)
index 0000000..9759767
--- /dev/null
@@ -0,0 +1,234 @@
+<?php\r
+/*\r
+Plugin Name: Gitweb\r
+Plugin URI: http://octo.it/yourls-gitweb/\r
+Description: Automatically redirect to a Gitweb installation if an appropriate Git object exists.\r
+Version: 1.0\r
+Author: Florian "octo" Forster\r
+Author URI: http://octo.it/\r
+*/\r
+\r
+/**\r
+ * yourls -- Gitweb plugin\r
+ * Copyright (C) 2011  Florian Forster\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r
+ * DEALINGS IN THE SOFTWARE.\r
+ *\r
+ * Authors:\r
+ *   Florian Forster <ff at octo.it>\r
+ **/\r
+\r
+function gitweb_check_repository ($obj, $repo, $dir, $url) /* {{{ */\r
+{\r
+  $output = array ();\r
+  $retval = 0;\r
+\r
+  $obj_name = shell_exec ('git --git-dir=' . escapeshellarg ($dir)\r
+    . ' rev-parse ' . escapeshellarg ($obj)\r
+    . ' 2>/dev/null');\r
+  if (!$obj_type)\r
+    return (false);\r
+\r
+  $obj_type = shell_exec ('git --git-dir=' . escapeshellarg ($dir)\r
+    . ' cat-file -t ' . escapeshellarg ($obj_name)\r
+    . ' 2>/dev/null');\r
+  if (!$obj_type)\r
+    return (false);\r
+\r
+  if ($obj_type == 'commit')\r
+  {\r
+    $to_url = "$url?p=" . urlencode ($repo) . ';a=commitdiff;h=' . urlencode ($obj_name);\r
+    yourls_redirect ($to_url, /* status = */ 301);\r
+    return (true);\r
+  }\r
+  elseif ($obj_type == 'tag')\r
+  {\r
+    $to_url = "$url?p=" . urlencode ($repo) . ';a=tag;h=' . urlencode ($obj_name);\r
+    yourls_redirect ($to_url, /* status = */ 301);\r
+    return (true);\r
+\r
+  }\r
+  elseif ($obj_type == 'tree')\r
+  {\r
+    $to_url = "$url?p=" . urlencode ($repo) . ";a=tree;h=" . urlencode ($obj_name);\r
+    yourls_redirect ($to_url, /* status = */ 301);\r
+    return (true);\r
+  }\r
+  elseif ($obj_type == 'blob')\r
+  {\r
+    $to_url = "$url?p=" . urlencode ($repo) . ";a=blob;h=" . urlencode ($obj_name);\r
+    yourls_redirect ($to_url, /* status = */ 301);\r
+    return (true);\r
+  }\r
+  else\r
+  {\r
+    error_log ("Gitweb plugin: Object \"$obj_name\" in repository \"$repo\" has unknown type \"$obj_type\".");\r
+    return (false);\r
+  }\r
+} /* }}} function gitweb_check_repository */\r
+\r
+/* This callback function is called when the given keyword was not found in the \r
+  * database. I'll see if this looks like an object identifier in a Git \r
+  * repository and, if so, try to locate the object using the local \r
+  * repositories. */\r
+function gitweb_redirect_keyword_not_found ($args) /* {{{ */\r
+{\r
+  $keyword = $args[0];\r
+\r
+  if (!preg_match ('/^[0-9a-fA-F]{6,40}$/', $keyword))\r
+    return;\r
+\r
+  $base_directory = yourls_get_option ('gitweb_base_directory');\r
+  if (!$base_directory)\r
+    return;\r
+\r
+  $base_url = yourls_get_option ('gitweb_base_url');\r
+  if (!$base_url)\r
+    return;\r
+\r
+  $dh = opendir ($base_directory);\r
+  if (!$dh)\r
+    return;\r
+\r
+  while (($subdir = readdir ($dh)) !== false)\r
+  {\r
+    /* Ignore all files and directories starting with a dot, including the \r
+     * special directories "." and "..". */\r
+    if (substr ($subdir, 0, 1) == '.')\r
+      continue;\r
+\r
+    $absdir = "$base_directory/$subdir";\r
+    if (!is_dir ($absdir))\r
+      continue;\r
+\r
+    /* Ignore repositories which are private (i.e. not exported by the \r
+     * git-daemon(1). We might leak information if we don't. */\r
+    if (!file_exists ("$absdir/git-daemon-export-ok"))\r
+      continue;\r
+\r
+    if (gitweb_check_repository ($keyword, $subdir, $absdir, $base_url))\r
+      break;\r
+  }\r
+\r
+  closedir ($dh);\r
+} /* }}} function gitweb_redirect_keyword_not_found */\r
+\r
+function gitweb_set_base_directory ($dir) /* {{{ */\r
+{\r
+  /* Remove trailing slashes. */\r
+  $dir = preg_replace ('/\/+$/', '', $dir);\r
+\r
+  if (!preg_match ('/^\//', $dir))\r
+  {\r
+    print ("<p class=\"error\">Not an absolute path: "\r
+      . htmlspecialchars ($dir)\r
+      . "</p>\n");\r
+    return (false);\r
+  }\r
+\r
+  if (!is_dir ($dir))\r
+  {\r
+    print ("<p class=\"error\">Not a directory: "\r
+      . htmlspecialchars ($dir)\r
+      . "</p>\n");\r
+    return (false);\r
+  }\r
+\r
+  /* Open the directory only to check its permissions. */\r
+  $dh = opendir ($dir);\r
+  if (!$dh)\r
+  {\r
+    print ("<p class=\"error\">Unable to open directory.</p>\n");\r
+    return (false);\r
+  }\r
+  closedir ($dh);\r
+\r
+  yourls_update_option ('gitweb_base_directory', $dir);\r
+  return (true);\r
+} /* }}} function gitweb_set_base_directory */\r
+\r
+function gitweb_set_base_url ($url) /* {{{ */\r
+{\r
+  if (!preg_match ('/https?:\/\//i', $url))\r
+  {\r
+    print ("<p class=\"error\">This does not look like a valid URL: "\r
+      . htmlspecialchars ($url)\r
+      . "</p>\n");\r
+    return (false);\r
+  }\r
+\r
+  $url = preg_replace ('/\?.*/', '', $url);\r
+\r
+  yourls_update_option ('gitweb_base_url', $url);\r
+  return (true);\r
+} /* }}} function gitweb_set_base_directory */\r
+\r
+function gitweb_show_plugin_page () /* {{{ */\r
+{\r
+  echo <<<HTML\r
+    <h2>Gitweb Plugin Administration Page</h2>\r
+    <p>This plugin redirects to a Gitweb installation if a keyword wasn't\r
+      found in the database, looks like a Git object ID and is found in a\r
+      local Git repository.</p>\r
+HTML;\r
+\r
+  if (isset ($_POST['base_directory']))\r
+    gitweb_set_base_directory ($_POST['base_directory']);\r
+\r
+  if (isset ($_POST['base_url']))\r
+    gitweb_set_base_url ($_POST['base_url']);\r
+\r
+  $base_directory = yourls_get_option ('gitweb_base_directory');\r
+  if ($base_directory)\r
+    $base_directory = htmlspecialchars ($base_directory);\r
+\r
+  $base_url = yourls_get_option ('gitweb_base_url');\r
+  if ($base_url)\r
+    $base_url = htmlspecialchars ($base_url);\r
+\r
+  echo <<<HTML\r
+    <form method="post">\r
+      <table style="background-color: #cdcdcd; border-spacing: 1px;">\r
+        <tr>\r
+          <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_directory">Base directory</label></th>\r
+          <td style="background-color: white; padding: 4px;"><input type="text" id="base_directory" name="base_directory" value="$base_directory" /></td>\r
+        </tr>\r
+        <tr>\r
+          <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_url">Gitweb URL</label></th>\r
+          <td style="background-color: white; padding: 4px;"><input type="text" id="base_url" name="base_url" value="$base_url" /></td>\r
+        </tr>\r
+        <tr>\r
+          <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
+        </tr>\r
+      </table>\r
+    </form>\r
+HTML;\r
+} /* }}} function gitweb_show_plugin_page */\r
+\r
+function gitweb_register_plugin_page () /* {{{ */\r
+{\r
+  yourls_register_plugin_page ('gitweb_page', 'Gitweb',\r
+    'gitweb_show_plugin_page');\r
+} /* }}} function gitweb_register_plugin_page */\r
+\r
+yourls_add_action ('plugins_loaded', 'gitweb_register_plugin_page');\r
+yourls_add_action ('redirect_keyword_not_found', 'gitweb_redirect_keyword_not_found');\r
+\r
+/* vim: set sw=2 sts=2 et fdm=marker : */\r
+?>\r