Kea 2.7.1
message_reader.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <errno.h>
10#include <string.h>
11
12#include <iostream>
13#include <fstream>
14
16#include <log/log_messages.h>
18#include <log/message_reader.h>
19#include <util/str.h>
20
21using namespace isc::util;
22using namespace std;
23
24namespace {
25const char DIRECTIVE_FLAG = '$'; // Starts each directive
26const char MESSAGE_FLAG = '%'; // Starts each message
27}
28
29
30namespace isc {
31namespace log {
32
33// Read the file.
34
35void
37
38 // Ensure the non-added collection is empty: we could be re-using this
39 // object.
40 not_added_.clear();
41
42 // Open the file.
43 ifstream infile(file.c_str());
44 if (infile.fail()) {
45 isc_throw_4(MessageException, "Failed to open message file",
46 LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
47 }
48
49 // Loop round reading it. As we process the file one line at a time,
50 // keep a track of line number of aid diagnosis of problems.
51 string line;
52 getline(infile, line);
53 lineno_ = 0;
54
55 while (infile.good()) {
56 ++lineno_;
57 processLine(line, mode);
58 getline(infile, line);
59 }
60
61 // Why did the loop terminate?
62 if (!infile.eof()) {
63 isc_throw_4(MessageException, "Error reading message file",
64 LOG_READ_ERROR, file, strerror(errno), 0);
65 }
66 infile.close();
67}
68
69// Parse a line of the file.
70
71void
73
74 // Get rid of leading and trailing spaces
75 string text(str::trim(line));
76
77 if (text.empty()) {
78 ; // Ignore blank lines
79
80 } else if (text[0] == DIRECTIVE_FLAG) {
81 parseDirective(text); // Process directives
82
83
84 } else if (text[0] == MESSAGE_FLAG) {
85 parseMessage(text, mode); // Process message definition line
86
87 } else {
88 ; // Other lines are extended message
89 // description so are ignored
90 }
91}
92
93// Process directive
94
95void
96MessageReader::parseDirective(const std::string& text) {
97
98
99 // Break into tokens
100 vector<string> tokens(str::tokens(text));
101
102 // Uppercase directive and branch on valid ones
103 str::uppercase(tokens[0]);
104 if (tokens[0] == "$PREFIX") {
105 parsePrefix(tokens);
106
107 } else if (tokens[0] == "$NAMESPACE") {
108 parseNamespace(tokens);
109
110 } else {
111
112 // Unrecognized directive
113 isc_throw_3(MessageException, "Unrecognized directive",
115 lineno_);
116 }
117}
118
119// Process $PREFIX
120void
121MessageReader::parsePrefix(const vector<string>& tokens) {
122
123 // Should not get here unless there is something in the tokens array.
124 isc_throw_assert(!tokens.empty());
125
126 // Process $PREFIX. With no arguments, the prefix is set to the empty
127 // string. One argument sets the prefix to the to its value and more than
128 // one argument is invalid.
129 if (tokens.size() == 1) {
130 prefix_ = "";
131
132 } else if (tokens.size() == 2) {
133 prefix_ = tokens[1];
134
135 // Token is potentially valid providing it only contains alphabetic
136 // and numeric characters (and underscores) and does not start with a
137 // digit.
138 if (invalidSymbol(prefix_)) {
139 isc_throw_3(MessageException, "Invalid prefix",
140 LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
141 }
142
143 } else {
144
145 // Too many arguments
146 isc_throw_2(MessageException, "Too many arguments",
147 LOG_PREFIX_EXTRA_ARGS, lineno_);
148 }
149}
150
151// Check if string is an invalid C++ symbol. It is valid if comprises only
152// alphanumeric characters and underscores, and does not start with a digit.
153// (Owing to the logic of the rest of the code, we check for its invalidity,
154// not its validity.)
155bool
156MessageReader::invalidSymbol(const string& symbol) {
157 static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
158 "abcdefghijklmnopqrstuvwxyz"
159 "0123456789_";
160 return ( symbol.empty() ||
161 (symbol.find_first_not_of(valid_chars) != string::npos) ||
162 (std::isdigit(symbol[0])));
163}
164
165// Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
166// except that only limited checks will be done on the namespace (to avoid a
167// lot of parsing and separating out of the namespace components.) Also, unlike
168// $PREFIX, there can only be one $NAMESPACE in a file.
169
170void
171MessageReader::parseNamespace(const vector<string>& tokens) {
172
173 // Check argument count
174 if (tokens.size() < 2) {
175 isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
176 lineno_);
177
178 } else if (tokens.size() > 2) {
179 isc_throw_2(MessageException, "Too many arguments",
180 LOG_NAMESPACE_EXTRA_ARGS, lineno_);
181
182 }
183
184 // Token is potentially valid providing it only contains alphabetic
185 // and numeric characters (and underscores and colons). As noted above,
186 // we won't be exhaustive - after all, and code containing the resultant
187 // namespace will have to be compiled, and the compiler will catch errors.
188 static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
189 "abcdefghijklmnopqrstuvwxyz"
190 "0123456789_:";
191 if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
192 isc_throw_3(MessageException, "Invalid argument",
193 LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
194 }
195
196 // All OK - unless the namespace has already been set.
197 if (ns_.size() != 0) {
198 isc_throw_2(MessageException, "Duplicate namespace",
199 LOG_DUPLICATE_NAMESPACE, lineno_);
200 }
201
202 // Prefix has not been set, so set it and return success.
203 ns_ = tokens[1];
204}
205
206// Process message. By the time this method is called, the line has been
207// stripped of leading and trailing spaces. The first character of the string
208// is the message introducer, so we can get rid of that. The remainder is
209// a line defining a message.
210//
211// The first token on the line, when concatenated to the prefix and converted to
212// upper-case, is the message ID. The first of the line from the next token
213// on is the message text.
214
215void
216MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
217
218 static string delimiters("\t\n "); // Delimiters
219
220 // The line passed should be at least one character long and start with the
221 // message introducer (else we should not have got here).
222 isc_throw_assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
223
224 // A line comprising just the message introducer is not valid.
225 if (text.size() == 1) {
226 isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
227 text, lineno_);
228 }
229
230 // Strip off the introducer and any leading space after that.
231 string message_line = str::trim(text.substr(1));
232
233 // Look for the first delimiter.
234 size_t first_delim = message_line.find_first_of(delimiters);
235 if (first_delim == string::npos) {
236
237 // Just a single token in the line - this is not valid
238 isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
239 message_line, lineno_);
240 }
241
242 // Extract the first token into the message ID, preceding it with the
243 // current prefix, then convert to upper-case. If the prefix is not set,
244 // perform the valid character check now - the string will become a C++
245 // symbol so we may as well identify problems early.
246 string ident = prefix_ + message_line.substr(0, first_delim);
247 if (prefix_.empty()) {
248 if (invalidSymbol(ident)) {
249 isc_throw_3(MessageException, "Invalid message ID",
250 LOG_INVALID_MESSAGE_ID, ident, lineno_);
251 }
252 }
253 str::uppercase(ident);
254
255 // Locate the start of the message text
256 size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
257 if (first_text == string::npos) {
258
259 // ?? This happens if there are trailing delimiters, which should not
260 // occur as we have stripped trailing spaces off the line. Just treat
261 // this as a single-token error for simplicity's sake.
262 isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
263 message_line, lineno_);
264 }
265
266 // Add the result to the dictionary and to the non-added list if the add to
267 // the dictionary fails.
268 bool added;
269 if (mode == ADD) {
270 added = dictionary_->add(ident, message_line.substr(first_text));
271 } else {
272 added = dictionary_->replace(ident, message_line.substr(first_text));
273 }
274 if (!added) {
275 not_added_.push_back(ident);
276 }
277}
278
279} // namespace log
280} // namespace isc
virtual bool replace(const MessageID &ident, const std::string &text)
Replace Message.
virtual bool add(const MessageID &ident, const std::string &text)
Add Message.
virtual void processLine(const std::string &line, Mode mode=ADD)
Process Line.
virtual void readFile(const std::string &file, Mode mode=ADD)
Read File.
#define isc_throw_3(type, stream, param1, param2, param3)
Similar as isc_throw, but allows the exception to have three additional parameters (the stream/text g...
#define isc_throw_2(type, stream, param1, param2)
Similar as isc_throw, but allows the exception to have two additional parameters (the stream/text goe...
#define isc_throw_4(type, stream, param1, param2, param3, param4)
Similar as isc_throw, but allows the exception to have four additional parameters (the stream/text go...
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
const isc::log::MessageID LOG_UNRECOGNIZED_DIRECTIVE
const isc::log::MessageID LOG_NAMESPACE_INVALID_ARG
const isc::log::MessageID LOG_NO_MESSAGE_TEXT
const isc::log::MessageID LOG_READ_ERROR
const isc::log::MessageID LOG_NO_MESSAGE_ID
const isc::log::MessageID LOG_NAMESPACE_EXTRA_ARGS
const isc::log::MessageID LOG_DUPLICATE_NAMESPACE
const isc::log::MessageID LOG_PREFIX_EXTRA_ARGS
const isc::log::MessageID LOG_INPUT_OPEN_FAIL
const isc::log::MessageID LOG_INVALID_MESSAGE_ID
const isc::log::MessageID LOG_NAMESPACE_NO_ARGS
const isc::log::MessageID LOG_PREFIX_INVALID_ARG
vector< string > tokens(const string &text, const string &delim, bool escape)
Split string into tokens.
Definition str.cc:52
string trim(const string &input)
Trim leading and trailing spaces.
Definition str.cc:32
void uppercase(string &text)
Convert string to uppercase.
Definition str.cc:124
Defines the logger used by the top-level component of kea-lfc.