blob: 0a2e7d755b51ba0b49dd0db228ca0919552a4ccf [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lib/json/json_parser.h"
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <functional>
#include <string>
#include <lib/fit/function.h>
#include "lib/fxl/files/file.h"
#include "lib/fxl/macros.h"
#include "lib/fxl/strings/join_strings.h"
#include "lib/fxl/strings/string_printf.h"
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
namespace json {
namespace {
using ErrorCallback = fit::function<void(size_t, const std::string&)>;
using fxl::StringPrintf;
void GetLineAndColumnForOffset(const std::string& input, size_t offset,
int32_t* output_line, int32_t* output_column) {
if (offset == 0) {
// Errors at position 0 are assumed to be related to the whole file.
*output_line = 0;
*output_column = 0;
return;
}
*output_line = 1;
*output_column = 1;
for (size_t i = 0; i < input.size() && i < offset; i++) {
if (input[i] == '\n') {
*output_line += 1;
*output_column = 1;
} else {
*output_column += 1;
}
}
}
} // namespace
rapidjson::Document JSONParser::ParseFromFile(const std::string& file) {
file_ = file;
std::string data;
if (!files::ReadFileToString(file, &data)) {
errors_.push_back(StringPrintf("Failed to read file: %s", file.c_str()));
return rapidjson::Document();
}
return ParseFromString(data, file);
}
rapidjson::Document JSONParser::ParseFromFileAt(int dirfd,
const std::string& file) {
file_ = file;
std::string data;
if (!files::ReadFileToStringAt(dirfd, file, &data)) {
errors_.push_back(StringPrintf("Failed to read file: %s", file.c_str()));
return rapidjson::Document();
}
return ParseFromString(data, file);
}
rapidjson::Document JSONParser::ParseFromString(const std::string& data,
const std::string& file) {
data_ = data;
file_ = file;
rapidjson::Document document;
document.Parse(data_);
if (document.HasParseError()) {
ReportErrorInternal(document.GetErrorOffset(),
GetParseError_En(document.GetParseError()));
}
return document;
}
void JSONParser::ParseFromDirectory(
const std::string& path, fit::function<void(rapidjson::Document)> cb) {
static constexpr char kPathTooLong[] =
"Config directory path is too long: %s";
char buf[PATH_MAX];
buf[0] = '\0';
if (strlcpy(buf, path.c_str(), PATH_MAX) >= PATH_MAX) {
file_ = path;
ReportError(fxl::StringPrintf(kPathTooLong, buf));
return;
}
if (buf[strlen(buf) - 2] != '/' && strlcat(buf, "/", PATH_MAX) >= PATH_MAX) {
file_ = path;
ReportError(fxl::StringPrintf(kPathTooLong, buf));
return;
}
const size_t dir_len = strlen(buf);
DIR* cfg_dir = opendir(path.c_str());
if (cfg_dir != nullptr) {
for (dirent* cfg = readdir(cfg_dir); cfg != nullptr;
cfg = readdir(cfg_dir)) {
if (strcmp(".", cfg->d_name) == 0 || strcmp("..", cfg->d_name) == 0) {
continue;
}
if (strlcat(buf, cfg->d_name, PATH_MAX) >= PATH_MAX) {
file_ = path;
ReportError(fxl::StringPrintf(kPathTooLong, buf));
continue;
}
rapidjson::Document document = ParseFromFile(buf);
if (!document.HasParseError() && !document.IsNull()) {
cb(std::move(document));
}
// Reset buf to directory path.
buf[dir_len] = '\0';
}
closedir(cfg_dir);
} else {
file_ = path;
ReportError("Could not open config directory.");
}
}
void JSONParser::ReportError(const std::string& error) {
ReportErrorInternal(0, error);
}
void JSONParser::ReportErrorInternal(size_t offset, const std::string& error) {
int32_t line;
int32_t column;
GetLineAndColumnForOffset(data_, offset, &line, &column);
if (line == 0) {
errors_.push_back(StringPrintf("%s: %s", file_.c_str(), error.c_str()));
} else {
errors_.push_back(StringPrintf("%s:%d:%d: %s", file_.c_str(), line, column,
error.c_str()));
}
}
bool JSONParser::HasError() const { return !errors_.empty(); }
std::string JSONParser::error_str() const {
return fxl::JoinStrings(errors_, "\n");
}
} // namespace json