diff --git a/modules/node.module b/modules/node.module
index 739be0142f7b53c77122999e9706681938845a22..b5facee02d12fb42e524827f5c12547ba0b75ca0 100644
--- a/modules/node.module
+++ b/modules/node.module
@@ -77,7 +77,7 @@ function node_system($field){
 function node_title_list($result, $title = NULL) {
   while ($node = db_fetch_object($result)) {
     $number = module_invoke("comment", "num_all", $node->nid);
-    $items[] = l($node->title, "node/view/$node->nid", array("title" => format_plural($number, "%count comment", "%count comments")));
+    $items[] = l($node->title, node_url($node), array("title" => format_plural($number, "%count comment", "%count comments")));
   }
 
   return theme("theme_node_list", $items, $title);
@@ -218,30 +218,6 @@ function node_teaser($body) {
 
   return substr($body, 0, $size);
 }
-/*
-function node_invoke() {
-
-  $args = func_get_args();
-
-  $node = array_shift($args);
-  $hook = array_shift($args);
-  array_unshift($args, $node);
-
-  if (is_array($node)) {
-    $function = $node["type"] ."_$hook";
-  }
-  else if (is_object($node)) {
-    $function = $node->type ."_$hook";
-  }
-  else if (is_string($node)) {
-    $function = $node ."_$hook";
-  }
-
-  if (function_exists($function)) {
-    return call_user_func_array($function, $args);
-  }
-}
-*/
 
 function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
   if (is_array($node)) {
@@ -489,7 +465,7 @@ function node_access($op, $node = 0) {
 }
 
 function node_perm() {
-  return array("administer nodes", "access content");
+  return array("administer nodes", "access content", "create custom URLs");
 }
 
 function node_search($keys) {
@@ -629,7 +605,7 @@ function node_admin_edit($node) {
     $output .= "<table border=\"1\" cellpadding=\"2\" cellspacing=\"2\">";
     $output .= " <tr><th>". t("older revisions") ."</th><th colspan=\"3\">". t("operations") ."</th></tr>";
     foreach ($node->revisions as $key => $revision) {
-      $output .= " <tr><td>". t("revision #%r revised by %u on %d", array("%r" => $key, "%u" => format_name(user_load(array("uid" => $revision["uid"]))), "%d" => format_date($revision["timestamp"], "small"))) . ($revision["history"] ? "<br /><small>". $revision["history"] ."</small>" : "") ."</td><td>". l(t("view revision"), "node/view/$node->nid", array(), "revision=$key") ."</td><td>". l(t("rollback revision"), "admin/node/rollback+revision/$node->nid/$key") ."</td><td>". l(t("delete revision"), "admin/node/delete+revision/$node->nid/$key") ."</td></tr>";
+      $output .= " <tr><td>". t("revision #%r revised by %u on %d", array("%r" => $key, "%u" => format_name(user_load(array("uid" => $revision["uid"]))), "%d" => format_date($revision["timestamp"], "small"))) . ($revision["history"] ? "<br /><small>". $revision["history"] ."</small>" : "") ."</td><td>". l(t("view revision"), node_url($node), array(), "revision=$key") ."</td><td>". l(t("rollback revision"), "admin/node/rollback+revision/$node->nid/$key") ."</td><td>". l(t("delete revision"), "admin/node/delete+revision/$node->nid/$key") ."</td></tr>";
     }
     $output .= "</table>";
   }
@@ -724,7 +700,7 @@ function node_admin_nodes() {
   $header = array(NULL, t("title"), t("type"), t("author"), t("status"), array ("data" => t("operations"), "colspan" => 2));
 
   while ($node = db_fetch_object($result)) {
-    $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, "node/view/$node->nid") ." ". (node_is_new($node->nid, $node->changed) ? theme_mark() : ""), module_invoke($node->type, "node", "name"), format_name($node), ($node->status ? t("published") : t("not published")), l(t("edit node"), "admin/node/edit/$node->nid"), l(t("delete node"), "admin/node/delete/$node->nid"));
+    $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, node_url($node)) ." ". (node_is_new($node->nid, $node->changed) ? theme_mark() : ""), module_invoke($node->type, "node", "name"), format_name($node), ($node->status ? t("published") : t("not published")), l(t("edit node"), "admin/node/edit/$node->nid"), l(t("delete node"), "admin/node/delete/$node->nid"));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
@@ -971,6 +947,23 @@ function node_block($op = "list", $delta = 0) {
   }
 }
 
+function node_get_alias($path) {
+
+  $result = db_query("SELECT nid FROM {node} WHERE path = '%s'", trim($path, "/"));
+  if ($node = db_fetch_object($result)) {
+    return "node/view/$node->nid";
+  }
+}
+
+function node_url($node) {
+  if ($node->path != NULL) {
+    return $node->path;
+  }
+  else {
+    return "node/view/$node->nid";
+  }
+}
+
 function node_feed($nodes = 0, $channel = array()) {
   global $base_url, $languages;
 
@@ -997,7 +990,7 @@ function node_feed($nodes = 0, $channel = array()) {
     ** Transform the node information into an RSS item:
     */
 
-    $items .= format_rss_item($item->title, url("node/view/$nide->nid"), ($item->teaser ? $item->teaser : $item->body));
+    $items .= format_rss_item($item->title, url(node_url($node)), ($item->teaser ? $item->teaser : $item->body));
 
     /*
     ** Determine the publication date:
@@ -1048,6 +1041,19 @@ function node_validate($node, &$error) {
     }
   }
 
+  /*
+  ** Clean the path field:
+  */
+
+  if ($node->path) {
+    if (!valid_url($node->path)) {
+      $error["path"] = theme("theme_error", t("The specified path is not valid."));
+    }
+    else if (db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE nid != %d AND path = '%s'", $node->nid, $node->path))) {
+      $error["path"] = theme("theme_error", t("The specified path is already in use."));
+    }
+  }
+
   /*
   ** Common default values:
   */
@@ -1240,6 +1246,10 @@ function node_form($edit, $error = NULL) {
   $output .= "<div class=\"standard\">";
   $output .= form_textfield(t("Title"), "title", $edit->title, 60, 128, $error["title"]);
 
+  if (user_access("create custom URLs")) {
+    $output .= form_textfield(t("Path alias"), "path", ($edit->path == "node/view/$edit->nid") ? "" : $edit->path, 60, 250, $error["path"] ? $error["path"] : t("Optionally specify an alternative URL by which this node can be accessed.  For example, type 'about' when writing an about page.  Don't add a trailing slash or the URL won't work."));
+  }
+
   /*
   ** Add the node specific fields:
   */
@@ -1461,7 +1471,7 @@ function node_submit($node) {
 
     if (node_access("update", $node)) {
       $node->nid = node_save($node);
-      watchdog("special", "$node->type: updated '$node->title'", l(t("view post"), "node/view/$node->nid"));
+      watchdog("special", "$node->type: updated '$node->title'", l(t("view post"), node_url($node)));
       $output = t("The %name has been updated.", array ("%name" => module_invoke($node->type, "node", "name")));
     }
   }
@@ -1482,7 +1492,7 @@ function node_submit($node) {
       throttle("node", variable_get("max_node_rate", 900));
 
       $node->nid = node_save($node);
-      watchdog("special", "$node->type: added '$node->title'", l(t("view post"), "node/view/$node->nid"));
+      watchdog("special", "$node->type: added '$node->title'", l(t("view post"), node_url($node)));
       $output = t("Thanks for your submission.");
     }
   }
@@ -1503,7 +1513,7 @@ function node_submit($node) {
   }
 
   if ($node->nid && node_access("view", $node)) {
-    $links[] = l(t("view"), "node/view/$node->nid");
+    $links[] = l(t("view"), node_url($node));
   }
 
   if ($node->nid && node_access("update", $node)) {
diff --git a/modules/node/node.module b/modules/node/node.module
index 739be0142f7b53c77122999e9706681938845a22..b5facee02d12fb42e524827f5c12547ba0b75ca0 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -77,7 +77,7 @@ function node_system($field){
 function node_title_list($result, $title = NULL) {
   while ($node = db_fetch_object($result)) {
     $number = module_invoke("comment", "num_all", $node->nid);
-    $items[] = l($node->title, "node/view/$node->nid", array("title" => format_plural($number, "%count comment", "%count comments")));
+    $items[] = l($node->title, node_url($node), array("title" => format_plural($number, "%count comment", "%count comments")));
   }
 
   return theme("theme_node_list", $items, $title);
@@ -218,30 +218,6 @@ function node_teaser($body) {
 
   return substr($body, 0, $size);
 }
-/*
-function node_invoke() {
-
-  $args = func_get_args();
-
-  $node = array_shift($args);
-  $hook = array_shift($args);
-  array_unshift($args, $node);
-
-  if (is_array($node)) {
-    $function = $node["type"] ."_$hook";
-  }
-  else if (is_object($node)) {
-    $function = $node->type ."_$hook";
-  }
-  else if (is_string($node)) {
-    $function = $node ."_$hook";
-  }
-
-  if (function_exists($function)) {
-    return call_user_func_array($function, $args);
-  }
-}
-*/
 
 function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
   if (is_array($node)) {
@@ -489,7 +465,7 @@ function node_access($op, $node = 0) {
 }
 
 function node_perm() {
-  return array("administer nodes", "access content");
+  return array("administer nodes", "access content", "create custom URLs");
 }
 
 function node_search($keys) {
@@ -629,7 +605,7 @@ function node_admin_edit($node) {
     $output .= "<table border=\"1\" cellpadding=\"2\" cellspacing=\"2\">";
     $output .= " <tr><th>". t("older revisions") ."</th><th colspan=\"3\">". t("operations") ."</th></tr>";
     foreach ($node->revisions as $key => $revision) {
-      $output .= " <tr><td>". t("revision #%r revised by %u on %d", array("%r" => $key, "%u" => format_name(user_load(array("uid" => $revision["uid"]))), "%d" => format_date($revision["timestamp"], "small"))) . ($revision["history"] ? "<br /><small>". $revision["history"] ."</small>" : "") ."</td><td>". l(t("view revision"), "node/view/$node->nid", array(), "revision=$key") ."</td><td>". l(t("rollback revision"), "admin/node/rollback+revision/$node->nid/$key") ."</td><td>". l(t("delete revision"), "admin/node/delete+revision/$node->nid/$key") ."</td></tr>";
+      $output .= " <tr><td>". t("revision #%r revised by %u on %d", array("%r" => $key, "%u" => format_name(user_load(array("uid" => $revision["uid"]))), "%d" => format_date($revision["timestamp"], "small"))) . ($revision["history"] ? "<br /><small>". $revision["history"] ."</small>" : "") ."</td><td>". l(t("view revision"), node_url($node), array(), "revision=$key") ."</td><td>". l(t("rollback revision"), "admin/node/rollback+revision/$node->nid/$key") ."</td><td>". l(t("delete revision"), "admin/node/delete+revision/$node->nid/$key") ."</td></tr>";
     }
     $output .= "</table>";
   }
@@ -724,7 +700,7 @@ function node_admin_nodes() {
   $header = array(NULL, t("title"), t("type"), t("author"), t("status"), array ("data" => t("operations"), "colspan" => 2));
 
   while ($node = db_fetch_object($result)) {
-    $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, "node/view/$node->nid") ." ". (node_is_new($node->nid, $node->changed) ? theme_mark() : ""), module_invoke($node->type, "node", "name"), format_name($node), ($node->status ? t("published") : t("not published")), l(t("edit node"), "admin/node/edit/$node->nid"), l(t("delete node"), "admin/node/delete/$node->nid"));
+    $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, node_url($node)) ." ". (node_is_new($node->nid, $node->changed) ? theme_mark() : ""), module_invoke($node->type, "node", "name"), format_name($node), ($node->status ? t("published") : t("not published")), l(t("edit node"), "admin/node/edit/$node->nid"), l(t("delete node"), "admin/node/delete/$node->nid"));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
@@ -971,6 +947,23 @@ function node_block($op = "list", $delta = 0) {
   }
 }
 
+function node_get_alias($path) {
+
+  $result = db_query("SELECT nid FROM {node} WHERE path = '%s'", trim($path, "/"));
+  if ($node = db_fetch_object($result)) {
+    return "node/view/$node->nid";
+  }
+}
+
+function node_url($node) {
+  if ($node->path != NULL) {
+    return $node->path;
+  }
+  else {
+    return "node/view/$node->nid";
+  }
+}
+
 function node_feed($nodes = 0, $channel = array()) {
   global $base_url, $languages;
 
@@ -997,7 +990,7 @@ function node_feed($nodes = 0, $channel = array()) {
     ** Transform the node information into an RSS item:
     */
 
-    $items .= format_rss_item($item->title, url("node/view/$nide->nid"), ($item->teaser ? $item->teaser : $item->body));
+    $items .= format_rss_item($item->title, url(node_url($node)), ($item->teaser ? $item->teaser : $item->body));
 
     /*
     ** Determine the publication date:
@@ -1048,6 +1041,19 @@ function node_validate($node, &$error) {
     }
   }
 
+  /*
+  ** Clean the path field:
+  */
+
+  if ($node->path) {
+    if (!valid_url($node->path)) {
+      $error["path"] = theme("theme_error", t("The specified path is not valid."));
+    }
+    else if (db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE nid != %d AND path = '%s'", $node->nid, $node->path))) {
+      $error["path"] = theme("theme_error", t("The specified path is already in use."));
+    }
+  }
+
   /*
   ** Common default values:
   */
@@ -1240,6 +1246,10 @@ function node_form($edit, $error = NULL) {
   $output .= "<div class=\"standard\">";
   $output .= form_textfield(t("Title"), "title", $edit->title, 60, 128, $error["title"]);
 
+  if (user_access("create custom URLs")) {
+    $output .= form_textfield(t("Path alias"), "path", ($edit->path == "node/view/$edit->nid") ? "" : $edit->path, 60, 250, $error["path"] ? $error["path"] : t("Optionally specify an alternative URL by which this node can be accessed.  For example, type 'about' when writing an about page.  Don't add a trailing slash or the URL won't work."));
+  }
+
   /*
   ** Add the node specific fields:
   */
@@ -1461,7 +1471,7 @@ function node_submit($node) {
 
     if (node_access("update", $node)) {
       $node->nid = node_save($node);
-      watchdog("special", "$node->type: updated '$node->title'", l(t("view post"), "node/view/$node->nid"));
+      watchdog("special", "$node->type: updated '$node->title'", l(t("view post"), node_url($node)));
       $output = t("The %name has been updated.", array ("%name" => module_invoke($node->type, "node", "name")));
     }
   }
@@ -1482,7 +1492,7 @@ function node_submit($node) {
       throttle("node", variable_get("max_node_rate", 900));
 
       $node->nid = node_save($node);
-      watchdog("special", "$node->type: added '$node->title'", l(t("view post"), "node/view/$node->nid"));
+      watchdog("special", "$node->type: added '$node->title'", l(t("view post"), node_url($node)));
       $output = t("Thanks for your submission.");
     }
   }
@@ -1503,7 +1513,7 @@ function node_submit($node) {
   }
 
   if ($node->nid && node_access("view", $node)) {
-    $links[] = l(t("view"), "node/view/$node->nid");
+    $links[] = l(t("view"), node_url($node));
   }
 
   if ($node->nid && node_access("update", $node)) {