Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/support_meta_send.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/support_meta_send.cgi

added on local at 2026-07-10 13:48:40

Added
+110
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 7e1015784044
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# Cross-site support reply endpoint -- PTMatrix variant.
4#
5# PTMatrix's support_threads/support_messages schema differs from the
6# other 6 sister sites (sender_kind vs sender_role, user_unread vs
7# customer_unread, opened_by_user_id, status enum open|answered|closed)
8# and it has no MODS::PTMatrix::Support module -- reply logic lives in
9# support.cgi's handle_reply sub. Rather than shell out into that sub,
10# this endpoint mirrors handle_reply's DB mutations directly.
11#
12# Wire-format matches the other sites (HMAC + JSON POST): see comments
13# in any sister-site support_meta_send.cgi.
14#======================================================================
15use strict;
16use warnings;
17
18my $raw = '';
19if (($ENV{REQUEST_METHOD} || '') eq 'POST') {
20 my $len = $ENV{CONTENT_LENGTH} || 0;
21 read STDIN, $raw, $len if $len > 0 && $len < 65536;
22}
23
24use lib '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com';
25use JSON::PP ();
26use Digest::SHA qw(hmac_sha256_hex sha256_hex);
27use MODS::DBConnect;
28use MODS::PTMatrix::Config;
29
30sub _json_out {
31 my ($code, $body) = @_;
32 my $status_line = $code == 200 ? '' : "Status: $code\n";
33 print "${status_line}Content-Type: application/json\n\n";
34 print JSON::PP::encode_json($body);
35 exit;
36}
37
38_json_out(405, { error => 'method_not_allowed' })
39 unless ($ENV{REQUEST_METHOD} || '') eq 'POST';
40_json_out(400, { error => 'empty_body' }) unless length $raw;
41
42my $sig_hdr = $ENV{HTTP_X_META_SIG} || '';
43$sig_hdr =~ s/[^a-f0-9]//gi;
44_json_out(400, { error => 'no_signature' }) unless length($sig_hdr) == 64;
45
46my $data = eval { JSON::PP::decode_json($raw) };
47_json_out(400, { error => 'bad_json' }) unless $data && ref($data) eq 'HASH';
48
49my $ts = int($data->{ts} || 0);
50my $thread_id = int($data->{thread_id} || 0);
51my $body_txt = defined($data->{body}) ? $data->{body} : '';
52
53_json_out(400, { error => 'missing_thread_id' }) unless $thread_id > 0;
54_json_out(400, { error => 'empty_body' }) unless length $body_txt;
55_json_out(400, { error => 'stale_timestamp' }) if abs(time - $ts) > 300;
56
57my $cfg = MODS::PTMatrix::Config->new;
58my $secret = $cfg->settings('meta_admin_hmac_secret') || '';
59_json_out(500, { error => 'no_secret_configured' }) unless length($secret) >= 32;
60
61my $body_hash = sha256_hex($body_txt);
62my $expected = hmac_sha256_hex("$ts.$thread_id.$body_hash", $secret);
63
64# Constant-time compare
65my $ok = length($sig_hdr) == length($expected);
66my $r = 0;
67if ($ok) {
68 for (my $i = 0; $i < length($sig_hdr); $i++) {
69 $r |= ord(substr($sig_hdr, $i, 1)) ^ ord(substr($expected, $i, 1));
70 }
71 $ok = ($r == 0);
72}
73_json_out(403, { error => 'bad_signature' }) unless $ok;
74
75# Signature valid -- mirror PT's handle_reply DB mutations.
76my $db = MODS::DBConnect->new;
77my $dbh = $db->db_connect();
78_json_out(500, { error => 'db_connect_failed' }) unless $dbh;
79my $DB = $cfg->settings('database_name');
80
81# Quote body safely via $dbh->quote (per rules: prefer $dbh->quote over s/'/''/g).
82my $body_q = $dbh->quote($body_txt);
83# PT's sender_user_id is NOT NULL (differs from 6 sister sites). Use
84# user_id=1 (Shawn -- Platform Owner) as the portfolio operator on PT.
85my $admin_uid = 1;
86$db->db_readwrite($dbh, qq~
87 INSERT INTO `${DB}`.support_messages
88 (thread_id, sender_user_id, sender_kind, body)
89 VALUES ($thread_id, $admin_uid, 'admin', $body_q)
90~, $ENV{SCRIPT_NAME}, __LINE__);
91
92$db->db_readwrite($dbh, qq~
93 UPDATE `${DB}`.support_threads
94 SET status = 'answered',
95 user_unread = 1,
96 last_message_by = 'admin',
97 last_message_at = NOW()
98 WHERE id = $thread_id
99 LIMIT 1
100~, $ENV{SCRIPT_NAME}, __LINE__);
101
102my $mid_row = $db->db_readwrite($dbh, qq~
103 SELECT id FROM `${DB}`.support_messages
104 WHERE thread_id = $thread_id
105 ORDER BY id DESC LIMIT 1
106~, $ENV{SCRIPT_NAME}, __LINE__);
107my $mid = ($mid_row && $mid_row->{id}) ? $mid_row->{id} : 0;
108
109$db->db_disconnect($dbh);
110_json_out(200, { ok => 1, message_id => $mid });