getMessage(); } } function SQLite3_connect($filename, $mode = SQLITE3_OPEN_READONLY) { if (strlen($filename) > 0) { $db = SQLite3_connect_try($filename, $mode, true); } else { exit('No database available'); } if (is_string($db)) { exit("Error connecting to database\n".$db); } // Add busy timeout so methods don't fail immediately when, e.g., FTL is currently reading from the DB $db->busyTimeout(5000); return $db; } /** * Add domains to a given table. * * @param $db object The SQLite3 database connection object * @param $table string The target table * @param $domains array Array of domains (strings) to be added to the table * @param $wildcardstyle boolean Whether to format the input domains in legacy wildcard notation * @param $returnnum boolean Whether to return an integer or a string * @param $type integer The target type (0 = exact whitelist, 1 = exact blacklist, 2 = regex whitelist, 3 = regex blacklist) * @param mixed|null $comment * * @return string Success/error and number of processed domains */ function add_to_table($db, $table, $domains, $comment = null, $wildcardstyle = false, $returnnum = false, $type = -1) { if (!is_int($type)) { return 'Error: Argument type has to be of type integer (is '.gettype($type).')'; } // Begin transaction if (!$db->exec('BEGIN TRANSACTION;')) { if ($returnnum) { return 0; } return "Error: Unable to begin transaction for {$table} table."; } // To which column should the record be added to? if ('adlist' === $table) { $field = 'address'; } else { $field = 'domain'; } // Get initial count of domains in this table if (-1 === $type) { $countquery = "SELECT COUNT(*) FROM {$table};"; } else { $countquery = "SELECT COUNT(*) FROM {$table} WHERE type = {$type};"; } $initialcount = intval($db->querySingle($countquery)); // Prepare INSERT SQLite statement $bindcomment = false; if ('domain_audit' === $table) { $querystr = "INSERT OR IGNORE INTO {$table} ({$field}) VALUES (:{$field});"; } elseif (-1 === $type) { $querystr = "INSERT OR IGNORE INTO {$table} ({$field},comment) VALUES (:{$field}, :comment);"; $bindcomment = true; } else { $querystr = "REPLACE INTO {$table} ({$field},comment,type) VALUES (:{$field}, :comment, {$type});"; $bindcomment = true; } $stmt = $db->prepare($querystr); // Return early if we failed to prepare the SQLite statement if (!$stmt) { if ($returnnum) { return 0; } return "Error: Failed to prepare statement for {$table} table (type = {$type}, field = {$field})."; } // Loop over domains and inject the lines into the database $num = 0; foreach ($domains as $domain) { // Limit max length for a domain entry to 253 chars if (strlen($domain) > 253) { continue; } if ($wildcardstyle) { $domain = '(\\.|^)'.str_replace('.', '\\.', $domain).'$'; } $stmt->bindValue(":{$field}", htmlentities($domain), SQLITE3_TEXT); if ($bindcomment) { $stmt->bindValue(':comment', htmlentities($comment), SQLITE3_TEXT); } if ($stmt->execute() && $stmt->reset()) { ++$num; } else { $stmt->close(); if ($returnnum) { return $num; } if (1 === $num) { $plural = ''; } else { $plural = 's'; } return 'Error: '.$db->lastErrorMsg().', added '.$num.' domain'.$plural; } } // Close prepared statement and return number of processed rows $stmt->close(); $db->exec('COMMIT;'); if ($returnnum) { return $num; } $finalcount = intval($db->querySingle($countquery)); $modified = $finalcount - $initialcount; // If we add less domains than the user specified, then they wanted to add duplicates if ($modified !== $num) { $delta = $num - $modified; $extra = ' (skipped '.$delta.' duplicates)'; } else { $extra = ''; } if (1 === $num) { $plural = ''; } else { $plural = 's'; } return 'Success, added '.$modified.' of '.$num.' domain'.$plural.$extra; } /** * Remove domains from a given table. * * @param $db object The SQLite3 database connection object * @param $table string The target table * @param $domains array Array of domains (strings) to be removed from the table * @param $returnnum boolean Whether to return an integer or a string * @param $type integer The target type (0 = exact whitelist, 1 = exact blacklist, 2 = regex whitelist, 3 = regex blacklist) * * @return string Success/error and number of processed domains */ function remove_from_table($db, $table, $domains, $returnnum = false, $type = -1) { if (!is_int($type)) { return 'Error: Argument type has to be of type integer (is '.gettype($type).')'; } // Begin transaction if (!$db->exec('BEGIN TRANSACTION;')) { if ($returnnum) { return 0; } return 'Error: Unable to begin transaction for domainlist table.'; } // Get initial count of domains in this table if (-1 === $type) { $countquery = "SELECT COUNT(*) FROM {$table};"; } else { $countquery = "SELECT COUNT(*) FROM {$table} WHERE type = {$type};"; } $initialcount = intval($db->querySingle($countquery)); // Prepare SQLite statement if (-1 === $type) { $querystr = "DELETE FROM {$table} WHERE domain = :domain AND type = {$type};"; } else { $querystr = "DELETE FROM {$table} WHERE domain = :domain;"; } $stmt = $db->prepare($querystr); // Return early if we failed to prepare the SQLite statement if (!$stmt) { if ($returnnum) { return 0; } return 'Error: Failed to prepare statement for '.$table.' table (type = '.$type.').'; } // Loop over domains and remove the lines from the database $num = 0; foreach ($domains as $domain) { $stmt->bindValue(':domain', $domain, SQLITE3_TEXT); if ($stmt->execute() && $stmt->reset()) { ++$num; } else { $stmt->close(); if ($returnnum) { return $num; } if (1 === $num) { $plural = ''; } else { $plural = 's'; } return 'Error: '.$db->lastErrorMsg().', removed '.$num.' domain'.$plural; } } // Close prepared statement and return number or processed rows $stmt->close(); $db->exec('COMMIT;'); if ($returnnum) { return $num; } if (1 === $num) { $plural = ''; } else { $plural = 's'; } return 'Success, removed '.$num.' domain'.$plural; } if (!class_exists('ListType')) { class ListType { public const whitelist = 0; public const blacklist = 1; public const regex_whitelist = 2; public const regex_blacklist = 3; } }