added on local at 2026-07-01 22:09:44
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # report_abuse.cgi | |
| 4 | # | |
| 5 | # Recipient-side abuse reporting. Linked from every email footer. | |
| 6 | # Accepts ?t=TOKEN&s=template_slug. | |
| 7 | # | |
| 8 | # GET: show a confirmation form ("did this email feel like abuse?") | |
| 9 | # POST: log report + run abuse_suspend_check on the inviter | |
| 10 | #====================================================================== | |
| 11 | ||
| 12 | use strict; | |
| 13 | use warnings; | |
| 14 | use lib '/var/www/vhosts/3dshawn.com/shop.3dshawn.com'; | |
| 15 | ||
| 16 | use CGI qw(:standard); | |
| 17 | use MODS::DBConnect; | |
| 18 | use MODS::ShopCart::Config; | |
| 19 | use MODS::ShopCart::Mailer; | |
| 20 | ||
| 21 | my $cfg = MODS::ShopCart::Config->new; | |
| 22 | my $DB = $cfg->settings('database_name'); | |
| 23 | my $brand = $cfg->settings('brand_name') || 'ShopCart'; | |
| 24 | my $db = MODS::DBConnect->new; | |
| 25 | my $dbh = $db->db_connect(); | |
| 26 | ||
| 27 | my $tok = param('t') || ''; | |
| 28 | my $slug = param('s') || ''; | |
| 29 | my $act = param('act') || ''; | |
| 30 | my $notes = param('notes') || ''; | |
| 31 | $tok =~ s/[^a-zA-Z0-9]//g; | |
| 32 | $slug =~ s/[^a-z0-9_-]//g; | |
| 33 | $notes = substr($notes, 0, 500); | |
| 34 | $notes =~ s/[\r\n]+/ /g; | |
| 35 | my $notes_q = $notes; $notes_q =~ s/'/''/g; | |
| 36 | my $slug_q = $slug; $slug_q =~ s/'/''/g; | |
| 37 | my $tok_q = $tok; $tok_q =~ s/'/''/g; | |
| 38 | ||
| 39 | # Resolve token -> inviter | |
| 40 | my $inviter_id; | |
| 41 | my $inviter_email = ''; | |
| 42 | if ($tok) { | |
| 43 | my $r = $db->db_readwrite($dbh, qq~ | |
| 44 | SELECT t.user_id, u.email | |
| 45 | FROM `${DB}`.email_unsubscribe_tokens t | |
| 46 | LEFT JOIN `${DB}`.users u ON u.id = t.user_id | |
| 47 | WHERE t.token = '$tok_q' LIMIT 1 | |
| 48 | ~, $0, __LINE__); | |
| 49 | if ($r && $r->{user_id}) { | |
| 50 | $inviter_id = $r->{user_id} + 0; | |
| 51 | $inviter_email = $r->{email} || ''; | |
| 52 | } | |
| 53 | } | |
| 54 | ||
| 55 | my $remote_ip = $ENV{HTTP_X_FORWARDED_FOR} || $ENV{REMOTE_ADDR} || ''; | |
| 56 | $remote_ip = substr($remote_ip, 0, 64); | |
| 57 | my $ip_q = $remote_ip; $ip_q =~ s/'/''/g; | |
| 58 | ||
| 59 | print "Content-Type: text/html; charset=utf-8\n\n"; | |
| 60 | ||
| 61 | if ($act eq 'report' && $inviter_id) { | |
| 62 | my $u_q = $inviter_id + 0; | |
| 63 | $db->db_readwrite($dbh, qq~ | |
| 64 | INSERT INTO `${DB}`.email_abuse_reports | |
| 65 | (inviter_user_id, template_slug, token, reporter_ip, notes) | |
| 66 | VALUES | |
| 67 | ('$u_q', '$slug_q', '$tok_q', '$ip_q', '$notes_q') | |
| 68 | ~, $0, __LINE__); | |
| 69 | ||
| 70 | my $mailer = MODS::ShopCart::Mailer->new; | |
| 71 | $mailer->abuse_suspend_check($db, $dbh, $DB, $inviter_id); | |
| 72 | ||
| 73 | print _shell(qq~ | |
| 74 | <h1>Thanks — report logged.</h1> | |
| 75 | <p>We've recorded your report. The sender will be reviewed and may | |
| 76 | be blocked from sending further email through $brand.</p> | |
| 77 | <p>You won't hear from us about this again.</p> | |
| 78 | ~); | |
| 79 | eval { $db->db_disconnect($dbh) }; | |
| 80 | exit; | |
| 81 | } | |
| 82 | ||
| 83 | # Default: show confirmation form | |
| 84 | my $who = $inviter_email ? "from <strong>$inviter_email</strong>" : 'from a $brand account'; | |
| 85 | print _shell(qq~ | |
| 86 | <h1>Report this email as abuse?</h1> | |
| 87 | <p>You're about to report the email $who as unwanted or abusive. | |
| 88 | Your report will be reviewed and the sender may be blocked from | |
| 89 | sending further email through $brand.</p> | |
| 90 | <form method="post" action="/report_abuse.cgi" style="margin-top:24px"> | |
| 91 | <input type="hidden" name="t" value="$tok"> | |
| 92 | <input type="hidden" name="s" value="$slug"> | |
| 93 | <input type="hidden" name="act" value="report"> | |
| 94 | <label style="display:block;margin-bottom:8px;color:#94a3b8;font-size:13px"> | |
| 95 | Optional — what made this feel like abuse? | |
| 96 | </label> | |
| 97 | <textarea name="notes" rows="3" maxlength="500" | |
| 98 | style="width:100%;background:#1a1f2c;color:#e5e7eb;border:1px solid #2a313d;border-radius:8px;padding:10px"></textarea> | |
| 99 | <div style="margin-top:16px;display:flex;gap:10px"> | |
| 100 | <button type="submit" | |
| 101 | style="background:#5aa9ff;color:#0b1220;border:0;border-radius:8px;padding:10px 18px;font-weight:600;cursor:pointer"> | |
| 102 | Report as abuse | |
| 103 | </button> | |
| 104 | <a href="/" style="background:#2a313d;color:#e5e7eb;border-radius:8px;padding:10px 18px;text-decoration:none"> | |
| 105 | Cancel | |
| 106 | </a> | |
| 107 | </div> | |
| 108 | </form> | |
| 109 | ~); | |
| 110 | ||
| 111 | eval { $db->db_disconnect($dbh) }; | |
| 112 | ||
| 113 | sub _shell { | |
| 114 | my ($body) = @_; | |
| 115 | return qq~<!doctype html> | |
| 116 | <html><head><meta charset="utf-8"><title>Report abuse · $brand</title> | |
| 117 | <style> | |
| 118 | body { background:#0b1220; color:#e5e7eb; font-family:-apple-system,Segoe UI,sans-serif; margin:0; padding:48px 16px; } | |
| 119 | .card { max-width:560px; margin:0 auto; background:#141a26; border:1px solid #2a313d; border-radius:12px; padding:32px; } | |
| 120 | h1 { margin:0 0 16px; font-size:22px; } | |
| 121 | p { color:#cbd5e1; line-height:1.55; } | |
| 122 | </style></head> | |
| 123 | <body><div class="card">$body</div></body></html>~; | |
| 124 | } |