<commit_before>/**
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
module Shumway.SWF {
  import assert = Shumway.Debug.assert;

  import Parser = Shumway.SWF.Parser;
  import Stream = SWF.Stream;
  import IDataDecoder = ArrayUtilities.IDataDecoder;
  import Inflate = ArrayUtilities.Inflate;
  import LzmaDecoder = ArrayUtilities.LzmaDecoder;

  import SWFTag = Parser.SwfTag;

  import DefinitionTags = Parser.DefinitionTags;
  import ImageDefinitionTags = Parser.ImageDefinitionTags;
  import FontDefinitionTags = Parser.FontDefinitionTags;
  import ImageDefinition = Parser.ImageDefinition;
  import ControlTags = Parser.ControlTags;

  export class SWFFile {
    isCompressed: boolean;
    swfVersion: number;
    useAVM1: boolean;
    backgroundColor: number;
    bounds: Bounds;
    frameRate: number;
    frameCount: number;
    attributes: any; // TODO: type strongly
    sceneAndFrameLabelData: any; // TODO: type strongly

    bytesLoaded: number;
    bytesTotal: number;
    pendingUpdateDelays: number;
    // Might be lower than frames.length if eagerly parsed assets pending resolution are blocking
    // us from reporting the given frame as loaded.
    framesLoaded: number;

    frames: SWFFrame[];
    abcBlocks: ABCBlock[];
    dictionary: DictionaryEntry[];
    fonts: {name: string; style: string; id: number}[];
    data: Uint8Array;

    symbolClassesMap: string[];
    symbolClassesList: {id: number; className: string}[];
    eagerlyParsedSymbolsMap: EagerlyParsedDictionaryEntry[];
    eagerlyParsedSymbolsList: EagerlyParsedDictionaryEntry[];

    private _uncompressedLength: number;
    private _uncompressedLoadedLength: number;
    private _dataView: DataView;
    private _dataStream: Stream;
    private _decompressor: IDataDecoder;
    private _jpegTables: any;
    private _endTagEncountered: boolean;
    private _loadStarted: number;
    private _lastScanPosition: number;

    private _currentFrameLabel: string;
    private _currentSoundStreamHead: Parser.SoundStream;
    private _currentSoundStreamBlock: Uint8Array;
    private _currentControlTags: UnparsedTag[];
    private _currentActionBlocks: Uint8Array[];
    private _currentInitActionBlocks: InitActionBlock[];
    private _currentExports: SymbolExport[];

    constructor(initialBytes: Uint8Array, length: number) {
      // TODO: cleanly abort loading/parsing instead of just asserting here.
      release || assert(initialBytes[0] === 67 || initialBytes[0] === 70 || initialBytes[0] === 90,
                        "Unsupported compression format: " + initialBytes[0]);
      release || assert(initialBytes[1] === 87);
      release || assert(initialBytes[2] === 83);
      release || assert(initialBytes.length >= 30, "At least the header must be complete here.");

      if (!release && SWF.traceLevel.value > 0) {
        console.log('Create SWFFile');
      }

      this.isCompressed = false;
      this.swfVersion = 0;
      this.useAVM1 = true;
      this.backgroundColor = 0xffffffff;
      this.bounds = null;
      this.frameRate = 0;
      this.frameCount = 0;
      this.attributes = null;
      this.sceneAndFrameLabelData = null;

      this.bytesLoaded = 0;
      this.bytesTotal = length;
      this.pendingUpdateDelays = 0;
      this.framesLoaded = 0;

      this.frames = [];
      this.abcBlocks = [];
      this.dictionary = [];
      this.fonts = [];

      this.symbolClassesMap = [];
      this.symbolClassesList = [];
      this.eagerlyParsedSymbolsMap = [];
      this.eagerlyParsedSymbolsList = [];
      this._jpegTables = null;

      this._currentFrameLabel = null;
      this._currentSoundStreamHead = null;
      this._currentSoundStreamBlock = null;
      this._currentControlTags = null;
      this._currentActionBlocks = null;
      this._currentInitActionBlocks = null;
      this._currentExports = null;
      this._endTagEncountered = false;
      this.readHeaderAndInitialize(initialBytes);
    }

    appendLoadedData(bytes: Uint8Array) {
      // TODO: only report decoded or sync-decodable bytes as loaded.
      this.bytesLoaded += bytes.length;
      release || assert(this.bytesLoaded <= this.bytesTotal);
      // Tags after the end tag are simply ignored, so we don't even have to scan them.
      if (this._endTagEncountered) {
        return;
      }
      if (this.isCompressed) {
        this._decompressor.push(bytes);
      } else {
        this.processDecompressedData(bytes);
      }
      this.scanLoadedData();
    }

    finishLoading() {
      if (this.isCompressed) {
        this._decompressor.close();
        this._decompressor = null;
        this.scanLoadedData();
      }
    }

    getSymbol(id: number) {
      if (this.eagerlyParsedSymbolsMap[id]) {
        return this.eagerlyParsedSymbolsMap[id];
      }
      var unparsed = this.dictionary[id];
      if (!unparsed) {
        return null;
      }
      var symbol;
      if (unparsed.tagCode === SWFTag.CODE_DEFINE_SPRITE) {
        // TODO: replace this whole silly `type` business with tagCode checking.
        symbol = this.parseSpriteTimeline(unparsed);
      } else {
        symbol = this.getParsedTag(unparsed);
      }
      symbol.className = this.symbolClassesMap[id] || null;
      return symbol;
    }

    getParsedTag(unparsed: UnparsedTag): any {
      SWF.enterTimeline('Parse tag ' + SWFTag[unparsed.tagCode]);
      this._dataStream.align();
      this._dataStream.pos = unparsed.byteOffset;
      var tag: any = {code: unparsed.tagCode};
      var handler = Parser.LowLevel.tagHandlers[unparsed.tagCode];
      release || Debug.assert(handler, 'handler shall exists here');
      var tagEnd = unparsed.byteOffset + unparsed.byteLength;
      handler(this.data, this._dataStream, tag, this.swfVersion, unparsed.tagCode, tagEnd);
      var finalPos = this._dataStream.pos;
      release || assert(finalPos <= tagEnd);
      if (finalPos < tagEnd) {
        var consumedBytes = finalPos - unparsed.byteOffset;
        Debug.warning('Scanning ' + SWFTag[unparsed.tagCode] + ' at offset ' +
                      unparsed.byteOffset + ' consumed ' + consumedBytes + ' of ' +
                      unparsed.byteLength + ' bytes. (' + (tagEnd - finalPos) + ' left)');
        this._dataStream.pos = tagEnd;
      }
      var symbol = defineSymbol(tag, this.dictionary);
      SWF.leaveTimeline();
      return symbol;
    }

    private readHeaderAndInitialize(initialBytes: Uint8Array) {
      SWF.enterTimeline('Initialize SWFFile');
      var isDeflateCompressed = initialBytes[0] === 67;
      var isLzmaCompressed = initialBytes[0] === 90;
      this.isCompressed = isDeflateCompressed || isLzmaCompressed;
      this.swfVersion = initialBytes[3];<commit_msg>Be more forgiving when parsing out-of-bound tag data<commit_after>/**
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
module Shumway.SWF {
  import assert = Shumway.Debug.assert;

  import Parser = Shumway.SWF.Parser;
  import Stream = SWF.Stream;
  import IDataDecoder = ArrayUtilities.IDataDecoder;
  import Inflate = ArrayUtilities.Inflate;
  import LzmaDecoder = ArrayUtilities.LzmaDecoder;

  import SWFTag = Parser.SwfTag;

  import DefinitionTags = Parser.DefinitionTags;
  import ImageDefinitionTags = Parser.ImageDefinitionTags;
  import FontDefinitionTags = Parser.FontDefinitionTags;
  import ImageDefinition = Parser.ImageDefinition;
  import ControlTags = Parser.ControlTags;

  export class SWFFile {
    isCompressed: boolean;
    swfVersion: number;
    useAVM1: boolean;
    backgroundColor: number;
    bounds: Bounds;
    frameRate: number;
    frameCount: number;
    attributes: any; // TODO: type strongly
    sceneAndFrameLabelData: any; // TODO: type strongly

    bytesLoaded: number;
    bytesTotal: number;
    pendingUpdateDelays: number;
    // Might be lower than frames.length if eagerly parsed assets pending resolution are blocking
    // us from reporting the given frame as loaded.
    framesLoaded: number;

    frames: SWFFrame[];
    abcBlocks: ABCBlock[];
    dictionary: DictionaryEntry[];
    fonts: {name: string; style: string; id: number}[];
    data: Uint8Array;

    symbolClassesMap: string[];
    symbolClassesList: {id: number; className: string}[];
    eagerlyParsedSymbolsMap: EagerlyParsedDictionaryEntry[];
    eagerlyParsedSymbolsList: EagerlyParsedDictionaryEntry[];

    private _uncompressedLength: number;
    private _uncompressedLoadedLength: number;
    private _dataView: DataView;
    private _dataStream: Stream;
    private _decompressor: IDataDecoder;
    private _jpegTables: any;
    private _endTagEncountered: boolean;
    private _loadStarted: number;
    private _lastScanPosition: number;

    private _currentFrameLabel: string;
    private _currentSoundStreamHead: Parser.SoundStream;
    private _currentSoundStreamBlock: Uint8Array;
    private _currentControlTags: UnparsedTag[];
    private _currentActionBlocks: Uint8Array[];
    private _currentInitActionBlocks: InitActionBlock[];
    private _currentExports: SymbolExport[];

    constructor(initialBytes: Uint8Array, length: number) {
      // TODO: cleanly abort loading/parsing instead of just asserting here.
      release || assert(initialBytes[0] === 67 || initialBytes[0] === 70 || initialBytes[0] === 90,
                        "Unsupported compression format: " + initialBytes[0]);
      release || assert(initialBytes[1] === 87);
      release || assert(initialBytes[2] === 83);
      release || assert(initialBytes.length >= 30, "At least the header must be complete here.");

      if (!release && SWF.traceLevel.value > 0) {
        console.log('Create SWFFile');
      }

      this.isCompressed = false;
      this.swfVersion = 0;
      this.useAVM1 = true;
      this.backgroundColor = 0xffffffff;
      this.bounds = null;
      this.frameRate = 0;
      this.frameCount = 0;
      this.attributes = null;
      this.sceneAndFrameLabelData = null;

      this.bytesLoaded = 0;
      this.bytesTotal = length;
      this.pendingUpdateDelays = 0;
      this.framesLoaded = 0;

      this.frames = [];
      this.abcBlocks = [];
      this.dictionary = [];
      this.fonts = [];

      this.symbolClassesMap = [];
      this.symbolClassesList = [];
      this.eagerlyParsedSymbolsMap = [];
      this.eagerlyParsedSymbolsList = [];
      this._jpegTables = null;

      this._currentFrameLabel = null;
      this._currentSoundStreamHead = null;
      this._currentSoundStreamBlock = null;
      this._currentControlTags = null;
      this._currentActionBlocks = null;
      this._currentInitActionBlocks = null;
      this._currentExports = null;
      this._endTagEncountered = false;
      this.readHeaderAndInitialize(initialBytes);
    }

    appendLoadedData(bytes: Uint8Array) {
      // TODO: only report decoded or sync-decodable bytes as loaded.
      this.bytesLoaded += bytes.length;
      release || assert(this.bytesLoaded <= this.bytesTotal);
      // Tags after the end tag are simply ignored, so we don't even have to scan them.
      if (this._endTagEncountered) {
        return;
      }
      if (this.isCompressed) {
        this._decompressor.push(bytes);
      } else {
        this.processDecompressedData(bytes);
      }
      this.scanLoadedData();
    }

    finishLoading() {
      if (this.isCompressed) {
        this._decompressor.close();
        this._decompressor = null;
        this.scanLoadedData();
      }
    }

    getSymbol(id: number) {
      if (this.eagerlyParsedSymbolsMap[id]) {
        return this.eagerlyParsedSymbolsMap[id];
      }
      var unparsed = this.dictionary[id];
      if (!unparsed) {
        return null;
      }
      var symbol;
      if (unparsed.tagCode === SWFTag.CODE_DEFINE_SPRITE) {
        // TODO: replace this whole silly `type` business with tagCode checking.
        symbol = this.parseSpriteTimeline(unparsed);
      } else {
        symbol = this.getParsedTag(unparsed);
      }
      symbol.className = this.symbolClassesMap[id] || null;
      return symbol;
    }

    getParsedTag(unparsed: UnparsedTag): any {
      SWF.enterTimeline('Parse tag ' + SWFTag[unparsed.tagCode]);
      this._dataStream.align();
      this._dataStream.pos = unparsed.byteOffset;
      var tag: any = {code: unparsed.tagCode};
      var handler = Parser.LowLevel.tagHandlers[unparsed.tagCode];
      release || Debug.assert(handler, 'handler shall exists here');
      var tagEnd = unparsed.byteOffset + unparsed.byteLength;
      try {
        handler(this.data, this._dataStream, tag, this.swfVersion, unparsed.tagCode, tagEnd);
      } catch (e) {
        if (!(e instanceof RangeError)) {
          throw e;
        }
      }
      var finalPos = this._dataStream.pos;
      release || assert(finalPos <= tagEnd);
      if (finalPos < tagEnd) {
        var consumedBytes = finalPos - unparsed.byteOffset;
        Debug.warning('Scanning ' + SWFTag[unparsed.tagCode] + ' at offset ' +
                      unparsed.byteOffset + ' consumed ' + consumedBytes + ' of ' +
                      unparsed.byteLength + ' bytes. (' + (tagEnd - finalPos) + ' left)');
        this._dataStream.pos = tagEnd;
      }
      var symbol = defineSymbol(tag, this.dictionary);
      SWF.leaveTimeline();
      return symbol;
    }

    private readHeaderAndInitialize(initialBytes: Uint8Array) {
      SWF.enterTimeline('Initialize SWFFile');
      var isDeflateCompressed = initialBytes[0] === 67;
      var isLzmaCompressed = initialBytes[0] === 90;
      this.isCompressed = isDeflateCompressed || isLzmaCompressed;
      this.swfVersion = initialBytes[3];
{'fraction_non_alphanumeric': 0.09987720016373311, 'fraction_numerical': 0.0066857688634192934, 'mean_word_length': 3.021673525377229, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '764183', 'n_tokens_mistral': 4283, 'n_tokens_neox': 4215, 'n_words': 1061}