@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorProtocolLog
4 extends Phobject {
5
6 private $logfile;
7 private $mode;
8 private $buffer = array();
9
10 public function __construct($logfile) {
11 $this->logfile = $logfile;
12 }
13
14 public function didStartSession($session_name) {
15 $this->setMode('!');
16 $this->buffer[] = $session_name;
17 $this->flush();
18 }
19
20 public function didEndSession() {
21 $this->setMode('_');
22 $this->buffer[] = pht('<End of Session>');
23 $this->flush();
24 }
25
26 public function didWriteBytes($bytes) {
27 if (!strlen($bytes)) {
28 return;
29 }
30
31 $this->setMode('>');
32 $this->buffer[] = $bytes;
33 }
34
35 public function didReadBytes($bytes) {
36 if (!strlen($bytes)) {
37 return;
38 }
39
40 $this->setMode('<');
41 $this->buffer[] = $bytes;
42 }
43
44 public function didReadFrame($frame) {
45 $this->writeFrame('<*', $frame);
46 }
47
48 public function didWriteFrame($frame) {
49 $this->writeFrame('>*', $frame);
50 }
51
52 private function writeFrame($header, $frame) {
53 $this->flush();
54
55 $frame = explode("\n", $frame);
56 foreach ($frame as $key => $line) {
57 $frame[$key] = $header.' '.$this->escapeBytes($line);
58 }
59 $frame = implode("\n", $frame)."\n\n";
60
61 $this->writeMessage($frame);
62 }
63
64 private function setMode($mode) {
65 if ($this->mode === $mode) {
66 return $this;
67 }
68
69 if ($this->mode !== null) {
70 $this->flush();
71 }
72
73 $this->mode = $mode;
74
75 return $this;
76 }
77
78 private function flush() {
79 $mode = $this->mode;
80 $bytes = $this->buffer;
81
82 $this->mode = null;
83 $this->buffer = array();
84
85 $bytes = implode('', $bytes);
86
87 if (strlen($bytes)) {
88 $this->writeBytes($mode, $bytes);
89 }
90 }
91
92 private function writeBytes($mode, $bytes) {
93 $header = $mode;
94 $len = strlen($bytes);
95
96 $out = array();
97 switch ($mode) {
98 case '<':
99 $out[] = pht('%s Write [%s bytes]', $header, new PhutilNumber($len));
100 break;
101 case '>':
102 $out[] = pht('%s Read [%s bytes]', $header, new PhutilNumber($len));
103 break;
104 default:
105 $out[] = pht(
106 '%s %s',
107 $header,
108 $this->escapeBytes($bytes));
109 break;
110 }
111
112 switch ($mode) {
113 case '<':
114 case '>':
115 $out[] = $this->renderBytes($header, $bytes);
116 break;
117 }
118
119 $out = implode("\n", $out)."\n\n";
120
121 $this->writeMessage($out);
122 }
123
124 private function renderBytes($header, $bytes) {
125 $bytes_per_line = 48;
126 $bytes_per_chunk = 4;
127
128 // Compute the width of the "bytes" display section, which looks like
129 // this:
130 //
131 // > 00112233 44556677 abcdefgh
132 // ^^^^^^^^^^^^^^^^^
133 //
134 // We need to figure this out so we can align the plain text in the far
135 // right column appropriately.
136
137 // The character width of the "bytes" part of a full display line. If
138 // we're rendering 48 bytes per line, we'll need 96 characters, since
139 // each byte is printed as a 2-character hexadecimal code.
140 $display_bytes = ($bytes_per_line * 2);
141
142 // The character width of the number of spaces in between the "bytes"
143 // chunks. If we're rendering 12 chunks per line, we'll put 11 spaces
144 // in between them to separate them.
145 $display_spaces = (($bytes_per_line / $bytes_per_chunk) - 1);
146
147 $pad_bytes = $display_bytes + $display_spaces;
148
149 // When the protocol is plaintext, try to break it on newlines so it's
150 // easier to read.
151 $pos = 0;
152 $lines = array();
153 while (true) {
154 $next_break = strpos($bytes, "\n", $pos);
155 if ($next_break === false) {
156 $len = strlen($bytes) - $pos;
157 } else {
158 $len = ($next_break - $pos) + 1;
159 }
160 $len = min($bytes_per_line, $len);
161
162 $next_bytes = substr($bytes, $pos, $len);
163
164 $chunk_parts = array();
165 foreach (str_split($next_bytes, $bytes_per_chunk) as $chunk) {
166 $chunk_display = '';
167 for ($ii = 0; $ii < strlen($chunk); $ii++) {
168 $chunk_display .= sprintf('%02x', ord($chunk[$ii]));
169 }
170 $chunk_parts[] = $chunk_display;
171 }
172 $chunk_parts = implode(' ', $chunk_parts);
173
174 $chunk_parts = str_pad($chunk_parts, $pad_bytes, ' ');
175
176
177 $lines[] = $header.' '.$chunk_parts.' '.$this->escapeBytes($next_bytes);
178
179 $pos += $len;
180
181 if ($pos >= strlen($bytes)) {
182 break;
183 }
184 }
185
186 $lines = implode("\n", $lines);
187
188 return $lines;
189 }
190
191 private function escapeBytes($bytes) {
192 $result = '';
193 for ($ii = 0; $ii < strlen($bytes); $ii++) {
194 $c = $bytes[$ii];
195 $o = ord($c);
196
197 if ($o >= 0x20 && $o <= 0x7F) {
198 $result .= $c;
199 } else {
200 $result .= '.';
201 }
202 }
203 return $result;
204 }
205
206 private function writeMessage($message) {
207 $f = fopen($this->logfile, 'a');
208 fwrite($f, $message);
209 fflush($f);
210 fclose($f);
211 }
212
213}