#include #define FREE_DBT_DATA(DATA) if (DATA.data) { \ free(DATA.data); \ DATA.data = NULL; \ } static bool list(evapp_ctx *ctx, evapp_db *db, HDF *hdf) { char tmpbuf[512]; DBT key, data; DBC *dbc; uint64_t id = 0; int count = 0, max = 25, ret = 0; int start = DB_LAST, dir = DB_PREV; HDF *data_hdf = NULL, *reparent = NULL; struct evhttp_request *request = evapp_request(ctx); /* Okay, we have a database handle - let's iterate! */ memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); /* Let's supply our own local memory for the key value */ key.data = &id; key.size = key.ulen = sizeof(id); key.flags = DB_DBT_USERMEM; /* let bdb allocate for us */ data.flags = DB_DBT_MALLOC; db->p->cursor(db->p, NULL, &dbc, 0); // Check the query for directionality if (hdf_get_value(hdf, "Query.first", NULL)) { start = DB_FIRST; dir = DB_NEXT; } ret = dbc->c_get(dbc, &key, &data, start); for (count = 0; ret == 0 && count < max; ++count) { if (data.size == 0) { // XXX: log this! continue; } if (!db->hdf(ctx, data.data, data.size, &data_hdf)) { // XXX: log ret = -1; FREE_DBT_DATA(data); continue; } snprintf(tmpbuf, sizeof(tmpbuf), "Data.%s", db->name); reparent = hdf_get_obj(data_hdf, tmpbuf); if (!reparent) { // XXX: log this error! hdf_destroy(&data_hdf); break; } snprintf(tmpbuf, sizeof(tmpbuf), "%llu", id); hdf_set_value(reparent, "id", tmpbuf); snprintf(tmpbuf, sizeof(tmpbuf), "List.%s.%d", db->name, count); hdf_copy(hdf, tmpbuf, reparent); free(data.data); data.data = NULL; data.size = 0; hdf_destroy(&data_hdf); ret = dbc->c_get(dbc, &key, &data, dir); } if (data.data) free(data.data); dbc->c_close(dbc); /* Add the final count for easy access */ hdf_set_int_value(hdf, "Count", count); return true; } // This is not sort_by - this just shows matches for the requested secondary // index. enum field_type_t { FT_STRING = 0, FT_INT, FT_INT64 }; static bool list_by(evapp_ctx *ctx, evapp_db *db, HDF *hdf) { char tmpbuf[512]; DBT key, data, pkey; DBC *dbc; evapp_db_index *db_index = NULL; uint64_t requested_int64 = 0, key_int64 = 0, pkey_id = 0; size_t value_len = 0; int requested_int = 0, key_int = 0, count = 0, max = 25, ret = 0, start = DB_LAST, dir = DB_PREV; enum field_type_t type = FT_STRING; char *by = NULL, *value = NULL, *requested_str = NULL; HDF *data_hdf = NULL, *reparent = NULL; bool ok = true; struct evhttp_request *request = evapp_request(ctx); /* Okay, we have a database handle - let's iterate! */ memset(&key, 0, sizeof(DBT)); memset(&pkey, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); by = hdf_get_value(hdf, "Query.by", NULL); value = hdf_get_value(hdf, "Query.value", NULL); /* We dup here so that we can use this as storage if the type is a string */ if (!by || !value) { send_not_found(ctx, "invalid index requested"); return false; } db_index = evapp_db_index_select(ctx, db, by); if (!db_index || !db_index->p) { // XXX: we could just crawl through, but it'd be ass slow. send_not_found(ctx, "the given field is not indexed"); return false; } /* We have to build the key off the correct type */ if (!strncmp(db_index->type, "int64", 5)) { key_int64 = requested_int64 = strtoll(value, NULL, 10); key.data = &key_int64; key.size = key.ulen = sizeof(uint64_t); key.flags = DB_DBT_USERMEM; type = FT_INT64; } else if (!strncmp(db_index->type, "int", 3)) { key_int = requested_int = strtol(value, NULL, 10); key.data = &key_int; key.size = key.ulen = sizeof(uint32_t); key.flags = DB_DBT_USERMEM; type = FT_INT; } else if (!strncmp(db_index->type, "string", 6)) { /* We can use by because it doesn't matter if stored keys don't * fit, we only want items that match _this_ key. */ requested_str = strdup(value); value_len = strlen(value); if (!requested_str) { send_error(ctx, "memory is tight!"); return false; } key.data = requested_str; key.size = key.ulen = value_len + 1; key.flags = DB_DBT_USERMEM; type = FT_STRING; } else { send_not_found(ctx, "unknown index type: schema may be newer than code."); return false; } /* Let's supply our own local memory for the pkey value */ /* It is erroring out when I use a static pkey even if the size * is correct. Is this a BDB bug? Or just not documented well? */ pkey.flags = DB_DBT_MALLOC; /* let bdb allocate for us */ data.flags = DB_DBT_MALLOC; db_index->p->cursor(db_index->p, NULL, &dbc, 0); /* Now that we have a cursor and valid keys, let's set our start * point. */ ret = dbc->c_pget(dbc, &key, &pkey, &data, DB_SET); if (hdf_get_value(hdf, "Query.first", NULL)) { dir = DB_NEXT; } else { /* If we're at our entry, step past it */ if (ret == 0) { DBT next_key; memset(&next_key, 0, sizeof(DBT)); next_key.flags = DB_DBT_MALLOC; FREE_DBT_DATA(data); FREE_DBT_DATA(pkey); /* We need a key holder because we have no size guarantees * on the key in the adjacent row */ ret = dbc->c_pget(dbc, &next_key, &pkey, &data, DB_NEXT_NODUP); FREE_DBT_DATA(data); FREE_DBT_DATA(next_key); FREE_DBT_DATA(pkey); /* Now step backwards one if this worked. */ if (ret == 0) { ret = dbc->c_pget(dbc, &key, &pkey, &data, DB_PREV); } else { /* If it failed, we know we have data so we must be at the * end of the database. */ ret = dbc->c_pget(dbc, &key, &pkey, &data, DB_LAST); } } dir = DB_PREV; } for (count = 0; ret == 0 && count < max; ++count) { if (pkey.size == 0) { FREE_DBT_DATA(data); continue; } if (data.size == 0) { // XXX: log this! FREE_DBT_DATA(pkey); continue; } pkey_id = *((uint64_t *)pkey.data); if (!db->hdf(ctx, data.data, data.size, &data_hdf)) { // XXX: log FREE_DBT_DATA(data); FREE_DBT_DATA(pkey); continue; } FREE_DBT_DATA(data); FREE_DBT_DATA(pkey); /* Let's ensure we are still looking at the data we're interested in */ ok = true; switch (type) { case FT_INT: if (key_int != requested_int) ok = false; break; case FT_INT64: if (key_int64 != requested_int64) ok = false; break; case FT_STRING: if (strncmp(value, key.data, value_len)) ok = false; break; } if (!ok) { hdf_destroy(&data_hdf); break; } snprintf(tmpbuf, sizeof(tmpbuf), "Data.%s", db->name); reparent = hdf_get_obj(data_hdf, tmpbuf); if (!reparent) { // XXX: log this error! hdf_destroy(&data_hdf); break; } snprintf(tmpbuf, sizeof(tmpbuf), "%llu", pkey_id); hdf_set_value(reparent, "id", tmpbuf); snprintf(tmpbuf, sizeof(tmpbuf), "List.%s.%d", db->name, count); hdf_copy(hdf, tmpbuf, reparent); hdf_destroy(&data_hdf); ret = dbc->c_pget(dbc, &key, &pkey, &data, dir); } FREE_DBT_DATA(data); FREE_DBT_DATA(pkey); dbc->c_close(dbc); /* Add the final count for easy access */ hdf_set_int_value(hdf, "Count", count); if (requested_str) free(requested_str); return true; } void handle_list(evapp_ctx *ctx) { char dbname[32], tmpbuf[512]; struct evapp_db_index *index_it = NULL; HDF *hdf, *query_hdf = NULL; evapp_db *db; bool status = true; int count = 0; char *query = NULL; struct evhttp_request *request = evapp_request(ctx); assert(ctx && request); if (!get_database_from_uri(request->uri, dbname, sizeof(dbname))) { send_not_found(ctx, "no valid object type was found"); return; } // For now, only handle GETs if ((query = index(request->uri, '?')) != NULL) { /* Note, this is safe. If query+1 is '\0' then strlen == 0. */ if (!parse_query(query+1, strlen(query+1), false, false, &query_hdf, NULL)) { send_not_found(ctx, "failed to parse data"); return; } } db = evapp_db_select(ctx, dbname); if (!db || !db->hdf || !db->p) { hdf_destroy(&query_hdf); send_not_found(ctx, "object type does not exist"); return; } hdf_init(&hdf); /* XXX: Do this in all handlers. Add the uri */ hdf_set_value(hdf, "Request.uri", request->uri); if (query_hdf) { // XXX: handle neoerr hdf_copy(hdf, "Query", query_hdf); hdf_destroy(&query_hdf); } if (!hdf) { send_not_found(ctx, "object type does not exist"); return; } /* Set a placeholder object */ snprintf(tmpbuf, sizeof(tmpbuf), "List.%s", dbname); hdf_set_value(hdf, tmpbuf, ""); /* Also, let's add a list of all the indices available for this object */ index_it = evapp_db_index_first(ctx, db); for (count = 0; index_it; index_it = index_it->entries.tqe_next, ++count) { snprintf(tmpbuf, sizeof(tmpbuf), "Index.%s", index_it->field); hdf_set_value(hdf, tmpbuf, index_it->type); /* NOTE: never trust a type sent by the client. instead look it up * yourself. (unless you can make it safe. */ } if (hdf_get_value(hdf, "Query.by", NULL)) { status = list_by(ctx, db, hdf); } else { status = list(ctx, db, hdf); } if (!status) { hdf_destroy(&hdf); return; } if (evapp_get_xsrf_protection(ctx) && !set_xsrf_token(ctx, hdf)) { send_not_found(ctx, "xsrf token subsystem failure"); hdf_destroy(&hdf); return; } if (!template_render(ctx, hdf, "/list/", dbname)) send_not_found(ctx, "template missing"); hdf_destroy(&hdf); return; }