1 // Copyright (C) 2016-2018 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 <pgsql/pgsql_exchange.h>
10 #include <pgsql/pgsql_connection.h>
11 
12 #include <boost/lexical_cast.hpp>
13 
14 #include <iomanip>
15 #include <sstream>
16 #include <vector>
17 
18 namespace isc {
19 namespace db {
20 
21 const int PsqlBindArray::TEXT_FMT = 0;
22 const int PsqlBindArray::BINARY_FMT = 1;
23 const char* PsqlBindArray::TRUE_STR = "TRUE";
24 const char* PsqlBindArray::FALSE_STR = "FALSE";
25 
add(const char * value)26 void PsqlBindArray::add(const char* value) {
27     if (!value) {
28         isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
29     }
30 
31     values_.push_back(value);
32     lengths_.push_back(strlen(value));
33     formats_.push_back(TEXT_FMT);
34 }
35 
add(const std::string & value)36 void PsqlBindArray::add(const std::string& value) {
37     values_.push_back(value.c_str());
38     lengths_.push_back(value.size());
39     formats_.push_back(TEXT_FMT);
40 }
41 
add(const std::vector<uint8_t> & data)42 void PsqlBindArray::add(const std::vector<uint8_t>& data) {
43     values_.push_back(reinterpret_cast<const char*>(&(data[0])));
44     lengths_.push_back(data.size());
45     formats_.push_back(BINARY_FMT);
46 }
47 
add(const uint8_t * data,const size_t len)48 void PsqlBindArray::add(const uint8_t* data, const size_t len) {
49     if (!data) {
50         isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
51     }
52 
53     values_.push_back(reinterpret_cast<const char*>(&(data[0])));
54     lengths_.push_back(len);
55     formats_.push_back(BINARY_FMT);
56 }
57 
add(const bool & value)58 void PsqlBindArray::add(const bool& value)  {
59     add(value ? TRUE_STR : FALSE_STR);
60 }
61 
add(const uint8_t & byte)62 void PsqlBindArray::add(const uint8_t& byte) {
63     // We static_cast to an unsigned int, otherwise lexical_cast may to
64     // treat byte as a character, which yields "" for unprintable values
65     addTempString(boost::lexical_cast<std::string>
66                               (static_cast<unsigned int>(byte)));
67 }
68 
add(const isc::asiolink::IOAddress & addr)69 void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
70     if (addr.isV4()) {
71         addTempString(boost::lexical_cast<std::string>
72                    (addr.toUint32()));
73     } else {
74         addTempString(addr.toText());
75     }
76 }
77 
addNull(const int format)78 void PsqlBindArray::addNull(const int format) {
79     values_.push_back(NULL);
80     lengths_.push_back(0);
81     formats_.push_back(format);
82 }
83 
84 /// @todo Eventually this could replace add(std::string&)? This would mean
85 /// all bound strings would be internally copies rather than perhaps belonging
86 /// to the originating object such as Host::hostname_.  One the one hand it
87 /// would make all strings handled one-way only, on the other hand it would
88 /// mean duplicating strings where it isn't strictly necessary.
addTempString(const std::string & str)89 void PsqlBindArray::addTempString(const std::string& str) {
90     bound_strs_.push_back(ConstStringPtr(new std::string(str)));
91     PsqlBindArray::add((bound_strs_.back())->c_str());
92 }
93 
toText() const94 std::string PsqlBindArray::toText() const {
95     std::ostringstream stream;
96     for (int i = 0; i < values_.size(); ++i) {
97         stream << i << " : ";
98         if (formats_[i] == TEXT_FMT) {
99             stream << "\"" << values_[i] << "\"" << std::endl;
100         } else {
101             const char *data = values_[i];
102             if (lengths_[i] == 0) {
103                 stream << "empty" << std::endl;
104             } else {
105                 stream << "0x";
106                 for (int x = 0; x < lengths_[i]; ++x) {
107                     stream << std::setfill('0') << std::setw(2)
108                            << std::setbase(16)
109                            << static_cast<unsigned int>(data[x]);
110                 }
111                 stream << std::endl;
112                 stream << std::setbase(10);
113             }
114         }
115     }
116 
117     return (stream.str());
118 }
119 
120 std::string
convertToDatabaseTime(const time_t input_time)121 PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
122     struct tm tinfo;
123     char buffer[20];
124     localtime_r(&input_time, &tinfo);
125     strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
126     return (std::string(buffer));
127 }
128 
129 std::string
convertToDatabaseTime(const time_t cltt,const uint32_t valid_lifetime)130 PgSqlExchange::convertToDatabaseTime(const time_t cltt,
131                                      const uint32_t valid_lifetime) {
132     // Calculate expiry time. Store it in the 64-bit value so as we can
133     // detect overflows.
134     int64_t expire_time_64 = static_cast<int64_t>(cltt)
135                              + static_cast<int64_t>(valid_lifetime);
136 
137     // It has been observed that the PostgreSQL doesn't deal well with the
138     // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
139     // beginning of the epoch (around year 2038). The value is often
140     // stored in the database but it is invalid when read back (overflow?).
141     // Hence, the maximum timestamp value is restricted here.
142     if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
143         isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
144     }
145 
146     return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
147 }
148 
149 time_t
convertFromDatabaseTime(const std::string & db_time_val)150 PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
151     // Convert string time value to time_t
152     time_t new_time;
153     try  {
154         new_time = (boost::lexical_cast<time_t>(db_time_val));
155     } catch (const std::exception& ex) {
156         isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
157     }
158 
159     return (new_time);
160 }
161 
162 const char*
getRawColumnValue(const PgSqlResult & r,const int row,const size_t col)163 PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
164                                  const size_t col) {
165     r.rowColCheck(row,col);
166     const char* value = PQgetvalue(r, row, col);
167     if (!value) {
168         isc_throw(DbOperationError, "getRawColumnValue no data for :"
169                     << getColumnLabel(r, col) << " row:" << row);
170     }
171     return (value);
172 }
173 
174 bool
isColumnNull(const PgSqlResult & r,const int row,const size_t col)175 PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
176                             const size_t col) {
177     r.rowColCheck(row,col);
178     return (PQgetisnull(r, row, col));
179 }
180 
181 void
getColumnValue(const PgSqlResult & r,const int row,const size_t col,std::string & value)182 PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
183                               const size_t col, std::string& value) {
184     value = getRawColumnValue(r, row, col);
185 }
186 
187 void
getColumnValue(const PgSqlResult & r,const int row,const size_t col,bool & value)188 PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
189                               const size_t col, bool &value) {
190     const char* data = getRawColumnValue(r, row, col);
191     if (!strlen(data) || *data == 'f') {
192         value = false;
193     } else if (*data == 't') {
194         value = true;
195     } else {
196         isc_throw(DbOperationError, "Invalid boolean data: " << data
197                   << " for: " << getColumnLabel(r, col) << " row:" << row
198                   << " : must be 't' or 'f'");
199     }
200 }
201 
202 void
getColumnValue(const PgSqlResult & r,const int row,const size_t col,uint8_t & value)203 PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
204                               const size_t col, uint8_t &value) {
205     const char* data = getRawColumnValue(r, row, col);
206     try {
207         // lexically casting as uint8_t doesn't convert from char
208         // so we use uint16_t and implicitly convert.
209         value = boost::lexical_cast<uint16_t>(data);
210     } catch (const std::exception& ex) {
211         isc_throw(DbOperationError, "Invalid uint8_t data: " << data
212                   << " for: " << getColumnLabel(r, col) << " row:" << row
213                   << " : " << ex.what());
214     }
215 }
216 
217 isc::asiolink::IOAddress
getIPv6Value(const PgSqlResult & r,const int row,const size_t col)218 PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
219                             const size_t col) {
220     const char* data = getRawColumnValue(r, row, col);
221     try {
222         return (isc::asiolink::IOAddress(data));
223     } catch (const std::exception& ex) {
224         isc_throw(DbOperationError, "Cannot convert data: " << data
225                   << " for: " << getColumnLabel(r, col) << " row:" << row
226                   << " : " << ex.what());
227     }
228 }
229 
230 void
convertFromBytea(const PgSqlResult & r,const int row,const size_t col,uint8_t * buffer,const size_t buffer_size,size_t & bytes_converted)231 PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
232                                 const size_t col, uint8_t* buffer,
233                                 const size_t buffer_size,
234                                 size_t &bytes_converted) {
235     // Returns converted bytes in a dynamically allocated buffer, and
236     // sets bytes_converted.
237     unsigned char* bytes = PQunescapeBytea((const unsigned char*)
238                                            (getRawColumnValue(r, row, col)),
239                                            &bytes_converted);
240 
241     // Unlikely it couldn't allocate it but you never know.
242     if (!bytes) {
243         isc_throw (DbOperationError, "PQunescapeBytea failed for:"
244                    << getColumnLabel(r, col) << " row:" << row);
245     }
246 
247     // Make sure it's not larger than expected.
248     if (bytes_converted > buffer_size) {
249         // Free the allocated buffer first!
250         PQfreemem(bytes);
251         isc_throw (DbOperationError, "Converted data size: "
252                    << bytes_converted << " is too large for: "
253                    << getColumnLabel(r, col) << " row:" << row);
254     }
255 
256     // Copy from the allocated buffer to caller's buffer the free up
257     // the allocated buffer.
258     memcpy(buffer, bytes, bytes_converted);
259     PQfreemem(bytes);
260 }
261 
262 std::string
getColumnLabel(const PgSqlResult & r,const size_t column)263 PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
264     return (r.getColumnLabel(column));
265 }
266 
267 std::string
dumpRow(const PgSqlResult & r,int row)268 PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
269     r.rowCheck(row);
270     std::ostringstream stream;
271     int columns = r.getCols();
272     for (int col = 0; col < columns; ++col) {
273         const char* val = getRawColumnValue(r, row, col);
274         std::string name = r.getColumnLabel(col);
275         int format = PQfformat(r, col);
276 
277         stream << col << "   " << name << " : " ;
278         if (format == PsqlBindArray::TEXT_FMT) {
279             stream << "\"" << val << "\"" << std::endl;
280         } else {
281             const char *data = val;
282             int length = PQfsize(r, col);
283             if (length == 0) {
284                 stream << "empty" << std::endl;
285             } else {
286                 stream << "0x";
287                 for (int i = 0; i < length; ++i) {
288                     stream << std::setfill('0') << std::setw(2)
289                            << std::setbase(16)
290                            << static_cast<unsigned int>(data[i]);
291                 }
292                 stream << std::endl;
293             }
294         }
295     }
296 
297     return (stream.str());
298 }
299 
300 }; // end of isc::db namespace
301 }; // end of isc namespace
302