h = $h; fseek($h, $offset); $this->base = $offset; $this->delimiter = $delimiter !== '' ? $delimiter : ';'; } public function __destruct() { if (is_resource($this->h)) { Fs::close($this->h); } } /** The delimiter in force after the last statement (a DELIMITER line changes it). */ public function delimiter(): string { return $this->delimiter; } /** @return array{sql: string, end: int}|null the next statement and the offset just after it; null at the end of the file */ public function next(): ?array { $out = ''; $conditional = false; $line_start = true; while (true) { if (!$this->ensure(64)) { $sql = trim($out); return $sql === '' ? null : ['sql' => $sql, 'end' => $this->base + $this->i]; } if ($line_start && preg_match('/\G[ \t]*DELIMITER[ \t]+(\S+)[ \t]*(?:\r?\n|$)/i', $this->buf, $m, 0, $this->i) && trim($out) === '') { $this->delimiter = $m[1]; $this->i += strlen($m[0]); continue; } $line_start = false; if (substr_compare($this->buf, $this->delimiter, $this->i, strlen($this->delimiter)) === 0) { $this->i += strlen($this->delimiter); $sql = trim($out); if ($sql !== '') { return ['sql' => $sql, 'end' => $this->base + $this->i]; } $out = ''; $line_start = true; continue; } $c = $this->buf[$this->i]; if ($c === "'" || $c === '"' || $c === '`') { $out .= $this->quoted($c); } elseif ($c === '#' || ($c === '-' && preg_match('/\G--(?:[ \t\r\n]|$)/', $this->buf, $m, 0, $this->i))) { $this->skip_to("\n"); $out .= "\n"; $line_start = true; } elseif ($c === '/' && ($this->buf[$this->i + 1] ?? '') === '*') { if (preg_match('/\G\/\*!\d*/', $this->buf, $m, 0, $this->i)) { $this->i += strlen($m[0]); $conditional = true; $out .= ' '; } else { $this->i += 2; $this->skip_to('*/'); $out .= ' '; } } elseif ($conditional && $c === '*' && ($this->buf[$this->i + 1] ?? '') === '/') { $this->i += 2; $conditional = false; $out .= ' '; } elseif ($c === "\n") { $out .= "\n"; $this->i++; $line_start = true; } else { $stop = "'\"`#-/\n" . $this->delimiter[0] . ($conditional ? '*' : ''); $len = max(1, strcspn($this->buf, $stop, $this->i + 1) + 1); $out .= substr($this->buf, $this->i, $len); $this->i += $len; } if (strlen($out) > self::MAX_STATEMENT) { throw new RelayError('import.statement_too_large', esc_html__('The SQL file contains a statement larger than 64 MB, which SiteRelay will not load.', 'siterelay')); } } } /** A quoted string or identifier, kept as written. */ private function quoted(string $q): string { $this->i++; $out = ''; while (true) { if (!$this->ensure(2)) { throw new RelayError('import.unterminated', esc_html__('The SQL file ends inside a quoted value. It may be cut off.', 'siterelay')); } $len = strcspn($this->buf, $q === '`' ? '`' : $q . '\\', $this->i); if ($this->i + $len >= strlen($this->buf)) { $out .= substr($this->buf, $this->i); $this->i = strlen($this->buf); if (strlen($out) > self::MAX_STATEMENT) { throw new RelayError('import.statement_too_large', esc_html__('The SQL file contains a statement larger than 64 MB, which SiteRelay will not load.', 'siterelay')); } continue; } $out .= substr($this->buf, $this->i, $len); $this->i += $len; if ($this->buf[$this->i] === '\\') { $this->ensure(2); $out .= substr($this->buf, $this->i, 2); $this->i += 2; continue; } // The quote: doubled means a literal quote character. $this->ensure(2); if (($this->buf[$this->i + 1] ?? '') === $q) { $out .= $q . $q; $this->i += 2; continue; } $this->i++; return $q . $out . $q; } } private function skip_to(string $end): void { while (true) { $at = strpos($this->buf, $end, $this->i); if ($at !== false) { $this->i = $at + strlen($end); return; } $this->i = max($this->i, strlen($this->buf) - strlen($end) + 1); if (!$this->ensure(strlen($end) + 1)) { $this->i = strlen($this->buf); return; } } } /** At least $n bytes from the current position, unless the file ends; false at the end of the file. */ private function ensure(int $n): bool { while (strlen($this->buf) - $this->i < $n && !$this->eof) { $chunk = Fs::read($this->h, self::CHUNK); if ($chunk === false || $chunk === '') { $this->eof = true; break; } if ($this->i > 0) { $this->buf = substr($this->buf, $this->i); $this->base += $this->i; $this->i = 0; } $this->buf .= $chunk; } return $this->i < strlen($this->buf); } }