зеркало из https://github.com/mozilla/gecko-dev.git
257 строки
8.1 KiB
C++
257 строки
8.1 KiB
C++
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsConverterInputStream.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsReadLine.h"
|
|
#include "nsStreamUtils.h"
|
|
|
|
#include <algorithm>
|
|
#include <tuple>
|
|
|
|
using namespace mozilla;
|
|
|
|
#define CONVERTER_BUFFER_SIZE 8192
|
|
|
|
NS_IMPL_ISUPPORTS(nsConverterInputStream, nsIConverterInputStream,
|
|
nsIUnicharInputStream, nsIUnicharLineInputStream)
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::Init(nsIInputStream* aStream, const char* aCharset,
|
|
int32_t aBufferSize, char16_t aReplacementChar) {
|
|
nsAutoCString label;
|
|
if (!aCharset) {
|
|
label.AssignLiteral("UTF-8");
|
|
} else {
|
|
label = aCharset;
|
|
}
|
|
|
|
auto encoding = Encoding::ForLabelNoReplacement(label);
|
|
if (!encoding) {
|
|
return NS_ERROR_UCONV_NOCONV;
|
|
}
|
|
// Previously, the implementation auto-switched only
|
|
// between the two UTF-16 variants and only when
|
|
// initialized with an endianness-unspecific label.
|
|
mConverter = encoding->NewDecoder();
|
|
|
|
size_t outputBufferSize;
|
|
if (aBufferSize <= 0) {
|
|
aBufferSize = CONVERTER_BUFFER_SIZE;
|
|
outputBufferSize = CONVERTER_BUFFER_SIZE;
|
|
} else {
|
|
// NetUtil.sys.mjs assumes that if buffer size equals
|
|
// the input size, the whole stream will be processed
|
|
// as one readString. This is not true with encoding_rs,
|
|
// because encoding_rs might want to see space for a
|
|
// surrogate pair, so let's compute a larger output
|
|
// buffer length.
|
|
CheckedInt<size_t> needed = mConverter->MaxUTF16BufferLength(aBufferSize);
|
|
if (!needed.isValid()) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
outputBufferSize = needed.value();
|
|
}
|
|
|
|
// set up our buffers.
|
|
if (!mByteData.SetCapacity(aBufferSize, mozilla::fallible) ||
|
|
!mUnicharData.SetLength(outputBufferSize, mozilla::fallible)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
mInput = aStream;
|
|
mErrorsAreFatal = !aReplacementChar;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::Close() {
|
|
nsresult rv = mInput ? mInput->Close() : NS_OK;
|
|
mLineBuffer = nullptr;
|
|
mInput = nullptr;
|
|
mConverter = nullptr;
|
|
mByteData.Clear();
|
|
mUnicharData.Clear();
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::Read(char16_t* aBuf, uint32_t aCount,
|
|
uint32_t* aReadCount) {
|
|
NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
|
|
uint32_t readCount = mUnicharDataLength - mUnicharDataOffset;
|
|
if (0 == readCount) {
|
|
// Fill the unichar buffer
|
|
readCount = Fill(&mLastErrorCode);
|
|
if (readCount == 0) {
|
|
*aReadCount = 0;
|
|
return mLastErrorCode;
|
|
}
|
|
}
|
|
if (readCount > aCount) {
|
|
readCount = aCount;
|
|
}
|
|
memcpy(aBuf, mUnicharData.Elements() + mUnicharDataOffset,
|
|
readCount * sizeof(char16_t));
|
|
mUnicharDataOffset += readCount;
|
|
*aReadCount = readCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter,
|
|
void* aClosure, uint32_t aCount,
|
|
uint32_t* aReadCount) {
|
|
NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
|
|
uint32_t codeUnitsToWrite = mUnicharDataLength - mUnicharDataOffset;
|
|
if (0 == codeUnitsToWrite) {
|
|
// Fill the unichar buffer
|
|
codeUnitsToWrite = Fill(&mLastErrorCode);
|
|
if (codeUnitsToWrite == 0) {
|
|
*aReadCount = 0;
|
|
return mLastErrorCode;
|
|
}
|
|
}
|
|
|
|
if (codeUnitsToWrite > aCount) {
|
|
codeUnitsToWrite = aCount;
|
|
}
|
|
|
|
uint32_t codeUnitsWritten;
|
|
uint32_t totalCodeUnitsWritten = 0;
|
|
|
|
while (codeUnitsToWrite) {
|
|
nsresult rv =
|
|
aWriter(this, aClosure, mUnicharData.Elements() + mUnicharDataOffset,
|
|
totalCodeUnitsWritten, codeUnitsToWrite, &codeUnitsWritten);
|
|
if (NS_FAILED(rv)) {
|
|
// don't propagate errors to the caller
|
|
break;
|
|
}
|
|
|
|
codeUnitsToWrite -= codeUnitsWritten;
|
|
totalCodeUnitsWritten += codeUnitsWritten;
|
|
mUnicharDataOffset += codeUnitsWritten;
|
|
}
|
|
|
|
*aReadCount = totalCodeUnitsWritten;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::ReadString(uint32_t aCount, nsAString& aString,
|
|
uint32_t* aReadCount) {
|
|
NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
|
|
uint32_t readCount = mUnicharDataLength - mUnicharDataOffset;
|
|
if (0 == readCount) {
|
|
// Fill the unichar buffer
|
|
readCount = Fill(&mLastErrorCode);
|
|
if (readCount == 0) {
|
|
*aReadCount = 0;
|
|
return mLastErrorCode;
|
|
}
|
|
}
|
|
if (readCount > aCount) {
|
|
readCount = aCount;
|
|
}
|
|
const char16_t* buf = mUnicharData.Elements() + mUnicharDataOffset;
|
|
aString.Assign(buf, readCount);
|
|
mUnicharDataOffset += readCount;
|
|
*aReadCount = readCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t nsConverterInputStream::Fill(nsresult* aErrorCode) {
|
|
if (!mInput) {
|
|
// We already closed the stream!
|
|
*aErrorCode = NS_BASE_STREAM_CLOSED;
|
|
return 0;
|
|
}
|
|
|
|
if (NS_FAILED(mLastErrorCode)) {
|
|
// We failed to completely convert last time, and error-recovery
|
|
// is disabled. We will fare no better this time, so...
|
|
*aErrorCode = mLastErrorCode;
|
|
return 0;
|
|
}
|
|
|
|
// mUnicharData.Length() is the buffer length, not the fill status.
|
|
// mUnicharDataLength reflects the current fill status.
|
|
mUnicharDataLength = 0;
|
|
// Whenever we convert, mUnicharData is logically empty.
|
|
mUnicharDataOffset = 0;
|
|
|
|
// Continue trying to read from the source stream until we successfully decode
|
|
// a character or encounter an error, as returning `0` here implies that the
|
|
// stream is complete.
|
|
//
|
|
// If the converter has been cleared, we've fully consumed the stream, and
|
|
// want to report EOF.
|
|
while (mUnicharDataLength == 0 && mConverter) {
|
|
// We assume a many to one conversion and are using equal sizes for
|
|
// the two buffers. However if an error happens at the very start
|
|
// of a byte buffer we may end up in a situation where n bytes lead
|
|
// to n+1 unicode chars. Thus we need to keep track of the leftover
|
|
// bytes as we convert.
|
|
|
|
uint32_t nb;
|
|
*aErrorCode = NS_FillArray(mByteData, mInput, mLeftOverBytes, &nb);
|
|
if (NS_FAILED(*aErrorCode)) {
|
|
return 0;
|
|
}
|
|
|
|
NS_ASSERTION(uint32_t(nb) + mLeftOverBytes == mByteData.Length(),
|
|
"mByteData is lying to us somewhere");
|
|
|
|
// If `NS_FillArray` failed to read any new bytes, this is the last read,
|
|
// and we're at the end of the stream.
|
|
bool last = (nb == 0);
|
|
|
|
// Now convert as much of the byte buffer to unicode as possible
|
|
auto src = AsBytes(Span(mByteData));
|
|
auto dst = Span(mUnicharData);
|
|
|
|
// Truncation from size_t to uint32_t below is OK, because the sizes
|
|
// are bounded by the lengths of mByteData and mUnicharData.
|
|
uint32_t result;
|
|
size_t read;
|
|
size_t written;
|
|
if (mErrorsAreFatal) {
|
|
std::tie(result, read, written) =
|
|
mConverter->DecodeToUTF16WithoutReplacement(src, dst, last);
|
|
} else {
|
|
std::tie(result, read, written, std::ignore) =
|
|
mConverter->DecodeToUTF16(src, dst, last);
|
|
}
|
|
mLeftOverBytes = mByteData.Length() - read;
|
|
mUnicharDataLength = written;
|
|
// Clear `mConverter` if we reached the end of the stream, as we can't
|
|
// call methods on it anymore. This will also signal EOF to the caller
|
|
// through the loop condition.
|
|
if (last) {
|
|
MOZ_ASSERT(mLeftOverBytes == 0,
|
|
"Failed to read all bytes on the last pass?");
|
|
mConverter = nullptr;
|
|
}
|
|
// If we got a decode error, we're done.
|
|
if (result != kInputEmpty && result != kOutputFull) {
|
|
MOZ_ASSERT(mErrorsAreFatal, "How come DecodeToUTF16() reported error?");
|
|
*aErrorCode = NS_ERROR_UDEC_ILLEGALINPUT;
|
|
return 0;
|
|
}
|
|
}
|
|
*aErrorCode = NS_OK;
|
|
return mUnicharDataLength;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsConverterInputStream::ReadLine(nsAString& aLine, bool* aResult) {
|
|
if (!mLineBuffer) {
|
|
mLineBuffer = MakeUnique<nsLineBuffer<char16_t>>();
|
|
}
|
|
return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
|
|
}
|