1 /*
2  * Copyright (c) 2014, 2021, Oracle and/or its affiliates.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License, version 2.0, as
6  * published by the Free Software Foundation.
7  *
8  * This program is also distributed with certain software (including
9  * but not limited to OpenSSL) that is licensed under separate terms,
10  * as designated in a particular file or component or in included license
11  * documentation.  The authors of MySQL hereby grant you an
12  * additional permission to link the program and your derivative works
13  * with the separately licensed software that they have included with
14  * MySQL.
15  *
16  * Without limiting anything contained in the foregoing, this file,
17  * which is part of MySQL Connector/Python, is also subject to the
18  * Universal FOSS Exception, version 1.0, a copy of which can be found at
19  * http://oss.oracle.com/licenses/universal-foss-exception.
20  *
21  * This program is distributed in the hope that it will be useful, but
22  * WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24  * See the GNU General Public License, version 2.0, for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software Foundation, Inc.,
28  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
29  */
30 
31 #include <ctype.h>
32 #include <stdio.h>
33 
34 #include <Python.h>
35 #include <datetime.h>
36 
37 #ifdef MS_WINDOWS
38 #include <windows.h>
39 #endif
40 #include <mysql.h>
41 
42 #include "exceptions.h"
43 
44 #define MINYEAR 1
45 #define MAXYEAR 9999
46 
47 /**
48   Check whether a year is a leap year.
49 
50   Check whether a year is a leap year or not.
51 
52   Year is not checked. This helper function is used by other
53   functions which validate temporal values.
54 
55   @param    year    year
56 
57   @return   1 if year is leap year, 0 otherwise.
58     @retval 1   Leap year
59     @retval 0   Not a leap year
60 */
61 static int
leap_year(int year)62 leap_year(int year)
63 {
64     if ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0))
65     {
66         return 1;
67     }
68 
69     return 0;
70 }
71 
72 /**
73   Return number of days in month.
74 
75   Return the number of days in a month considering leap years.
76 
77   Year and month are not checked. This helper function is used
78   by other functions which validate temporal values.
79 
80   @param    year    year
81   @param    month   month
82 
83   @return   Number of days in month
84     @retval int number of days
85 */
86 static int
nr_days_month(int year,int month)87 nr_days_month(int year, int month)
88 {
89     int days[]= {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
90     if (month == 2 && leap_year(year))
91     {
92         return 29;
93     }
94     return days[month];
95 }
96 
97 /**
98   Check whether a date is valid or not.
99 
100   Check whether the date defined by the arguments year,
101   month, and day, is valid or not.
102 
103   @param    day     day
104   @param    month   month
105   @param    day     day
106 
107   @return   1 if date is valid, 0 otherwise.
108     @retval 1   Valid
109     @retval 0   Invalid
110 */
111 static int
is_valid_date(int year,int month,int day)112 is_valid_date(int year, int month, int day)
113 {
114     if ((year < MINYEAR || year > MAXYEAR)
115         || (month < 1 || month > 12)
116         || (day < 1 || day > nr_days_month(year, month)))
117     {
118         return 0;
119     }
120 
121     return 1;
122 }
123 
124 /**
125   Check whether a time is valid or not.
126 
127   Check whether the time defined by the arguments hours,
128   mins, secs and usecs, is valid or not.
129 
130   @param    hours   hours
131   @param    mins    minutes
132   @param    secs    secs
133   @param    usecs   microsecons
134 
135   @return   1 if time is valid, 0 otherwise.
136     @retval 1   Valid
137     @retval 0   Invalid
138 */
139 static int
is_valid_time(int hours,int mins,int secs,int usecs)140 is_valid_time(int hours, int mins, int secs, int usecs)
141 {
142     if ((hours < 0 || hours > 23)
143         || (mins < 0 || mins > 59)
144         || (secs < 0 || secs > 59)
145         || (usecs < 0 || usecs > 999999))
146     {
147         return 0;
148     }
149 
150     return 1;
151 }
152 
153 /**
154   Convert a Python datetime.timedelta to MySQL TIME.
155 
156   Convert the PyObject obj, which must be a datetime.timedelta,
157   to MySQL TIME value.
158 
159   Raises TypeError when obj is not a PyDelta_Type.
160 
161   @param    obj     the PyObject to be converted
162 
163   @return   Converted timedelta object.
164     @retval PyBytes     Python v3
165     @retval NULL        Exception
166 */
167 PyObject*
pytomy_timedelta(PyObject * obj)168 pytomy_timedelta(PyObject *obj)
169 {
170     int days= 0, secs= 0 , micro_secs= 0, total_secs= 0;
171     int hours= 0, mins= 0, remainder= 0;
172     char fmt[32]= {0};
173     char result[17]= {0};
174 
175     PyDateTime_IMPORT;
176 
177     if (!obj || !PyDelta_Check(obj))
178     {
179         PyErr_SetString(PyExc_ValueError,
180                         "Object must be a datetime.timedelta");
181         return NULL;
182     }
183 
184     // Cannot use PyDateTime_DELTA_* (new in Python v3.3)
185     days= ((PyDateTime_Delta*)obj)->days;
186     secs= ((PyDateTime_Delta*)obj)->seconds;
187     micro_secs= ((PyDateTime_Delta*)obj)->microseconds;
188 
189     total_secs= abs(days * 86400 + secs);
190 
191 #pragma warning(push)
192 // result of strncpy does not accept direct user input
193 #pragma warning(disable: 4996)
194 
195 
196     if (micro_secs)
197     {
198         strncpy(fmt, "%02d:%02d:%02d.%06d", 19);
199         if (days < 0)
200         {
201             micro_secs= 1000000 - micro_secs;
202             total_secs-= 1;
203         }
204     }
205     else
206     {
207         strncpy(fmt, "%02d:%02d:%02d", 14);
208     }
209 
210     if (days < 0)
211     {
212         int index;
213         for(index = 31; index > 0; index--){
214             fmt[index] = fmt[index - 1];
215         }
216         fmt[0] = '-';
217     }
218 #pragma warning(pop)
219 
220     hours= total_secs / 3600;
221     remainder= total_secs % 3600;
222     mins= remainder / 60;
223     secs= remainder % 60;
224 
225     if (micro_secs)
226     {
227         PyOS_snprintf(result, 17, fmt, hours, mins, secs, micro_secs);
228     }
229     else
230     {
231         PyOS_snprintf(result, 17, fmt, hours, mins, secs);
232     }
233 
234     return PyBytes_FromString(result);
235 }
236 
237 /**
238   Convert a Python datetime.time to MySQL TIME.
239 
240   Convert the PyObject obj, which must be a datetime.time,
241   to MySQL TIME value.
242 
243   Raises TypeError when obj is not a PyTime_Type.
244 
245   @param    obj     the PyObject to be converted
246 
247   @return   Converted time object.
248     @retval PyBytes     Python v3
249     @retval NULL        Exception
250 */
251 PyObject*
pytomy_time(PyObject * obj)252 pytomy_time(PyObject *obj)
253 {
254     char result[17]= {0};
255 
256     PyDateTime_IMPORT;
257 
258     if (!obj || !PyTime_Check(obj))
259     {
260         PyErr_SetString(PyExc_ValueError,
261                         "Object must be a datetime.time");
262         return NULL;
263     }
264 
265     if (PyDateTime_TIME_GET_MICROSECOND(obj))
266     {
267         PyOS_snprintf(result, 17, "%02d:%02d:%02d.%06d",
268                  PyDateTime_TIME_GET_HOUR(obj),
269                  PyDateTime_TIME_GET_MINUTE(obj),
270                  PyDateTime_TIME_GET_SECOND(obj),
271                  PyDateTime_TIME_GET_MICROSECOND(obj));
272     }
273     else
274     {
275         PyOS_snprintf(result, 17, "%02d:%02d:%02d",
276                  PyDateTime_TIME_GET_HOUR(obj),
277                  PyDateTime_TIME_GET_MINUTE(obj),
278                  PyDateTime_TIME_GET_SECOND(obj));
279     }
280 
281     return PyBytes_FromString(result);
282 }
283 
284 /**
285   Convert a Python datetime.datetime to MySQL DATETIME.
286 
287   Convert the PyObject obj, which must be a datetime.datetime,
288   to MySQL DATETIME value.
289 
290   Raises TypeError when obj is not a PyDateTime_Type.
291 
292   @param    obj     the PyObject to be converted
293 
294   @return   Converted datetime object.
295     @retval PyBytes     Python v3
296     @retval NULL        Exception
297 */
298 PyObject*
pytomy_datetime(PyObject * obj)299 pytomy_datetime(PyObject *obj)
300 {
301     char result[27]= {0};
302     PyDateTime_IMPORT;
303 
304     if (!obj || !PyDateTime_Check(obj))
305     {
306         PyErr_SetString(PyExc_ValueError,
307                         "Object must be a datetime.datetime");
308         return NULL;
309     }
310 
311     if (PyDateTime_DATE_GET_MICROSECOND(obj))
312     {
313         PyOS_snprintf(result, 27, "%04d-%02d-%02d %02d:%02d:%02d.%06d",
314                  PyDateTime_GET_YEAR(obj),
315                  PyDateTime_GET_MONTH(obj),
316                  PyDateTime_GET_DAY(obj),
317                  PyDateTime_DATE_GET_HOUR(obj),
318                  PyDateTime_DATE_GET_MINUTE(obj),
319                  PyDateTime_DATE_GET_SECOND(obj),
320                  PyDateTime_DATE_GET_MICROSECOND(obj));
321     }
322     else
323     {
324         PyOS_snprintf(result, 27, "%04d-%02d-%02d %02d:%02d:%02d",
325                  PyDateTime_GET_YEAR(obj),
326                  PyDateTime_GET_MONTH(obj),
327                  PyDateTime_GET_DAY(obj),
328                  PyDateTime_DATE_GET_HOUR(obj),
329                  PyDateTime_DATE_GET_MINUTE(obj),
330                  PyDateTime_DATE_GET_SECOND(obj));
331     }
332     return PyBytes_FromString(result);
333 }
334 
335 /**
336   Convert a Python datetime.date to MySQL DATE.
337 
338   Convert the PyObject obj, which must be a datetime.date,
339   to MySQL DATE value.
340 
341   Raises TypeError when obj is not a PyDate_Type.
342 
343   @param    date    the PyObject to be converted
344 
345   @return   Converted date object.
346     @retval PyBytes     Python v3
347     @retval NULL        Exception
348 */
349 PyObject*
pytomy_date(PyObject * obj)350 pytomy_date(PyObject *obj)
351 {
352     PyDateTime_IMPORT;
353 
354     if (!obj || !PyDate_Check(obj))
355     {
356         PyErr_SetString(PyExc_TypeError, "Object must be a datetime.date");
357         return NULL;
358     }
359 
360     return PyBytes_FromFormat("%04d-%02d-%02d",
361                               PyDateTime_GET_YEAR(obj),
362                               PyDateTime_GET_MONTH(obj),
363                               PyDateTime_GET_DAY(obj));
364 }
365 
366 /**
367   Convert a DATE MySQL value to Python datetime.date.
368 
369   Convert a DATETIME MySQL value to Python datetime.date. When a date
370   can be parsed, but it is invalid, None is returned.
371 
372   Raises ValueError when the date is not for format %d-%d-%d.
373 
374   @param    data        string to be converted
375 
376   @return   datetime.date object.
377     @retval PyDate  OK
378     @retval None    Invalid date
379     @retval NULL    Exception
380 */
381 PyObject*
mytopy_date(const char * data)382 mytopy_date(const char *data)
383 {
384     int year= 0, month= 0, day= 0;
385 
386     PyDateTime_IMPORT;
387 
388 #pragma warning(push)
389 // sscanf data comes from MySQL and is fixed
390 #pragma warning(disable: 4996)
391     if (3 == sscanf(data, "%d-%d-%d", &year, &month, &day))
392 #pragma warning(pop)
393     {
394         // Invalid dates are returned as None instead of raising ValueError
395         if (!is_valid_date(year, month, day))
396         {
397             Py_RETURN_NONE;
398         }
399         return PyDate_FromDate(year, month, day);
400     }
401 
402     PyErr_SetString(PyExc_ValueError,
403                     "Received incorrect DATE value from MySQL server");
404     return NULL;
405 }
406 
407 /**
408   Convert a DATETIME MySQL value to Python datetime.datetime.
409 
410   Convert a DATETIME MySQL value to Python datetime.datetime. The
411   fractional part is supported.
412 
413   @param    data        string to be converted
414   @param    length      length of data
415 
416   @return   datetime.datetime object.
417     @retval PyDateTime OK
418 */
419 PyObject*
mytopy_datetime(const char * data,const unsigned long length)420 mytopy_datetime(const char *data, const unsigned long length)
421 {
422 	int year= 0, month= 0, day= 0;
423 	int hours= 0, mins= 0, secs= 0, usecs= 0;
424     int value= 0;
425     int parts[7]= {0};
426     int part= 0;
427     const char *end= data + length;
428 
429     PyDateTime_IMPORT;
430 
431     /* Parse year, month, days, hours, minutes and seconds */
432     for (;;)
433     {
434         for (value= 0; data != end && isdigit(*data) ; data++)
435         {
436             value= (value * 10) + (unsigned int)(*data - '0');
437         }
438         parts[part++]= (unsigned int)value;
439         if (part == 8 || (end-data) < 2
440             || (*data != '-' && *data != ':' && *data != ' ')
441             || !isdigit(data[1]))
442         {
443             break;
444         }
445         data++;  // skip separators '-' and ':'
446     }
447 
448     if (data != end && end - data >= 2 && *data == '.')
449     {
450         // Fractional part
451         int field_length= 5;
452         data++;
453         value= (unsigned int)(*data - '0');
454         while (data++ != end && isdigit(*data))
455         {
456             if (field_length-- > 0)
457             {
458                 value= (value * 10) + (unsigned int)(*data - '0');
459             }
460         }
461         if (field_length >= 0)
462         {
463             while (field_length-- > 0)
464             {
465                 value*= 10;
466             }
467         }
468         parts[6]= value;
469     }
470 
471     year= parts[0];
472     month= parts[1];
473     day= parts[2];
474     hours= parts[3];
475     mins= parts[4];
476     secs= parts[5];
477     usecs= parts[6];
478 
479     if (!is_valid_date(year, month, day))
480     {
481         Py_RETURN_NONE;
482     }
483 
484     if (!is_valid_time(hours, mins, secs, usecs))
485     {
486         Py_RETURN_NONE;
487     }
488 
489     return PyDateTime_FromDateAndTime(year, month, day,
490                                       hours, mins, secs, usecs);
491 }
492 
493 /**
494   Convert a TIME MySQL value to Python datetime.timedelta.
495 
496   Convert a TIME MySQL value to a Python datetime.timedelta returned
497   as PyDelta_FromDSU object.
498 
499   @param    data        string to be converted
500   @param    length      length of data
501 
502   @return   datetime.timedelta object.
503     @retval PyDelta_FromDSU OK
504 */
505 PyObject*
mytopy_time(const char * data,const unsigned long length)506 mytopy_time(const char *data, const unsigned long length)
507 {
508     int hr= 0, min= 0, sec= 0, usec= 0;
509     int days= 0, hours= 0, seconds= 0;
510     int negative= 0;
511     int value= 0;
512     int parts[4]= {0};
513     int part= 0;
514     const char *end= data + length;
515 
516     PyDateTime_IMPORT;
517 
518     // Negative times
519     if (*data == '-')
520     {
521         negative= 1;
522         data++;
523     }
524 
525     /* Parse hours, minutes and seconds */
526     for (;;)
527     {
528         for (value= 0; data != end && isdigit(*data) ; data++)
529         {
530             value= (value * 10) + (unsigned int)(*data - '0');
531         }
532         parts[part++]= (unsigned int)value;
533         if (part == 4 || (end-data) < 2 || *data != ':' || !isdigit(data[1]))
534         {
535             break;
536         }
537         data++;  // skip time separator ':'
538     }
539 
540     if (data != end && end - data >= 2 && *data == '.')
541     {
542         // Fractional part
543         int field_length= 5;
544         data++;
545         value= (unsigned int)(*data - '0');
546         while (data++ != end && isdigit(*data))
547         {
548             if (field_length-- > 0)
549             {
550                 value= (value * 10) + (unsigned int)(*data - '0');
551             }
552         }
553         if (field_length >= 0)
554         {
555             while (field_length-- > 0)
556             {
557                 value*= 10;
558             }
559         }
560         parts[3]= value;
561     }
562 
563     hr= parts[0];
564     min= parts[1];
565     sec= parts[2];
566     usec= parts[3];
567 
568     // negative time
569     if (negative) {
570         hr= hr * -1;
571         min= min * -1;
572         sec= sec * -1;
573         usec= usec * -1;
574     }
575 
576     days= hr / 24;
577     hours= hr % 24;
578 
579     seconds= (hours * 3600) + (min * 60) + sec;
580 
581     return PyDelta_FromDSU(days, seconds, usec);
582 }
583 
584 /**
585   Convert a Python datetime.datetime to MySQL DATETIME.
586 
587   Convert a Python datetime.datetime to MySQL DATETIME using the
588   pytomy_date()function.
589 
590   datetime_to_mysql() is a module function and can be used as
591   _mysql_connector.datetime_to_mysql.
592 
593   Raises TypeError when obj is not a PyDateTime_Type.
594 
595   @param    self        module instance
596   @param    datetime    the PyObject to be converted
597 
598   @return   Converted datetime object.
599     @retval PyBytes     Python v3
600     @retval NULL        Exception
601 */
602 PyObject*
datetime_to_mysql(PyObject * self,PyObject * datetime)603 datetime_to_mysql(PyObject *self, PyObject *datetime)
604 {
605     return pytomy_datetime(datetime);
606 }
607 
608 /**
609   Convert a Python datetime.time to MySQL TIME.
610 
611   Convert a Python datetime.time to MySQL TIME using the
612   pytomy_time()function.
613 
614   time_to_mysql() is a module function and can be used as
615   _mysql_connector.time_to_mysql.
616 
617   Raises TypeError when obj is not a PyTime_Type.
618 
619   @param    self    module instance
620   @param    time    the PyObject to be converted
621 
622   @return   Converted time object.
623     @retval PyBytes     Python v3
624     @retval NULL        Exception
625 */
626 PyObject*
time_to_mysql(PyObject * self,PyObject * time)627 time_to_mysql(PyObject *self, PyObject *time)
628 {
629     return pytomy_time(time);
630 }
631 
632 /**
633   Convert a Python datetime.date to MySQL DATE.
634 
635   Convert a Python datetime.date to MySQL DATE using the
636   pytomy_date()function.
637 
638   date_to_mysql() is a module function and can be used as
639   _mysql_connector.date_to_mysql.
640 
641   Raises TypeError when obj is not a PyDate_Type.
642 
643   @param    self    module instance
644   @param    date    the PyObject to be converted
645 
646   @return   Converted date object.
647     @retval PyBytes     Python v3
648     @retval NULL        Exception
649 */
650 PyObject*
date_to_mysql(PyObject * self,PyObject * date)651 date_to_mysql(PyObject *self, PyObject *date)
652 {
653     return pytomy_date(date);
654 }
655 
656 /**
657   Convert a MySQL BIT to Python int/long.
658 
659   @param    obj         PyObject to be converted
660 
661   @return   Converted decimal as string
662     @retval PyInt   Python v3
663     @retval PyLong  Python v2
664 */
665 PyObject*
mytopy_bit(const char * data,const unsigned long length)666 mytopy_bit(const char *data, const unsigned long length)
667 {
668 #ifdef HAVE_LONG_LONG
669     unsigned PY_LONG_LONG value= 0;
670 #else
671     unsigned PY_LONG value= 0;
672 #endif
673     const unsigned char *d= (const unsigned char*)data;
674     unsigned long size= length;
675     while (size > 0)
676     {
677         value= (value << 8) | *d++;
678         size--;
679     }
680 #ifdef HAVE_LONG_LONG
681     return PyLong_FromUnsignedLongLong(value);
682 #else
683     return PyLong_FromUnsignedLong(value);
684 #endif
685 }
686 
687 /**
688   Convert a Python decimal.Decimal to MySQL DECIMAL.
689 
690   Convert a Python decimal.Decimal to MySQL DECIMAL. This function also
691   removes the 'L' suffix from the resulting string when using Python v2.
692 
693   @param    obj         PyObject to be converted
694 
695   @return   Converted decimal as string
696     @retval PyBytes     Python v3
697 */
698 PyObject*
pytomy_decimal(PyObject * obj)699 pytomy_decimal(PyObject *obj)
700 {
701     PyObject *str= PyObject_Str(obj);
702     PyObject *tmp= (const char *)PyUnicode_1BYTE_DATA(str);
703     PyObject *ret= PyBytes_FromString(tmp);
704     Py_DECREF(tmp);
705     return ret;
706 }
707 
708 /**
709   Convert a string MySQL value to Python str or bytes.
710 
711   Convert, and decode if needed, a string MySQL value to
712   Python str or bytes.
713 
714   @param    data             string to be converted
715   @param    field_type       field type
716   @param    field_charsetnr  charset number
717   @param    field_length     length of data
718   @param    charset          character used for decoding
719   @param    use_unicode      use unicode
720 
721   @return   Converted string
722     @retval PyUnicode   if use unicode
723     @retval PyBytes     if not use_unicode or charset is 'binary'
724     @retval NULL        Exception
725  */
726 PyObject*
mytopy_string(const char * data,enum_field_types field_type,const unsigned int field_charsetnr,const unsigned long field_length,const char * charset,unsigned int use_unicode)727 mytopy_string(const char *data,
728               enum_field_types field_type,
729               const unsigned int field_charsetnr,
730               const unsigned long field_length,
731               const char *charset,
732               unsigned int use_unicode)
733 {
734     if (!charset || !data) {
735         return NULL;
736     }
737 
738     if (strcmp(charset, "binary") == 0)
739     {
740         return PyByteArray_FromStringAndSize(data, field_length);
741     }
742 
743     /* 'binary' charset = 63 */
744     if (use_unicode && (field_type == MYSQL_TYPE_JSON || field_charsetnr != 63))
745     {
746         return PyUnicode_Decode(data, field_length, charset, NULL);
747     }
748 
749     return PyByteArray_FromStringAndSize(data, field_length);
750 }
751