PrintUsageAndExit()
{
    fprintf(stderr,
            BANNER
            "\n\nusage: mp42hls [options] <input>\n"
            "Options:\n"
            "  --verbose\n"
            "  --show-info\n"
            "  --hls-version <n> (default: 3)\n"
            "  --pmt-pid <pid>\n"
            "    PID to use for the PMT (default: 0x100)\n"
            "  --audio-pid <pid>\n"
            "    PID to use for audio packets (default: 0x101)\n"
            "  --video-pid <pid>\n"
            "    PID to use for video packets (default: 0x102)\n"
            "  --audio-track-id <n>\n"
            "    Read audio from track ID <n> (0 means no audio)\n"
            "  --video-track-id <n>\n"
            "    Read video from track ID <n> (0 means no video)\n"
            "  --audio-format <format>\n"
            "    Format to use for audio-only segments: 'ts' or 'packed' (default: 'ts')\n"
            "  --segment-duration <n>\n"
            "    Target segment duration in seconds (default: 6)\n"
            "  --segment-duration-threshold <t>\n"
            "    Segment duration threshold in milliseconds (default: 15)\n"
            "  --pcr-offset <offset> in units of 90kHz (default 10000)\n"
            "  --index-filename <filename>\n"
            "    Filename to use for the playlist/index (default: stream.m3u8)\n"
            "  --allow-cache <YES|NO>\n"
            "    set #EXT-X-ALLOW-CACHE to YES or NO\n"
            "  --segment-filename-template <pattern>\n"
            "    Filename pattern to use for the segments. Use a printf-style pattern with\n"
            "    one number field for the segment number, unless using single file mode\n"
            "    (default: segment-%%d.<ext> for separate segment files, or stream.<ext> for single file)\n"
            "  --segment-url-template <pattern>\n"
            "    URL pattern to use for the segments. Use a printf-style pattern with\n"
            "    one number field for the segment number unless unsing single file mode.\n"
            "    (may be a relative or absolute URI).\n"
            "    (default: segment-%%d.<ext> for separate segment files, or stream.<ext> for single file)\n"
            "  --iframe-index-filename <filename>\n"
            "    Filename to use for the I-Frame playlist (default: iframes.m3u8 when HLS version >= 4)\n"
            "  --output-single-file\n"
            "    Output all the media in a single file instead of separate segment files.\n"
            "    The segment filename template and segment URL template must be simple strings\n"
            "    without '%%d' or other printf-style patterns\n"
            "  --encryption-mode <mode>\n"
            "    Encryption mode (only used when --encryption-key is specified). AES-128 or SAMPLE-AES (default: AES-128)\n"
            "  --encryption-key <key>\n"
            "    Encryption key in hexadecimal (default: no encryption)\n"
            "  --encryption-iv-mode <mode>\n"
            "    Encryption IV mode: 'sequence', 'random' or 'fps' (FairPlay Streaming) (default: sequence)\n"
            "    (when the mode is 'fps', the encryption key must be 32 bytes: 16 bytes for the key\n"
            "    followed by 16 bytes for the IV).\n"
            "  --encryption-key-uri <uri>\n"
            "    Encryption key URI (may be a realtive or absolute URI). (default: key.bin)\n"
            "  --encryption-key-format <format>\n"
            "    Encryption key format. (default: 'identity')\n"
            "  --encryption-key-format-versions <versions>\n"
            "    Encryption key format versions.\n"
            "  --encryption-key-line <ext-x-key-line>\n"
            "    Preformatted encryption key line (only the portion after the #EXT-X-KEY: tag).\n"
            "    This option can be used multiple times, once for each preformatted key line to be included in the playlist.\n"
            "    (this option is mutually exclusive with the --encryption-key-uri, --encryption-key-format and --encryption-key-format-versions options)\n"
            "    (the IV and METHOD parameters will automatically be added, so they must not appear in the <ext-x-key-line> argument)\n"
            );
    exit(1);
}


virtual AP4_Result GetSize(AP4_LargeSize& size) { size = m_Size;     return AP4_SUCCESS; }


