TableColumns columns() const { TableColumns cols; for (int i = 0; i < 20; i++) { cols.push_back({"test_" + std::to_string(i), INTEGER_TYPE}); } return cols; }
std::string columnDefinition(const PluginResponse& response) { TableColumns columns; for (const auto& column : response) { columns.push_back(make_pair(column.at("name"), column.at("type"))); } return columnDefinition(columns); }
std::string columnDefinition(const TableColumns& columns) { std::string statement = "("; for (size_t i = 0; i < columns.size(); ++i) { statement += columns.at(i).first + " " + columns.at(i).second; if (i < columns.size() - 1) { statement += ", "; } } return statement += ")"; }
Status getQueryColumnsInternal(const std::string& q, TableColumns& columns, sqlite3* db) { // Turn the query into a prepared statement sqlite3_stmt* stmt{nullptr}; auto rc = sqlite3_prepare_v2( db, q.c_str(), static_cast<int>(q.length() + 1), &stmt, nullptr); if (rc != SQLITE_OK || stmt == nullptr) { if (stmt != nullptr) { sqlite3_finalize(stmt); } return Status(1, sqlite3_errmsg(db)); } // Get column count auto num_columns = sqlite3_column_count(stmt); TableColumns results; results.reserve(num_columns); // Get column names and types Status status = Status(); bool unknown_type = false; for (int i = 0; i < num_columns; ++i) { auto col_name = sqlite3_column_name(stmt, i); auto col_type = sqlite3_column_decltype(stmt, i); if (col_name == nullptr) { status = Status(1, "Could not get column type"); break; } if (col_type == nullptr) { // Types are only returned for table columns (not expressions). col_type = "UNKNOWN"; unknown_type = true; } results.push_back(std::make_tuple( col_name, columnTypeName(col_type), ColumnOptions::DEFAULT)); } // An unknown type means we have to parse the plan and SQLite opcodes. if (unknown_type) { QueryPlanner planner(q, db); planner.applyTypes(results); } if (status.ok()) { columns = std::move(results); } sqlite3_finalize(stmt); return status; }
TEST_F(SQLiteUtilTests, test_get_query_columns) { auto dbc = getTestDBC(); TableColumns results; std::string query = "SELECT seconds, version FROM time JOIN osquery_info"; auto status = getQueryColumnsInternal(query, results, dbc->db()); ASSERT_TRUE(status.ok()); ASSERT_EQ(2U, results.size()); EXPECT_EQ(std::make_pair(std::string("seconds"), INTEGER_TYPE), results[0]); EXPECT_EQ(std::make_pair(std::string("version"), TEXT_TYPE), results[1]); query = "SELECT * FROM foo"; status = getQueryColumnsInternal(query, results, dbc->db()); ASSERT_FALSE(status.ok()); }
Status getQueryColumnsExternal(const std::string& manager_path, const std::string& query, TableColumns& columns) { // Make sure the extension path exists, and is writable. auto status = extensionPathActive(manager_path); if (!status.ok()) { return status; } ExtensionResponse response; try { auto client = EXManagerClient(manager_path); client.get()->getQueryColumns(response, query); } catch (const std::exception& e) { return Status(1, "Extension call failed: " + std::string(e.what())); } // Translate response map: {string: string} to a vector: pair(name, type). for (const auto& column : response.response) { for (const auto& col : column) { columns.push_back( std::make_tuple(col.first, columnTypeName(col.second), DEFAULT)); } } return Status(response.status.code, response.status.message); }
Status QueryPlanner::applyTypes(TableColumns& columns) { std::map<size_t, ColumnType> column_types; for (const auto& row : program_) { if (row.at("opcode") == "ResultRow") { // The column parsing is finished. auto k = boost::lexical_cast<size_t>(row.at("p1")); for (const auto& type : column_types) { if (type.first - k < columns.size()) { std::get<1>(columns[type.first - k]) = type.second; } } } if (row.at("opcode") == "Copy") { // Copy P1 -> P1 + P3 into P2 -> P2 + P3. auto from = boost::lexical_cast<size_t>(row.at("p1")); auto to = boost::lexical_cast<size_t>(row.at("p2")); auto size = boost::lexical_cast<size_t>(row.at("p3")); for (size_t i = 0; i <= size; i++) { if (column_types.count(from + i)) { column_types[to + i] = std::move(column_types[from + i]); column_types.erase(from + i); } } } if (kSQLOpcodes.count(row.at("opcode"))) { const auto& op = kSQLOpcodes.at(row.at("opcode")); auto k = boost::lexical_cast<size_t>(row.at(Opcode::regString(op.reg))); column_types[k] = op.type; } } return Status(0); }
void GUITable::setTable(const TableOptions &options, const TableColumns &columns, std::vector<std::string> &content) { clear(); // Naming conventions: // i is always a row index, 0-based // j is always a column index, 0-based // k is another index, for example an option index // Handle a stupid error case... (issue #1187) if (columns.empty()) { TableColumn text_column; text_column.type = "text"; TableColumns new_columns; new_columns.push_back(text_column); setTable(options, new_columns, content); return; } // Handle table options video::SColor default_color(255, 255, 255, 255); s32 opendepth = 0; for (size_t k = 0; k < options.size(); ++k) { const std::string &name = options[k].name; const std::string &value = options[k].value; if (name == "color") parseColorString(value, m_color, false); else if (name == "background") parseColorString(value, m_background, false); else if (name == "border") m_border = is_yes(value); else if (name == "highlight") parseColorString(value, m_highlight, false); else if (name == "highlight_text") parseColorString(value, m_highlight_text, false); else if (name == "opendepth") opendepth = stoi(value); else errorstream<<"Invalid table option: \""<<name<<"\"" <<" (value=\""<<value<<"\")"<<std::endl; } // Get number of columns and rows // note: error case columns.size() == 0 was handled above s32 colcount = columns.size(); assert(colcount >= 1); // rowcount = ceil(cellcount / colcount) but use integer arithmetic s32 rowcount = (content.size() + colcount - 1) / colcount; assert(rowcount >= 0); // Append empty strings to content if there is an incomplete row s32 cellcount = rowcount * colcount; while (content.size() < (u32) cellcount) content.push_back(""); // Create temporary rows (for processing columns) struct TempRow { // Current horizontal position (may different between rows due // to indent/tree columns, or text/image columns with width<0) s32 x; // Tree indentation level s32 indent; // Next cell: Index into m_strings or m_images s32 content_index; // Next cell: Width in pixels s32 content_width; // Vector of completed cells in this row std::vector<Cell> cells; // Stores colors and how long they last (maximum column index) std::vector<std::pair<video::SColor, s32> > colors; TempRow(): x(0), indent(0), content_index(0), content_width(0) {} }; TempRow *rows = new TempRow[rowcount]; // Get em width. Pedantically speaking, the width of "M" is not // necessarily the same as the em width, but whatever, close enough. s32 em = 6; if (m_font) em = m_font->getDimension(L"M").Width; s32 default_tooltip_index = allocString(""); std::map<s32, s32> active_image_indices; // Process content in column-major order for (s32 j = 0; j < colcount; ++j) { // Check column type ColumnType columntype = COLUMN_TYPE_TEXT; if (columns[j].type == "text") columntype = COLUMN_TYPE_TEXT; else if (columns[j].type == "image") columntype = COLUMN_TYPE_IMAGE; else if (columns[j].type == "color") columntype = COLUMN_TYPE_COLOR; else if (columns[j].type == "indent") columntype = COLUMN_TYPE_INDENT; else if (columns[j].type == "tree") columntype = COLUMN_TYPE_TREE; else errorstream<<"Invalid table column type: \"" <<columns[j].type<<"\""<<std::endl; // Process column options s32 padding = myround(0.5 * em); s32 tooltip_index = default_tooltip_index; s32 align = 0; s32 width = 0; s32 span = colcount; if (columntype == COLUMN_TYPE_INDENT) { padding = 0; // default indent padding } if (columntype == COLUMN_TYPE_INDENT || columntype == COLUMN_TYPE_TREE) { width = myround(em * 1.5); // default indent width } for (size_t k = 0; k < columns[j].options.size(); ++k) { const std::string &name = columns[j].options[k].name; const std::string &value = columns[j].options[k].value; if (name == "padding") padding = myround(stof(value) * em); else if (name == "tooltip") tooltip_index = allocString(value); else if (name == "align" && value == "left") align = 0; else if (name == "align" && value == "center") align = 1; else if (name == "align" && value == "right") align = 2; else if (name == "align" && value == "inline") align = 3; else if (name == "width") width = myround(stof(value) * em); else if (name == "span" && columntype == COLUMN_TYPE_COLOR) span = stoi(value); else if (columntype == COLUMN_TYPE_IMAGE && !name.empty() && string_allowed(name, "0123456789")) { s32 content_index = allocImage(value); active_image_indices.insert(std::make_pair( stoi(name), content_index)); } else { errorstream<<"Invalid table column option: \""<<name<<"\"" <<" (value=\""<<value<<"\")"<<std::endl; } } // If current column type can use information from "color" columns, // find out which of those is currently active if (columntype == COLUMN_TYPE_TEXT) { for (s32 i = 0; i < rowcount; ++i) { TempRow *row = &rows[i]; while (!row->colors.empty() && row->colors.back().second < j) row->colors.pop_back(); } } // Make template for new cells Cell newcell; memset(&newcell, 0, sizeof newcell); newcell.content_type = columntype; newcell.tooltip_index = tooltip_index; newcell.reported_column = j+1; if (columntype == COLUMN_TYPE_TEXT) { // Find right edge of column s32 xmax = 0; for (s32 i = 0; i < rowcount; ++i) { TempRow *row = &rows[i]; row->content_index = allocString(content[i * colcount + j]); const core::stringw &text = m_strings[row->content_index]; row->content_width = m_font ? m_font->getDimension(text.c_str()).Width : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); } // Add a new cell (of text type) to each row for (s32 i = 0; i < rowcount; ++i) { newcell.xmin = rows[i].x + padding; alignContent(&newcell, xmax, rows[i].content_width, align); newcell.content_index = rows[i].content_index; newcell.color_defined = !rows[i].colors.empty(); if (newcell.color_defined) newcell.color = rows[i].colors.back().first; rows[i].cells.push_back(newcell); rows[i].x = newcell.xmax; } } else if (columntype == COLUMN_TYPE_IMAGE) { // Find right edge of column s32 xmax = 0; for (s32 i = 0; i < rowcount; ++i) { TempRow *row = &rows[i]; row->content_index = -1; // Find content_index. Image indices are defined in // column options so check active_image_indices. s32 image_index = stoi(content[i * colcount + j]); std::map<s32, s32>::iterator image_iter = active_image_indices.find(image_index); if (image_iter != active_image_indices.end()) row->content_index = image_iter->second; // Get texture object (might be NULL) video::ITexture *image = NULL; if (row->content_index >= 0) image = m_images[row->content_index]; // Get content width and update xmax row->content_width = image ? image->getOriginalSize().Width : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); } // Add a new cell (of image type) to each row for (s32 i = 0; i < rowcount; ++i) { newcell.xmin = rows[i].x + padding; alignContent(&newcell, xmax, rows[i].content_width, align); newcell.content_index = rows[i].content_index; rows[i].cells.push_back(newcell); rows[i].x = newcell.xmax; } active_image_indices.clear(); } else if (columntype == COLUMN_TYPE_COLOR) { for (s32 i = 0; i < rowcount; ++i) { video::SColor cellcolor(255, 255, 255, 255); if (parseColorString(content[i * colcount + j], cellcolor, true)) rows[i].colors.push_back(std::make_pair(cellcolor, j+span)); } } else if (columntype == COLUMN_TYPE_INDENT || columntype == COLUMN_TYPE_TREE) { // For column type "tree", reserve additional space for +/- // Also enable special processing for treeview-type tables s32 content_width = 0; if (columntype == COLUMN_TYPE_TREE) { content_width = m_font ? m_font->getDimension(L"+").Width : 0; m_has_tree_column = true; } // Add a new cell (of indent or tree type) to each row for (s32 i = 0; i < rowcount; ++i) { TempRow *row = &rows[i]; s32 indentlevel = stoi(content[i * colcount + j]); indentlevel = MYMAX(indentlevel, 0); if (columntype == COLUMN_TYPE_TREE) row->indent = indentlevel; newcell.xmin = row->x + padding; newcell.xpos = newcell.xmin + indentlevel * width; newcell.xmax = newcell.xpos + content_width; newcell.content_index = 0; newcell.color_defined = !rows[i].colors.empty(); if (newcell.color_defined) newcell.color = rows[i].colors.back().first; row->cells.push_back(newcell); row->x = newcell.xmax; } } } // Copy temporary rows to not so temporary rows if (rowcount >= 1) { m_rows.resize(rowcount); for (s32 i = 0; i < rowcount; ++i) { Row *row = &m_rows[i]; row->cellcount = rows[i].cells.size(); row->cells = new Cell[row->cellcount]; memcpy((void*) row->cells, (void*) &rows[i].cells[0], row->cellcount * sizeof(Cell)); row->indent = rows[i].indent; row->visible_index = i; m_visible_rows.push_back(i); } } if (m_has_tree_column) { // Treeview: convert tree to indent cells on leaf rows for (s32 i = 0; i < rowcount; ++i) { if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent) for (s32 j = 0; j < m_rows[i].cellcount; ++j) if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE) m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT; } // Treeview: close rows according to opendepth option std::set<s32> opened_trees; for (s32 i = 0; i < rowcount; ++i) if (m_rows[i].indent < opendepth) opened_trees.insert(i); setOpenedTrees(opened_trees); } // Delete temporary information used only during setTable() delete[] rows; allocationComplete(); // Clamp scroll bar position updateScrollBar(); }
Status ATCConfigParserPlugin::update(const std::string& source, const ParserConfig& config) { auto cv = config.find(kParserKey); if (cv == config.end()) { return Status(1, "No configuration for ATC (Auto Table Construction)"); } auto obj = data_.getObject(); data_.copyFrom(cv->second.doc(), obj); data_.add(kParserKey, obj); const auto& ac_tables = data_.doc()[kParserKey]; auto tables = RegistryFactory::get().registry("table"); auto registered = registeredATCTables(); for (const auto& ac_table : ac_tables.GetObject()) { std::string table_name{ac_table.name.GetString()}; auto params = ac_table.value.GetObject(); std::string query{params.HasMember("query") && params["query"].IsString() ? params["query"].GetString() : ""}; std::string path{params.HasMember("path") && params["path"].IsString() ? params["path"].GetString() : ""}; std::string platform{params.HasMember("platform") && params["platform"].IsString() ? params["platform"].GetString() : ""}; if (query.empty() || path.empty()) { LOG(WARNING) << "ATC Table: " << table_name << " is misconfigured"; continue; } if (!checkPlatform(platform)) { VLOG(1) << "Skipping ATC table: " << table_name << " because platform doesn't match"; continue; } TableColumns columns; std::string columns_value; columns_value.reserve(256); for (const auto& column : params["columns"].GetArray()) { columns.push_back(make_tuple( std::string(column.GetString()), TEXT_TYPE, ColumnOptions::DEFAULT)); columns_value += std::string(column.GetString()) + ","; } registered.erase(table_name); std::string table_settings{table_name + query + columns_value + path}; std::string old_setting; auto s = getDatabaseValue( kPersistentSettings, kDatabaseKeyPrefix + table_name, old_setting); // The ATC table hasn't changed so we skip ahead if (table_settings == old_setting) { continue; } // Remove the old table to replace with the new one s = removeATCTables({table_name}); if (!s.ok()) { LOG(WARNING) << "ATC table overrides core table; Refusing registration"; continue; } s = setDatabaseValue( kPersistentSettings, kDatabaseKeyPrefix + table_name, table_settings); if (!s.ok()) { LOG(WARNING) << "Could not write to database"; continue; } s = tables->add( table_name, std::make_shared<ATCPlugin>(path, columns, query), true); if (!s.ok()) { LOG(WARNING) << s.getMessage(); deleteDatabaseValue(kPersistentSettings, kDatabaseKeyPrefix + table_name); continue; } PluginResponse resp; Registry::call( "sql", "sql", {{"action", "attach"}, {"table", table_name}}, resp); LOG(INFO) << "Registered ATC table: " << table_name; } if (registered.size() > 0) { VLOG(1) << "Removing any ATC tables that were removed in this configuration " "change"; removeATCTables(registered); } return Status(); }