mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-04-14 02:12:12 +02:00
Signature validation
This commit is contained in:
parent
f6c5431770
commit
3e6f4ada3d
@ -19,6 +19,7 @@
|
|||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
#include "pdfencoding.h"
|
#include "pdfencoding.h"
|
||||||
#include "pdfform.h"
|
#include "pdfform.h"
|
||||||
|
#include "pdfutils.h"
|
||||||
#include "pdfsignaturehandler_impl.h"
|
#include "pdfsignaturehandler_impl.h"
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
@ -105,7 +106,7 @@ PDFSignature PDFSignature::parse(const PDFObjectStorage* storage, PDFObject obje
|
|||||||
result.m_byteRanges.reserve(byteRangeCount);
|
result.m_byteRanges.reserve(byteRangeCount);
|
||||||
for (size_t i = 0; i < byteRangeCount; ++i)
|
for (size_t i = 0; i < byteRangeCount; ++i)
|
||||||
{
|
{
|
||||||
ByteRange byteRange = { byteRangesArray[i], byteRangesArray[i + 1] };
|
ByteRange byteRange = { byteRangesArray[2 * i], byteRangesArray[2 * i + 1] };
|
||||||
result.m_byteRanges.push_back(byteRange);
|
result.m_byteRanges.push_back(byteRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +288,18 @@ void PDFSignatureVerificationResult::addSignatureDataOtherError()
|
|||||||
m_errors << PDFTranslationContext::tr("Signed data are invalid.");
|
m_errors << PDFTranslationContext::tr("Signed data are invalid.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFSignatureVerificationResult::addSignatureDataCoveredBySignatureMissingError()
|
||||||
|
{
|
||||||
|
m_flags.setFlag(Error_Signature_DataCoveredBySignatureMissing);
|
||||||
|
m_errors << PDFTranslationContext::tr("Data covered by signature are not present.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFSignatureVerificationResult::addSignatureNotCoveredBytesWarning(PDFInteger count)
|
||||||
|
{
|
||||||
|
m_flags.setFlag(Warning_Signature_NotCoveredBytes);
|
||||||
|
m_warnings << PDFTranslationContext::tr("%1 bytes are not covered by signature.").arg(count);
|
||||||
|
}
|
||||||
|
|
||||||
void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName)
|
void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName)
|
||||||
{
|
{
|
||||||
m_signatureFieldQualifiedName = signatureFieldQualifiedName;
|
m_signatureFieldQualifiedName = signatureFieldQualifiedName;
|
||||||
@ -297,6 +310,14 @@ void PDFSignatureVerificationResult::setSignatureFieldReference(PDFObjectReferen
|
|||||||
m_signatureFieldReference = signatureFieldReference;
|
m_signatureFieldReference = signatureFieldReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFSignatureVerificationResult::validate()
|
||||||
|
{
|
||||||
|
if (isCertificateValid() && isSignatureValid())
|
||||||
|
{
|
||||||
|
m_flags.setFlag(OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PDFPublicKeySignatureHandler::initializeResult(PDFSignatureVerificationResult& result) const
|
void PDFPublicKeySignatureHandler::initializeResult(PDFSignatureVerificationResult& result) const
|
||||||
{
|
{
|
||||||
PDFObjectReference signatureFieldReference = m_signatureField->getSelfReference();
|
PDFObjectReference signatureFieldReference = m_signatureField->getSelfReference();
|
||||||
@ -457,6 +478,86 @@ void PDFPublicKeySignatureHandler::verifyCertificate(PDFSignatureVerificationRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIO* PDFPublicKeySignatureHandler::getSignedDataBuffer(pdf::PDFSignatureVerificationResult& result, QByteArray& outputBuffer) const
|
||||||
|
{
|
||||||
|
const PDFSignature& signature = m_signatureField->getSignature();
|
||||||
|
const QByteArray& contents = signature.getContents();
|
||||||
|
const QByteArray& sourceData = m_sourceData;
|
||||||
|
|
||||||
|
PDFInteger size = 0;
|
||||||
|
const PDFSignature::ByteRanges& byteRanges = signature.getByteRanges();
|
||||||
|
for (const PDFSignature::ByteRange& byteRange : byteRanges)
|
||||||
|
{
|
||||||
|
size += byteRange.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
if (size > sourceData.size())
|
||||||
|
{
|
||||||
|
result.addSignatureDataCoveredBySignatureMissingError();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFClosedIntervalSet bytesCoveredBySignature;
|
||||||
|
|
||||||
|
outputBuffer.reserve(size);
|
||||||
|
for (const PDFSignature::ByteRange& byteRange : byteRanges)
|
||||||
|
{
|
||||||
|
PDFInteger startOffset = byteRange.offset; // Offset to the first data byte
|
||||||
|
PDFInteger endOffset = byteRange.offset + byteRange.size; // Offset to the byte following last data byte
|
||||||
|
|
||||||
|
if (startOffset == endOffset)
|
||||||
|
{
|
||||||
|
// This means byte range is zero
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startOffset > endOffset || startOffset < 0 || endOffset < 0 || startOffset >= m_sourceData.size() || endOffset > m_sourceData.size())
|
||||||
|
{
|
||||||
|
result.addSignatureDataCoveredBySignatureMissingError();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int length = endOffset - startOffset;
|
||||||
|
outputBuffer.append(sourceData.constData() + startOffset, length);
|
||||||
|
bytesCoveredBySignature.addInterval(startOffset, endOffset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jakub Melka: We must find byte string, which corresponds to signature.
|
||||||
|
// We find only first occurence, because second one should not exist - because
|
||||||
|
// it will mean that signature must be covered by itself.
|
||||||
|
QByteArray hexContents = contents.toHex();
|
||||||
|
int index = sourceData.indexOf(hexContents);
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
index = sourceData.indexOf(hexContents.toUpper());
|
||||||
|
}
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
int firstByteIndex = index;
|
||||||
|
int lastByteIndex = index + hexContents.size() - 1;
|
||||||
|
|
||||||
|
if (firstByteIndex > 0 && sourceData[firstByteIndex - 1] == '<')
|
||||||
|
{
|
||||||
|
--firstByteIndex;
|
||||||
|
}
|
||||||
|
if (lastByteIndex + 1 < sourceData.size() && sourceData[lastByteIndex + 1] == '>')
|
||||||
|
{
|
||||||
|
++lastByteIndex;
|
||||||
|
}
|
||||||
|
bytesCoveredBySignature.addInterval(firstByteIndex, lastByteIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We add a warning, that this signature doesn't cover whole source byte range
|
||||||
|
if (!bytesCoveredBySignature.isCovered(0, sourceData.size() - 1))
|
||||||
|
{
|
||||||
|
const PDFInteger notCoveredBytes = sourceData.size() - int(bytesCoveredBySignature.getTotalLength());
|
||||||
|
result.addSignatureNotCoveredBytesWarning(notCoveredBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BIO_new_mem_buf(outputBuffer.data(), outputBuffer.length());
|
||||||
|
}
|
||||||
|
|
||||||
void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResult& result) const
|
void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResult& result) const
|
||||||
{
|
{
|
||||||
PDFOpenSSLGlobalLock lock;
|
PDFOpenSSLGlobalLock lock;
|
||||||
@ -471,7 +572,8 @@ void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResul
|
|||||||
const unsigned char* data = reinterpret_cast<const unsigned char*>(content.data());
|
const unsigned char* data = reinterpret_cast<const unsigned char*>(content.data());
|
||||||
if (PKCS7* pkcs7 = d2i_PKCS7(nullptr, &data, content.size()))
|
if (PKCS7* pkcs7 = d2i_PKCS7(nullptr, &data, content.size()))
|
||||||
{
|
{
|
||||||
if (BIO* inputBuffer = getSignedDataBuffer(result))
|
QByteArray buffer;
|
||||||
|
if (BIO* inputBuffer = getSignedDataBuffer(result, buffer))
|
||||||
{
|
{
|
||||||
if (BIO* dataBio = PKCS7_dataInit(pkcs7, inputBuffer))
|
if (BIO* dataBio = PKCS7_dataInit(pkcs7, inputBuffer))
|
||||||
{
|
{
|
||||||
@ -534,6 +636,10 @@ void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResul
|
|||||||
|
|
||||||
BIO_free(inputBuffer);
|
BIO_free(inputBuffer);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There is no need for adding error, error is in this case added by getSignedDataBuffer function
|
||||||
|
}
|
||||||
|
|
||||||
PKCS7_free(pkcs7);
|
PKCS7_free(pkcs7);
|
||||||
}
|
}
|
||||||
@ -554,6 +660,7 @@ PDFSignatureVerificationResult PDFSignatureHandler_adbe_pkcs7_detached::verify()
|
|||||||
initializeResult(result);
|
initializeResult(result);
|
||||||
verifyCertificate(result);
|
verifyCertificate(result);
|
||||||
verifySignature(result);
|
verifySignature(result);
|
||||||
|
result.validate();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,36 +247,39 @@ public:
|
|||||||
|
|
||||||
enum VerificationFlag
|
enum VerificationFlag
|
||||||
{
|
{
|
||||||
None = 0x00000, ///< Used only for initialization
|
None = 0x00000000, ///< Used only for initialization
|
||||||
OK = 0x00001, ///< Both certificate and signature is OK
|
OK = 0x00000001, ///< Both certificate and signature is OK
|
||||||
Certificate_OK = 0x00002, ///< Certificate is OK
|
Certificate_OK = 0x00000002, ///< Certificate is OK
|
||||||
Signature_OK = 0x00004, ///< Signature is OK
|
Signature_OK = 0x00000004, ///< Signature is OK
|
||||||
Error_NoHandler = 0x00008, ///< No signature handler for given signature
|
Error_NoHandler = 0x00000008, ///< No signature handler for given signature
|
||||||
Error_Generic = 0x00010, ///< Generic error (uknown general error)
|
Error_Generic = 0x00000010, ///< Generic error (uknown general error)
|
||||||
|
|
||||||
Error_Certificate_Invalid = 0x00020, ///< Certificate is invalid
|
Error_Certificate_Invalid = 0x00000020, ///< Certificate is invalid
|
||||||
Error_Certificate_NoSignatures = 0x00040, ///< No signature found in certificate data
|
Error_Certificate_NoSignatures = 0x00000040, ///< No signature found in certificate data
|
||||||
Error_Certificate_Missing = 0x00080, ///< Certificate is missing
|
Error_Certificate_Missing = 0x00000080, ///< Certificate is missing
|
||||||
Error_Certificate_Generic = 0x00100, ///< Generic error during certificate verification
|
Error_Certificate_Generic = 0x00000100, ///< Generic error during certificate verification
|
||||||
Error_Certificate_Expired = 0x00200, ///< Certificate has expired
|
Error_Certificate_Expired = 0x00000200, ///< Certificate has expired
|
||||||
Error_Certificate_SelfSigned = 0x00400, ///< Self signed certificate
|
Error_Certificate_SelfSigned = 0x00000400, ///< Self signed certificate
|
||||||
Error_Certificate_SelfSignedChain = 0x00800, ///< Self signed certificate in chain
|
Error_Certificate_SelfSignedChain = 0x00000800, ///< Self signed certificate in chain
|
||||||
Error_Certificate_TrustedNotFound = 0x01000, ///< No trusted certificate was found
|
Error_Certificate_TrustedNotFound = 0x00001000, ///< No trusted certificate was found
|
||||||
Error_Certificate_Revoked = 0x02000, ///< Certificate has been revoked
|
Error_Certificate_Revoked = 0x00002000, ///< Certificate has been revoked
|
||||||
Error_Certificate_Other = 0x04000, ///< Other certificate error. See OpenSSL code for details.
|
Error_Certificate_Other = 0x00004000, ///< Other certificate error. See OpenSSL code for details.
|
||||||
|
|
||||||
Error_Signature_Invalid = 0x08000, ///< Signature is invalid for some reason
|
Error_Signature_Invalid = 0x00008000, ///< Signature is invalid for some reason
|
||||||
Error_Signature_SourceCertificateMissing = 0x10000, ///< Source certificate of signature is missing
|
Error_Signature_SourceCertificateMissing = 0x00010000, ///< Source certificate of signature is missing
|
||||||
Error_Signature_NoSignaturesFound = 0x20000, ///< No signatures found
|
Error_Signature_NoSignaturesFound = 0x00020000, ///< No signatures found
|
||||||
Error_Signature_DigestFailure = 0x40000, ///< Digest failure
|
Error_Signature_DigestFailure = 0x00040000, ///< Digest failure
|
||||||
Error_Signature_DataOther = 0x80000, ///< Signed data were not verified
|
Error_Signature_DataOther = 0x00080000, ///< Signed data were not verified
|
||||||
|
Error_Signature_DataCoveredBySignatureMissing = 0x00100000, ///< Data covered by signature are not present
|
||||||
|
|
||||||
|
Warning_Signature_NotCoveredBytes = 0x00200000, ///< Some bytes in source data are not covered by signature
|
||||||
|
|
||||||
Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic |
|
Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic |
|
||||||
Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound |
|
Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound |
|
||||||
Error_Certificate_Revoked | Error_Certificate_Other,
|
Error_Certificate_Revoked | Error_Certificate_Other,
|
||||||
|
|
||||||
Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound |
|
Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound |
|
||||||
Error_Signature_DigestFailure | Error_Signature_DataOther,
|
Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing,
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag)
|
Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag)
|
||||||
|
|
||||||
@ -299,6 +302,8 @@ public:
|
|||||||
void addSignatureCertificateMissingError();
|
void addSignatureCertificateMissingError();
|
||||||
void addSignatureDigestFailureError();
|
void addSignatureDigestFailureError();
|
||||||
void addSignatureDataOtherError();
|
void addSignatureDataOtherError();
|
||||||
|
void addSignatureDataCoveredBySignatureMissingError();
|
||||||
|
void addSignatureNotCoveredBytesWarning(PDFInteger count);
|
||||||
|
|
||||||
bool isValid() const { return hasFlag(OK); }
|
bool isValid() const { return hasFlag(OK); }
|
||||||
bool isCertificateValid() const { return hasFlag(Certificate_OK); }
|
bool isCertificateValid() const { return hasFlag(Certificate_OK); }
|
||||||
@ -312,17 +317,22 @@ public:
|
|||||||
PDFObjectReference getSignatureFieldReference() const { return m_signatureFieldReference; }
|
PDFObjectReference getSignatureFieldReference() const { return m_signatureFieldReference; }
|
||||||
const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; }
|
const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; }
|
||||||
const QStringList& getErrors() const { return m_errors; }
|
const QStringList& getErrors() const { return m_errors; }
|
||||||
|
const QStringList& getWarnings() const { return m_warnings; }
|
||||||
|
|
||||||
void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName);
|
void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName);
|
||||||
void setSignatureFieldReference(PDFObjectReference signatureFieldReference);
|
void setSignatureFieldReference(PDFObjectReference signatureFieldReference);
|
||||||
|
|
||||||
void addCertificateInfo(PDFCertificateInfo info) { m_certificateInfos.emplace_back(qMove(info)); }
|
void addCertificateInfo(PDFCertificateInfo info) { m_certificateInfos.emplace_back(qMove(info)); }
|
||||||
|
|
||||||
|
/// Adds OK flag, if both certificate and signature are valid
|
||||||
|
void validate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VerificationFlags m_flags = None;
|
VerificationFlags m_flags = None;
|
||||||
PDFObjectReference m_signatureFieldReference;
|
PDFObjectReference m_signatureFieldReference;
|
||||||
QString m_signatureFieldQualifiedName;
|
QString m_signatureFieldQualifiedName;
|
||||||
QStringList m_errors;
|
QStringList m_errors;
|
||||||
|
QStringList m_warnings;
|
||||||
PDFCertificateInfos m_certificateInfos;
|
PDFCertificateInfos m_certificateInfos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@ protected:
|
|||||||
void verifySignature(PDFSignatureVerificationResult& result) const;
|
void verifySignature(PDFSignatureVerificationResult& result) const;
|
||||||
void addTrustedCertificates(X509_STORE* store) const;
|
void addTrustedCertificates(X509_STORE* store) const;
|
||||||
|
|
||||||
|
BIO* getSignedDataBuffer(PDFSignatureVerificationResult& result, QByteArray& outputBuffer) const;
|
||||||
|
|
||||||
/// Return a list of certificates from PKCS7 object
|
/// Return a list of certificates from PKCS7 object
|
||||||
static STACK_OF(X509)* getCertificates(PKCS7* pkcs7);
|
static STACK_OF(X509)* getCertificates(PKCS7* pkcs7);
|
||||||
|
|
||||||
|
@ -278,4 +278,75 @@ std::vector<PDFDependentLibraryInfo> PDFDependentLibraryInfo::getLibraryInfo()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFClosedIntervalSet::addInterval(PDFInteger low, PDFInteger high)
|
||||||
|
{
|
||||||
|
m_intervals.emplace_back(low, high);
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PDFClosedIntervalSet::isCovered(PDFInteger low, PDFInteger high)
|
||||||
|
{
|
||||||
|
PDFClosedIntervalSet temporary;
|
||||||
|
temporary.addInterval(low, high);
|
||||||
|
return *this == temporary;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFInteger PDFClosedIntervalSet::getTotalLength() const
|
||||||
|
{
|
||||||
|
return std::accumulate(m_intervals.cbegin(), m_intervals.cend(), 0, [](PDFInteger count, const auto& b) { return count + b.second - b.first + 1; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFClosedIntervalSet::normalize()
|
||||||
|
{
|
||||||
|
// Algorithm:
|
||||||
|
// 1) sort all ranges
|
||||||
|
// 2) merge adjacent ones
|
||||||
|
|
||||||
|
qSort(m_intervals);
|
||||||
|
|
||||||
|
std::vector<ClosedInterval> intervals;
|
||||||
|
auto it = m_intervals.cbegin();
|
||||||
|
auto itEnd = m_intervals.cend();
|
||||||
|
|
||||||
|
while (it != itEnd)
|
||||||
|
{
|
||||||
|
ClosedInterval interval = *it++;
|
||||||
|
|
||||||
|
while (it != itEnd && overlapsOrAdjacent(interval, *it))
|
||||||
|
{
|
||||||
|
interval = std::make_pair(qMin(interval.first, it->first), qMax(interval.second, it->second));
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
intervals.push_back(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_intervals = qMove(intervals);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PDFClosedIntervalSet::overlapsOrAdjacent(ClosedInterval a, ClosedInterval b)
|
||||||
|
{
|
||||||
|
if (a.first > b.first)
|
||||||
|
{
|
||||||
|
std::swap(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are 3 cases
|
||||||
|
// a b
|
||||||
|
// |---| |---| 1)
|
||||||
|
//
|
||||||
|
// a b
|
||||||
|
// |---||---| 2)
|
||||||
|
//
|
||||||
|
// a b
|
||||||
|
// |---| 3)
|
||||||
|
// |---|
|
||||||
|
//
|
||||||
|
// We cover cases 2) and 3) with following condition:
|
||||||
|
// [a1, a2], [b1, b2]
|
||||||
|
// b1 <= a2 + 1, because we can have intervals [1,2], [3,4] - these should be merged
|
||||||
|
|
||||||
|
return b.first <= a.second + 1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -526,6 +526,47 @@ private:
|
|||||||
QString m_errorMessage;
|
QString m_errorMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Set of closed intervals
|
||||||
|
class PDFClosedIntervalSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit inline PDFClosedIntervalSet() = default;
|
||||||
|
|
||||||
|
bool operator ==(const PDFClosedIntervalSet&) const = default;
|
||||||
|
|
||||||
|
using ClosedInterval = std::pair<PDFInteger, PDFInteger>;
|
||||||
|
|
||||||
|
/// Adds closed interval, where \p low is lower bound
|
||||||
|
/// of the closed interval, and high is upper bound
|
||||||
|
/// of closed interval.
|
||||||
|
/// \param low Lower bound of interval
|
||||||
|
/// \param high Upper bound of interval
|
||||||
|
void addInterval(PDFInteger low, PDFInteger high);
|
||||||
|
|
||||||
|
/// Adds a single value to the interval (closed interval
|
||||||
|
/// of single value)
|
||||||
|
/// \param value Value
|
||||||
|
void addValue(PDFInteger value) { addInterval(value, value); }
|
||||||
|
|
||||||
|
/// Returns true, if given closed range is subset of
|
||||||
|
/// this interval set.
|
||||||
|
bool isCovered(PDFInteger low, PDFInteger high);
|
||||||
|
|
||||||
|
/// Returns sum of interval lengths
|
||||||
|
PDFInteger getTotalLength() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Normalizes interval ranges - merges adjacent intervals
|
||||||
|
void normalize();
|
||||||
|
|
||||||
|
/// Returns true, if interval overlaps, or is adjacent to the other one
|
||||||
|
/// \param a First interval
|
||||||
|
/// \param b Second interval
|
||||||
|
static bool overlapsOrAdjacent(ClosedInterval a, ClosedInterval b);
|
||||||
|
|
||||||
|
std::vector<ClosedInterval> m_intervals;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
|
||||||
#endif // PDFUTILS_H
|
#endif // PDFUTILS_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user