Index: branches/release-34/lib/MT/App/Search.pm
===================================================================
--- branches/release-34/lib/MT/App/Search.pm (revision 1855)
+++ branches/release-34/lib/MT/App/Search.pm (revision 1882)
@@ -33,6 +33,8 @@
     #}
     $app->_register_core_callbacks({
-        'search_post_execute' => \&log_search,
-        'search_post_render'  => \&cache_out,
+        'MT::App::Search::search_post_execute' => \&log_search,
+        'MT::App::Search::search_post_render'  => \&cache_out,
+        'MT::App::Search::prepare_throttle'    => \&_default_throttle,
+        'MT::App::Search::take_down'           => \&_default_takedown,
     });
     $app;
@@ -88,4 +90,5 @@
         delete $app->{$_} if exists $app->{$_}
     }
+    delete $app->{__have_throttle} if exists $app->{__have_throttle};
 
     my %no_override;
@@ -115,5 +118,5 @@
     my $processed = 0;
     my $list      = {};
-    if ( MT->run_callbacks( 'search_blog_list', $app, $list, \$processed ) ) {
+    if ( $app->run_callbacks( 'search_blog_list', $app, $list, \$processed ) ) {
         if ( $processed ) {
             $app->{searchparam}{IncludeBlogs} = $list;
@@ -243,4 +246,7 @@
 sub process {
     my $app = shift;
+
+    my @messages;
+    return $app->throttle_response(\@messages) unless $app->throttle_control(\@messages);
 
     my ( $count, $out ) = $app->check_cache();
@@ -773,4 +779,76 @@
 }
 
+# throttling related methods
+sub throttle_control {
+    my $app = shift;
+    my ( $messages ) = @_;
+    my $result;
+    $app->run_callbacks( 'prepare_throttle', $app, \$result, $messages );
+    $result;
+}
+
+sub throttle_response {
+    my $app = shift;
+    my ( $messages ) = @_;
+    my $tmpl = $app->param('Template') || '';
+    if ($tmpl eq 'feed') {
+        $app->response_code(503);
+        $app->set_header('Retry-After' => $app->config('ThrottleSeconds'));
+        $app->send_http_header("text/plain");
+        $app->{no_print_body} = 1;
+    }
+    my $msg = $messages && @$messages
+      ? join '; ', @$messages
+      : $app->translate('Throttled');
+    return $app->error($msg);
+}
+
+sub _default_throttle {
+    my ( $cb, $app, $result, $messages ) = @_;
+
+    # Don't bother if a callback proiritized higher
+    # set up its throttle already
+    return $$result if defined $$result;
+
+    ## Get login information if user is logged in (via cookie).
+    ## If no login cookie, this fails silently, and that's fine.
+    ($app->{user}) = $app->login;
+
+    ## Don't throttle MT registered users
+    if ( $app->{user} && $app->{user}->type == MT::Author::AUTHOR() ) {
+        $$result = 1;
+        return 1;
+    }
+
+    my $ip = $app->remote_ip;
+    my $whitelist = $app->config->SearchThrottleIPWhitelist;
+    if ($whitelist) {
+        # check for $ip in $whitelist
+        my @list = split /(\s*[,;]\s*|\s+)/, $whitelist;
+        foreach (@list) {
+            next unless $_ =~ m/^\d{1,3}(\.\d{0,3}){0,3}$/;
+            if (($ip eq $_) || ($ip =~ m/^\Q$_\E/)) {
+                $$result = 1;
+                return 1;
+            }
+        }
+    }
+
+    unless ( $^O eq 'Win32' ) {
+        # Use SIGALRM to stop processing in 5 seconds for each request
+        $SIG{ALRM} = sub { $app->errtrans('Throttled'); die; };
+        $app->{__have_throttle} = 1;
+        alarm($app->config->SearchThrottleSeconds);
+        $$result = 1;
+    }
+    1;
+}
+
+sub _default_takedown {
+    my ( $cb, $app ) = @_;
+    alarm(0) if $app->{__have_throttle};
+    1;
+}
+
 1;
 __END__
@@ -820,4 +898,20 @@
 the app must not overwrite the blog list created by the plugin.
 
+=item prepare_throttle
+
+    callback($cb, $app, \$result, \@messages);
+
+Called right before the beginning of the search processing.
+Each callback should see if certain condition is met, and
+set 0 to $$result if the request should be throttled.
+
+There can be more than one throttling method set up.
+Callbacks are called in order of priority set up when add_callback
+was called.  Each callback should start its own code by something like
+below, to prevent itself overwriting throttle set up in the callback
+whose priority is higher than itself.
+
+    return $$result if defined $$result;
+
 =head1 AUTHOR & COPYRIGHT
 