WriteSamples(AP4_Mpeg2TsWriter*               ts_writer,
             PackedAudioWriter*               packed_writer,
             AP4_Track*                       audio_track,
             SampleReader*                    audio_reader,
             AP4_Mpeg2TsWriter::SampleStream* audio_stream,
             AP4_Track*                       video_track,
             SampleReader*                    video_reader,
             AP4_Mpeg2TsWriter::SampleStream* video_stream,
             unsigned int                     segment_duration_threshold,
             AP4_UI08                         nalu_length_size)
{
    AP4_Sample              audio_sample;
    AP4_DataBuffer          audio_sample_data;
    unsigned int            audio_sample_count = 0;
    double                  audio_ts = 0.0;
    double                  audio_frame_duration = 0.0;
    bool                    audio_eos = false;
    AP4_Sample              video_sample;
    AP4_DataBuffer          video_sample_data;
    unsigned int            video_sample_count = 0;
    double                  video_ts = 0.0;
    double                  video_frame_duration = 0.0;
    bool                    video_eos = false;
    double                  last_ts = 0.0;
    unsigned int            segment_number = 0;
    AP4_ByteStream*         segment_output = NULL;
    double                  segment_duration = 0.0;
    AP4_Array<double>       segment_durations;
    AP4_Array<AP4_UI32>     segment_sizes;
    AP4_Position            segment_position = 0;
    AP4_Array<AP4_Position> segment_positions;
    AP4_Array<AP4_Position> iframe_positions;
    AP4_Array<AP4_UI32>     iframe_sizes;
    AP4_Array<double>       iframe_times;
    AP4_Array<double>       iframe_durations;
    AP4_Array<AP4_UI32>     iframe_segment_indexes;
    bool                    new_segment = true;
    AP4_ByteStream*         raw_output = NULL;
    AP4_ByteStream*         playlist = NULL;
    char                    string_buffer[4096];
    SampleEncrypter*        sample_encrypter = NULL;
    AP4_Result              result = AP4_SUCCESS;

    // prime the samples
    if (audio_reader) {
        result = ReadSample(*audio_reader, *audio_track, audio_sample, audio_sample_data, audio_ts, audio_frame_duration, audio_eos);
        if (AP4_FAILED(result)) return result;
    }
    if (video_reader) {
        result = ReadSample(*video_reader, *video_track, video_sample, video_sample_data, video_ts, video_frame_duration, video_eos);
        if (AP4_FAILED(result)) return result;
    }

    for (;;) {
        bool sync_sample = false;
        AP4_Track* chosen_track= NULL;
        if (audio_track && !audio_eos) {
            chosen_track = audio_track;
            if (video_track == NULL) {
                if (audio_track->GetSampleDescription(0)->GetFormat() == AP4_SAMPLE_FORMAT_AC_4) {
                    if (audio_sample.IsSync()) {
                        sync_sample = true;
                    }
                } else {
                    sync_sample = true;
                }
            }
        }
        if (video_track && !video_eos) {
            if (audio_track) {
                if (video_ts <= audio_ts) {
                    chosen_track = video_track;
                }
            } else {
                chosen_track = video_track;
            }
            if (chosen_track == video_track && video_sample.IsSync()) {
                sync_sample = true;
            }
        }

        // check if we need to start a new segment
        if (Options.segment_duration && (sync_sample || chosen_track == NULL)) {
            if (video_track) {
                segment_duration = video_ts - last_ts;
            } else {
                segment_duration = audio_ts - last_ts;
            }
            if ((segment_duration >= (double)Options.segment_duration - (double)segment_duration_threshold/1000.0) ||
                chosen_track == NULL) {
                if (video_track) {
                    last_ts = video_ts;
                } else {
                    last_ts = audio_ts;
                }
                if (segment_output) {
                    // flush the output stream
                    segment_output->Flush();

                    // compute the segment size (including padding)
                    AP4_Position segment_end = 0;
                    segment_output->Tell(segment_end);
                    AP4_UI32 segment_size = 0;
                    if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                        segment_size = (AP4_UI32)segment_end;
                    } else if (segment_end > segment_position) {
                        segment_size = (AP4_UI32)(segment_end-segment_position);
                    }

                    // update counters
                    segment_sizes.Append(segment_size);
                    segment_positions.Append(segment_position);
                    segment_durations.Append(segment_duration);

                    if (segment_duration != 0.0) {
                        double segment_bitrate = 8.0*(double)segment_size/segment_duration;
                        if (segment_bitrate > Stats.max_segment_bitrate) {
                            Stats.max_segment_bitrate = segment_bitrate;
                        }
                    }
                    if (Options.verbose) {
                        printf("Segment %d, duration=%.2f, %d audio samples, %d video samples, %d bytes @%lld\n",
                               segment_number,
                               segment_duration,
                               audio_sample_count,
                               video_sample_count,
                               segment_size,
                               segment_position);
                    }
                    if (!Options.output_single_file) {
                        segment_output->Release();
                        segment_output = NULL;
                    }
                    ++segment_number;
                    audio_sample_count = 0;
                    video_sample_count = 0;
                }
                new_segment = true;
            }
        }

        // check if we're done
        if (chosen_track == NULL) break;

        if (new_segment) {
            new_segment = false;

            // compute the new segment position
            segment_position = 0;
            if (Options.output_single_file) {
                if (raw_output) {
                    raw_output->Tell(segment_position);
                }
            }

            // manage the new segment stream
            if (segment_output == NULL) {
                segment_output = OpenOutput(Options.segment_filename_template, segment_number);
                raw_output = segment_output;
                if (segment_output == NULL) return AP4_ERROR_CANNOT_OPEN_FILE;
            }
            if (Options.encryption_mode != ENCRYPTION_MODE_NONE) {
                if (Options.encryption_iv_mode == ENCRYPTION_IV_MODE_SEQUENCE) {
                    AP4_SetMemory(Options.encryption_iv, 0, sizeof(Options.encryption_iv));
                    AP4_BytesFromUInt32BE(&Options.encryption_iv[12], segment_number);
                }
            }
            if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                EncryptingStream* encrypting_stream = NULL;
                result = EncryptingStream::Create(Options.encryption_key, Options.encryption_iv, raw_output, encrypting_stream);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to create encrypting stream (%d)\n", result);
                    return result;
                }
                segment_output->Release();
                segment_output = encrypting_stream;
            } else if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                delete sample_encrypter;
                sample_encrypter = NULL;
                result = SampleEncrypter::Create(Options.encryption_key, Options.encryption_iv, sample_encrypter);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to create sample encrypter (%d)\n", result);
                    return result;
                }
            }

            // write the PAT and PMT
            if (ts_writer) {
                // update the descriptors if needed
                if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                    AP4_DataBuffer descriptor;
                    if (audio_track) {
                        result = MakeSampleAesAudioDescriptor(descriptor, audio_track->GetSampleDescription(0), audio_sample_data);
                        if (AP4_SUCCEEDED(result) && descriptor.GetDataSize()) {
                            audio_stream->SetDescriptor(descriptor.GetData(), descriptor.GetDataSize());
                        } else {
                            fprintf(stderr, "ERROR: failed to create sample-aes descriptor (%d)\n", result);
                            return result;
                        }
                    }
                    if (video_track) {
                        result = MakeSampleAesVideoDescriptor(descriptor);
                        if (AP4_SUCCEEDED(result) && descriptor.GetDataSize()) {
                            video_stream->SetDescriptor(descriptor.GetData(), descriptor.GetDataSize());
                        } else {
                            fprintf(stderr, "ERROR: failed to create sample-aes descriptor (%d)\n", result);
                            return result;
                        }
                    }
                }

                ts_writer->WritePAT(*segment_output);
                ts_writer->WritePMT(*segment_output);
            } else if (packed_writer && chosen_track == audio_track) {
                AP4_DataBuffer       private_extension_buffer;
                const char*          private_extension_name = NULL;
                const unsigned char* private_extension_data = NULL;
                unsigned int         private_extension_data_size = 0;
                if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                    private_extension_name = "com.apple.streaming.audioDescription";
                    result = MakeAudioSetupData(private_extension_buffer, audio_track->GetSampleDescription(0), audio_sample_data);
                    if (AP4_FAILED(result)) {
                        fprintf(stderr, "ERROR: failed to make audio setup data (%d)\n", result);
                        return 1;
                    }
                    private_extension_data      = private_extension_buffer.GetData();
                    private_extension_data_size = private_extension_buffer.GetDataSize();
                }
                packed_writer->WriteHeader(audio_ts,
                                           private_extension_name,
                                           private_extension_data,
                                           private_extension_data_size,
                                           *segment_output);
            }
        }

        // write the samples out and advance to the next sample
        if (chosen_track == audio_track) {
            // perform sample-level encryption if needed
            if (sample_encrypter) {
                result = sample_encrypter->EncryptAudioSample(audio_sample_data, audio_track->GetSampleDescription(audio_sample.GetDescriptionIndex()));
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to encrypt audio sample (%d)\n", result);
                    return result;
                }
            }

            // write the sample data
            if (audio_stream) {
                result = audio_stream->WriteSample(audio_sample,
                                                   audio_sample_data,
                                                   audio_track->GetSampleDescription(audio_sample.GetDescriptionIndex()),
                                                   video_track==NULL,
                                                   *segment_output);
            } else if (packed_writer) {
                result = packed_writer->WriteSample(audio_sample,
                                                    audio_sample_data,
                                                    audio_track->GetSampleDescription(audio_sample.GetDescriptionIndex()),
                                                    *segment_output);
            } else {
                return AP4_ERROR_INTERNAL;
            }
            if (AP4_FAILED(result)) return result;

            result = ReadSample(*audio_reader, *audio_track, audio_sample, audio_sample_data, audio_ts, audio_frame_duration, audio_eos);
            if (AP4_FAILED(result)) return result;
            ++audio_sample_count;
        } else if (chosen_track == video_track) {
            // perform sample-level encryption if needed
            if (sample_encrypter) {
                result = sample_encrypter->EncryptVideoSample(video_sample_data, nalu_length_size);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to encrypt video sample (%d)\n", result);
                    return result;
                }
            }

            // write the sample data
            AP4_Position frame_start = 0;
            segment_output->Tell(frame_start);
            result = video_stream->WriteSample(video_sample,
                                               video_sample_data,
                                               video_track->GetSampleDescription(video_sample.GetDescriptionIndex()),
                                               true,
                                               *segment_output);
            if (AP4_FAILED(result)) return result;
            AP4_Position frame_end = 0;
            segment_output->Tell(frame_end);

            // measure I frames
            if (video_sample.IsSync()) {
                AP4_UI64 frame_size = 0;
                if (frame_end > frame_start) {
                    frame_size = frame_end-frame_start;
                }
                if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                    frame_start += segment_position;
                }
                iframe_positions.Append(frame_start);
                iframe_sizes.Append((AP4_UI32)frame_size);
                iframe_times.Append(video_ts);
                iframe_segment_indexes.Append(segment_number);
                iframe_durations.Append(0.0); // will be computed later
                if (Options.verbose) {
                    printf("I-Frame: %d@%lld, t=%f\n", (AP4_UI32)frame_size, frame_start, video_ts);
                }
            }

            // read the next sample
            result = ReadSample(*video_reader, *video_track, video_sample, video_sample_data, video_ts, video_frame_duration, video_eos);
            if (AP4_FAILED(result)) return result;
            ++video_sample_count;
        } else {
            break;
        }
    }

    // create the media playlist/index file
    playlist = OpenOutput(Options.index_filename, 0);
    if (playlist == NULL) return AP4_ERROR_CANNOT_OPEN_FILE;

    unsigned int target_duration = 0;
    double       total_duration = 0.0;
    for (unsigned int i=0; i<segment_durations.ItemCount(); i++) {
        if ((unsigned int)(segment_durations[i]+0.5) > target_duration) {
            target_duration = (unsigned int)segment_durations[i];
        }
        total_duration += segment_durations[i];
    }

    playlist->WriteString("#EXTM3U\r\n");
    if (Options.hls_version > 1) {
        sprintf(string_buffer, "#EXT-X-VERSION:%d\r\n", Options.hls_version);
        playlist->WriteString(string_buffer);
    }
    playlist->WriteString("#EXT-X-PLAYLIST-TYPE:VOD\r\n");
    if (video_track) {
        playlist->WriteString("#EXT-X-INDEPENDENT-SEGMENTS\r\n");
    }
    if (Options.allow_cache) {
        playlist->WriteString("#EXT-X-ALLOW-CACHE:");
        playlist->WriteString(Options.allow_cache);
        playlist->WriteString("\r\n");
    }
    playlist->WriteString("#EXT-X-TARGETDURATION:");
    sprintf(string_buffer, "%d\r\n", target_duration);
    playlist->WriteString(string_buffer);
    playlist->WriteString("#EXT-X-MEDIA-SEQUENCE:0\r\n");

    if (Options.encryption_mode != ENCRYPTION_MODE_NONE) {
        if (Options.encryption_key_lines.ItemCount()) {
            for (unsigned int i=0; i<Options.encryption_key_lines.ItemCount(); i++) {
                AP4_String& key_line = Options.encryption_key_lines[i];
                const char* key_line_cstr = key_line.GetChars();
                bool omit_iv = false;

                // omit the IV if the key line starts with a "!" (and skip the "!")
                if (key_line[0] == '!') {
                    ++key_line_cstr;
                    omit_iv = true;
                }

                playlist->WriteString("#EXT-X-KEY:METHOD=");
                if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                    playlist->WriteString("AES-128");
                } else if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                    playlist->WriteString("SAMPLE-AES");
                }
                playlist->WriteString(",");
                playlist->WriteString(key_line_cstr);
                if ((Options.encryption_iv_mode == ENCRYPTION_IV_MODE_RANDOM ||
                     Options.encryption_iv_mode == ENCRYPTION_IV_MODE_FPS) && !omit_iv) {
                    playlist->WriteString(",IV=0x");
                    char iv_hex[33];
                    iv_hex[32] = 0;
                    AP4_FormatHex(Options.encryption_iv, 16, iv_hex);
                    playlist->WriteString(iv_hex);
                }
                playlist->WriteString("\r\n");
            }
        } else {
            playlist->WriteString("#EXT-X-KEY:METHOD=");
            if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                playlist->WriteString("AES-128");
            } else if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                playlist->WriteString("SAMPLE-AES");
            }
            playlist->WriteString(",URI=\"");
            playlist->WriteString(Options.encryption_key_uri);
            playlist->WriteString("\"");
            if (Options.encryption_iv_mode == ENCRYPTION_IV_MODE_RANDOM) {
                playlist->WriteString(",IV=0x");
                char iv_hex[33];
                iv_hex[32] = 0;
                AP4_FormatHex(Options.encryption_iv, 16, iv_hex);
                playlist->WriteString(iv_hex);
            }
            if (Options.encryption_key_format) {
                playlist->WriteString(",KEYFORMAT=\"");
                playlist->WriteString(Options.encryption_key_format);
                playlist->WriteString("\"");
            }
            if (Options.encryption_key_format_versions) {
                playlist->WriteString(",KEYFORMATVERSIONS=\"");
                playlist->WriteString(Options.encryption_key_format_versions);
                playlist->WriteString("\"");
            }
            playlist->WriteString("\r\n");
        }
    }

    for (unsigned int i=0; i<segment_durations.ItemCount(); i++) {
        if (Options.hls_version >= 3) {
            sprintf(string_buffer, "#EXTINF:%f,\r\n", segment_durations[i]);
        } else {
            sprintf(string_buffer, "#EXTINF:%u,\r\n", (unsigned int)(segment_durations[i]+0.5));
        }
        playlist->WriteString(string_buffer);
        if (Options.output_single_file) {
            sprintf(string_buffer, "#EXT-X-BYTERANGE:%d@%lld\r\n", segment_sizes[i], segment_positions[i]);
            playlist->WriteString(string_buffer);
        }
        sprintf(string_buffer, Options.segment_url_template, i);
        playlist->WriteString(string_buffer);
        playlist->WriteString("\r\n");
    }

    playlist->WriteString("#EXT-X-ENDLIST\r\n");
    playlist->Release();

    // create the iframe playlist/index file
    if (video_track && Options.hls_version >= 4) {
        // compute the iframe durations and target duration
        for (unsigned int i=0; i<iframe_positions.ItemCount(); i++) {
            double iframe_duration = 0.0;
            if (i+1 < iframe_positions.ItemCount()) {
                iframe_duration = iframe_times[i+1]-iframe_times[i];
            } else if (total_duration > iframe_times[i]) {
                iframe_duration = total_duration-iframe_times[i];
            }
            iframe_durations[i] = iframe_duration;
        }
        unsigned int iframes_target_duration = 0;
        for (unsigned int i=0; i<iframe_durations.ItemCount(); i++) {
            if ((unsigned int)(iframe_durations[i]+0.5) > iframes_target_duration) {
                iframes_target_duration = (unsigned int)iframe_durations[i];
            }
        }

        playlist = OpenOutput(Options.iframe_index_filename, 0);
        if (playlist == NULL) return AP4_ERROR_CANNOT_OPEN_FILE;

        playlist->WriteString("#EXTM3U\r\n");
        if (Options.hls_version > 1) {
            sprintf(string_buffer, "#EXT-X-VERSION:%d\r\n", Options.hls_version);
            playlist->WriteString(string_buffer);
        }
        playlist->WriteString("#EXT-X-PLAYLIST-TYPE:VOD\r\n");
        playlist->WriteString("#EXT-X-I-FRAMES-ONLY\r\n");
        playlist->WriteString("#EXT-X-INDEPENDENT-SEGMENTS\r\n");
        playlist->WriteString("#EXT-X-TARGETDURATION:");
        sprintf(string_buffer, "%d\r\n", iframes_target_duration);
        playlist->WriteString(string_buffer);
        playlist->WriteString("#EXT-X-MEDIA-SEQUENCE:0\r\n");

        if (Options.encryption_mode != ENCRYPTION_MODE_NONE) {
            playlist->WriteString("#EXT-X-KEY:METHOD=");
            if (Options.encryption_mode == ENCRYPTION_MODE_AES_128) {
                playlist->WriteString("AES-128");
            } else if (Options.encryption_mode == ENCRYPTION_MODE_SAMPLE_AES) {
                playlist->WriteString("SAMPLE-AES");
            }
            playlist->WriteString(",URI=\"");
            playlist->WriteString(Options.encryption_key_uri);
            playlist->WriteString("\"");
            if (Options.encryption_iv_mode == ENCRYPTION_IV_MODE_RANDOM) {
                playlist->WriteString(",IV=0x");
                char iv_hex[33];
                iv_hex[32] = 0;
                AP4_FormatHex(Options.encryption_iv, 16, iv_hex);
                playlist->WriteString(iv_hex);
            }
            if (Options.encryption_key_format) {
                playlist->WriteString(",KEYFORMAT=\"");
                playlist->WriteString(Options.encryption_key_format);
                playlist->WriteString("\"");
            }
            if (Options.encryption_key_format_versions) {
                playlist->WriteString(",KEYFORMATVERSIONS=\"");
                playlist->WriteString(Options.encryption_key_format_versions);
                playlist->WriteString("\"");
            }
            playlist->WriteString("\r\n");
        }

        for (unsigned int i=0; i<iframe_positions.ItemCount(); i++) {
            sprintf(string_buffer, "#EXTINF:%f,\r\n", iframe_durations[i]);
            playlist->WriteString(string_buffer);
            sprintf(string_buffer, "#EXT-X-BYTERANGE:%d@%lld\r\n", iframe_sizes[i], iframe_positions[i]);
            playlist->WriteString(string_buffer);
            sprintf(string_buffer, Options.segment_url_template, iframe_segment_indexes[i]);
            playlist->WriteString(string_buffer);
            playlist->WriteString("\r\n");
        }

        playlist->WriteString("#EXT-X-ENDLIST\r\n");
        playlist->Release();
    }

    // update stats
    Stats.segment_count = segment_sizes.ItemCount();
    for (unsigned int i=0; i<segment_sizes.ItemCount(); i++) {
        Stats.segments_total_size     += segment_sizes[i];
        Stats.segments_total_duration += segment_durations[i];
    }
    Stats.iframe_count = iframe_sizes.ItemCount();
    for (unsigned int i=0; i<iframe_sizes.ItemCount(); i++) {
        Stats.iframes_total_size += iframe_sizes[i];
    }
    for (unsigned int i=0; i<iframe_positions.ItemCount(); i++) {
        if (iframe_durations[i] != 0.0) {
            double iframe_bitrate = 8.0*(double)iframe_sizes[i]/iframe_durations[i];
            if (iframe_bitrate > Stats.max_iframe_bitrate) {
                Stats.max_iframe_bitrate = iframe_bitrate;
            }
        }
    }

    if (Options.verbose) {
        printf("Conversion complete, total duration=%.2f secs\n", total_duration);
    }

    if (segment_output) segment_output->Release();
    delete sample_encrypter;

    return result;
}


