UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

239 lines (203 loc) 7.22 kB
'use strict'; const Transform = require('stream').Transform; // transform catches eos and _flush gets called const ApplicationHttpReader = require('./ApplicationHttpReader'); const Cache = require('./Cache'); const ContentTypeInfo = require('../format/ContentTypeInfo'); const DataReader = require('./DataReader'); const MultipartParser = require('./MultipartReader'); const DeserializationError = require('../errors/DeserializationError'); const DATA_EVENTS = DataReader.EVENTS; const REQUEST_EVENTS = ApplicationHttpReader.EVENTS; const PART_EVENTS = require('./PartReader').EVENTS; const MULTIPART_EVENTS = MultipartParser.EVENTS; /** * States */ const STATES = { START: 0, PROCESS: 1, FINISHED: 2 }; /** * Events */ const EVENTS = { UNCONSUMED: 'reader.unconsumed', START: 'reader.start', END: 'reader.end', REQUEST_START: REQUEST_EVENTS.START, REQUEST_REQUESTLINE: REQUEST_EVENTS.REQUEST_LINE, REQUEST_HEADERS: REQUEST_EVENTS.HEADERS, REQUEST_BODY_START: REQUEST_EVENTS.BODY_START, REQUEST_BODY_DATA: ApplicationHttpReader.BODY_PREFIX + '.' + DATA_EVENTS.DATA, REQUEST_BODY_END: REQUEST_EVENTS.BODY_END, REQUEST_END: REQUEST_EVENTS.END, PART_START: PART_EVENTS.START, PART_HEADERS: PART_EVENTS.HEADERS, MULTIPART_START: MULTIPART_EVENTS.START, MULTIPART_END: MULTIPART_EVENTS.END }; /** * Main entry class for reading requests. Allows registering various readers for mime types (e.g. multipart/mixed, * application/http and application/octet-stream). * Implement as transform to catch the end of input event {@see _flush}. * Events are emitted to the emitter object given to constructor. * * Usage example: * const reader = new Reader(partReader, emitter) * request.pipe(reader) * * @extends Transform */ class ContentDeserializer extends Transform { /** * @param {Object} reader - Reader used when starting reading * @param {EventEmitter} emitter - Emitter to be used for reading events */ constructor(reader, emitter) { super(); this._cache = new Cache(Buffer.from([])); // State Info this._state = STATES.START; // Stack info this._mimetypeReader = null; this._mimetypeReaderStack = []; this._mimetypeReaderFactories = new Map(); this._addMimeReaderFactories('application/http', ApplicationHttpReader.createInstance); this._addMimeReaderFactories('application/octet-stream', DataReader.createInstance); this._addMimeReaderFactories('multipart/mixed', MultipartParser.createInstance); this._fallBackReaderFactory = DataReader.createInstance; this.pushReader(reader); this._emitter = emitter; } /** * Overwritten _transform implementation * * @param {Buffer} buffer - Chunk of bytes * @param {string} enc - Encoding of the chunk * @param {Function} next - Callback function * @private */ _transform(buffer, enc, next) { try { this._cache.append(buffer); this.readCache(false); next(); } catch (err) { next(err); } } /** * Overwritten _flush method to catch the end of the source stream * * @param {Function} next - Callback function * @private */ _flush(next) { try { this.readCache(true); // last next(); } catch (err) { next(err); } } /** * Read data from cache until the catch is empty. The data is passed to the current reader until the * processor is finished or more data is required by the reader. * * @param {boolean} last * @returns {boolean} * @throws {Error} In case of internal stack error * @throws {Error} In case last === true and more data is required */ readCache(last) { let needMoreData = false; // this._cache.setConsumedBytes(0); while (!needMoreData && this._state !== STATES.FINISHED) { switch (this._state) { case STATES.START: this._emitter.emit(EVENTS.START); this._state = STATES.PROCESS; break; case STATES.PROCESS: needMoreData = this._mimetypeReader.readCache(this, this._cache, last); if (needMoreData === false) { this.popReader(); } if (!this._mimetypeReader) { this._state = STATES.FINISHED; } break; default: throw new DeserializationError('default reached'); } } this._cache.shrink(); // Ensure proper cleanup if (last) { if (needMoreData) throw new DeserializationError('More data required to process multipart'); if (this._cache.getLength()) { this._emitter.emit(EVENTS.UNCONSUMED, this._cache.getBytes(this._cache.getLength())); } this._emitter.emit(EVENTS.END); } return needMoreData; } /** * Register a reader factory to a mime type * * @param {string} mimetype - Mimetype (e.g. multipart/mixed). * @param {Function} factory - Factory for the reader which can parse this mime type payload * @private */ _addMimeReaderFactories(mimetype, factory) { this._mimetypeReaderFactories[mimetype] = factory; } /** * Push a reader to the readers stack * * @param {*} mimetypeReader */ pushReader(mimetypeReader) { if (this._mimetypeReader) { this._mimetypeReaderStack.push(this._mimetypeReader); } this._mimetypeReader = mimetypeReader; } /** * Pop a reader from the readers stack */ popReader() { this._mimetypeReader = this._mimetypeReaderStack.pop(); // this._mimetypeReader may be null } /** * Determine next reader depending on a content type header * * @param {*} headers Http headers, only "content-type" header is used * @returns {*} */ getNextReader(headers) { // default content-type as defined in [RFC7231] 3.1.1.5. Content-Type const contentTypeInfo = headers['content-type'] || new ContentTypeInfo().setMimeType('application/octet-stream'); const nextReader = this.createReader(contentTypeInfo, headers); if (!nextReader) { throw new DeserializationError('missing mimetype Reader for ' + contentTypeInfo.getMimeType()); } return nextReader; } /** * Create and reader * * @param {ContentTypeInfo} contentTypeInfo - Type information used to create this reader * @param {*} headers * @returns {*} */ createReader(contentTypeInfo, headers) { let factory = this._mimetypeReaderFactories[contentTypeInfo.getMimeType()] || this._fallBackReaderFactory; return factory(contentTypeInfo, headers); } } ContentDeserializer.EVENTS = EVENTS; module.exports = ContentDeserializer;