checkout: introduce --detach synonym for "git checkout foo^{commit}"
[gitweb.git] / gitweb / gitweb.perl
index b044d18acaff0527a4da9a37f8587be955eb4f0f..59b2e08b980b968e3ba5e282fcb237b1af5bb579 100755 (executable)
@@ -7,11 +7,12 @@
 #
 # This program is licensed under the GPLv2
 
+use 5.008;
 use strict;
 use warnings;
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
-use CGI::Carp qw(fatalsToBrowser);
+use CGI::Carp qw(fatalsToBrowser set_message);
 use Encode;
 use Fcntl ':mode';
 use File::Find qw();
@@ -232,6 +233,29 @@ sub evaluate_uri {
 # Leave it undefined (or set to 'undef') to turn off load checking.
 our $maxload = 300;
 
+# configuration for 'highlight' (http://www.andre-simon.de/)
+# match by basename
+our %highlight_basename = (
+       #'Program' => 'py',
+       #'Library' => 'py',
+       'SConstruct' => 'py', # SCons equivalent of Makefile
+       'Makefile' => 'make',
+);
+# match by extension
+our %highlight_ext = (
+       # main extensions, defining name of syntax;
+       # see files in /usr/share/highlight/langDefs/ directory
+       map { $_ => $_ }
+               qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+       # alternate extensions, see /etc/highlight/filetypes.conf
+       'h' => 'c',
+       map { $_ => 'cpp' } qw(cxx c++ cc),
+       map { $_ => 'php' } qw(php3 php4),
+       map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+       'mak' => 'make',
+       map { $_ => 'xml' } qw(xhtml html htm),
+);
+
 # You define site-wide feature defaults here; override them with
 # $GITWEB_CONFIG as necessary.
 our %feature = (
@@ -245,7 +269,7 @@ sub evaluate_uri {
        # return value of feature-sub indicates if to enable specified feature
        #
        # if there is no 'sub' key (no feature-sub), then feature cannot be
-       # overriden
+       # overridden
        #
        # use gitweb_get_feature(<feature>) to retrieve the <feature> value
        # (an array) or gitweb_check_feature(<feature>) to check if <feature>
@@ -450,6 +474,19 @@ sub evaluate_uri {
        'javascript-actions' => {
                'override' => 0,
                'default' => [0]},
+
+       # Syntax highlighting support. This is based on Daniel Svensson's
+       # and Sham Chukoury's work in gitweb-xmms2.git.
+       # It requires the 'highlight' program present in $PATH,
+       # and therefore is disabled by default.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'highlight'}{'default'} = [1];
+
+       'highlight' => {
+               'sub' => sub { feature_bool('highlight', @_) },
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -976,6 +1013,21 @@ sub configure_gitweb_features {
        }
 }
 
+# custom error handler: 'die <message>' is Internal Server Error
+sub handle_errors_html {
+       my $msg = shift; # it is already HTML escaped
+
+       # to avoid infinite loop where error occurs in die_error,
+       # change handler to default handler, disabling handle_errors_html
+       set_message("Error occured when inside die_error:\n$msg");
+
+       # you cannot jump out of die_error when called as error handler;
+       # the subroutine set via CGI::Carp::set_message is called _after_
+       # HTTP headers are already written, so it cannot write them itself
+       die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
+}
+set_message(\&handle_errors_html);
+
 # dispatch
 sub dispatch {
        if (!defined $action) {
@@ -999,13 +1051,17 @@ sub dispatch {
        $actions{$action}->();
 }
 
-sub run_request {
+sub reset_timer {
        our $t0 = [Time::HiRes::gettimeofday()]
                if defined $t0;
+       our $number_of_git_cmds = 0;
+}
+
+sub run_request {
+       reset_timer();
 
        evaluate_uri();
        evaluate_gitweb_config();
-       evaluate_git_version();
        check_loadavg();
 
        # $projectroot and $projects_list might be set in gitweb config file
@@ -1025,19 +1081,24 @@ sub run_request {
 our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
 our $CGI = 'CGI';
 our $cgi;
+sub configure_as_fcgi {
+       require CGI::Fast;
+       our $CGI = 'CGI::Fast';
+
+       my $request_number = 0;
+       # let each child service 100 requests
+       our $is_last_request = sub { ++$request_number > 100 };
+}
 sub evaluate_argv {
+       my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
+       configure_as_fcgi()
+               if $script_name =~ /\.fcgi$/;
+
        return unless (@ARGV);
 
        require Getopt::Long;
        Getopt::Long::GetOptions(
-               'fastcgi|fcgi|f' => sub {
-                       require CGI::Fast;
-                       our $CGI = 'CGI::Fast';
-
-                       my $request_number = 0;
-                       # let each child service 100 requests
-                       our $is_last_request = sub { ++$request_number > 100 };
-               },
+               'fastcgi|fcgi|f' => \&configure_as_fcgi,
                'nproc|n=i' => sub {
                        my ($arg, $val) = @_;
                        return unless eval { require FCGI::ProcManager; 1; };
@@ -1053,6 +1114,7 @@ sub evaluate_argv {
 
 sub run {
        evaluate_argv();
+       evaluate_git_version();
 
        $pre_listen_hook->()
                if $pre_listen_hook;
@@ -1064,7 +1126,7 @@ sub run {
 
                run_request();
 
-               $pre_dispatch_hook->()
+               $post_dispatch_hook->()
                        if $post_dispatch_hook;
 
                last REQUEST if ($is_last_request->());
@@ -1076,6 +1138,15 @@ sub run {
 
 run();
 
+if (defined caller) {
+       # wrapped in a subroutine processing requests,
+       # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
+       return;
+} else {
+       # pure CGI script, serving single request
+       exit;
+}
+
 ## ======================================================================
 ## action links
 
@@ -1276,16 +1347,22 @@ sub esc_param {
        return $str;
 }
 
-# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+# quote unsafe chars in whole URL, so some characters cannot be quoted
 sub esc_url {
        my $str = shift;
        return undef unless defined $str;
-       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
-       $str =~ s/\+/%2B/g;
+       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
 }
 
+# quote unsafe characters in HTML attributes
+sub esc_attr {
+
+       # for XHTML conformance escaping '"' to '&quot;' is not enough
+       return esc_html(@_);
+}
+
 # replace invalid utf8 character with SUBSTITUTION sequence
 sub esc_html {
        my $str = shift;
@@ -1691,7 +1768,7 @@ sub format_ref_marker {
                                        hash=>$dest
                                )}, $name);
 
-                       $markers .= " <span class=\"$class\" title=\"$ref\">" .
+                       $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
                }
        }
@@ -1775,7 +1852,7 @@ sub git_get_avatar {
                return $pre_white .
                       "<img width=\"$size\" " .
                            "class=\"avatar\" " .
-                           "src=\"$url\" " .
+                           "src=\"".esc_url($url)."\" " .
                            "alt=\"\" " .
                       "/>" . $post_white;
        } else {
@@ -2486,7 +2563,7 @@ sub git_show_project_tagcloud {
        } else {
                my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
                return '<p align="center">' . join (', ', map {
-                       "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
+                       $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname})
                } splice(@tags, 0, $count)) . '</p>';
        }
 }
@@ -2527,6 +2604,9 @@ sub git_get_projects_list {
                        follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
+                               # global variables
+                               our $project_maxdepth;
+                               our $projectroot;
                                # skip project-list toplevel, if we get it.
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
@@ -3262,26 +3342,109 @@ sub blob_contenttype {
        return $type;
 }
 
+# guess file syntax for syntax highlighting; return undef if no highlighting
+# the name of syntax can (in the future) depend on syntax highlighter used
+sub guess_file_syntax {
+       my ($highlight, $mimetype, $file_name) = @_;
+       return undef unless ($highlight && defined $file_name);
+       my $basename = basename($file_name, '.in');
+       return $highlight_basename{$basename}
+               if exists $highlight_basename{$basename};
+
+       $basename =~ /\.([^.]*)$/;
+       my $ext = $1 or return undef;
+       return $highlight_ext{$ext}
+               if exists $highlight_ext{$ext};
+
+       return undef;
+}
+
+# run highlighter and return FD of its output,
+# or return original FD if no highlighting
+sub run_highlighter {
+       my ($fd, $highlight, $syntax) = @_;
+       return $fd unless ($highlight && defined $syntax);
+
+       close $fd
+               or die_error(404, "Reading blob failed");
+       open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 "highlight --xhtml --fragment --syntax $syntax |"
+               or die_error(500, "Couldn't open file or run syntax highlighter");
+       return $fd;
+}
+
 ## ======================================================================
 ## functions printing HTML: header, footer, error page
 
-sub git_header_html {
-       my $status = shift || "200 OK";
-       my $expires = shift;
+sub get_page_title {
+       my $title = to_utf8($site_name);
+
+       return $title unless (defined $project);
+       $title .= " - " . to_utf8($project);
+
+       return $title unless (defined $action);
+       $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
+
+       return $title unless (defined $file_name);
+       $title .= " - " . esc_path($file_name);
+       if ($action eq "tree" && $file_name !~ m|/$|) {
+               $title .= "/";
+       }
 
-       my $title = "$site_name";
+       return $title;
+}
+
+sub print_feed_meta {
        if (defined $project) {
-               $title .= " - " . to_utf8($project);
-               if (defined $action) {
-                       $title .= "/$action";
-                       if (defined $file_name) {
-                               $title .= " - " . esc_path($file_name);
-                               if ($action eq "tree" && $file_name !~ m|/$|) {
-                                       $title .= "/";
-                               }
-                       }
+               my %href_params = get_feed_info();
+               if (!exists $href_params{'-title'}) {
+                       $href_params{'-title'} = 'log';
+               }
+
+               foreach my $format qw(RSS Atom) {
+                       my $type = lc($format);
+                       my %link_attr = (
+                               '-rel' => 'alternate',
+                               '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
+                               '-type' => "application/$type+xml"
+                       );
+
+                       $href_params{'action'} = $type;
+                       $link_attr{'-href'} = href(%href_params);
+                       print "<link ".
+                             "rel=\"$link_attr{'-rel'}\" ".
+                             "title=\"$link_attr{'-title'}\" ".
+                             "href=\"$link_attr{'-href'}\" ".
+                             "type=\"$link_attr{'-type'}\" ".
+                             "/>\n";
+
+                       $href_params{'extra_options'} = '--no-merges';
+                       $link_attr{'-href'} = href(%href_params);
+                       $link_attr{'-title'} .= ' (no merges)';
+                       print "<link ".
+                             "rel=\"$link_attr{'-rel'}\" ".
+                             "title=\"$link_attr{'-title'}\" ".
+                             "href=\"$link_attr{'-href'}\" ".
+                             "type=\"$link_attr{'-type'}\" ".
+                             "/>\n";
                }
+
+       } else {
+               printf('<link rel="alternate" title="%s projects list" '.
+                      'href="%s" type="text/plain; charset=utf-8" />'."\n",
+                      esc_attr($site_name), href(project=>undef, action=>"project_index"));
+               printf('<link rel="alternate" title="%s projects feeds" '.
+                      'href="%s" type="text/x-opml" />'."\n",
+                      esc_attr($site_name), href(project=>undef, action=>"opml"));
        }
+}
+
+sub git_header_html {
+       my $status = shift || "200 OK";
+       my $expires = shift;
+       my %opts = @_;
+
+       my $title = get_page_title();
        my $content_type;
        # require explicit support from the UA if we are to send the page as
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
@@ -3295,7 +3458,8 @@ sub git_header_html {
                $content_type = 'text/html';
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-                          -status=> $status, -expires => $expires);
+                          -status=> $status, -expires => $expires)
+               unless ($opts{'-no_http_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
@@ -3317,57 +3481,17 @@ sub git_header_html {
        # print out each stylesheet that exist, providing backwards capability
        # for those people who defined $stylesheet in a config file
        if (defined $stylesheet) {
-               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
                        next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
                }
        }
-       if (defined $project) {
-               my %href_params = get_feed_info();
-               if (!exists $href_params{'-title'}) {
-                       $href_params{'-title'} = 'log';
-               }
-
-               foreach my $format qw(RSS Atom) {
-                       my $type = lc($format);
-                       my %link_attr = (
-                               '-rel' => 'alternate',
-                               '-title' => "$project - $href_params{'-title'} - $format feed",
-                               '-type' => "application/$type+xml"
-                       );
-
-                       $href_params{'action'} = $type;
-                       $link_attr{'-href'} = href(%href_params);
-                       print "<link ".
-                             "rel=\"$link_attr{'-rel'}\" ".
-                             "title=\"$link_attr{'-title'}\" ".
-                             "href=\"$link_attr{'-href'}\" ".
-                             "type=\"$link_attr{'-type'}\" ".
-                             "/>\n";
-
-                       $href_params{'extra_options'} = '--no-merges';
-                       $link_attr{'-href'} = href(%href_params);
-                       $link_attr{'-title'} .= ' (no merges)';
-                       print "<link ".
-                             "rel=\"$link_attr{'-rel'}\" ".
-                             "title=\"$link_attr{'-title'}\" ".
-                             "href=\"$link_attr{'-href'}\" ".
-                             "type=\"$link_attr{'-type'}\" ".
-                             "/>\n";
-               }
-
-       } else {
-               printf('<link rel="alternate" title="%s projects list" '.
-                      'href="%s" type="text/plain; charset=utf-8" />'."\n",
-                      $site_name, href(project=>undef, action=>"project_index"));
-               printf('<link rel="alternate" title="%s projects feeds" '.
-                      'href="%s" type="text/x-opml" />'."\n",
-                      $site_name, href(project=>undef, action=>"opml"));
-       }
+       print_feed_meta()
+               if ($status eq '200 OK');
        if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
+               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
        }
 
        print "</head>\n" .
@@ -3380,7 +3504,7 @@ sub git_header_html {
        print "<div class=\"page_header\">\n" .
              $cgi->a({-href => esc_url($logo_url),
                       -title => $logo_label},
-                     qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+                     qq(<img src=").esc_url($logo).qq(" width="72" height="27" alt="git" class="logo"/>));
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        if (defined $project) {
                print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
@@ -3478,7 +3602,7 @@ sub git_footer_html {
                insert_file($site_footer);
        }
 
-       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
        if (defined $action &&
            $action eq 'blame_incremental') {
                print qq!<script type="text/javascript">\n!.
@@ -3512,6 +3636,7 @@ sub die_error {
        my $status = shift || 500;
        my $error = esc_html(shift) || "Internal Server Error";
        my $extra = shift;
+       my %opts = @_;
 
        my %http_responses = (
                400 => '400 Bad Request',
@@ -3520,7 +3645,7 @@ sub die_error {
                500 => '500 Internal Server Error',
                503 => '503 Service Unavailable',
        );
-       git_header_html($http_responses{$status});
+       git_header_html($http_responses{$status}, undef, %opts);
        print <<EOF;
 <div class="page_body">
 <br /><br />
@@ -3534,7 +3659,8 @@ sub die_error {
        print "</div>\n";
 
        git_footer_html();
-       goto DONE_GITWEB;
+       goto DONE_GITWEB
+               unless ($opts{'-error_handler'});
 }
 
 ## ----------------------------------------------------------------------
@@ -3668,9 +3794,9 @@ sub git_print_authorship {
 }
 
 # Outputs table rows containing the full author or committer information,
-# in the format expected for 'commit' view (& similia).
+# in the format expected for 'commit' view (& similar).
 # Parameters are a commit hash reference, followed by the list of people
-# to output information for. If the list is empty it defalts to both
+# to output information for. If the list is empty it defaults to both
 # author and committer.
 sub git_print_authorship_rows {
        my $co = shift;
@@ -4399,8 +4525,8 @@ sub git_patchset_body {
                print "</div>\n"; # class="patch"
        }
 
-       # for compact combined (--cc) format, with chunk and patch simpliciaction
-       # patchset might be empty, but there might be unprocessed raw lines
+       # for compact combined (--cc) format, with chunk and patch simplification
+       # the patchset might be empty, but there might be unprocessed raw lines
        for (++$patch_idx if $patch_number > 0;
             $patch_idx < @$difftree;
             ++$patch_idx) {
@@ -5078,15 +5204,15 @@ sub git_summary {
 }
 
 sub git_tag {
-       my $head = git_get_head_hash($project);
-       git_header_html();
-       git_print_page_nav('','', $head,undef,$head);
        my %tag = parse_tag($hash);
 
        if (! %tag) {
                die_error(404, "Unknown tag object");
        }
 
+       my $head = git_get_head_hash($project);
+       git_header_html();
+       git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('commit', esc_html($tag{'name'}), $hash);
        print "<div class=\"title_text\">\n" .
              "<table class=\"object_header\">\n" .
@@ -5453,6 +5579,7 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
+       # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
        if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
@@ -5460,6 +5587,11 @@ sub git_blob {
        # we can have blame only for text/* mimetype
        $have_blame &&= ($mimetype =~ m!^text/!);
 
+       my $highlight = gitweb_check_feature('highlight');
+       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax)
+               if $syntax;
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -5490,14 +5622,14 @@ sub git_blob {
        } else {
                print "<div class=\"page_nav\">\n" .
                      "<br/><br/></div>\n" .
-                     "<div class=\"title\">$hash</div>\n";
+                     "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
-               print qq!<img type="$mimetype"!;
+               print qq!<img type="!.esc_attr($mimetype).qq!"!;
                if ($file_name) {
-                       print qq! alt="$file_name" title="$file_name"!;
+                       print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
                print qq! src="! .
                      href(action=>"blob_plain", hash=>$hash,
@@ -5509,9 +5641,8 @@ sub git_blob {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
-                               . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                              $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@ -5573,7 +5704,7 @@ sub git_tree {
                undef $hash_base;
                print "<div class=\"page_nav\">\n";
                print "<br/><br/></div>\n";
-               print "<div class=\"title\">$hash</div>\n";
+               print "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        if (defined $file_name) {
                $basedir = $file_name;
@@ -6041,7 +6172,7 @@ sub git_blobdiff {
                        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
                } else {
                        print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
-                       print "<div class=\"title\">$hash vs $hash_parent</div>\n";
+                       print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
                }
                if (defined $file_name) {
                        git_print_page_path($file_name, "blob", $hash_base);
@@ -6224,8 +6355,8 @@ sub git_commitdiff {
                        }
                        push @commit_spec, '--root', $hash;
                }
-               open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
-                       '--stdout', @commit_spec
+               open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
+                       '--encoding=utf8', '--stdout', @commit_spec
                        or die_error(500, "Open git-format-patch failed");
        } else {
                die_error(400, "Unknown commitdiff format");
@@ -6403,12 +6534,13 @@ sub git_search {
                        $paging_nav .= " &sdot; next";
                }
 
-               if ($#commitlist >= 100) {
-               }
-
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
                git_print_header_div('commit', esc_html($co{'title'}), $hash);
-               git_search_grep_body(\@commitlist, 0, 99, $next_link);
+               if ($page == 0 && !@commitlist) {
+                       print "<p>No match.</p>\n";
+               } else {
+                       git_search_grep_body(\@commitlist, 0, 99, $next_link);
+               }
        }
 
        if ($searchtype eq 'pickaxe') {
@@ -6738,7 +6870,7 @@ sub git_feed {
                if (defined $favicon) {
                        print "<icon>" . esc_url($favicon) . "</icon>\n";
                }
-               if (defined $logo_url) {
+               if (defined $logo) {
                        # not twice as wide as tall: 72 x 27 pixels
                        print "<logo>" . esc_url($logo) . "</logo>\n";
                }