FragmentedSampleReader::ReadSample(AP4_Sample& sample, AP4_DataBuffer& sample_data)
{
    return m_FragmentReader.ReadNextSample(m_TrackId, sample, sample_data);
}


    ~SampleEncrypter() {
        delete m_StreamCipher;
    }


    FragmentedSampleReader(AP4_LinearReader& fragment_reader, AP4_UI32 track_id) :
        m_FragmentReader(fragment_reader), m_TrackId(track_id) {
        fragment_reader.EnableTrack(track_id);
    }


WriteAdtsHeader(AP4_ByteStream& output,
                unsigned int    frame_size,
                unsigned int    sampling_frequency_index,
                unsigned int    channel_configuration)
{
	unsigned char bits[7];

	bits[0] = 0xFF;
	bits[1] = 0xF1; // 0xF9 (MPEG2)
    bits[2] = 0x40 | (sampling_frequency_index << 2) | (channel_configuration >> 2);
    bits[3] = ((channel_configuration&0x3)<<6) | ((frame_size+7) >> 11);
    bits[4] = ((frame_size+7) >> 3)&0xFF;
	bits[5] = (((frame_size+7) << 5)&0xFF) | 0x1F;
	bits[6] = 0xFC;

	return output.Write(bits, 7);

	/*
        0:  syncword 12 always: '111111111111'
        12: ID 1 0: MPEG-4, 1: MPEG-2
        13: layer 2 always: '00'
        15: protection_absent 1
        16: profile 2
        18: sampling_frequency_index 4
        22: private_bit 1
        23: channel_configuration 3
        26: original/copy 1
        27: home 1
        28: emphasis 2 only if ID == 0

        ADTS Variable header: these can change from frame to frame
        28: copyright_identification_bit 1
        29: copyright_identification_start 1
        30: aac_frame_length 13 length of the frame including header (in bytes)
        43: adts_buffer_fullness 11 0x7FF indicates VBR
        54: no_raw_data_blocks_in_frame 2
        ADTS Error check
        crc_check 16 only if protection_absent == 0
   */
}


    virtual AP4_Result ReadPartial(void* , AP4_Size, AP4_Size&) {
        return AP4_ERROR_NOT_SUPPORTED;
    }


fiber.c: should pack 15+ arguments in an array.

mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
{
  struct mrb_context *c = mrb->c;

  if (!c->prev) {
    mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
  }

  fiber_check_cfunc(mrb, c);
  c->prev->status = MRB_FIBER_RUNNING;
  c->status = MRB_FIBER_SUSPENDED;
  fiber_switch_context(mrb, c->prev);
  c->prev = NULL;
  if (c->vmexec) {
    c->vmexec = FALSE;
    mrb->c->ci->cci = CINFO_RESUMED;
    c->ci--;                    /* pop callinfo for yield */
  }
  MARK_CONTEXT_MODIFY(mrb->c);
  return fiber_result(mrb, a, len);
}

NULL Pointer Dereference in GitHub repository mruby/mruby prior to 3.2.


fiber_current(mrb_state *mrb, mrb_value self)
{
  if (!mrb->c->fib) {
    struct RFiber *f = MRB_OBJ_ALLOC(mrb, MRB_TT_FIBER, mrb_class_ptr(self));

    f->cxt = mrb->c;
    mrb->c->fib = f;
  }
  return mrb_obj_value(mrb->c->fib);
}


mrb_mruby_fiber_gem_init(mrb_state* mrb)
{
  struct RClass *c;

  c = mrb_define_class(mrb, "Fiber", mrb->object_class);
  MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);

  mrb_define_method(mrb, c, "initialize", fiber_init,    MRB_ARGS_NONE()|MRB_ARGS_BLOCK());
  mrb_define_method(mrb, c, "resume",     fiber_resume,  MRB_ARGS_ANY());
  mrb_define_method(mrb, c, "transfer",   fiber_transfer, MRB_ARGS_ANY());
  mrb_define_method(mrb, c, "alive?",     fiber_alive_p, MRB_ARGS_NONE());
  mrb_define_method(mrb, c, "==",         fiber_eq,      MRB_ARGS_REQ(1));

  mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
  mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());

  mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
}


fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec)
{
  struct mrb_context *c = fiber_check(mrb, self);
  struct mrb_context *old_c = mrb->c;
  enum mrb_fiber_state status;
  mrb_value value;

  fiber_check_cfunc(mrb, c);
  status = c->status;
  switch (status) {
  case MRB_FIBER_TRANSFERRED:
    if (resume) {
      mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
    }
    break;
  case MRB_FIBER_RUNNING:
  case MRB_FIBER_RESUMED:
    mrb_raise(mrb, E_FIBER_ERROR, "double resume");
    break;
  case MRB_FIBER_TERMINATED:
    mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
    break;
  default:
    break;
  }
  old_c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
  c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
  fiber_switch_context(mrb, c);
  if (status == MRB_FIBER_CREATED) {
    mrb_value *b, *e;

    if (!c->ci->proc) {
      mrb_raise(mrb, E_FIBER_ERROR, "double resume (current)");
    }
    if (vmexec) {
      c->ci--;                    /* pop dummy callinfo */
    }
    if (len >= 15) {
      mrb_stack_extend(mrb, 3);   /* for receiver, args and (optional) block */
      c->stbase[1] = mrb_ary_new_from_values(mrb, len, a);
      len = 15;
    }
    else {
      mrb_stack_extend(mrb, len+2); /* for receiver and (optional) block */
      b = c->stbase+1;
      e = b + len;
      while (b<e) {
        *b++ = *a++;
      }
    }
    c->cibase->n = len;
    value = c->stbase[0] = MRB_PROC_ENV(c->cibase->proc)->stack[0];
  }
  else {
    value = fiber_result(mrb, a, len);
    if (vmexec) {
      c->ci[1].stack[0] = value;
    }
  }

  if (vmexec) {
    c->vmexec = TRUE;
    value = mrb_vm_exec(mrb, c->ci->proc, c->ci->pc);
    mrb->c = old_c;
  }
  else {
    MARK_CONTEXT_MODIFY(c);
  }
  return value;
}


fiber_transfer(mrb_state *mrb, mrb_value self)
{
  struct mrb_context *c = fiber_check(mrb, self);
  const mrb_value* a;
  mrb_int len;

  fiber_check_cfunc(mrb, mrb->c);
  mrb_get_args(mrb, "*!", &a, &len);

  if (c == mrb->root_c) {
    mrb->c->status = MRB_FIBER_TRANSFERRED;
    fiber_switch_context(mrb, c);
    MARK_CONTEXT_MODIFY(c);
    return fiber_result(mrb, a, len);
  }

  if (c == mrb->c) {
    return fiber_result(mrb, a, len);
  }

  return fiber_switch(mrb, self, len, a, FALSE, FALSE);
}


fiber_resume(mrb_state *mrb, mrb_value self)
{
  const mrb_value *a;
  mrb_int len;
  mrb_bool vmexec = FALSE;

  mrb_get_args(mrb, "*!", &a, &len);
  if (mrb->c->ci->cci > 0) {
    vmexec = TRUE;
  }
  return fiber_switch(mrb, self, len, a, TRUE, vmexec);
}


fiber_init(mrb_state *mrb, mrb_value self)
{
  static const struct mrb_context mrb_context_zero = { 0 };
  struct RFiber *f = fiber_ptr(self);
  struct mrb_context *c;
  struct RProc *p;
  mrb_callinfo *ci;
  mrb_value blk;
  size_t slen;

  mrb_get_args(mrb, "&!", &blk);

  if (f->cxt) {
    mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice");
  }
  p = mrb_proc_ptr(blk);
  if (MRB_PROC_CFUNC_P(p)) {
    mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
  }

  c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
  *c = mrb_context_zero;
  f->cxt = c;

  /* initialize VM stack */
  slen = FIBER_STACK_INIT_SIZE;
  if (p->body.irep->nregs > slen) {
    slen += p->body.irep->nregs;
  }
  c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
  c->stend = c->stbase + slen;

  {
    mrb_value *p = c->stbase;
    mrb_value *pend = c->stend;

    while (p < pend) {
      SET_NIL_VALUE(*p);
      p++;
    }
  }

  /* copy receiver from a block */
  c->stbase[0] = mrb->c->ci->stack[0];

  /* initialize callinfo stack */
  c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
  c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
  c->ci = c->cibase;

  /* adjust return callinfo */
  ci = c->ci;
  mrb_vm_ci_target_class_set(ci, MRB_PROC_TARGET_CLASS(p));
  mrb_vm_ci_proc_set(ci, p);
  mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p);
  ci->stack = c->stbase;
  ci[1] = ci[0];
  c->ci++;                      /* push dummy callinfo */

  c->fib = f;
  c->status = MRB_FIBER_CREATED;

  return self;
}


fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
{
  mrb_callinfo *ci;

  for (ci = c->ci; ci >= c->cibase; ci--) {
    if (ci->cci > 0) {
      mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
    }
  }
}


fiber_switch_context(mrb_state *mrb, struct mrb_context *c)
{
  if (mrb->c->fib) {
    mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib);
  }
  c->status = MRB_FIBER_RUNNING;
  mrb->c = c;
}


fiber_yield(mrb_state *mrb, mrb_value self)
{
  const mrb_value *a;
  mrb_int len;

  mrb_get_args(mrb, "*!", &a, &len);
  return mrb_fiber_yield(mrb, len, a);
}


fiber_eq(mrb_state *mrb, mrb_value self)
{
  mrb_value other = mrb_get_arg1(mrb);

  if (!mrb_fiber_p(other)) {
    return mrb_false_value();
  }
  return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
}


Fix wrong boundary checks in inflections parser resulting in stack buffer over-read with corrupt input

MOBI_RET mobi_get_indxentry_tagvalue(uint32_t *tagvalue, const MOBIIndexEntry *entry, const unsigned tag_arr[]) {
    if (entry == NULL) {
        debug_print("%s", "INDX entry not initialized\n");
        return MOBI_INIT_FAILED;
    }
    size_t i = 0;
    while (i < entry->tags_count) {
        if (entry->tags[i].tagid == tag_arr[0]) {
            if (entry->tags[i].tagvalues_count > tag_arr[1]) {
                *tagvalue = entry->tags[i].tagvalues[tag_arr[1]];
                return MOBI_SUCCESS;
            }
            break;
        }
        i++;
    }
    //debug_print("tag[%i][%i] not found in entry: %s\n", tag_arr[0], tag_arr[1], entry->label);
    return MOBI_DATA_CORRUPT;
}

Buffer Over-read in GitHub repository bfabiszewski/libmobi prior to 0.11. This vulnerability is capable of arbitrary code execution.


size_t mobi_indx_get_label(unsigned char *output, MOBIBuffer *buf, const size_t length, const size_t has_ligatures) {
    if (!output) {
        buf->error = MOBI_PARAM_ERR;
        return 0;
    }
    if (buf->offset + length > buf->maxlen) {
        debug_print("%s", "End of buffer\n");
        buf->error = MOBI_BUFFER_END;
        return 0;
    }
    const unsigned char replacement = 0x3f;
    size_t output_length = 0;
    size_t i = 0;
    while (i < length && output_length < INDX_LABEL_SIZEMAX) {
        unsigned char c = mobi_buffer_get8(buf);
        i++;
        if (c == 0) {
            /* FIXME: is it safe to replace zeroes? */
            debug_print("Invalid character: %u\n", c);
            c = replacement;
        }
        if (c <= 5 && has_ligatures) {
            unsigned char c2 = mobi_buffer_get8(buf);
            c = mobi_ligature_to_cp1252(c, c2);
            if (c == 0) {
                debug_print("Invalid ligature sequence%s", "\n");
                mobi_buffer_seek(buf, -1);
                c = replacement;
            } else {
                i++;
            }
        }
        *output++ = c;
        output_length++;
    }
    *output = '\0';
    return output_length;
}


