htmanning has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks, I'm having trouble with escaping an apostrophe. I'm using a javascript popup and a perl script to add something to the database. The problem is the $favtitle variable in my javascript link has an apostrophe in it, so I escape it. When it hits my script I read in the $favtitle with cgi param. However, I can't get my script to stuff the title in the database because of the apostrophe. I try switching it back and getting rid of the escape, but it doesn't work. Maybe I'm doing something wrong. The javascript that calls the popup is here. You'll notice I'm escaping the apostrophe for favtitle:
<a href="javascript:popUp('/cgi-bin/script.pl?favadd=yes&num=6210&fav +title=jones\'s title&favlink=6210.html')">
Here is the script:
$favtitle = param('favtitle'); if ($favadd && $username_valid == 1) { if ($favtitle && $favlink) { $favtitle =~ s/\\'/\'/g; $favorites = $favorites . "$favtitle|||$favlink|||$num~~~"; $oldtitle = $favtitle; $oldlink = $favlink; @favs = split /~~~/,$favorites; if (@favs > $maxfavs) { print <<HTML; Currently, the maximum number is $maxfavs. <br> <br>Max number reached! <br> <br> <font face="arial" size="-1"><a href="javascript:window.close()">Close + Window</a></font> HTML } else { @sorted = sort {uc($a) cmp uc($b)} @favs; $favorites = ""; foreach (@sorted){ ($favtitle,$favlink,$num) = split /\|\|\|/,$_; $favorites = $favorites . $favtitle . "|||" . $favlink + . "|||" . $num . "~~~"; } # update existing record $SQL = "UPDATE $regtable SET favorites='$favorites' WHERE +ID='$ID'"; &Do_SQL; # get favorites $favslist = ""; $rowcolor = "#EEEEEE"; @favs = split /~~~/,$favorites; foreach (@favs){ ($favtitle,$favlink,$num) = split /\|\|\|/,$_; $favslist = $favslist . "<tr><td bgcolor=$rowcolor><a +href=\"$favlink\">$favtitle</a></td></tr>"; # alternate grey and white bar if ($rowcolor eq "#EEEEEE") { $rowcolor = "#FFFFFF"; } else { $rowcolor = "#EEEEEE"; } # end if ($rowcolor eq "#EEEEEE") } $favslist = "<table border=0 width=100% cellpadding=4 cell +spacing=0><tr><td> <b>My Saves:</b><br></td></tr>".$favslist."</table +>"; print <<HTML; Saved! HTML
I get the confirmation pages like I'm supposed to, but nothing goes in the database. If I take out this line:
$favtitle =~ s/\\'/\'/g;
It makes no difference. If I don't escape the apostrophe in the javascript the popup doesn't work. The only way I can get it to work is to completely remove the apostrophe in the javascript link, but then the title goes in the database without an apostrophe which isn't ideal. The other weird thing is, if I don't use a javascript popup, and force people to change pages to the confirmatoin page, it works with the apostrophe. See anything I'm doing wrong? Thanks.

Replies are listed 'Best First'.
Re: Escaping then un-escaping an apostrophe
by wind (Priest) on Mar 10, 2011 at 19:52 UTC
    Don't use raw sql statements like this:
    $SQL = "UPDATE $regtable SET favorites='$favorites' WHERE ID='$ID'";
    Use placeholders instead.
    my $sth = $dbh->prepare(qq{UPDATE $regtable SET favorites=? WHERE ID=? +}); $sth->execute($favorites, $ID) or die $dbh->errstr;

    That way the escaping is done for you, and you avoid sql injection attacks.

    Also, I advise you to use strict and use warnings. It appears that you're relying on global variables instead of passing parameters to your subs. This is very bad practice.

      Thanks for the suggestions. Here is the Do_Sql sub referred to in my code. Is this still bad practice?
      $SQL = "UPDATE $regtable SET favorites='$favorites' WHERE ID='$ID'"; &Do_SQL; sub Do_SQL{ eval { $sth = $dbh->prepare($SQL); }; # end of eval # check for errors if($@){ $dbh->disconnect; print "Content-type: text/html\n\n"; print "An ERROR occurred! $@\n"; exit; } else { $sth->execute; } # end of if/else return ($sth); }

        Yes, there are a few problems with that code:

        • 1) Your subroutine relies on the global variable $SQL, instead of a passed parameter.
        • 2) Your $SQL statements are vulnerable to injection attacks since they aren't escaped and/or you're not using placeholders.
        • 3) You rolling your own to browser error reporting, instead of letting CGI::Carp do the work for you.
        #!/usr/bin/perl -wT use CGI; use CGI::Carp qw(fatalsToBrowser); use DBI; use strict; use warnings; ... my $sth = $dbh->prepare(qq{UPDATE $regtable SET favorites=? WHERE +ID=?}); $sth->execute($favorites, $ID) or die $dbh->errstr; ...

        The above is all you need to accomplish the same thing. If there is an error with your statement, then CGI::Carp will trap it and display it in the browser and also the error log.

Re: Escaping then un-escaping an apostrophe
by ikegami (Patriarch) on Mar 10, 2011 at 20:09 UTC

    You'll have way fewer problems if you were to think a bit more abstractly. The things you really want to do are:

    • Convert text pairs into a URL query.
    • Convert text into a JS string literal.
    • Convert text into an HTML attribute.
    • Convert text into an SQL string literal.

    So make a function for each of them!

    sub build_url { my $url = URI->new(shift(@_)); $url->query_form(@_); return $url; } sub text_to_js_lit { my $s = $_[0]; $s =~ s/\\/\\\\/g; $s =~ s/'/\''/g; return "'$s'"; } sub text_to_html_val { my $s = $_[0]; $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/"/&quote;/g; return qq{"$s"}; }
    my $url = build_url('/cgi-bin/script.pl', favadd => 'yes', num => '6210', favtitle => "jones's title", favlink => '6210.html', ); my $js_url = 'javascript:popUp(' . text_to_js_lit($url) . ')'; print('<a href=' . text_to_html_val($js_url) . '>');

    As for converting text into an SQL string literal, the function already exists as $dbh->quote(). That said, you'd be better off using placeholders as previously mentioned.