Load what we can from broken/truncated MIDI files.
[ardour.git] / libs / evoral / src / libsmf / smf_load.c
index cebca73b82aab9cc18997010309670e40c1afcee..1db09a7a458ecda1ca0a3b88084d4f79d0fa1ba9 100644 (file)
 #include <math.h>
 #include <errno.h>
 #include <ctype.h>
+#ifdef PLATFORM_WINDOWS
+#include <winsock2.h>
+#else
 #include <arpa/inet.h>
+#endif
 #include "smf.h"
 #include "smf_private.h"
 
@@ -79,7 +83,6 @@ next_chunk(smf_t *smf)
 
        if (smf->next_chunk_offset > smf->file_buffer_length) {
                g_critical("SMF error: malformed chunk; truncated file?");
-               return (NULL);
        }
 
        return (chunk);
@@ -119,7 +122,7 @@ parse_mthd_header(smf_t *smf)
                return (-1);
        }
 
-       tmp_mthd = smf->file_buffer;
+       tmp_mthd = (struct chunk_header_struct*)smf->file_buffer;
 
        if (!chunk_signature_matches(tmp_mthd, "MThd")) {
                g_critical("SMF error: MThd signature not found, is that a MIDI file?");
@@ -206,8 +209,8 @@ parse_mthd_chunk(smf_t *smf)
  * Explanation of Variable Length Quantities is here: http://www.borg.com/~jglatt/tech/midifile/vari.htm
  * Returns 0 iff everything went OK, different value in case of error.
  */
-static int
-extract_vlq(const unsigned char *buf, const size_t buffer_length, uint32_t *value, uint32_t *len)
+int
+smf_extract_vlq(const unsigned char *buf, const size_t buffer_length, uint32_t *value, uint32_t *len)
 {
        uint32_t val = 0;
        const unsigned char *c = buf;
@@ -275,16 +278,21 @@ is_escape_byte(const unsigned char status)
 static int32_t
 expected_sysex_length(const unsigned char status, const unsigned char *second_byte, const size_t buffer_length, int32_t *consumed_bytes)
 {
-       uint32_t sysex_length, len;
+       uint32_t sysex_length = 0;
+       uint32_t len = 0;
 
+#ifndef NDEBUG
+       (void) status;
+#else
        assert(status == 0xF0);
+#endif
 
        if (buffer_length < 3) {
                g_critical("SMF error: end of buffer in expected_sysex_length().");
                return (-1);
        }
 
-       extract_vlq(second_byte, buffer_length, &sysex_length, &len);
+       smf_extract_vlq(second_byte, buffer_length, &sysex_length, &len);
 
        if (consumed_bytes != NULL)
                *consumed_bytes = len;
@@ -387,7 +395,10 @@ extract_sysex_event(const unsigned char *buf, const size_t buffer_length, smf_ev
 
        status = *buf;
 
-       assert(is_sysex_byte(status));
+       if (!(is_sysex_byte(status))) {
+               g_critical("Corrupt sysex status byte in extract_sysex_event().");
+               return (-6);
+       }
 
        c++;
 
@@ -404,7 +415,7 @@ extract_sysex_event(const unsigned char *buf, const size_t buffer_length, smf_ev
        }
 
        event->midi_buffer_length = message_length;
-       event->midi_buffer = malloc(event->midi_buffer_length);
+       event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
        if (event->midi_buffer == NULL) {
                g_critical("Cannot allocate memory in extract_sysex_event(): %s", strerror(errno));
                return (-4);
@@ -424,12 +435,16 @@ extract_escaped_event(const unsigned char *buf, const size_t buffer_length, smf_
        (void) last_status;
        
        int status;
-       int32_t message_length, vlq_length;
+       int32_t message_length = 0;
+       int32_t vlq_length = 0;
        const unsigned char *c = buf;
 
        status = *buf;
 
-       assert(is_escape_byte(status));
+       if (!(is_escape_byte(status))) {
+               g_critical("Corrupt escape status byte in extract_escaped_event().");
+               return (-6);
+       }
 
        c++;
 
@@ -446,7 +461,7 @@ extract_escaped_event(const unsigned char *buf, const size_t buffer_length, smf_
        }
 
        event->midi_buffer_length = message_length;
-       event->midi_buffer = malloc(event->midi_buffer_length);
+       event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
        if (event->midi_buffer == NULL) {
                g_critical("Cannot allocate memory in extract_escaped_event(): %s", strerror(errno));
                return (-4);
@@ -516,7 +531,7 @@ extract_midi_event(const unsigned char *buf, const size_t buffer_length, smf_eve
        }
 
        event->midi_buffer_length = message_length;
-       event->midi_buffer = malloc(event->midi_buffer_length);
+       event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
        if (event->midi_buffer == NULL) {
                g_critical("Cannot allocate memory in extract_midi_event(): %s", strerror(errno));
                return (-4);
@@ -539,7 +554,7 @@ extract_midi_event(const unsigned char *buf, const size_t buffer_length, smf_eve
 static smf_event_t *
 parse_next_event(smf_track_t *track)
 {
-       uint32_t time = 0;
+       uint32_t etime = 0;
        uint32_t len;
        size_t buffer_length;
        unsigned char *c, *start;
@@ -555,11 +570,17 @@ parse_next_event(smf_track_t *track)
        assert(track->next_event_offset > 0);
 
        buffer_length = track->file_buffer_length - track->next_event_offset;
-       assert(buffer_length > 0);
-
+       /* if there was no meta-EOT event, buffer_length can be zero. This is
+          an error in the SMF file, but it shouldn't be treated as fatal.
+       */
+       if (buffer_length == 0) {
+               g_warning ("SMF warning: expected EOT at end of track, but none found");
+               goto error;
+       }
        /* First, extract time offset from previous event. */
-       if (extract_vlq(c, buffer_length, &time, &len))
+       if (smf_extract_vlq(c, buffer_length, &etime, &len)) {
                goto error;
+       }
 
        c += len;
        buffer_length -= len;
@@ -568,15 +589,16 @@ parse_next_event(smf_track_t *track)
                goto error;
 
        /* Now, extract the actual event. */
-       if (extract_midi_event(c, buffer_length, event, &len, track->last_status))
+       if (extract_midi_event(c, buffer_length, event, &len, track->last_status)) {
                goto error;
+       }
 
        c += len;
        buffer_length -= len;
        track->last_status = event->midi_buffer[0];
        track->next_event_offset += c - start;
 
-       smf_track_add_event_delta_pulses(track, event, time);
+       smf_track_add_event_delta_pulses(track, event, etime);
 
        return (event);
 
@@ -605,7 +627,7 @@ make_string(const unsigned char *buf, const size_t buffer_length, uint32_t len)
                len = buffer_length;
        }
 
-       str = malloc(len + 1);
+       str = (char*)malloc(len + 1);
        if (str == NULL) {
                g_critical("Cannot allocate memory in make_string().");
                return (NULL);
@@ -631,7 +653,7 @@ smf_event_is_textual(const smf_event_t *event)
        if (event->midi_buffer_length < 4)
                return (0);
 
-       if (event->midi_buffer[3] < 1 && event->midi_buffer[3] > 9)
+       if (event->midi_buffer[3] < 1 || event->midi_buffer[3] > 9)
                return (0);
 
        return (1);
@@ -645,7 +667,8 @@ smf_event_is_textual(const smf_event_t *event)
 char *
 smf_event_extract_text(const smf_event_t *event)
 {
-       uint32_t string_length, length_length;
+       uint32_t string_length = 0;
+       uint32_t length_length = 0;
 
        if (!smf_event_is_textual(event))
                return (NULL);
@@ -655,14 +678,14 @@ smf_event_extract_text(const smf_event_t *event)
                return (NULL);
        }
 
-       extract_vlq((void *)&(event->midi_buffer[2]), event->midi_buffer_length - 2, &string_length, &length_length);
+       smf_extract_vlq((const unsigned char*)(void *)&(event->midi_buffer[2]), event->midi_buffer_length - 2, &string_length, &length_length);
 
        if (string_length <= 0) {
                g_critical("smf_event_extract_text: truncated MIDI message.");
                return (NULL);
        }
 
-       return (make_string((void *)(&event->midi_buffer[2] + length_length), event->midi_buffer_length - 2 - length_length, string_length));
+       return (make_string((const unsigned char*)(void *)(&event->midi_buffer[2] + length_length), event->midi_buffer_length - 2 - length_length, string_length));
 }
 
 /**
@@ -769,6 +792,7 @@ static int
 parse_mtrk_chunk(smf_track_t *track)
 {
        smf_event_t *event;
+       int ret = 0;
 
        if (parse_mtrk_header(track))
                return (-1);
@@ -777,10 +801,10 @@ parse_mtrk_chunk(smf_track_t *track)
                event = parse_next_event(track);
 
                /* Couldn't parse an event? */
-               if (event == NULL)
-                       return (-1);
-
-               assert(smf_event_is_valid(event));
+               if (event == NULL || !smf_event_is_valid(event)) {
+                       ret = -1;
+                       break;
+               }
 
                if (event_is_end_of_track(event))
                        break;
@@ -790,7 +814,7 @@ parse_mtrk_chunk(smf_track_t *track)
        track->file_buffer_length = 0;
        track->next_event_offset = -1;
 
-       return (0);
+       return (ret);
 }
 
 /**
@@ -830,13 +854,14 @@ load_file_into_buffer(void **file_buffer, size_t *file_buffer_length, FILE* stre
        *file_buffer = malloc(*file_buffer_length);
        if (*file_buffer == NULL) {
                g_critical("malloc(3) failed: %s", strerror(errno));
-
+               
                return (-5);
        }
 
        if (fread(*file_buffer, 1, *file_buffer_length, stream) != *file_buffer_length) {
                g_critical("fread(3) failed: %s", strerror(errno));
-
+               free (*file_buffer);
+               *file_buffer = NULL;
                return (-6);
        }
        
@@ -851,6 +876,7 @@ smf_t *
 smf_load_from_memory(const void *buffer, const size_t buffer_length)
 {
        int i;
+       int ret;
 
        smf_t *smf = smf_new();
 
@@ -868,15 +894,16 @@ smf_load_from_memory(const void *buffer, const size_t buffer_length)
 
                smf_add_track(smf, track);
 
-               /* Skip unparseable chunks. */
-               if (parse_mtrk_chunk(track)) {
-                       g_warning("SMF warning: Cannot load track.");
-                       smf_track_delete(track);
-               }
+               ret = parse_mtrk_chunk(track);
 
                track->file_buffer = NULL;
                track->file_buffer_length = 0;
                track->next_event_offset = -1;
+
+               if (ret) {
+                       g_warning("SMF warning: Error parsing track, continuing with data loaded so far.");
+                       break;
+               }
        }
 
        if (smf->expected_number_of_tracks != smf->number_of_tracks) {