static MOBI_RET mobi_parse_tagx(MOBIBuffer *buf, MOBITagx *tagx) {
    tagx->control_byte_count = 0;
    tagx->tags_count = 0;
    tagx->tags = NULL;
    mobi_buffer_seek(buf, 4); /* skip header */
    uint32_t tagx_record_length = mobi_buffer_get32(buf);
    if (tagx_record_length < 12) {
        debug_print("INDX record too short: %u\n", tagx_record_length);
        return MOBI_DATA_CORRUPT;
    }
    tagx->control_byte_count = mobi_buffer_get32(buf);
    tagx_record_length -= 12;
    if (tagx_record_length + buf->offset > buf->maxlen) {
        debug_print("INDX record too long: %u\n", tagx_record_length);
        return MOBI_DATA_CORRUPT;
    }
    tagx->tags = malloc(tagx_record_length * sizeof(TAGXTags));
    if (tagx->tags == NULL) {
        debug_print("%s", "Memory allocation failed for TAGX tags\n");
        return MOBI_MALLOC_FAILED;
    }
    size_t i = 0;
    const size_t tagx_data_length = tagx_record_length / 4;
    size_t control_byte_count = 0;
    while (i < tagx_data_length) {
        tagx->tags[i].tag = mobi_buffer_get8(buf);
        tagx->tags[i].values_count = mobi_buffer_get8(buf);
        tagx->tags[i].bitmask = mobi_buffer_get8(buf);
        const uint8_t control_byte = mobi_buffer_get8(buf);
        if (control_byte) { control_byte_count++; }
        tagx->tags[i].control_byte = control_byte;
        debug_print("tagx[%zu]:\t%i\t%i\t%i\t%i\n", i, tagx->tags[i].tag, tagx->tags[i].values_count, tagx->tags[i].bitmask, control_byte);
        i++;
    }
    if (tagx->control_byte_count != control_byte_count) {
        debug_print("Wrong count of control bytes: %zu != %zu\n", tagx->control_byte_count, control_byte_count);
        free(tagx->tags);
        tagx->tags = NULL;
        tagx->control_byte_count = 0;
        return MOBI_DATA_CORRUPT;
    }
    tagx->tags_count = i;
    return MOBI_SUCCESS;
}


char * mobi_get_cncx_string_flat(const MOBIPdbRecord *cncx_record, const uint32_t cncx_offset, const size_t length) {
    /* TODO: handle multiple cncx records */
    MOBIBuffer *buf = mobi_buffer_init_null(cncx_record->data, cncx_record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return NULL;
    }
    mobi_buffer_setpos(buf, cncx_offset);
    char *string = malloc(length + 1);
    if (string) {
        mobi_buffer_getstring(string, buf, length);
    }
    mobi_buffer_free_null(buf);
    return string;
}


uint32_t mobi_get_orth_entry_length(const MOBIIndexEntry *entry) {

    uint32_t entry_textlen;
    MOBI_RET ret = mobi_get_indxentry_tagvalue(&entry_textlen, entry, INDX_TAG_ORTH_LENGTH);
    if (ret != MOBI_SUCCESS) {
        return MOBI_NOTSET;
    }

    return entry_textlen;
}


char * mobi_get_cncx_string(const MOBIPdbRecord *cncx_record, const uint32_t cncx_offset) {
    /* TODO: handle multiple cncx records */
    MOBIBuffer *buf = mobi_buffer_init_null(cncx_record->data, cncx_record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return NULL;
    }
    mobi_buffer_setpos(buf, cncx_offset);
    size_t len = 0;
    const uint32_t string_length = mobi_buffer_get_varlen(buf, &len);
    char *string = malloc(string_length + 1);
    if (string) {
        mobi_buffer_getstring(string, buf, string_length);
    }
    mobi_buffer_free_null(buf);
    return string;
}


size_t mobi_trie_get_inflgroups(char **infl_strings, MOBITrie * const root, const char *string) {
    /* travers trie and get values for each substring */
    if (root == NULL) {
        return MOBI_PARAM_ERR;
    }
    size_t count = 0;
    size_t length = strlen(string);
    MOBITrie *node = root;
    while (node && length > 0) {
        char **values = NULL;
        size_t values_count = 0;
        node = mobi_trie_get_next(&values, &values_count, node, string[length - 1]);
        length--;
        for (size_t j = 0; j < values_count; j++) {
            if (count == INDX_INFLSTRINGS_MAX) {
                debug_print("Inflection strings array too small (%d)\n", INDX_INFLSTRINGS_MAX);
                break;
            }
            char infl_string[INDX_LABEL_SIZEMAX + 1];
            const size_t suffix_length = strlen(values[j]);
            if (length + suffix_length > INDX_LABEL_SIZEMAX) {
                debug_print("Label too long (%zu + %zu)\n", length, suffix_length);
                continue;
            }
            memcpy(infl_string, string, length);
            memcpy(infl_string + length, values[j], suffix_length);
            infl_string[length + suffix_length] = '\0';
            infl_strings[count++] = strdup(infl_string);
        }
    }
    return count;
}


uint32_t mobi_get_orth_entry_offset(const MOBIIndexEntry *entry) {

    uint32_t entry_startpos;
    MOBI_RET ret = mobi_get_indxentry_tagvalue(&entry_startpos, entry, INDX_TAG_ORTH_POSITION);
    if (ret != MOBI_SUCCESS) {
        return MOBI_NOTSET;
    }

    return entry_startpos;
}


static MOBI_RET mobi_parse_ordt(MOBIBuffer *buf, MOBIOrdt *ordt) {
    /* read ORDT1 */
    mobi_buffer_setpos(buf, ordt->ordt1_pos);
    if (mobi_buffer_match_magic(buf, ORDT_MAGIC)) {
        debug_print("%s\n", "ORDT1 section found");
        mobi_buffer_seek(buf, 4);
        if (ordt->offsets_count + buf->offset > buf->maxlen) {
            debug_print("ORDT1 section too long (%zu)", ordt->offsets_count);
            return MOBI_DATA_CORRUPT;
        }
        ordt->ordt1 = malloc(ordt->offsets_count * sizeof(*ordt->ordt1));
        if (ordt->ordt1 == NULL) {
            debug_print("%s", "Memory allocation failed for ORDT1 offsets\n");
            return MOBI_MALLOC_FAILED;
        }
        size_t i = 0;
        while (i < ordt->offsets_count) {
            ordt->ordt1[i++] = mobi_buffer_get8(buf);
        }
        debug_print("ORDT1: read %zu entries\n", ordt->offsets_count);
    }
    /* read ORDT2 */
    mobi_buffer_setpos(buf, ordt->ordt2_pos);
    if (mobi_buffer_match_magic(buf, ORDT_MAGIC)) {
        debug_print("%s\n", "ORDT2 section found");
        mobi_buffer_seek(buf, 4);
        if (ordt->offsets_count * 2 + buf->offset > buf->maxlen) {
            debug_print("ORDT2 section too long (%zu)", ordt->offsets_count);
            return MOBI_DATA_CORRUPT;
        }
        ordt->ordt2 = malloc(ordt->offsets_count * sizeof(*ordt->ordt2));
        if (ordt->ordt2 == NULL) {
            debug_print("%s", "Memory allocation failed for ORDT2 offsets\n");
            return MOBI_MALLOC_FAILED;
        }
        size_t i = 0;
        while (i < ordt->offsets_count) {
            ordt->ordt2[i++] = mobi_buffer_get16(buf);
        }
        debug_print("ORDT2: read %zu entries\n", ordt->offsets_count);
    }
    return MOBI_SUCCESS;
}


uint16_t mobi_ordt_lookup(const MOBIOrdt *ordt, const uint16_t offset) {
    uint16_t utf16;
    if (offset < ordt->offsets_count) {
        utf16 = ordt->ordt2[offset];
    } else {
        utf16 = offset;
    }
    return utf16;
}


size_t mobi_getstring_ordt(const MOBIOrdt *ordt, MOBIBuffer *buf, unsigned char *output, size_t length) {
    size_t i = 0;
    size_t output_length = 0;
    const uint32_t bytemask = 0xbf;
    const uint32_t bytemark = 0x80;
    const uint32_t uni_replacement = 0xfffd;
    const uint32_t surrogate_offset = 0x35fdc00;
    static const uint8_t init_byte[7] = { 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc };
    while (i < length) {
        uint16_t offset;
        i += mobi_ordt_getbuffer(ordt, buf, &offset);
        uint32_t codepoint = mobi_ordt_lookup(ordt, offset);
        if (codepoint <= 5) {
            size_t k = mobi_ordt_getbuffer(ordt, buf, &offset);
            uint32_t codepoint2 = mobi_ordt_lookup(ordt, offset);
            codepoint = mobi_ligature_to_utf16(codepoint, codepoint2);
            if (codepoint == uni_replacement) {
                /* rewind buffer to codepoint2 */
                debug_print("Invalid ligature sequence%s", "\n");
                mobi_buffer_seek(buf, - (int) k);
            } else {
                i += k;
            }
        }
        /* convert UTF-16 surrogates into UTF-32 */
        if (codepoint >= 0xd800 && codepoint <= 0xdbff) {
            size_t k = mobi_ordt_getbuffer(ordt, buf, &offset);
            uint32_t codepoint2 = mobi_ordt_lookup(ordt, offset);
            if (codepoint2 >= 0xdc00 && codepoint2 <= 0xdfff) {
                i += k;
                codepoint = (codepoint << 10) + codepoint2 - surrogate_offset;
            } else {
                /* illegal unpaired high surrogate */
                /* rewind buffer to codepoint2 */
                debug_print("Invalid code point: %u\n", codepoint);
                mobi_buffer_seek(buf, - (int) k);
                codepoint = uni_replacement;
            }
        }
        if ((codepoint >= 0xdc00 && codepoint <= 0xdfff) /* unpaired low surrogate */
            || (codepoint >= 0xfdd0 && codepoint <= 0xfdef) /* invalid characters */
            || (codepoint & 0xfffe) == 0xfffe /* reserved characters */
            || codepoint == 0 /* remove zeroes */) {
            codepoint = uni_replacement;
            debug_print("Invalid code point: %u\n", codepoint);
        }
        /* Conversion routine based on unicode's ConvertUTF.c */
        size_t bytes;
        if (codepoint < 0x80) { bytes = 1; }
        else if (codepoint < 0x800) { bytes = 2; }
        else if (codepoint < 0x10000) { bytes = 3; }
        else if (codepoint < 0x110000) { bytes = 4; }
        else {
            bytes = 3;
            codepoint = uni_replacement;
            debug_print("Invalid code point: %u\n", codepoint);
        }
        if (output_length + bytes >= INDX_LABEL_SIZEMAX) {
            debug_print("%s\n", "INDX label too long");
            break;
        }
        output += bytes;
        switch (bytes) {
            case 4: *--output = (uint8_t)((codepoint | bytemark) & bytemask); codepoint >>= 6; /* falls through */
            case 3: *--output = (uint8_t)((codepoint | bytemark) & bytemask); codepoint >>= 6; /* falls through */
            case 2: *--output = (uint8_t)((codepoint | bytemark) & bytemask); codepoint >>= 6; /* falls through */
            case 1: *--output = (uint8_t)(codepoint | init_byte[bytes]);
        }
        output += bytes;
        output_length += bytes;
    }
    *output = '\0';
    return output_length;
}


char * mobi_get_cncx_string_utf8(const MOBIPdbRecord *cncx_record, const uint32_t cncx_offset, MOBIEncoding cncx_encoding) {
    char *string = mobi_get_cncx_string(cncx_record, cncx_offset);
    if (string != NULL && cncx_encoding == MOBI_CP1252) {
        size_t in_len = strlen(string);
        size_t out_len = in_len * 3 + 1;
        char *decoded = malloc(out_len);
        if (decoded) {
            mobi_cp1252_to_utf8(decoded, string, &out_len, in_len);
            free(string);
            string = strdup(decoded);
            free(decoded);
        }
    }
    return string;
}


