Kea  1.5.0
message_reader.cc
Go to the documentation of this file.
1 // Copyright (C) 2011-2015 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 <cassert>
10 #include <errno.h>
11 #include <string.h>
12 #include <iostream>
13 
14 #include <iostream>
15 #include <fstream>
16 
17 #include <log/log_messages.h>
18 #include <log/message_exception.h>
19 #include <log/message_reader.h>
20 #include <util/strutil.h>
21 
22 using namespace std;
23 
24 namespace {
25 const char DIRECTIVE_FLAG = '$'; // Starts each directive
26 const char MESSAGE_FLAG = '%'; // Starts each message
27 }
28 
29 
30 namespace isc {
31 namespace log {
32 
33 // Read the file.
34 
35 void
36 MessageReader::readFile(const string& file, MessageReader::Mode mode) {
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 
71 void
72 MessageReader::processLine(const string& line, MessageReader::Mode mode) {
73 
74  // Get rid of leading and trailing spaces
75  string text = isc::util::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 
95 void
96 MessageReader::parseDirective(const std::string& text) {
97 
98 
99  // Break into tokens
100  vector<string> tokens = isc::util::str::tokens(text);
101 
102  // Uppercase directive and branch on valid ones
104  if (tokens[0] == string("$PREFIX")) {
105  parsePrefix(tokens);
106 
107  } else if (tokens[0] == string("$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
120 void
121 MessageReader::parsePrefix(const vector<string>& tokens) {
122 
123  // Should not get here unless there is something in the tokens array.
124  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.)
155 bool
156 MessageReader::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 
170 void
171 MessageReader::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 
215 void
216 MessageReader::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  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 = isc::util::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  }
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  }
272  else {
273  added = dictionary_->replace(ident, message_line.substr(first_text));
274  }
275  if (!added) {
276  not_added_.push_back(ident);
277  }
278 }
279 
280 } // namespace log
281 } // namespace isc
isc::util::str::tokens
vector< string > tokens(const std::string &text, const std::string &delim, bool escape)
Split String into Tokens.
Definition: strutil.cc:77
isc::util::str::trim
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
isc::log::LOG_DUPLICATE_NAMESPACE
const isc::log::MessageID LOG_DUPLICATE_NAMESPACE
Definition: log_messages.h:15
message_exception.h
isc_throw_3
#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...
Definition: exceptions/exceptions.h:222
isc::log::LOG_NAMESPACE_NO_ARGS
const isc::log::MessageID LOG_NAMESPACE_NO_ARGS
Definition: log_messages.h:20
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
isc::log::LOG_NO_MESSAGE_TEXT
const isc::log::MessageID LOG_NO_MESSAGE_TEXT
Definition: log_messages.h:22
strutil.h
isc::log::LOG_INVALID_MESSAGE_ID
const isc::log::MessageID LOG_INVALID_MESSAGE_ID
Definition: log_messages.h:17
isc_throw_2
#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...
Definition: exceptions/exceptions.h:212
isc_throw_4
#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...
Definition: exceptions/exceptions.h:233
isc::log::LOG_NAMESPACE_EXTRA_ARGS
const isc::log::MessageID LOG_NAMESPACE_EXTRA_ARGS
Definition: log_messages.h:18
isc::log::MessageReader::Mode
Mode
Read Mode.
Definition: message_reader.h:36
isc::log::LOG_NO_MESSAGE_ID
const isc::log::MessageID LOG_NO_MESSAGE_ID
Definition: log_messages.h:21
isc::log::LOG_UNRECOGNIZED_DIRECTIVE
const isc::log::MessageID LOG_UNRECOGNIZED_DIRECTIVE
Definition: log_messages.h:29
isc::log::LOG_PREFIX_EXTRA_ARGS
const isc::log::MessageID LOG_PREFIX_EXTRA_ARGS
Definition: log_messages.h:25
message_reader.h
isc::log::MessageException
Message Exception.
Definition: message_exception.h:28
isc::log::LOG_READ_ERROR
const isc::log::MessageID LOG_READ_ERROR
Definition: log_messages.h:28
isc::log::LOG_INPUT_OPEN_FAIL
const isc::log::MessageID LOG_INPUT_OPEN_FAIL
Definition: log_messages.h:16
isc::log::LOG_NAMESPACE_INVALID_ARG
const isc::log::MessageID LOG_NAMESPACE_INVALID_ARG
Definition: log_messages.h:19
isc::test::readFile
std::string readFile(const std::string &file_path)
Reads contents of the specified file.
Definition: io_utils.cc:24
isc::util::str::uppercase
void uppercase(std::string &text)
Uppercase String.
Definition: strutil.h:110
isc::log::LOG_PREFIX_INVALID_ARG
const isc::log::MessageID LOG_PREFIX_INVALID_ARG
Definition: log_messages.h:26
log_messages.h