/// <summary> /// Fills the <see cref="readBuffers"/> with more data from the underlying stream /// passed through all the stream filters. /// </summary> /// <param name="chunkSize">Maximum number of bytes to be read from the stream.</param> /// <returns>A <see cref="string"/> or <see cref="PhpBytes"/> containing the /// data as returned from the last stream filter or <b>null</b> in case of an error or <c>EOF</c>.</returns> protected TextElement ReadFiltered(int chunkSize) { byte[] chunk = new byte[chunkSize]; var filtered = TextElement.Null; while (filtered.IsNull) { // Read data until there is an output or error or EOF. if (RawEof) return TextElement.Null; int read = RawRead(chunk, 0, chunkSize); if (read <= 0) { // Error or EOF. return TextElement.Null; } if (read < chunkSize) { byte[] sub = new byte[read]; Array.Copy(chunk, 0, sub, 0, read); chunk = sub; } filtered = new TextElement(chunk); bool closing = RawEof; if (textReadFilter != null) { // First use the text-input filter if any. filtered = textReadFilter.Filter(_ctx, filtered, closing); } if (readFilters != null) { // After that apply the user-filters. foreach (IFilter f in readFilters) { if (filtered.IsNull) { // This is the last chance to output something. Give chance to all filters. if (closing) filtered = TextElement.Empty; else break; // Continue with next RawRead() } filtered = f.Filter(_ctx, filtered, closing); } // foreach } // if } // while return filtered; }
/// <summary> /// Put a buffer at the end of the <see cref="readBuffers"/>. /// </summary> /// <param name="data">The buffer to append.</param> internal void EnqueueReadBuffer(TextElement data) { Debug.Assert(!data.IsNull); // This may be the first access to the buffers. if (readBuffers == null) readBuffers = new Queue<TextElement>(2); // Append the filtered output to the buffers. readBuffers.Enqueue(data); }
/// <summary> /// Split a <see cref="String"/> or <see cref="PhpBytes"/> to "upto" bytes at left and the rest or <c>null</c> at right. /// </summary> private static void SplitData(TextElement data, int upto, out TextElement left, out TextElement right) { Debug.Assert(upto >= 0); //if (this.IsText) if (data.IsText) { string s = data.GetText(); if (upto < s.Length - 1) { left = new TextElement(s.Substring(0, upto + 1)); right = new TextElement(s.Substring(upto + 2)); } else { left = data; right = TextElement.Null; } } else { Debug.Assert(data.IsBinary); var bin = data.GetBytes(); if (upto < bin.Length - 1) { byte[] l = new byte[upto + 1], r = new byte[bin.Length - upto - 1]; Array.Copy(bin, 0, l, 0, upto + 1); Array.Copy(bin, upto + 1, r, 0, bin.Length - upto - 1); left = new TextElement(l); right = new TextElement(r); } else { left = data; right = TextElement.Null; } } }
/// <summary> /// Passes the data through output filter-chain to the output buffer. /// When the buffer is full or buffering is disabled, passes the data to the low-level stream. /// </summary> /// <param name="data">The data to store (filters will handle the type themselves).</param> /// <param name="closing"><c>true</c> when this method is called from <c>close()</c> /// to prune all the pending filters with closing set to <c>true</c>.</param> /// <returns>Number of character entities successfully written or <c>-1</c> on an error.</returns> protected int WriteData(TextElement data, bool closing = false) { // Set file access to writing CurrentAccess = FileAccess.Write; if (!CanWrite) return -1; Debug.Assert(!data.IsNull); int consumed = data.Length; writeFilteredCount += consumed; if (writeFilters != null) { // Process the data through the custom write filters first. foreach (IFilter f in writeFilters) { if (data.IsNull) { // When closing, feed all the filters with data. if (closing) data = TextElement.Empty; else return consumed; // Eaten all } data = f.Filter(_ctx, data, closing); if (closing) f.OnClose(); } } if (textWriteFilter != null) { // Then pass it through the text-conversion filter if any. data = textWriteFilter.Filter(_ctx, data, closing); } // From now on, the data is treated just as binary byte[] bin = data.AsBytes(_ctx.StringEncoding); if (bin.Length == 0) { return consumed; } // Append the resulting data to the output buffer if any. if (IsWriteBuffered) { // Is this the first access? if (writeBuffer == null) { writeBuffer = new byte[writeBufferSize]; writePosition = 0; } // The whole binary data fits in the buffer, great! if (writeBufferSize - writePosition > bin.Length) { Array.Copy(bin, 0, writeBuffer, writePosition, bin.Length); writePosition += bin.Length; return consumed; } int copied = 0; // Use the buffer for small data only if (writeBufferSize > bin.Length) { // Otherwise fill the buffer and flush it. copied = writeBufferSize - writePosition; Array.Copy(bin, 0, writeBuffer, writePosition, copied); writePosition += copied; } // Flush the buffer if ((writePosition > 0) && (!FlushWriteBuffer())) return (copied > 0) ? copied : -1; // It is an error but still some output was written. if (bin.Length - copied >= writeBufferSize) { // If the binary data is really big, write it directly to stream. while (copied < bin.Length) { int written = RawWrite(bin, copied, bin.Length - copied); if (written <= 0) { PhpException.Throw(PhpError.Warning, ErrResources.stream_write_failed, copied.ToString(), bin.Length.ToString()); return (copied > 0) ? copied : -1; // It is an error but still some output was written. } copied += written; writeOffset += written; } } else { // Otherwise just start a new buffer with the rest of the data. Array.Copy(bin, copied, writeBuffer, 0, bin.Length - copied); writePosition = bin.Length - copied; } return consumed; } else { // No write buffer. Write the data directly. int copied = 0; while (copied < bin.Length) { int written = RawWrite(bin, copied, bin.Length - copied); if (written <= 0) { PhpException.Throw(PhpError.Warning, ErrResources.stream_write_failed, copied.ToString(), bin.Length.ToString()); return (copied > 0) ? copied : -1; // ERROR but maybe some was written. } copied += written; writeOffset += written; } return consumed; } }
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public abstract TextElement Filter(Context ctx, TextElement input, bool closing);
/// <summary> /// Finds the '\n' in a string or PhpBytes and returns its offset or <c>-1</c> /// if not found. /// </summary> /// <param name="data">Data to scan.</param> /// <param name="from">Index of the first character to scan.</param> /// <returns></returns> private static int FindEoln(TextElement data, int from) { Debug.Assert(!data.IsNull); if (data.IsText) { return data.GetText().IndexOf('\n', from); } else { Debug.Assert(data.IsBinary); return Array.IndexOf(data.GetBytes(), (byte)'\n', from); } }
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public TextElement Filter(Context ctx, TextElement input, bool closing) { string str = input.AsText(ctx.StringEncoding); if (pending) { // Both \r\n together make a pair which would consume a pending \r. if (str.Length == 0) str = "\r"; else if (str[0] != '\n') str.Insert(0, "\r"); } // Replace the pair. str = str.Replace("\r\n", "\n"); if (str.Length != 0) { // Check for pending \r at the end. pending = str[str.Length - 1] == '\r'; // Postpone the resolution of \r\n vs. \r to the next filtering if this is not the last one. if (!closing && pending) str.Remove(str.Length - 1, 1); } // return new TextElement(str); }
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public TextElement Filter(Context ctx, TextElement input, bool closing) { return new TextElement(input.AsText(ctx.StringEncoding).Replace("\n", "\r\n")); }
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public TextElement Filter(Context ctx, TextElement input, bool closing) { return(new TextElement(input.AsText(ctx.StringEncoding).Replace("\n", "\r\n"))); }
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public abstract TextElement Filter(Context ctx, TextElement input, bool closing);
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public abstract TextElement Filter(IEncodingProvider enc, TextElement input, bool closing);
/// <summary> /// Processes the <paramref name="input"/> (either of type <see cref="string"/> or <see cref="byte"/>[]) /// data and returns the filtered data in one of the formats above or <c>null</c>. /// </summary> public TextElement Filter(IEncodingProvider enc, TextElement input, bool closing) { return(new TextElement(input.AsText(enc.StringEncoding).Replace("\n", "\r\n"))); }
public override TextElement Filter(IEncodingProvider enc, TextElement input, bool closing) { // TODO: not the most efficient method - after the filters are upgraded to bucket lists, update this var bInput = input.AsBytes(enc.StringEncoding); if (bInput != null) { if (_state == UncompressionState.Failed) { // failed filter should not get any more calls PhpException.Throw(PhpError.Warning, "using filter in failed state"); return(TextElement.Null); } if (_state == UncompressionState.PostTrailer) { // post trailer - ignore everything if (closing) { _state = UncompressionState.Finished; } return(TextElement.Empty); } if (_state == UncompressionState.Finished) { // finished filter should not get any more data PhpException.Throw(PhpError.Warning, "using filter in finished state"); return(TextElement.Null); } if (_state == UncompressionState.Passthrough) { // this is not gzip data format - pass the data through return(new TextElement(bInput)); } // enqueue the block _chunkQueue.EnqueueByteBlock(bInput, 0, bInput.Length); if (_state == UncompressionState.Header) { #region Header handling //beginning of the stream byte[] beginning = _chunkQueue.DequeueByteBlock(Zlib.GZIP_HEADER_LENGTH); if (beginning == null && !closing) { // we do not have enough data, but we know there would be more data ahead return(TextElement.Empty); } else { //check the header format if (beginning.Length >= 2 && beginning[0] == Zlib.GZIP_HEADER[0] && beginning[1] == Zlib.GZIP_HEADER[1]) { //header magic bytes are OK if (beginning.Length < Zlib.GZIP_HEADER_LENGTH) { // header is too short -> this is an error PhpException.Throw(PhpError.Warning, "unexpected end of file"); return(TextElement.Null); } else { // check the rest of the header if (beginning[2] != Zlib.Z_DEFLATED) { PhpException.Throw(PhpError.Warning, "unknown compression method"); return(TextElement.Null); } if ((beginning[3] & Zlib.GZIP_HEADER_RESERVED_FLAGS) != 0) { PhpException.Throw(PhpError.Warning, "unknown header flags set"); return(TextElement.Null); } _headerFlags = beginning[3]; //change the header state based on the header flags UpdateHeaderState(); } } else { // this is not a gzip format -> passthrough the data _state = UncompressionState.Passthrough; return(new TextElement(beginning)); } } #endregion } if (_state == UncompressionState.HeaderExtraField) { #region Header Extra Field Handling if (_extraHeaderLength == null) { //length was not yet detected if (_chunkQueue.AvailableBytes < 2) { //wait for more input return(TextElement.Empty); } else { //assemble length _extraHeaderLength = _chunkQueue.DequeueByte(); _extraHeaderLength &= (_chunkQueue.DequeueByte() << 8); } } if (_extraHeaderLength != null) { //length was already read if (_chunkQueue.AvailableBytes < _extraHeaderLength) { //wait for more input return(TextElement.Empty); } else { Debug.Assert(_extraHeaderLength.HasValue); //skip the extra header _chunkQueue.SkipByteBlock(_extraHeaderLength.Value); UpdateHeaderState(); } } #endregion } if (_state == UncompressionState.HeaderFilename || _state == UncompressionState.HeaderComment) { #region Header Filename and Comment Handling // filename or comment // cycle until input ends or zero character is encountered while (true) { byte?nextByte = _chunkQueue.DequeueByte(); if (nextByte == null) { //wait for more input return(TextElement.Empty); } if (nextByte == 0) { // end the cycle break; } } // go to the next state UpdateHeaderState(); #endregion } if (_state == UncompressionState.HeaderCRC) { #region CRC Handling // header CRC if (_chunkQueue.AvailableBytes < 2) { //wait for more input return(TextElement.Empty); } else { //skip the CRC _chunkQueue.DequeueByte(); _chunkQueue.DequeueByte(); UpdateHeaderState(); } #endregion } //filled by data handling and sometimes returned by trailer handling byte[] output = null; if (_state == UncompressionState.Data) { #region Deflated Data Handling //get all available bytes byte[] inputBytes = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); int inputOffset = 0; // perform the inner operation try { output = FilterInner(inputBytes, ref inputOffset, closing); } catch { // exception was thrown _state = UncompressionState.Failed; throw; } if (output == null) { // error happened and exception was not thrown _state = UncompressionState.Failed; return(TextElement.Null); } // update the hash algorithm _crc.Update(output); if (inputOffset != inputBytes.Length) { // push the rest of the data into the chunk queue _chunkQueue.PushByteBlock(inputBytes, inputOffset, inputBytes.Length - inputOffset); // end of deflated block reached _state = UncompressionState.Trailer; // pass through to Trailer handling } else { //normal decompressed block - return it return(new TextElement(output)); } #endregion } if (_state == UncompressionState.Trailer) { #region Trailer Handling // the deflate block has already ended, we are processing trailer if (closing || _chunkQueue.AvailableBytes >= Zlib.GZIP_FOOTER_LENGTH) { byte[] trailer; trailer = _chunkQueue.DequeueByteBlock(_chunkQueue.AvailableBytes); if (trailer.Length >= Zlib.GZIP_FOOTER_LENGTH) { byte[] crc = _crc.Final(); if (crc[3] != trailer[0] || crc[2] != trailer[1] || crc[1] != trailer[2] || crc[0] != trailer[3]) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect data check"); return(TextElement.Null); } if (BitConverter.ToInt32(trailer, 4) != _stream.total_out) { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "incorrect length check"); return(TextElement.Null); } _state = closing ? UncompressionState.Finished : UncompressionState.PostTrailer; // everything is fine, return the output if available return(output != null ? new TextElement(output) : TextElement.Empty); } else { _state = UncompressionState.Failed; PhpException.Throw(PhpError.Warning, "unexpected end of file"); return(TextElement.Null); } } else { //stream is not closing yet - return the remaining output, otherwise empty return(output != null ? new TextElement(output) : TextElement.Empty); } #endregion } //this should not happen Debug.Fail(null); return(TextElement.Null); } else { Debug.Fail("GzipUncompressionFilter expects chunks to be convertible to PhpBytes."); return(TextElement.Null); } }
public override TextElement Filter(IEncodingProvider enc, TextElement input, bool closing) { var bInput = input.AsBytes(enc.StringEncoding); if (bInput != null) { if (_state == CompressionState.Failed) { PhpException.Throw(PhpError.Warning, "using filter in failed state"); return(TextElement.Null); } if (_state == CompressionState.Finished) { PhpException.Throw(PhpError.Warning, "using filter in finished state"); return(TextElement.Null); } byte[] header = null; byte[] footer = null; if (_state == CompressionState.Header) { header = new byte[Zlib.GZIP_HEADER_LENGTH]; header[0] = Zlib.GZIP_HEADER[0]; header[1] = Zlib.GZIP_HEADER[1]; header[2] = Zlib.Z_DEFLATED; header[3] = 0; // 3-8 represent time and are set to zero header[9] = Zlib.OS_CODE; _crc.Init(); _state = CompressionState.Data; } int outputOffset = 0; byte[] output; try { output = FilterInner(bInput, ref outputOffset, closing); } catch { _state = CompressionState.Failed; throw; } if (output == null) { _state = CompressionState.Failed; return(TextElement.Null); } // input should be read to the end Debug.Assert(outputOffset == bInput.Length); _crc.Update(bInput); if (closing) { byte[] crcBytes = _crc.Final(); footer = new byte[Zlib.GZIP_FOOTER_LENGTH]; // well this implementation simply has the hash inverted compared to C implementation footer[0] = crcBytes[3]; footer[1] = crcBytes[2]; footer[2] = crcBytes[1]; footer[3] = crcBytes[0]; footer[4] = (byte)(_stream.total_in & 0xFF); footer[5] = (byte)((_stream.total_in >> 8) & 0xFF); footer[6] = (byte)((_stream.total_in >> 16) & 0xFF); footer[7] = (byte)((_stream.total_in >> 24) & 0xFF); _state = CompressionState.Finished; } if (header != null || footer != null) { int offset = 0; byte[] appended = new byte[(header != null ? header.Length : 0) + output.Length + (footer != null ? footer.Length : 0)]; if (header != null) { Buffer.BlockCopy(header, 0, appended, 0, header.Length); offset += header.Length; } if (output != null && output.Length > 0) { Buffer.BlockCopy(output, 0, appended, offset, output.Length); offset += output.Length; } if (footer != null) { Buffer.BlockCopy(footer, 0, appended, offset, footer.Length); } return(new TextElement(appended)); } else { return(new TextElement(output)); } } else { Debug.Fail("GzipCompresionFilter expects chunks to be of type PhpBytes."); return(TextElement.Null); } }
public sealed override TextElement /*PhpFilter.*/ Filter(IEncodingProvider enc, TextElement input, bool closing) { var @in = new UserFilterBucketBrigade() { bucket = input.ToPhpString() }; var @out = new UserFilterBucketBrigade(); var consumed = new PhpAlias(0L); switch ((PhpFilters.FilterStatus)filter(@in, @out, consumed, closing)) { case PhpFilters.FilterStatus.OK: return(new TextElement(@out.bucket, enc.StringEncoding)); case PhpFilters.FilterStatus.MoreData: return(TextElement.Empty); case PhpFilters.FilterStatus.FatalError: default: // silently stop feeding this filter return(TextElement.Null); } }