static MOBI_RET mobi_parse_idxt(MOBIBuffer *buf, MOBIIdxt *idxt, const size_t entries_count) {
    const uint32_t idxt_offset = (uint32_t) buf->offset;
    idxt->offsets_count = 0;
    char idxt_magic[5];
    mobi_buffer_getstring(idxt_magic, buf, 4);
    if (strncmp(idxt_magic, IDXT_MAGIC, 4) != 0) {
        debug_print("IDXT wrong magic: %s\n", idxt_magic);
        return MOBI_DATA_CORRUPT;
    }
    size_t i = 0;
    while (i < entries_count) {
        /* entry offsets */
        idxt->offsets[i++] = mobi_buffer_get16(buf);
    }
    /* last entry end position is IDXT tag offset */
    idxt->offsets[i] = idxt_offset;
    idxt->offsets_count = i;
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_index(const MOBIData *m, MOBIIndx *indx, const size_t indx_record_number) {
    MOBI_RET ret;
    /* tagx->tags array will be allocated in mobi_parse_tagx */
    MOBITagx *tagx = calloc(1, sizeof(MOBITagx));
    if (tagx == NULL) {
        mobi_free_indx(indx);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    /* ordt->ordt1 and ordt.ordt2 arrays will be allocated in mobi_parse_ordt */
    MOBIOrdt *ordt = calloc(1, sizeof(MOBIOrdt));
    if (ordt == NULL) {
        mobi_free_indx(indx);
        mobi_free_tagx(tagx);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    /* parse first meta INDX record */
    MOBIPdbRecord *record = mobi_get_record_by_seqnumber(m, indx_record_number);
    ret = mobi_parse_indx(record, indx, tagx, ordt);
    if (ret != MOBI_SUCCESS) {
        mobi_free_indx(indx);
        mobi_free_tagx(tagx);
        mobi_free_ordt(ordt);
        return ret;
    }
    /* parse remaining INDX records for the index */
    size_t count = indx->entries_count;
    indx->entries_count = 0;
    while (count--) {
        record = record->next;
        ret = mobi_parse_indx(record, indx, tagx, ordt);
        if (ret != MOBI_SUCCESS) {
            mobi_free_indx(indx);
            mobi_free_tagx(tagx);
            mobi_free_ordt(ordt);
            return ret;
        }
    }
    if (indx->entries_count != indx->total_entries_count) {
        debug_print("Entries count %zu != total entries count %zu\n", indx->entries_count, indx->total_entries_count);
        mobi_free_indx(indx);
        mobi_free_tagx(tagx);
        mobi_free_ordt(ordt);
        return MOBI_DATA_CORRUPT;
    }
    /* copy pointer to first cncx record if present and set info from first record */
    if (indx->cncx_records_count) {
        indx->cncx_record = record->next;
    }
    mobi_free_tagx(tagx);
    mobi_free_ordt(ordt);
    return MOBI_SUCCESS;
}


bool mobi_indx_has_tag(const MOBIIndx *indx, const size_t tagid) {
    if (indx) {
        for (size_t i = 0; i < indx->entries_count; i++) {
            MOBIIndexEntry entry = indx->entries[i];
            for(size_t j = 0; j < entry.tags_count; j++) {
                if (entry.tags[j].tagid == tagid) {
                    return true;
                }
            }
        }
    }
    return false;
}


MOBI_RET mobi_decode_infl(unsigned char *decoded, int *decoded_size, const unsigned char *rule) {
    int pos = *decoded_size;
    char mod = 'i';
    char dir = '<';
    char olddir;
    unsigned char c;
    while ((c = *rule++)) {
        if (c <= 4) {
            mod = (c <= 2) ? 'i' : 'd'; /* insert, delete */
            olddir = dir;
            dir = (c & 2) ? '<' : '>'; /* left, right */
            if (olddir != dir && olddir) {
                pos = (c & 2) ? *decoded_size : 0;
            }
        }
        else if (c > 10 && c < 20) {
            if (dir == '>') {
                pos = *decoded_size;
            }
            pos -= c - 10;
            dir = 0;
        }
        else {
            if (mod == 'i') {
                const unsigned char *s = decoded + pos;
                unsigned char *d = decoded + pos + 1;
                const int l = *decoded_size - pos;
                if (pos < 0 || l < 0 || d + l > decoded + INDX_INFLBUF_SIZEMAX) {
                    debug_print("Out of buffer in %s at pos: %i\n", decoded, pos);
                    return MOBI_DATA_CORRUPT;
                }
                memmove(d, s, (size_t) l);
                decoded[pos] = c;
                (*decoded_size)++;
                if (dir == '>') { pos++; }
            } else {
                if (dir == '<') { pos--; }
                const unsigned char *s = decoded + pos + 1;
                unsigned char *d = decoded + pos;
                const int l = *decoded_size - pos;
                if (pos < 0 || l < 0 || s + l > decoded + INDX_INFLBUF_SIZEMAX) {
                    debug_print("Out of buffer in %s at pos: %i\n", decoded, pos);
                    return MOBI_DATA_CORRUPT;
                }
                if (decoded[pos] != c) {
                    debug_print("Character mismatch in %s at pos: %i (%c != %c)\n", decoded, pos, decoded[pos], c);
                    return MOBI_DATA_CORRUPT;
                }
                memmove(d, s, (size_t) l);
                (*decoded_size)--;
            }
        }
    }
    return MOBI_SUCCESS;
}


size_t mobi_get_indxentry_tagarray(uint32_t **tagarr, const MOBIIndexEntry *entry, const size_t tagid) {
    if (entry == NULL) {
        debug_print("%s", "INDX entry not initialized\n");
        return 0;
    }
    size_t i = 0;
    while (i < entry->tags_count) {
        if (entry->tags[i].tagid == tagid) {
            *tagarr = entry->tags[i].tagvalues;
            return entry->tags[i].tagvalues_count;
        }
        i++;
    }
    //debug_print("tag[%zu] not found in entry: %s\n", tagid, entry->label);
    return 0;
}


MOBI_RET mobi_trie_insert_infl(MOBITrie **root, const MOBIIndx *indx, size_t i) {
    MOBIIndexEntry e = indx->entries[i];
    char *inflected = e.label;
    for (size_t j = 0; j < e.tags_count; j++) {
        MOBIIndexTag t = e.tags[j];
        if (t.tagid == INDX_TAGARR_INFL_PARTS_V1) {
            for (size_t k = 0; k + 1 < t.tagvalues_count; k += 2) {
                uint32_t len = t.tagvalues[k];
                uint32_t offset = t.tagvalues[k + 1];
                char *base = mobi_get_cncx_string_flat(indx->cncx_record, offset, len);
                if (base == NULL) {
                    return MOBI_MALLOC_FAILED;
                }
                MOBI_RET ret = mobi_trie_insert_reversed(root, base, inflected);
                free(base);
                if (ret != MOBI_SUCCESS) {
                    return ret;
                }
            }
        }
    }
    return MOBI_SUCCESS;
}


size_t mobi_ordt_getbuffer(const MOBIOrdt *ordt, MOBIBuffer *buf, uint16_t *offset) {
    size_t i = 0;
    if (ordt->type == 1) {
        *offset = mobi_buffer_get8(buf);
        i++;
    } else {
        *offset = mobi_buffer_get16(buf);
        i += 2;
    }
    return i;
}


MOBI_RET mobi_parse_indx(const MOBIPdbRecord *indx_record, MOBIIndx *indx, MOBITagx *tagx, MOBIOrdt *ordt) {
    if (indx_record == NULL || indx == NULL || tagx == NULL || ordt == NULL) {
        debug_print("%s", "index structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    MOBI_RET ret = MOBI_SUCCESS;
    MOBIBuffer *buf = mobi_buffer_init_null(indx_record->data, indx_record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    char indx_magic[5];
    mobi_buffer_getstring(indx_magic, buf, 4); /* 0: INDX magic */
    const uint32_t header_length = mobi_buffer_get32(buf); /* 4: header length */
    if (strncmp(indx_magic, INDX_MAGIC, 4) != 0 ||
        header_length == 0 || header_length > indx_record->size) {
        debug_print("INDX wrong magic: %s or header length: %u\n", indx_magic, header_length);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    mobi_buffer_seek(buf, 4); /* 8: unk, usually zeroes */
    const uint32_t type = mobi_buffer_get32(buf); /* 12: 0 - normal, 2 - inflection */
    mobi_buffer_seek(buf, 4); /* 16: unk */
    const uint32_t idxt_offset = mobi_buffer_get32(buf); /* 20: IDXT offset */
    const uint32_t entries_count = mobi_buffer_get32(buf); /* 24: entries count */
    if (entries_count > INDX_RECORD_MAXCNT) {
        debug_print("Too many index entries (%u)\n", entries_count);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* if record contains TAGX section, read it (and ORDT) and return */
    if (mobi_buffer_match_magic_offset(buf, TAGX_MAGIC, header_length) && indx->total_entries_count == 0) {
        buf->maxlen = header_length;
        /* TAGX metadata */
        uint32_t encoding = mobi_buffer_get32(buf); /* 28: encoding */
        if (encoding == MOBI_NOTSET) { encoding = MOBI_CP1252; }
        mobi_buffer_seek(buf, 4); /* 32 */
        const uint32_t total_entries_count = mobi_buffer_get32(buf); /* 36: total entries count */
        if (total_entries_count > INDX_TOTAL_MAXCNT) {
            debug_print("Too many total index entries (%u)\n", total_entries_count);
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        uint32_t ordt_offset = mobi_buffer_get32(buf); /* 40: ORDT offset; currently not used */
        if (ordt_offset + ORDT_RECORD_MAXCNT + 4 > indx_record->size) {
            ordt_offset = 0;
        }
        uint32_t ligt_offset = mobi_buffer_get32(buf); /* 44: LIGT offset; currently static table used instead */
        uint32_t ligt_entries_count = mobi_buffer_get32(buf); /* 48: LIGT entries count */
        if (ligt_offset + 4 * ligt_entries_count + 4 > indx_record->size) {
            ligt_offset = 0;
            ligt_entries_count = 0;
        }
        const uint32_t cncx_records_count = mobi_buffer_get32(buf); /* 52: CNCX entries count */
        if (cncx_records_count > CNCX_RECORD_MAXCNT) {
            debug_print("Too many CNCX records (%u)\n", cncx_records_count);
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        /* 56: unk count */
        /* 60-148: phonetizer */
        uint32_t ordt_type = 0;
        uint32_t ordt_entries_count = 0;
        uint32_t ordt1_offset = 0;
        uint32_t ordt2_offset = 0;
        uint32_t index_name_offset = 0;
        uint32_t index_name_length = 0;
        if (header_length >= 180) {
            mobi_buffer_setpos(buf, 164);
            ordt_type = mobi_buffer_get32(buf); /* 164: ORDT type */
            ordt_entries_count = mobi_buffer_get32(buf); /* 168: ORDT entries count */
            ordt1_offset = mobi_buffer_get32(buf); /* 172: ORDT1 offset; currently not used */
            ordt2_offset = mobi_buffer_get32(buf); /* 176: ORDT2 offset */
            const size_t entry_size = (ordt_type == 0) ? 1 : 2;
            if (ordt1_offset + entry_size * ordt_entries_count > indx_record->size
                || ordt2_offset + 2 * ordt_entries_count > indx_record->size) {
                ordt1_offset = 0;
                ordt2_offset = 0;
                ordt_entries_count = 0;
            }
            index_name_offset = mobi_buffer_get32(buf); /* 180: Index name offset */
            index_name_length = mobi_buffer_get32(buf); /* 184: Index name length */
        }
        buf->maxlen = indx_record->size;
        mobi_buffer_setpos(buf, header_length);
        ret = mobi_parse_tagx(buf, tagx);
        if (ret != MOBI_SUCCESS) {
            mobi_buffer_free_null(buf);
            return ret;
        }
        if (ordt_entries_count > 0) {
            /* parse ORDT sections */
            ordt->offsets_count = ordt_entries_count;
            ordt->type = ordt_type;
            ordt->ordt1_pos = ordt1_offset;
            ordt->ordt2_pos = ordt2_offset;
            ret = mobi_parse_ordt(buf, ordt);
            debug_print("ORDT: %u, %u, %u, %u\n", ordt_type, ordt_entries_count, ordt1_offset, ordt2_offset);
            if (ret != MOBI_SUCCESS) {
                mobi_buffer_free_null(buf);
                return ret;
            }
        }
        if (index_name_offset > 0 && index_name_length > 0) {
            if (index_name_length <= header_length - index_name_offset && index_name_length < INDX_NAME_SIZEMAX) {
                mobi_buffer_setpos(buf, index_name_offset);
                char *name = malloc(index_name_length + 1);
                if (name == NULL) {
                    debug_print("%s", "Memory allocation failed\n");
                    mobi_buffer_free_null(buf);
                    return MOBI_MALLOC_FAILED;
                }
                mobi_buffer_getstring(name, buf, index_name_length);
                indx->orth_index_name = name;
                debug_print("Orth index name: %s\n", name);
            }
        }
        indx->encoding = encoding;
        indx->type = type;
        indx->entries_count = entries_count;
        indx->total_entries_count = total_entries_count;
        if (ligt_entries_count != 0 && !mobi_buffer_match_magic_offset(buf, LIGT_MAGIC, ligt_offset)) {
            ligt_offset = 0;
            ligt_entries_count = 0;
        }
        indx->ligt_offset = ligt_offset;
        indx->ligt_entries_count = ligt_entries_count;
        indx->ordt_offset = ordt_offset;
        indx->cncx_records_count = cncx_records_count;
    } else {
        /* else parse IDXT entries offsets */
        if (idxt_offset == 0) {
            debug_print("%s", "Missing IDXT offset\n");
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        if (idxt_offset + 2 * entries_count + 4 > indx_record->size ) {
            debug_print("IDXT entries beyond record end%s", "\n");
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        mobi_buffer_setpos(buf, idxt_offset);
        MOBIIdxt idxt;
        uint32_t *offsets = malloc((entries_count + 1) * sizeof(uint32_t));
        if (offsets == NULL) {
            mobi_buffer_free_null(buf);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        idxt.offsets = offsets;
        ret = mobi_parse_idxt(buf, &idxt, entries_count);
        if (ret != MOBI_SUCCESS) {
            debug_print("%s", "IDXT parsing failed\n");
            mobi_buffer_free_null(buf);
            free(offsets);
            return ret;
        }
        /* parse entries */
        if (entries_count > 0) {
            if (indx->entries == NULL) {
                indx->entries = malloc(indx->total_entries_count * sizeof(MOBIIndexEntry));
                if (indx->entries == NULL) {
                    mobi_buffer_free_null(buf);
                    free(offsets);
                    debug_print("%s\n", "Memory allocation failed");
                    return MOBI_MALLOC_FAILED;
                }
            }
            size_t i = 0;
            while (i < entries_count) {
                ret = mobi_parse_index_entry(indx, idxt, tagx, ordt, buf, i++);
                if (ret != MOBI_SUCCESS) {
                    mobi_buffer_free_null(buf);
                    free(offsets);
                    return ret;
                }
            }
            indx->entries_count += entries_count;
        }
        free(offsets);
    }
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}


Check range of integer values in PPM text file

Add checks to ensure values are within the specified range.

Fixes mozilla/mozjpeg#141, closes #8

get_text_gray_row (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
/* This version is for reading text-format PGM files with any maxval */
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  FILE * infile = source->pub.input_file;
  register JSAMPROW ptr;
  register JSAMPLE *rescale = source->rescale;
  JDIMENSION col;
  int maxval = source->maxval;

  ptr = source->pub.buffer[0];
  for (col = cinfo->image_width; col > 0; col--) {
    *ptr++ = rescale[read_pbm_integer(cinfo, infile, maxval)];
  }
  return 1;
}

An issue was discovered in libjpeg 9a. The get_text_gray_row function in rdppm.c allows remote attackers to cause a denial of service (Segmentation fault) via a crafted file.


get_word_gray_row (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
/* This version is for reading raw-word-format PGM files with any maxval */
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  register JSAMPROW ptr;
  register U_CHAR * bufferptr;
  register JSAMPLE *rescale = source->rescale;
  JDIMENSION col;

  if (! ReadOK(source->pub.input_file, source->iobuffer, source->buffer_width))
    ERREXIT(cinfo, JERR_INPUT_EOF);
  ptr = source->pub.buffer[0];
  bufferptr = source->iobuffer;
  for (col = cinfo->image_width; col > 0; col--) {
    register int temp;
    temp  = UCH(*bufferptr++) << 8;
    temp |= UCH(*bufferptr++);
    *ptr++ = rescale[temp];
  }
  return 1;
}


get_word_rgb_row (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
/* This version is for reading raw-word-format PPM files with any maxval */
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  register JSAMPROW ptr;
  register U_CHAR * bufferptr;
  register JSAMPLE *rescale = source->rescale;
  JDIMENSION col;

  if (! ReadOK(source->pub.input_file, source->iobuffer, source->buffer_width))
    ERREXIT(cinfo, JERR_INPUT_EOF);
  ptr = source->pub.buffer[0];
  bufferptr = source->iobuffer;
  for (col = cinfo->image_width; col > 0; col--) {
    register int temp;
    temp  = UCH(*bufferptr++) << 8;
    temp |= UCH(*bufferptr++);
    *ptr++ = rescale[temp];
    temp  = UCH(*bufferptr++) << 8;
    temp |= UCH(*bufferptr++);
    *ptr++ = rescale[temp];
    temp  = UCH(*bufferptr++) << 8;
    temp |= UCH(*bufferptr++);
    *ptr++ = rescale[temp];
  }
  return 1;
}


start_input_ppm (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  int c;
  unsigned int w, h, maxval;
  boolean need_iobuffer, use_raw_buffer, need_rescale;

  if (getc(source->pub.input_file) != 'P')
    ERREXIT(cinfo, JERR_PPM_NOT);

  c = getc(source->pub.input_file); /* subformat discriminator character */

  /* detect unsupported variants (ie, PBM) before trying to read header */
  switch (c) {
  case '2':                     /* it's a text-format PGM file */
  case '3':                     /* it's a text-format PPM file */
  case '5':                     /* it's a raw-format PGM file */
  case '6':                     /* it's a raw-format PPM file */
    break;
  default:
    ERREXIT(cinfo, JERR_PPM_NOT);
    break;
  }

  /* fetch the remaining header info */
  w = read_pbm_integer(cinfo, source->pub.input_file, 65535);
  h = read_pbm_integer(cinfo, source->pub.input_file, 65535);
  maxval = read_pbm_integer(cinfo, source->pub.input_file, 65535);

  if (w <= 0 || h <= 0 || maxval <= 0) /* error check */
    ERREXIT(cinfo, JERR_PPM_NOT);

  cinfo->data_precision = BITS_IN_JSAMPLE; /* we always rescale data to this */
  cinfo->image_width = (JDIMENSION) w;
  cinfo->image_height = (JDIMENSION) h;
  source->maxval = maxval;

  /* initialize flags to most common settings */
  need_iobuffer = TRUE;         /* do we need an I/O buffer? */
  use_raw_buffer = FALSE;       /* do we map input buffer onto I/O buffer? */
  need_rescale = TRUE;          /* do we need a rescale array? */

  switch (c) {
  case '2':                     /* it's a text-format PGM file */
    cinfo->input_components = 1;
    cinfo->in_color_space = JCS_GRAYSCALE;
    TRACEMS2(cinfo, 1, JTRC_PGM_TEXT, w, h);
    source->pub.get_pixel_rows = get_text_gray_row;
    need_iobuffer = FALSE;
    break;

  case '3':                     /* it's a text-format PPM file */
    cinfo->input_components = 3;
    cinfo->in_color_space = JCS_RGB;
    TRACEMS2(cinfo, 1, JTRC_PPM_TEXT, w, h);
    source->pub.get_pixel_rows = get_text_rgb_row;
    need_iobuffer = FALSE;
    break;

  case '5':                     /* it's a raw-format PGM file */
    cinfo->input_components = 1;
    cinfo->in_color_space = JCS_GRAYSCALE;
    TRACEMS2(cinfo, 1, JTRC_PGM, w, h);
    if (maxval > 255) {
      source->pub.get_pixel_rows = get_word_gray_row;
    } else if (maxval == MAXJSAMPLE && sizeof(JSAMPLE) == sizeof(U_CHAR)) {
      source->pub.get_pixel_rows = get_raw_row;
      use_raw_buffer = TRUE;
      need_rescale = FALSE;
    } else {
      source->pub.get_pixel_rows = get_scaled_gray_row;
    }
    break;

  case '6':                     /* it's a raw-format PPM file */
    cinfo->input_components = 3;
    cinfo->in_color_space = JCS_RGB;
    TRACEMS2(cinfo, 1, JTRC_PPM, w, h);
    if (maxval > 255) {
      source->pub.get_pixel_rows = get_word_rgb_row;
    } else if (maxval == MAXJSAMPLE && sizeof(JSAMPLE) == sizeof(U_CHAR)) {
      source->pub.get_pixel_rows = get_raw_row;
      use_raw_buffer = TRUE;
      need_rescale = FALSE;
    } else {
      source->pub.get_pixel_rows = get_scaled_rgb_row;
    }
    break;
  }

  /* Allocate space for I/O buffer: 1 or 3 bytes or words/pixel. */
  if (need_iobuffer) {
    source->buffer_width = (size_t) w * cinfo->input_components *
      ((maxval<=255) ? sizeof(U_CHAR) : (2*sizeof(U_CHAR)));
    source->iobuffer = (U_CHAR *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
                                  source->buffer_width);
  }

  /* Create compressor input buffer. */
  if (use_raw_buffer) {
    /* For unscaled raw-input case, we can just map it onto the I/O buffer. */
    /* Synthesize a JSAMPARRAY pointer structure */
    source->pixrow = (JSAMPROW) source->iobuffer;
    source->pub.buffer = & source->pixrow;
    source->pub.buffer_height = 1;
  } else {
    /* Need to translate anyway, so make a separate sample buffer. */
    source->pub.buffer = (*cinfo->mem->alloc_sarray)
      ((j_common_ptr) cinfo, JPOOL_IMAGE,
       (JDIMENSION) w * cinfo->input_components, (JDIMENSION) 1);
    source->pub.buffer_height = 1;
  }

  /* Compute the rescaling array if required. */
  if (need_rescale) {
    INT32 val, half_maxval;

    /* On 16-bit-int machines we have to be careful of maxval = 65535 */
    source->rescale = (JSAMPLE *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
                                  (size_t) (((long) maxval + 1L) * sizeof(JSAMPLE)));
    half_maxval = maxval / 2;
    for (val = 0; val <= (INT32) maxval; val++) {
      /* The multiplication here must be done in 32 bits to avoid overflow */
      source->rescale[val] = (JSAMPLE) ((val*MAXJSAMPLE + half_maxval)/maxval);
    }
  }
}


get_text_rgb_row (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
/* This version is for reading text-format PPM files with any maxval */
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  FILE * infile = source->pub.input_file;
  register JSAMPROW ptr;
  register JSAMPLE *rescale = source->rescale;
  JDIMENSION col;
  int maxval = source->maxval;

  ptr = source->pub.buffer[0];
  for (col = cinfo->image_width; col > 0; col--) {
    *ptr++ = rescale[read_pbm_integer(cinfo, infile, maxval)];
    *ptr++ = rescale[read_pbm_integer(cinfo, infile, maxval)];
    *ptr++ = rescale[read_pbm_integer(cinfo, infile, maxval)];
  }
  return 1;
}


get_scaled_rgb_row (j_compress_ptr cinfo, cjpeg_source_ptr sinfo)
/* This version is for reading raw-byte-format PPM files with any maxval */
{
  ppm_source_ptr source = (ppm_source_ptr) sinfo;
  register JSAMPROW ptr;
  register U_CHAR * bufferptr;
  register JSAMPLE *rescale = source->rescale;
  JDIMENSION col;

  if (! ReadOK(source->pub.input_file, source->iobuffer, source->buffer_width))
    ERREXIT(cinfo, JERR_INPUT_EOF);
  ptr = source->pub.buffer[0];
  bufferptr = source->iobuffer;
  for (col = cinfo->image_width; col > 0; col--) {
    *ptr++ = rescale[UCH(*bufferptr++)];
    *ptr++ = rescale[UCH(*bufferptr++)];
    *ptr++ = rescale[UCH(*bufferptr++)];
  }
  return 1;
}


read_pbm_integer (j_compress_ptr cinfo, FILE * infile, int maxval)
/* Read an unsigned decimal integer from the PPM file */
/* Swallows one trailing character after the integer */
/* Note that on a 16-bit-int machine, only values up to 64k can be read. */
/* This should not be a problem in practice. */
{
  register int ch;
  register unsigned int val;

  /* Skip any leading whitespace */
  do {
    ch = pbm_getc(infile);
    if (ch == EOF)
      ERREXIT(cinfo, JERR_INPUT_EOF);
  } while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');

  if (ch < '0' || ch > '9')
    ERREXIT(cinfo, JERR_PPM_NONNUMERIC);

  val = ch - '0';
  while ((ch = pbm_getc(infile)) >= '0' && ch <= '9') {
    val *= 10;
    val += ch - '0';
  }

  if (val > maxval)
    ERREXIT(cinfo, JERR_PPM_TOOLARGE);

  return val;
}


patch 8.2.5122: lisp indenting my run over the end of the line

Problem:    Lisp indenting my run over the end of the line.
Solution:   Check for NUL earlier.

ins_try_si(int c)
{
    pos_T	*pos, old_pos;
    char_u	*ptr;
    int		i;
    int		temp;

    // do some very smart indenting when entering '{' or '}'
    if (((did_si || can_si_back) && c == '{')
	    || (can_si && c == '}' && inindent(0)))
    {
	// for '}' set indent equal to indent of line containing matching '{'
	if (c == '}' && (pos = findmatch(NULL, '{')) != NULL)
	{
	    old_pos = curwin->w_cursor;
	    // If the matching '{' has a ')' immediately before it (ignoring
	    // white-space), then line up with the start of the line
	    // containing the matching '(' if there is one.  This handles the
	    // case where an "if (..\n..) {" statement continues over multiple
	    // lines -- webb
	    ptr = ml_get(pos->lnum);
	    i = pos->col;
	    if (i > 0)		// skip blanks before '{'
		while (--i > 0 && VIM_ISWHITE(ptr[i]))
		    ;
	    curwin->w_cursor.lnum = pos->lnum;
	    curwin->w_cursor.col = i;
	    if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL)
		curwin->w_cursor = *pos;
	    i = get_indent();
	    curwin->w_cursor = old_pos;
	    if (State & VREPLACE_FLAG)
		change_indent(INDENT_SET, i, FALSE, NUL, TRUE);
	    else
		(void)set_indent(i, SIN_CHANGED);
	}
	else if (curwin->w_cursor.col > 0)
	{
	    // when inserting '{' after "O" reduce indent, but not
	    // more than indent of previous line
	    temp = TRUE;
	    if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1)
	    {
		old_pos = curwin->w_cursor;
		i = get_indent();
		while (curwin->w_cursor.lnum > 1)
		{
		    ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum)));

		    // ignore empty lines and lines starting with '#'.
		    if (*ptr != '#' && *ptr != NUL)
			break;
		}
		if (get_indent() >= i)
		    temp = FALSE;
		curwin->w_cursor = old_pos;
	    }
	    if (temp)
		shift_line(TRUE, FALSE, 1, TRUE);
	}
    }

    // set indent of '#' always to 0
    if (curwin->w_cursor.col > 0 && can_si && c == '#' && inindent(0))
    {
	// remember current indent for next line
	old_indent = get_indent();
	(void)set_indent(0, SIN_CHANGED);
    }

    // Adjust ai_col, the char at this position can be deleted.
    if (ai_col > curwin->w_cursor.col)
	ai_col = curwin->w_cursor.col;
}

Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.


get_expr_indent(void)
{
    int		indent = -1;
    char_u	*inde_copy;
    pos_T	save_pos;
    colnr_T	save_curswant;
    int		save_set_curswant;
    int		save_State;
    int		use_sandbox = was_set_insecurely((char_u *)"indentexpr",
								   OPT_LOCAL);
    sctx_T	save_sctx = current_sctx;

    // Save and restore cursor position and curswant, in case it was changed
    // via :normal commands
    save_pos = curwin->w_cursor;
    save_curswant = curwin->w_curswant;
    save_set_curswant = curwin->w_set_curswant;
    set_vim_var_nr(VV_LNUM, curwin->w_cursor.lnum);
    if (use_sandbox)
	++sandbox;
    ++textlock;
    current_sctx = curbuf->b_p_script_ctx[BV_INDE];

    // Need to make a copy, the 'indentexpr' option could be changed while
    // evaluating it.
    inde_copy = vim_strsave(curbuf->b_p_inde);
    if (inde_copy != NULL)
    {
	indent = (int)eval_to_number(inde_copy);
	vim_free(inde_copy);
    }

    if (use_sandbox)
	--sandbox;
    --textlock;
    current_sctx = save_sctx;

    // Restore the cursor position so that 'indentexpr' doesn't need to.
    // Pretend to be in Insert mode, allow cursor past end of line for "o"
    // command.
    save_State = State;
    State = MODE_INSERT;
    curwin->w_cursor = save_pos;
    curwin->w_curswant = save_curswant;
    curwin->w_set_curswant = save_set_curswant;
    check_cursor();
    State = save_State;

    // Reset did_throw, unless 'debug' has "throw" and inside a try/catch.
    if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0))
    {
	handle_did_throw();
	did_throw = FALSE;
    }

    // If there is an error, just keep the current indent.
    if (indent < 0)
	indent = get_indent();

    return indent;
}


f_indent(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
	rettv->vval.v_number = get_indent_lnum(lnum);
    else
    {
	if (in_vim9script())
	    semsg(_(e_invalid_line_number_nr), lnum);
	rettv->vval.v_number = -1;
    }
}


get_indent_buf(buf_T *buf, linenr_T lnum)
{
# ifdef FEAT_VARTABS
    return get_indent_str_vtab(ml_get_buf(buf, lnum, FALSE),
			       (int)curbuf->b_p_ts, buf->b_p_vts_array, FALSE);
# else
    return get_indent_str(ml_get_buf(buf, lnum, FALSE), (int)buf->b_p_ts, FALSE);
# endif
}


op_reindent(oparg_T *oap, int (*how)(void))
{
    long	i = 0;
    char_u	*l;
    int		amount;
    linenr_T	first_changed = 0;
    linenr_T	last_changed = 0;
    linenr_T	start_lnum = curwin->w_cursor.lnum;

    // Don't even try when 'modifiable' is off.
    if (!curbuf->b_p_ma)
    {
	emsg(_(e_cannot_make_changes_modifiable_is_off));
	return;
    }

    // Save for undo.  Do this once for all lines, much faster than doing this
    // for each line separately, especially when undoing.
    if (u_savecommon(start_lnum - 1, start_lnum + oap->line_count,
				     start_lnum + oap->line_count, FALSE) == OK)
	for (i = oap->line_count; --i >= 0 && !got_int; )
	{
	    // it's a slow thing to do, so give feedback so there's no worry
	    // that the computer's just hung.

	    if (i > 1
		    && (i % 50 == 0 || i == oap->line_count - 1)
		    && oap->line_count > p_report)
		smsg(_("%ld lines to indent... "), i);

	    // Be vi-compatible: For lisp indenting the first line is not
	    // indented, unless there is only one line.
	    if (i != oap->line_count - 1 || oap->line_count == 1
						     || how != get_lisp_indent)
	    {
		l = skipwhite(ml_get_curline());
		if (*l == NUL)		    // empty or blank line
		    amount = 0;
		else
		    amount = how();	    // get the indent for this line

		if (amount >= 0 && set_indent(amount, 0))
		{
		    // did change the indent, call changed_lines() later
		    if (first_changed == 0)
			first_changed = curwin->w_cursor.lnum;
		    last_changed = curwin->w_cursor.lnum;
		}
	    }
	    ++curwin->w_cursor.lnum;
	    curwin->w_cursor.col = 0;  // make sure it's valid
	}

    // put cursor on first non-blank of indented line
    curwin->w_cursor.lnum = start_lnum;
    beginline(BL_SOL | BL_FIX);

    // Mark changed lines so that they will be redrawn.  When Visual
    // highlighting was present, need to continue until the last line.  When
    // there is no change still need to remove the Visual highlighting.
    if (last_changed != 0)
	changed_lines(first_changed, 0,
		oap->is_VIsual ? start_lnum + oap->line_count :
		last_changed + 1, 0L);
    else if (oap->is_VIsual)
	redraw_curbuf_later(INVERTED);

    if (oap->line_count > p_report)
    {
	i = oap->line_count - (i + 1);
	smsg(NGETTEXT("%ld line indented ",
						 "%ld lines indented ", i), i);
    }
    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
    {
	// set '[ and '] marks
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;
    }
}


f_lispindent(typval_T *argvars UNUSED, typval_T *rettv)
{
    pos_T	pos;
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    pos = curwin->w_cursor;
    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
    {
	curwin->w_cursor.lnum = lnum;
	rettv->vval.v_number = get_lisp_indent();
	curwin->w_cursor = pos;
    }
    else if (in_vim9script())
	semsg(_(e_invalid_line_number_nr), lnum);
    else
	rettv->vval.v_number = -1;
}


fix_indent(void)
{
    if (p_paste)
	return;
    if (curbuf->b_p_lisp && curbuf->b_p_ai)
	fixthisline(get_lisp_indent);
    else
	if (cindent_on())
	    do_c_expr_indent();
}


inindent(int extra)
{
    char_u	*ptr;
    colnr_T	col;

    for (col = 0, ptr = ml_get_curline(); VIM_ISWHITE(*ptr); ++col)
	++ptr;
    if (col >= curwin->w_cursor.col + extra)
	return TRUE;
    else
	return FALSE;
}


get_indent_lnum(linenr_T lnum)
{
#ifdef FEAT_VARTABS
    return get_indent_str_vtab(ml_get(lnum), (int)curbuf->b_p_ts,
						 curbuf->b_p_vts_array, FALSE);
#else
    return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, FALSE);
#endif
}


set_indent(
    int		size,		    // measured in spaces
    int		flags)
{
    char_u	*p;
    char_u	*newline;
    char_u	*oldline;
    char_u	*s;
    int		todo;
    int		ind_len;	    // measured in characters
    int		line_len;
    int		doit = FALSE;
    int		ind_done = 0;	    // measured in spaces
#ifdef FEAT_VARTABS
    int		ind_col = 0;
#endif
    int		tab_pad;
    int		retval = FALSE;
    int		orig_char_len = -1; // number of initial whitespace chars when
				    // 'et' and 'pi' are both set

    // First check if there is anything to do and compute the number of
    // characters needed for the indent.
    todo = size;
    ind_len = 0;
    p = oldline = ml_get_curline();

    // Calculate the buffer size for the new indent, and check to see if it
    // isn't already set

    // if 'expandtab' isn't set: use TABs; if both 'expandtab' and
    // 'preserveindent' are set count the number of characters at the
    // beginning of the line to be copied
    if (!curbuf->b_p_et || (!(flags & SIN_INSERT) && curbuf->b_p_pi))
    {
	// If 'preserveindent' is set then reuse as much as possible of
	// the existing indent structure for the new indent
	if (!(flags & SIN_INSERT) && curbuf->b_p_pi)
	{
	    ind_done = 0;

	    // count as many characters as we can use
	    while (todo > 0 && VIM_ISWHITE(*p))
	    {
		if (*p == TAB)
		{
#ifdef FEAT_VARTABS
		    tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
							curbuf->b_p_vts_array);
#else
		    tab_pad = (int)curbuf->b_p_ts
					   - (ind_done % (int)curbuf->b_p_ts);
#endif
		    // stop if this tab will overshoot the target
		    if (todo < tab_pad)
			break;
		    todo -= tab_pad;
		    ++ind_len;
		    ind_done += tab_pad;
		}
		else
		{
		    --todo;
		    ++ind_len;
		    ++ind_done;
		}
		++p;
	    }

#ifdef FEAT_VARTABS
	    // These diverge from this point.
	    ind_col = ind_done;
#endif
	    // Set initial number of whitespace chars to copy if we are
	    // preserving indent but expandtab is set
	    if (curbuf->b_p_et)
		orig_char_len = ind_len;

	    // Fill to next tabstop with a tab, if possible
#ifdef FEAT_VARTABS
	    tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
						curbuf->b_p_vts_array);
#else
	    tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
#endif
	    if (todo >= tab_pad && orig_char_len == -1)
	    {
		doit = TRUE;
		todo -= tab_pad;
		++ind_len;
		// ind_done += tab_pad;
#ifdef FEAT_VARTABS
		ind_col += tab_pad;
#endif
	    }
	}

	// count tabs required for indent
#ifdef FEAT_VARTABS
	for (;;)
	{
	    tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts,
							curbuf->b_p_vts_array);
	    if (todo < tab_pad)
		break;
	    if (*p != TAB)
		doit = TRUE;
	    else
		++p;
	    todo -= tab_pad;
	    ++ind_len;
	    ind_col += tab_pad;
	}
#else
	while (todo >= (int)curbuf->b_p_ts)
	{
	    if (*p != TAB)
		doit = TRUE;
	    else
		++p;
	    todo -= (int)curbuf->b_p_ts;
	    ++ind_len;
	    // ind_done += (int)curbuf->b_p_ts;
	}
#endif
    }
    // count spaces required for indent
    while (todo > 0)
    {
	if (*p != ' ')
	    doit = TRUE;
	else
	    ++p;
	--todo;
	++ind_len;
	// ++ind_done;
    }

    // Return if the indent is OK already.
    if (!doit && !VIM_ISWHITE(*p) && !(flags & SIN_INSERT))
	return FALSE;

    // Allocate memory for the new line.
    if (flags & SIN_INSERT)
	p = oldline;
    else
	p = skipwhite(p);
    line_len = (int)STRLEN(p) + 1;

    // If 'preserveindent' and 'expandtab' are both set keep the original
    // characters and allocate accordingly.  We will fill the rest with spaces
    // after the if (!curbuf->b_p_et) below.
    if (orig_char_len != -1)
    {
	newline = alloc(orig_char_len + size - ind_done + line_len);
	if (newline == NULL)
	    return FALSE;
	todo = size - ind_done;
	ind_len = orig_char_len + todo;    // Set total length of indent in
					   // characters, which may have been
					   // undercounted until now
	p = oldline;
	s = newline;
	while (orig_char_len > 0)
	{
	    *s++ = *p++;
	    orig_char_len--;
	}

	// Skip over any additional white space (useful when newindent is less
	// than old)
	while (VIM_ISWHITE(*p))
	    ++p;

    }
    else
    {
	todo = size;
	newline = alloc(ind_len + line_len);
	if (newline == NULL)
	    return FALSE;
	s = newline;
    }

    // Put the characters in the new line.
    // if 'expandtab' isn't set: use TABs
    if (!curbuf->b_p_et)
    {
	// If 'preserveindent' is set then reuse as much as possible of
	// the existing indent structure for the new indent
	if (!(flags & SIN_INSERT) && curbuf->b_p_pi)
	{
	    p = oldline;
	    ind_done = 0;

	    while (todo > 0 && VIM_ISWHITE(*p))
	    {
		if (*p == TAB)
		{
#ifdef FEAT_VARTABS
		    tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
							curbuf->b_p_vts_array);
#else
		    tab_pad = (int)curbuf->b_p_ts
					   - (ind_done % (int)curbuf->b_p_ts);
#endif
		    // stop if this tab will overshoot the target
		    if (todo < tab_pad)
			break;
		    todo -= tab_pad;
		    ind_done += tab_pad;
		}
		else
		{
		    --todo;
		    ++ind_done;
		}
		*s++ = *p++;
	    }

	    // Fill to next tabstop with a tab, if possible
#ifdef FEAT_VARTABS
	    tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
						curbuf->b_p_vts_array);
#else
	    tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
#endif
	    if (todo >= tab_pad)
	    {
		*s++ = TAB;
		todo -= tab_pad;
#ifdef FEAT_VARTABS
		ind_done += tab_pad;
#endif
	    }

	    p = skipwhite(p);
	}

#ifdef FEAT_VARTABS
	for (;;)
	{
	    tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
							curbuf->b_p_vts_array);
	    if (todo < tab_pad)
		break;
	    *s++ = TAB;
	    todo -= tab_pad;
	    ind_done += tab_pad;
	}
#else
	while (todo >= (int)curbuf->b_p_ts)
	{
	    *s++ = TAB;
	    todo -= (int)curbuf->b_p_ts;
	}
#endif
    }
    while (todo > 0)
    {
	*s++ = ' ';
	--todo;
    }
    mch_memmove(s, p, (size_t)line_len);

    // Replace the line (unless undo fails).
    if (!(flags & SIN_UNDO) || u_savesub(curwin->w_cursor.lnum) == OK)
    {
	colnr_T old_offset = (colnr_T)(p - oldline);
	colnr_T new_offset = (colnr_T)(s - newline);

	// this may free "newline"
	ml_replace(curwin->w_cursor.lnum, newline, FALSE);
	if (flags & SIN_CHANGED)
	    changed_bytes(curwin->w_cursor.lnum, 0);

	// Correct saved cursor position if it is in this line.
	if (saved_cursor.lnum == curwin->w_cursor.lnum)
	{
	    if (saved_cursor.col >= old_offset)
		// cursor was after the indent, adjust for the number of
		// bytes added/removed
		saved_cursor.col += ind_len - old_offset;
	    else if (saved_cursor.col >= new_offset)
		// cursor was in the indent, and is now after it, put it back
		// at the start of the indent (replacing spaces with TAB)
		saved_cursor.col = new_offset;
	}
#ifdef FEAT_PROP_POPUP
	{
	    int added = ind_len - old_offset;

	    // When increasing indent this behaves like spaces were inserted at
	    // the old indent, when decreasing indent it behaves like spaces
	    // were deleted at the new indent.
	    adjust_prop_columns(curwin->w_cursor.lnum,
			  added > 0 ? old_offset : (colnr_T)ind_len, added, 0);
	}
#endif
	retval = TRUE;
    }
    else
	vim_free(newline);

    curwin->w_cursor.col = ind_len;
    return retval;
}


change_indent(
    int		type,
    int		amount,
    int		round,
    int		replaced,	// replaced character, put on replace stack
    int		call_changed_bytes)	// call changed_bytes()
{
    int		vcol;
    int		last_vcol;
    int		insstart_less;		// reduction for Insstart.col
    int		new_cursor_col;
    int		i;
    char_u	*ptr;
    int		save_p_list;
    int		start_col;
    colnr_T	vc;
    colnr_T	orig_col = 0;		// init for GCC
    char_u	*new_line, *orig_line = NULL;	// init for GCC

    // MODE_VREPLACE state needs to know what the line was like before changing
    if (State & VREPLACE_FLAG)
    {
	orig_line = vim_strsave(ml_get_curline());  // Deal with NULL below
	orig_col = curwin->w_cursor.col;
    }

    // for the following tricks we don't want list mode
    save_p_list = curwin->w_p_list;
    curwin->w_p_list = FALSE;
    vc = getvcol_nolist(&curwin->w_cursor);
    vcol = vc;

    // For Replace mode we need to fix the replace stack later, which is only
    // possible when the cursor is in the indent.  Remember the number of
    // characters before the cursor if it's possible.
    start_col = curwin->w_cursor.col;

    // determine offset from first non-blank
    new_cursor_col = curwin->w_cursor.col;
    beginline(BL_WHITE);
    new_cursor_col -= curwin->w_cursor.col;

    insstart_less = curwin->w_cursor.col;

    // If the cursor is in the indent, compute how many screen columns the
    // cursor is to the left of the first non-blank.
    if (new_cursor_col < 0)
	vcol = get_indent() - vcol;

    if (new_cursor_col > 0)	    // can't fix replace stack
	start_col = -1;

    // Set the new indent.  The cursor will be put on the first non-blank.
    if (type == INDENT_SET)
	(void)set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0);
    else
    {
	int	save_State = State;

	// Avoid being called recursively.
	if (State & VREPLACE_FLAG)
	    State = MODE_INSERT;
	shift_line(type == INDENT_DEC, round, 1, call_changed_bytes);
	State = save_State;
    }
    insstart_less -= curwin->w_cursor.col;

    // Try to put cursor on same character.
    // If the cursor is at or after the first non-blank in the line,
    // compute the cursor column relative to the column of the first
    // non-blank character.
    // If we are not in insert mode, leave the cursor on the first non-blank.
    // If the cursor is before the first non-blank, position it relative
    // to the first non-blank, counted in screen columns.
    if (new_cursor_col >= 0)
    {
	// When changing the indent while the cursor is touching it, reset
	// Insstart_col to 0.
	if (new_cursor_col == 0)
	    insstart_less = MAXCOL;
	new_cursor_col += curwin->w_cursor.col;
    }
    else if (!(State & MODE_INSERT))
	new_cursor_col = curwin->w_cursor.col;
    else
    {
	// Compute the screen column where the cursor should be.
	vcol = get_indent() - vcol;
	curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol);

	// Advance the cursor until we reach the right screen column.
	vcol = last_vcol = 0;
	new_cursor_col = -1;
	ptr = ml_get_curline();
	while (vcol <= (int)curwin->w_virtcol)
	{
	    last_vcol = vcol;
	    if (has_mbyte && new_cursor_col >= 0)
		new_cursor_col += (*mb_ptr2len)(ptr + new_cursor_col);
	    else
		++new_cursor_col;
	    if (ptr[new_cursor_col] == NUL)
		break;
	    vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol);
	}
	vcol = last_vcol;

	// May need to insert spaces to be able to position the cursor on
	// the right screen column.
	if (vcol != (int)curwin->w_virtcol)
	{
	    curwin->w_cursor.col = (colnr_T)new_cursor_col;
	    i = (int)curwin->w_virtcol - vcol;
	    ptr = alloc(i + 1);
	    if (ptr != NULL)
	    {
		new_cursor_col += i;
		ptr[i] = NUL;
		while (--i >= 0)
		    ptr[i] = ' ';
		ins_str(ptr);
		vim_free(ptr);
	    }
	}

	// When changing the indent while the cursor is in it, reset
	// Insstart_col to 0.
	insstart_less = MAXCOL;
    }

    curwin->w_p_list = save_p_list;

    if (new_cursor_col <= 0)
	curwin->w_cursor.col = 0;
    else
	curwin->w_cursor.col = (colnr_T)new_cursor_col;
    curwin->w_set_curswant = TRUE;
    changed_cline_bef_curs();

    // May have to adjust the start of the insert.
    if (State & MODE_INSERT)
    {
	if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0)
	{
	    if ((int)Insstart.col <= insstart_less)
		Insstart.col = 0;
	    else
		Insstart.col -= insstart_less;
	}
	if ((int)ai_col <= insstart_less)
	    ai_col = 0;
	else
	    ai_col -= insstart_less;
    }

    // For MODE_REPLACE state, may have to fix the replace stack, if it's
    // possible.  If the number of characters before the cursor decreased, need
    // to pop a few characters from the replace stack.
    // If the number of characters before the cursor increased, need to push a
    // few NULs onto the replace stack.
    if (REPLACE_NORMAL(State) && start_col >= 0)
    {
	while (start_col > (int)curwin->w_cursor.col)
	{
	    replace_join(0);	    // remove a NUL from the replace stack
	    --start_col;
	}
	while (start_col < (int)curwin->w_cursor.col || replaced)
	{
	    replace_push(NUL);
	    if (replaced)
	    {
		replace_push(replaced);
		replaced = NUL;
	    }
	    ++start_col;
	}
    }

    // For MODE_VREPLACE state, we also have to fix the replace stack.  In this
    // case it is always possible because we backspace over the whole line and
    // then put it back again the way we wanted it.
    if (State & VREPLACE_FLAG)
    {
	// If orig_line didn't allocate, just return.  At least we did the job,
	// even if you can't backspace.
	if (orig_line == NULL)
	    return;

	// Save new line
	new_line = vim_strsave(ml_get_curline());
	if (new_line == NULL)
	    return;

	// We only put back the new line up to the cursor
	new_line[curwin->w_cursor.col] = NUL;

	// Put back original line
	ml_replace(curwin->w_cursor.lnum, orig_line, FALSE);
	curwin->w_cursor.col = orig_col;

	// Backspace from cursor to start of line
	backspace_until_column(0);

	// Insert new stuff into line again
	ins_bytes(new_line);

	vim_free(new_line);
    }
}