ITextSharp SetVisibleSignature not working as expected - c#

So I have been working with the Java implementation of IText, but now I'm pretty much writing a port of our signing process to C#, and I've hit a snag. So when im signing my document using the SetVisibleSignature(rect, page, name) overload, it signs the document as expected(as long as the signature field does not exist), but when i use the overload SetVisibleSignature(name) to sign an existing field, it does not actually sign the document. Am I doing something stupid perhaps and missing something?
Thank you for any help.
Updated Code
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using BouncyCastle = Org.BouncyCastle;
public class DocumentSigner : IDocumentSigner
{
private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss";
private readonly IDateTimeProvider _dateTimeProvider;
private readonly ISettingManager _settingManager;
public DocumentSigner(IDateTimeProvider dateTimeProvider, ISettingManager settingManager)
{
Guard.ArgumentNotNull(dateTimeProvider, "dateTimeProvider");
Guard.ArgumentNotNull(settingManager, "settingManager");
_dateTimeProvider = dateTimeProvider;
_settingManager = settingManager;
}
public byte[] Sign(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify)
{
document = AddMetaData(information, document);
document = AddSignatureFields(information, signingBlocks, document);
return SignDocument(certificate, information, signingBlocks, signatureImages, document, certify);
}
private byte[] AddMetaData(SigningInformation information, byte[] document)
{
if (information.CustomProperties != null && information.CustomProperties.Any())
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
Dictionary<string, string> currentProperties = reader.Info;
foreach (string key in information.CustomProperties.Keys)
{
if (currentProperties.ContainsKey(key))
{
currentProperties[key] = information.CustomProperties[key];
}
else
{
currentProperties.Add(key, information.CustomProperties[key]);
}
}
stamper.MoreInfo = currentProperties;
}
}
return outputStream.ToArray();
}
}
return document;
}
private byte[] AddSignatureFields(SigningInformation information, List<SigningBlock> signingBlocks, byte[] document)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
CreateSignatureField(reader, stamper, signingBlocks[i]);
}
}
document = outputStream.ToArray();
}
}
return document;
}
private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, SigningInformation information, bool certify)
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Location = information.Location;
appearance.Reason = information.Purpose;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(appearance, certify);
return appearance;
}
private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify)
{
if (certify)
{
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING;
}
else
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}
private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document)
{
return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true);
}
private void CreateSignatureField(PdfReader reader, PdfStamper stamper, SigningBlock signingBlock)
{
if (signingBlock == null)
{
return;
}
if (!DoesSignatureFieldExist(reader, signingBlock.Name))
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.SetWidget(new Rectangle(signingBlock.X, signingBlock.Y, signingBlock.X + signingBlock.Width, signingBlock.Y + signingBlock.Height), null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.FieldName = signingBlock.Name;
signatureField.Page = signingBlock.Page;
stamper.AddAnnotation(signatureField, signingBlock.Page);
}
}
private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
{
if (String.IsNullOrWhiteSpace(signatureFieldName))
{
return false;
}
return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}
private byte[] GetSignatureImage(List<MemberItemSignature> signatureImages, string signingBlockName)
{
MemberItemSignature signature = signingBlockName.Contains("Initial") ? signatureImages.Where(image => image.Use == SignatureUses.Initial).FirstOrDefault() : signatureImages.Where(image => image.Use == SignatureUses.Signature).FirstOrDefault();
if (signature != null)
{
return signature.Image;
}
else
{
return null;
}
}
private byte[] SignDocument(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document))
{
SigningBlock signingBlock = signingBlocks[i];
PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, information, certify && i == 0);
SignDocumentSigningBlock(certificate, information, signingBlock, appearance, stamper, GetSignatureImage(signatureImages, signingBlock.Name));
}
}
document = outputStream.ToArray();
}
}
return document;
}
private void SignDocumentSigningBlock(Certificate certificate, SigningInformation information, SigningBlock block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
{
X509Certificate2 x509Certificate = new X509Certificate2(certificate.Bytes, certificate.Password, X509KeyStorageFlags.Exportable);
appearance.SetVisibleSignature(block.Name);
SignDocumentSigningBlockWithImage(signatureImage, appearance);
SignDocumentSigningBlockWithText(appearance, x509Certificate);
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509Certificate.PrivateKey)
{
IExternalSignature externalSignature = new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsa).Private, _settingManager["DocumentSigningEncryptionHashAlgorithm"]);
MakeSignature.SignDetached(appearance, externalSignature, new BouncyCastle::X509.X509Certificate[] { DotNetUtilities.FromX509Certificate(x509Certificate) }, null, null, new TSAClientBouncyCastle(_settingManager["DocumentSigningTimestampingServiceAddress"]), Int32.Parse(_settingManager["DocumentSigningEstimatedTimestampSize"]), CryptoStandard.CMS);
}
}
private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance)
{
if (signatureImage != null && signatureImage.Length > 0)
{
Image signatureImageInstance = Image.GetInstance(signatureImage);
appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}
private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate2 x509Certificate)
{
if (x509Certificate == null)
{
return;
}
appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate);
appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = true;
}
private string SignDocumentSigningBlockWithTextBuildText(X509Certificate2 x509Certificate)
{
Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerName.Name);
string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty;
string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty;
string signDate = _dateTimeProvider.Now.ToString(_datetimeFormat);
string expirationDate = x509Certificate.NotAfter.ToString(_datetimeFormat);
return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}
private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string[] issuerFields = issuer.Split(',');
foreach (string field in issuerFields)
{
string[] fieldSplit = field.Split('=');
string key = fieldSplit[0].Trim();
string value = fieldSplit[1].Trim();
if (!fields.Keys.Contains(key))
{
fields.Add(key, value);
}
else
{
fields[key] = value;
}
}
return fields;
}
}
Values
_settingManager["DocumentSigningEncryptionHashAlgorithm"] = "SHA-256";
_settingManager["DocumentSigningTimestampingServiceAddress"] = "http://services.globaltrustfinder.com/adss/tsa";
_settingManager["DocumentSigningEstimatedTimestampSize"] = 104000;

The code provided by the OP references and accesses multiple objects of unknown classes. To make it runnable, therefore, it had to be cut down to be self-contained.
The cut-down version fortunately could still be used to reproduce and analyze the issue, cf. the post scriptum. Any statement from here on is based on the behavior of this cut-down version.
The issue observed by the OP could be reproduced using iTextSharp 5.5.7 (and analogously using iText 5.5.7), and also very interestingly it could not be reproduced using version 5.5.6 of either library. As I'm more into Java, I looked into the changes in iText. They had been ported to iTextSharp in a very faithful fashion.
Indeed, this issue is a regression, signing pre-existing empty signature fields in append mode is broken in iText(Sharp) 5.5.7.
Between 5.5.6 and 5.5.7 a change has been made to PdfSignatureAppearance.preClose. If signing an existing signature field, the code used to manipulate the first widget of the signature field in question (af.getFieldItem(name).getWidget(0)), now it works on the associated merged dictionary (af.getFieldItem(name).getMerged(0)).
Unfortunately, while the former was an object actually existing in the original PDF and, therefore, calling writer.markUsed for it marked its changed contents for writing to the incremental update section, the latter does not correspond to an object in the original PDF (it is a virtual aggregation of multiple objects), so calling writer.markUsed for it does not mark the changes to be written as incremental update anymore.
So, while the actual signature value still is written to the file, it is not connected to the designated signature field anymore.
The change has been done to fix the method behavior.
Before this preClosed worked incorrectly because it retrieved field dictionary as widget annotation. It's incorrect in case when field and widget dicts are not merged. In case they were merged, everything worked as expected. The latter is the most possible case for digital signature fields, but it isn't obligated according to spec.
(DEV-1448)
This is correct, in case of separate field and widget dictionaries certain changes have to be made to the field, not the widget. Merely the implementation does not work as desired in append mode.
PS: This is the cut-down version of the OP's code:
public class DocumentSigner
{
private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss";
public byte[] Sign(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify, String pattern = null)
{
document = AddMetaData(document);
if (pattern != null)
File.WriteAllBytes(String.Format(pattern, "1"), document);
document = AddSignatureFields(signingBlock, document);
if (pattern != null)
File.WriteAllBytes(String.Format(pattern, "2"), document);
return SignDocument(chain, pk, signingBlock, document, certify);
}
private byte[] AddMetaData(byte[] document)
{
return document;
}
private byte[] AddSignatureFields(string signingBlock, byte[] document)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
CreateSignatureField(reader, stamper, signingBlock);
}
}
document = outputStream.ToArray();
}
return document;
}
private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, bool certify)
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Location = "information.Location";
appearance.Reason = "information.Purpose";
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(appearance, certify);
return appearance;
}
private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify)
{
if (certify)
{
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING;
}
else
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}
private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document)
{
return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true);
}
private void CreateSignatureField(PdfReader reader, PdfStamper stamper, string signingBlock)
{
if (signingBlock == null)
{
return;
}
if (!DoesSignatureFieldExist(reader, signingBlock))
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.SetWidget(new Rectangle(100, 100, 200, 200), null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.FieldName = signingBlock;
signatureField.Page = 1;
stamper.AddAnnotation(signatureField, 1);
}
}
private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
{
if (String.IsNullOrWhiteSpace(signatureFieldName))
{
return false;
}
return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}
private byte[] GetSignatureImage(string signingBlockName)
{
return null;
}
private byte[] SignDocument(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document))
{
PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, certify);
SignDocumentSigningBlock(chain, pk, signingBlock, appearance, stamper, GetSignatureImage(signingBlock));
}
}
document = outputStream.ToArray();
}
return document;
}
private void SignDocumentSigningBlock(ICollection<X509Certificate> chain, ICipherParameters pk, string block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
{
appearance.SetVisibleSignature(block);
SignDocumentSigningBlockWithImage(signatureImage, appearance);
SignDocumentSigningBlockWithText(appearance, chain.First());
IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-256");
MakeSignature.SignDetached(appearance, externalSignature, chain, null, null, new TSAClientBouncyCastle("http://services.globaltrustfinder.com/adss/tsa"), 104000, CryptoStandard.CMS);
}
private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance)
{
if (signatureImage != null && signatureImage.Length > 0)
{
Image signatureImageInstance = Image.GetInstance(signatureImage);
appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}
private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate x509Certificate)
{
if (x509Certificate == null)
{
return;
}
appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate);
appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = true;
}
private string SignDocumentSigningBlockWithTextBuildText(X509Certificate x509Certificate)
{
Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerDN.ToString());
string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty;
string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty;
string signDate = System.DateTime.Now.ToString(_datetimeFormat);
string expirationDate = x509Certificate.NotAfter.ToString();
return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}
private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string[] issuerFields = issuer.Split(',');
foreach (string field in issuerFields)
{
string[] fieldSplit = field.Split('=');
string key = fieldSplit[0].Trim();
string value = fieldSplit[1].Trim();
if (!fields.Keys.Contains(key))
{
fields.Add(key, value);
}
else
{
fields[key] = value;
}
}
return fields;
}
}
PPS: The Java/iText tests have been done using the signTest_2_user2699460 unit test in ComplexSignatureFields.java which works on test-2-user2699460.pdf, an intermediary output of the C# code above.
PPPS: Meanwhile the changes resulting in the regression have been rolled back:
Returned the use of .getWidget method instead of .getMerged since the case, when signature field dictionary and dictionary its widget annotation are not merged, is rather uncommon if can be encountered at all. Moreover the use of merged dictionary instead of widget requires more efforts since .getMerged method returns not actually the dictionary obtained by merging signature field dict and widget annotation dict, but also AcroForm dict.
(DEV-1579)
Thus, the regression most likely will be resolved in version 5.5.8

Related

iText 7 - Problem when signing PDF document

I've tried to sign a PDF file by USB token with CRL distribution points as below:
URI = ldap:///CN=CA2,CN=www,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=cavn,DC=vn?certificateRevocationList?base?objectClass=cRLDistributionPoint
URI = http://cavn.vn/new/CA2.crl
URI = http://www.cavn.vn/new/CA2.crl
The first URI failed to parse
The first question is: why don't iText7 try to read next URI when the first one fails?
public static String GetCRLURL(X509Certificate certificate) {
Asn1Object obj;
try {
obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id);
}
catch (System.IO.IOException) {
obj = (Asn1Object)null;
}
if (obj == null) {
return null;
}
CrlDistPoint dist = CrlDistPoint.GetInstance(obj);
DistributionPoint[] dists = dist.GetDistributionPoints();
foreach (DistributionPoint p in dists) {
DistributionPointName distributionPointName = p.DistributionPointName;
if (DistributionPointName.FullName != distributionPointName.PointType) {
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.Name;
GeneralName[] names = generalNames.GetNames();
foreach (GeneralName name in names) {
if (name.TagNo != GeneralName.UniformResourceIdentifier) {
continue;
}
DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false);
**//Here iText7 always return the first URI. Why?**
return derStr.GetString();
}
}
return null;
}
I tried to modify above code to read next URI and when I use CRLVerifier to verify it as below code
CRLVerifier crlVerifier = new CRLVerifier(null, null);
IList<VerificationOK> verificationOks = crlVerifier.Verify(signCert, issuerCert, date);
It verifies successfully.
But when I sign the document with below code:
void Sign(string srcFile, SysCert.X509Certificate2 signerCert, BCCert.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword)
{
PdfReader pdfReader = null;
PdfDocument pdfDocument = null;
FileStream outfileStream = null;
string tempFileName = string.Empty;
try
{
pdfReader = new PdfReader(srcFile);
pdfDocument = new PdfDocument(pdfReader);
PdfPage lastPage = pdfDocument.GetLastPage();
int pageNumber = pdfDocument.GetPageNumber(lastPage);
iText.Kernel.Geom.Rectangle mediaBox = lastPage.GetMediaBox();
pdfDocument.Close();
pdfReader.Close();
pdfReader = new PdfReader(srcFile);
tempFileName = Path.Combine(Path.GetDirectoryName(srcFile), $"{Path.GetFileNameWithoutExtension(srcFile)}_Signed_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.pdf");
outfileStream = new FileStream(tempFileName, FileMode.Create);
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(pdfReader, outfileStream, stampingProperties);
PdfSignatureAppearance signatureAppearance = pdfSigner.GetSignatureAppearance();
pdfDocument = pdfSigner.GetDocument();
SignatureUtil signUtil = new SignatureUtil(pdfDocument);
IList<String> sigNames = signUtil.GetSignatureNames();
string lastSignatureName = sigNames.LastOrDefault();
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDocument, false);
iText.Kernel.Geom.Rectangle rect = null;
if (acroForm != null && !string.IsNullOrEmpty(lastSignatureName))
{
PdfFormField pdfFormField = acroForm.GetField(lastSignatureName);
PdfArray pdfArray = pdfFormField.GetWidgets().First().GetRectangle();
rect = new iText.Kernel.Geom.Rectangle(pdfArray.ToFloatArray()[0] + 150 + 5, mediaBox.GetY(), 150, 10);
}
else
{
rect = new iText.Kernel.Geom.Rectangle(mediaBox.GetX(), mediaBox.GetY(), 150, 10);
}
string signerName = arrBCChain[0].SubjectDN.GetValueList(X509Name.CN)[0].ToString();
signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
signatureAppearance.SetLayer2Text("Signed by " + signerName);
PdfFont font = PdfFontFactory.CreateFont(Path.Combine(Application.StartupPath, "VietnameseFonts", "vuTimesBold.ttf"), PdfEncodings.IDENTITY_H, true);
signatureAppearance.SetLayer2Font(font);
signatureAppearance.SetLayer2FontSize(5);
signatureAppearance.SetLayer2FontColor(ColorConstants.BLACK);
signatureAppearance.SetPageRect(rect);
signatureAppearance.SetPageNumber(pageNumber);
pdfSigner.SetFieldName(TwofishCryptEngine.Encrypt("MZH_METIT_Signature_" + DateTime.Now.ToString("yyyyMMdd_HHmmss")));
IExternalSignature externalSignature = new AsymmetricAlgorithmSignature((RSACryptoServiceProvider)signerCert.PrivateKey, DigestAlgorithms.SHA256);
IOcspClient ocspClient = new OcspClientBouncyCastle(null);
ICrlClient crlClient = new CrlClientOnline(arrBCChain);
List<ICrlClient> lstCRL = new List<ICrlClient>() { crlClient };
ITSAClient tsaClient = null;
if (string.IsNullOrWhiteSpace(tsaUserName))
tsaClient = new TSAClientBouncyCastle(tsaURL);
else
tsaClient = new TSAClientBouncyCastle(tsaURL, tsaUserName, tsaPassword);
pdfSigner.SignDetached(externalSignature, arrBCChain, lstCRL, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
pdfReader.Close();
outfileStream.Close();
if (File.Exists(srcFile))
File.Delete(srcFile);
if (File.Exists(tempFileName))
File.Move(tempFileName, srcFile);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (pdfDocument != null && !pdfDocument.IsClosed())
pdfDocument.Close();
if (pdfReader != null)
pdfReader.Close();
if (outfileStream != null)
outfileStream.Close();
if (File.Exists(tempFileName))
File.Delete(tempFileName);
}
}
It fails with exception "Unknown tag 13 encountered" in BouncyCastle.Crypto.dll.
But when I try to sign the document by Adobe Reader with the same USB token, it succeeds.
Please tell me why and how I can fix it. Thanks
UPDATE 1
Modified code to read the next URI
public static String GetCRLURL(X509Certificate certificate)
{
Asn1Object obj;
try
{
obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id);
}
catch (System.IO.IOException)
{
obj = (Asn1Object)null;
}
if (obj == null)
{
return null;
}
CrlDistPoint dist = CrlDistPoint.GetInstance(obj);
DistributionPoint[] dists = dist.GetDistributionPoints();
foreach (DistributionPoint p in dists)
{
DistributionPointName distributionPointName = p.DistributionPointName;
if (DistributionPointName.FullName != distributionPointName.PointType)
{
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.Name;
GeneralName[] names = generalNames.GetNames();
foreach (GeneralName name in names)
{
if (name.TagNo != GeneralName.UniformResourceIdentifier)
{
continue;
}
//Hack by AnND: 07 - 12 - 2020: try to parse URL until get valid URL
try
{
DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false);
string url = derStr.GetString();
X509Crl x509Crl = GetCRL(url);
return url;
}
catch
{
}
//End hack
}
}
return null;
}
Full stack trace
> BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length) (IL=0x0087, Native=0x0B31AD68+0x1D6)
BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.ReadObject() (IL≈0x00FB, Native=0x0B31A5F8+0x392)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeSet(byte[] secondDigest, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL≈0x0169, Native=0x14768008+0x64C)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeBytes(byte[] secondDigest, iText.Signatures.PdfSigner.CryptoStandard sigtype, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes) (IL≈0x0000, Native=0x14767F60+0x46)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype, Org.BouncyCastle.Asn1.Esf.SignaturePolicyIdentifier signaturePolicy) (IL≈0x01F5, Native=0x14674510+0x70A)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL=0x0012, Native=0x14673F88+0x3C)
METIT.exe!METIT.SignDocument.Sign(string srcFile, System.Security.Cryptography.X509Certificates.X509Certificate2 signerCert, Org.BouncyCastle.X509.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword) (IL=0x0290, Native=0x0D203A40+0x920)
METIT.exe!METIT.SignDocument.btnSign_Click(object sender, System.EventArgs e) (IL=0x0793, Native=0x0B2B7838+0x166F)
System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e) (IL=0x0021, Native=0x0B8E47F0+0x87)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e) (IL=0x0035, Native=0x0B8E4680+0x78)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent) (IL=0x007E, Native=0x0B8E4190+0x177)
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) (IL=0x0189, Native=0x0B8E34E0+0x59C)
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) (IL=0x04AC, Native=0x07932360+0x7B2)
System.Windows.Forms.dll!System.Windows.Forms.ButtonBase.WndProc(ref System.Windows.Forms.Message m) (IL=0x00DB, Native=0x0B3F9698+0x1E8)
System.Windows.Forms.dll!System.Windows.Forms.Button.WndProc(ref System.Windows.Forms.Message m) (IL=0x0044, Native=0x0B3F95D0+0xB0)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) (IL=0x000C, Native=0x07931F18+0x2E)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) (IL=0x009A, Native=0x07931DA8+0x123)
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) (IL=0x002D, Native=0x07931B90+0xA1)
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) (IL≈0x0177, Native=0x0B29CDA8+0x49D)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) (IL≈0x01FA, Native=0x094BADD8+0x550)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) (IL=0x001C, Native=0x094BA928+0x5D)
System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) (IL=0x0011, Native=0x0B3F9238+0x4F)
METIT.exe!METIT.METITControlForm.Main() (IL=0x0114, Native=0x056CD658+0x29B)
Debuging image
Local variable's value
+ this {Org.BouncyCastle.Asn1.Asn1InputStream} Org.BouncyCastle.Asn1.Asn1InputStream
tag 0x0000002D int
tagNo 0x0000000D int
length 0x0000002D int
isConstructed true bool
+ defIn {Org.BouncyCastle.Asn1.DefiniteLengthInputStream} Org.BouncyCastle.Asn1.DefiniteLengthInputStream
The problem is in URI: http://cavn.vn/new/CA2.crl
After use this code to get byte array from that URI
IList<byte[]> ar = new List<byte[]>();
foreach (Uri urlt in urllist) {
try {
LOGGER.Info("Checking CRL: " + urlt);
Stream inp = SignUtils.GetHttpResponse(urlt);
byte[] buf = new byte[1024];
MemoryStream bout = new MemoryStream();
while (true) {
int n = inp.JRead(buf, 0, buf.Length);
if (n <= 0) {
break;
}
bout.Write(buf, 0, n);
}
inp.Dispose();
ar.Add(bout.ToArray());
LOGGER.Info("Added CRL found at: " + urlt);
}
catch (Exception e) {
LOGGER.Info("Skipped CRL: " + e.Message + " for " + urlt);
}
}
It's still OK. But when it runs into BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length) => fail here because wrong tagNo.
UPDATE 2
My new implementation for CrlClientOnline
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlData = Encoding.UTF8.GetString(crl);
if (crlData.StartsWith("-----BEGIN"))
{
string[] array2 = Regex.Split(crlData, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < array2.Length; i++)
{
if (!array2[i].StartsWith("-----BEGIN") && !array2[i].StartsWith("-----END"))
{
stringBuilder.Append(array2[i] + "\r\n");
}
}
string text = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
array2 = Regex.Split(text, "\r\n|\r|\n");
result.Add(Encoding.UTF8.GetBytes(text));
}
else
{
result.Add(crl);
}
}
return result;
}
}
When I open PDF file, in Revocation tab show valid with local cache of CRL instead of embedded one.
UPDATE 3 - Final code
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlString = Encoding.UTF8.GetString(crl);
if (crlString.StartsWith("-----BEGIN"))
{
string[] linesOfCRL = Regex.Split(crlString, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < linesOfCRL.Length; i++)
{
if (!linesOfCRL[i].StartsWith("-----BEGIN") && !linesOfCRL[i].StartsWith("-----END"))
{
stringBuilder.Append(linesOfCRL[i] + "\r\n");
}
}
string derString = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
result.Add(Convert.FromBase64String(derString));
}
else
{
result.Add(crl);
}
}
return result;
}
}
In short: iText assumes that the CRL to embed is in DER format. But the CRL retrieved from http://cavn.vn/new/CA2.crl is in PEM format. Thus, the attempt to parse its DER structure fails as you observe.
In Detail
The CRL file pointed to by the http URL in the CRL distribution points of your certificate is in PEM format:
-----BEGIN X509 CRL-----
MIMDuB0wgwO3BAIBATANBgkqhkiG9w0BAQUFADAzMQswCQYDVQQGEwJWTjEWMBQG
A1UEChMNTkFDRU5DT01NIFNDVDEMMAoGA1UEAxMDQ0EyFw0yMDA4MDgwODMwMjJa
...
rTh3AXJjJlSJinM/d0jO7o3JcKp2DGaD07DlObWTIQBf+oOs9SDDq+IZHMSolp51
5UE=
-----END X509 CRL-----
But according to RFC 5280 (Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile), section 4.2.1.13 (CRL Distribution Points), it must be in DER format:
If the DistributionPointName contains a general name of type URI, the
following semantics MUST be assumed: the URI is a pointer to the
current CRL for the associated reasons and will be issued by the
associated cRLIssuer. When the HTTP or FTP URI scheme is used, the
URI MUST point to a single DER encoded CRL as specified in
[RFC2585].
Thus, the PKI of your CA is set up incorrectly.
What To Do
As the error is in the setup of your CA, this setup should be fixed. You should inform them accordingly.
Unfortunately, even if they react (which isn't clear to start with), it might take considerable time until the PKI setup is fixed.
Thus, you should also change your code to also be able to handle downloaded PEM format CRLs. You could do so by creating your own ICrlClient implementation that transforms the retrieved CRLs to DER format if need be, e.g. by deriving from CrlClientOnline and overriding GetEncoded(X509Certificate, String) by a version that after retrieving the CRLs via the base method checks each byte[] and transforms it if necessary.

Get all metadata from an existing PDF using iText7

How can I retrieve all metadata stored in a PDF with iText7?
using (var pdfReader = new iText.Kernel.Pdf.PdfReader("path-to-a-pdf-file"))
{
var pdfDocument = new iText.Kernel.Pdf.PdfDocument(pdfReader);
var pdfDocumentInfo = pdfDocument.GetDocumentInfo();
// Getting basic metadata
var author = pdfDocumentInfo.GetAuthor();
var title = pdfDocumentInfo.GetTitle();
// Getting everything else
var someMetadata = pdfDocumentInfo.GetMoreInfo("need-a-key-here");
// How to get all metadata ?
}
I was using this with iTextSharp but I can't figure how to do it with the new iText7.
using (var pdfReader = new iTextSharp.text.pdf.PdfReader("path-to-a-pdf-file"))
{
// Getting basic metadata
var author = pdfReader.Info.ContainsKey("Author") ? pdfReader.Info["Author"] : null;
var title = pdfReader.Info.ContainsKey("Title") ? pdfReader.Info["Title"] : null;
// Getting everything else
var metadata = pdfReader.Info;
metadata.Remove("Author");
metadata.Remove("Title");
// Print metadata
Console.WriteLine($"Author: {author}");
Console.WriteLine($"Title: {title}");
foreach (var line in metadata)
{
Console.WriteLine($"{line.Key}: {line.Value}");
}
}
I am using version 7.1.1 of iText7.
In iText 7 the PdfDocumentInfo class unfortunately does not expose a method to retrieve the keys in the underlying dictionary.
But you can simply retrieve the Info dictionary contents by immediately accessing that dictionary from the trailer dictionary. E.g. for a PdfDocument pdfDocument:
PdfDictionary infoDictionary = pdfDocument.GetTrailer().GetAsDictionary(PdfName.Info);
foreach (PdfName key in infoDictionary.KeySet())
Console.WriteLine($"{key}: {infoDictionary.GetAsString(key)}");
There is problem with "UnicodeBig", "UTF-8" or "PDF" encoded strings.
For example, if PDF is created with Microsoft Word, then "/Creator" is unreadable encoded and needs to be converted:
.
iText7 has own function for that convert:
...ToUnicodeString().
But it is a Method of the PdfString object and PdfDictionary value (PdfObject) hast to be first casted to this PdfString type.
Complete solution as async, "unbreakable" and auto-disposed function:
public static async Task<(Dictionary<string, string> MetaInfo, string Error)> GetMetaInfoAsync(string path)
{
try
{
var metaInfo = await Task.Run(() =>
{
var metaInfoDict = new Dictionary<string, string>();
using (var pdfReader = new PdfReader(path))
using (var pdfDocument = new PdfDocument(pdfReader))
{
metaInfoDict["PDF.PageCount"] = $"{pdfDocument.GetNumberOfPages():D}";
metaInfoDict["PDF.Version"] = $"{pdfDocument.GetPdfVersion()}";
var pdfTrailer = pdfDocument.GetTrailer();
var pdfDictInfo = pdfTrailer.GetAsDictionary(PdfName.Info);
foreach (var pdfEntryPair in pdfDictInfo.EntrySet())
{
var key = "PDF." + pdfEntryPair.Key.ToString().Substring(1);
string value;
switch (pdfEntryPair.Value)
{
case PdfString pdfString:
value = pdfString.ToUnicodeString();
break;
default:
value = pdfEntryPair.Value.ToString();
break;
}
metaInfoDict[key] = value;
}
return metaInfoDict;
}
});
return (metaInfo, null);
}
catch (Exception ex)
{
if (Debugger.IsAttached) Debugger.Break();
return (null, ex.Message);
}
}

Fill in Word template and save as pdf using openxml and openoffice

I am trying to fill a word document with data from an XML. I am using openXML to fill the document, which works great and save it as .docx. The thing is I have to open Word and save the document as an .odt and then use the OpenOffice SDK to open the .docx and save it as a pdf. When I don't save the .docx as .odt, the formatting is off.
What I need to be able to do is be able to either convert the .docx to .odt or save it originally as .odt.
Here is what I have right now:
static void Main()
{
string documentText;
XmlDocument xmlDoc = new XmlDocument(); // Create an XML document object
xmlDoc.Load("C:\\Cache\\MMcache.xml"); // Load the XML document from the specified file
XmlNodeList PatientFirst = xmlDoc.GetElementsByTagName("PatientFirst");
XmlNodeList PatientSignatureImg = xmlDoc.GetElementsByTagName("PatientSignatureImg");
byte[] byteArray = File.ReadAllBytes("C:\\Cache\\TransportationRunReporttemplate.docx");
using (MemoryStream stream = new MemoryStream())
{
stream.Write(byteArray, 0, (int)byteArray.Length);
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(stream, true))
{
using (StreamReader reader = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
{
documentText = reader.ReadToEnd();
}
using (StreamWriter writer = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
{
writer.Write(documentText);
}
}
// Save the file with the new name
File.WriteAllBytes("C:\\Cache\\MYFINISHEDTEMPLATE.docx", stream.ToArray());
}
}
private static void AddPicture(Bitmap bitmap)
{
using (WordprocessingDocument doc = WordprocessingDocument.Open("C:\\Cache\\MYFINISHEDTEMPLATE.docx", true))
{
//Bitmap image = new Bitmap("C:\\Cache\\scribus.jpg");
SdtElement controlBlock = doc.MainDocumentPart.Document.Body
.Descendants<SdtElement>()
.Where
(r =>
r.SdtProperties.GetFirstChild<Tag>().Val == "Signature"
).SingleOrDefault();
// Find the Blip element of the content control.
A.Blip blip = controlBlock.Descendants<A.Blip>().FirstOrDefault();
ImagePart imagePart = doc.MainDocumentPart
.AddImagePart(ImagePartType.Jpeg);
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
imagePart.FeedData(stream);
}
blip.Embed = doc.MainDocumentPart.GetIdOfPart(imagePart);
/* DW.Inline inline = controlBlock
.Descendants<DW.Inline>().FirstOrDefault();
// 9525 = pixels to points
inline.Extent.Cy = image.Size.Height * 9525;
inline.Extent.Cx = image.Size.Width * 9525;
PIC.Picture pic = inline
.Descendants<PIC.Picture>().FirstOrDefault();
pic.ShapeProperties.Transform2D.Extents.Cy
= image.Size.Height * 9525;
pic.ShapeProperties.Transform2D.Extents.Cx
= image.Size.Width * 9525;*/
}
ConvertToPDF(#"C:\Cache\MYFINISHEDTEMPLATE2.docx",#"C:\Cache\OpenPdf.pdf");
}
public static Bitmap Base64StringToBitmap(string base64String)
{
Bitmap bmpReturn = null;
byte[] byteBuffer = Convert.FromBase64String(base64String);
MemoryStream memoryStream = new MemoryStream(byteBuffer);
memoryStream.Position = 0;
bmpReturn = (Bitmap)Bitmap.FromStream(memoryStream);
memoryStream.Close();
memoryStream = null;
byteBuffer = null;
return bmpReturn;
}
public static void ConvertToPDF(string inputFile, string outputFile)
{
if (ConvertExtensionToFilterType(System.IO.Path.GetExtension(inputFile)) == null)
throw new InvalidProgramException("Unknown file type for OpenOffice. File = " + inputFile);
StartOpenOffice();
//Get a ComponentContext
var xLocalContext =
Bootstrap.bootstrap();
//Get MultiServiceFactory
var xRemoteFactory =
(XMultiServiceFactory)
xLocalContext.getServiceManager();
//Get a CompontLoader
var aLoader =
(XComponentLoader)xRemoteFactory.createInstance("com.sun.star.frame.Desktop");
//Load the sourcefile
XComponent xComponent = null;
try
{
xComponent = InitDocument(aLoader,
PathConverter(inputFile), "_blank");
//Wait for loading
while (xComponent == null)
{
Thread.Sleep(1000);
}
// save/export the document
SaveDocument(xComponent, inputFile, PathConverter(outputFile));
}
finally
{
if (xComponent != null) xComponent.dispose();
}
}
private static void StartOpenOffice()
{
var ps = Process.GetProcessesByName("soffice.exe");
if (ps.Length != 0)
throw new InvalidProgramException("OpenOffice not found. Is OpenOffice installed?");
if (ps.Length > 0)
return;
var p = new Process
{
StartInfo =
{
Arguments = "-headless -nofirststartwizard",
FileName = "soffice.exe",
CreateNoWindow = true
}
};
var result = p.Start();
if (result == false)
throw new InvalidProgramException("OpenOffice failed to start.");
}
private static XComponent InitDocument(XComponentLoader aLoader, string file, string target)
{
var openProps = new PropertyValue[1];
openProps[0] = new PropertyValue { Name = "Hidden", Value = new Any(true) };
XComponent xComponent = aLoader.loadComponentFromURL(
file, target, 0,
openProps);
return xComponent;
}
private static void SaveDocument(XComponent xComponent, string sourceFile, string destinationFile)
{
var propertyValues = new PropertyValue[2];
// Setting the flag for overwriting
propertyValues[1] = new PropertyValue { Name = "Overwrite", Value = new Any(true) };
//// Setting the filter name
propertyValues[0] = new PropertyValue
{
Name = "FilterName",
Value = new Any(ConvertExtensionToFilterType(System.IO.Path.GetExtension(sourceFile)))
};
((XStorable)xComponent).storeToURL(destinationFile, propertyValues);
}
private static string PathConverter(string file)
{
if (file == null || file.Length == 0)
throw new NullReferenceException("Null or empty path passed to OpenOffice");
return String.Format("file:///{0}", file.Replace(#"\", "/"));
}
public static string ConvertExtensionToFilterType(string extension)
{
switch (extension)
{
case ".odt":
case ".doc":
case ".docx":
case ".txt":
case ".rtf":
case ".html":
case ".htm":
case ".xml":
case ".wps":
case ".wpd":
return "writer_pdf_Export";
case ".xls":
case ".xlsb":
case ".ods":
return "calc_pdf_Export";
case ".ppt":
case ".pptx":
case ".odp":
return "impress_pdf_Export";
default: return null;
}
}
}
}
Just for information I cannot use anything that uses Interop because word will not be installed the machine. I am using OpenXML and OpenOffice
This is what I would try (details below):
1) try Doc format instead of DocX
2) switch to Libre Office and try DocX again
2) use the odf-converter to get a better DocX -> ODT conversion.
More details...
There's something called the odf-conveter (opensource) that can convert DocX->ODT which gives you (typically) more accurate DocX->ODT than Open Office. Take a look at odf-conveter-integrator by OONinja for pre-packaged versions.
Also, Libre Office has DocX support ahead of OpenOffice so you might get a better result simply by switching to Libre Office.
A further option is to start from Doc format rather than DocX. In the OpenOffice world that translates much better to ODT and then to PDF.
Hope that helps.
You may try to use Docxpresso to generate your .odt directly from HTML + CSS code and avoid any conversion issue.
Docxpresso is free for non-commercial use.

iterate through and infoPath form

I have a infopath form that may contain multiple attachments: By using a group of repeating elements, the user can click an “click add item” option he will be able to upload more attachments.
In Sharepoint I am using a workflow to extract the attachments and put them in a separate list. So far I manage only to extract the first one and the workflow finishes successfully.
can I put a loop or something to iterate trough the form?
I attach the code below:
public sealed partial class FileCopyFeature : SharePointSequentialWorkflowActivity
{
public FileCopyFeature()
{
InitializeComponent();
}
public Guid workflowId = default(System.Guid);
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();
private void CopyFile(object sender, EventArgs e)
{
// Retrieve the file associated with the item
// on which the workflow has been instantiated
SPFile file = workflowProperties.Item.File;
if (file == null)
return;
// Get the binary data of the file
byte[] xmlFormData = null;
xmlFormData = file.OpenBinary();
// Load the data into an XPathDocument object
XPathDocument ipForm = null;
if (xmlFormData != null)
{
using (MemoryStream ms = new MemoryStream(xmlFormData))
{
ipForm = new XPathDocument(ms);
ms.Close();
}
}
if (ipForm == null)
return;
// Create an XPathNavigator object to navigate the XML
XPathNavigator ipFormNav = ipForm.CreateNavigator();
ipFormNav.MoveToFollowing(XPathNodeType.Element);
XmlNamespaceManager nsManager =
new XmlNamespaceManager(new NameTable());
foreach (KeyValuePair<string, string> ns
in ipFormNav.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (ns.Key == String.Empty)
{
nsManager.AddNamespace("def", ns.Value);
}
else
{
nsManager.AddNamespace(ns.Key, ns.Value);
}
}
do
{
XPathNavigator nodeNav = ipFormNav.SelectSingleNode("//my:field2", nsManager);
// Retrieve the value of the attachment in the InfoPath form
//XPathNavigator nodeNav = ipFormNav.SelectSingleNode(
//"//my:field2", nsManager);
string ipFieldValue = string.Empty;
if (nodeNav != null)
{
ipFieldValue = nodeNav.Value;
// Decode the InfoPath file attachment
InfoPathAttachmentDecoder dec =
new InfoPathAttachmentDecoder(ipFieldValue);
string fileName = dec.Filename;
byte[] data = dec.DecodedAttachment;
// Add the file to a document library
using (SPWeb web = workflowProperties.Web)
{
SPFolder docLib = web.Folders["Doc"];
docLib.Files.Add(fileName, data);
docLib.Update();
// workflowProperties.Item.CopyTo(data + "/Doc/" + fileName);
}
}
}
while (ipFormNav.MoveToNext());
}
}
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
It appears that your issue has to do with your use of MoveToNext. Per the documentation, this function moves to the next sibling, and does not navigate to children elements. Your code appears to go to the first element it finds (presumably my:myFields), looks for the first child named my:field2 (it only pulls the first one since you are using SelectSingleNode, and then goes to the next sibling of my:myFields (not the next sibling of my:field2). One way to fix this might be to replace your current do-while loop with a call to SelectNodes like the following and then iterate over nodeList.
XmlNodeList nodelist = ipFormNav.SelectNodes("//my:field2", nsManager);

Open-XML saving word document produces corrupted file

Although I'm new to the Open-XML world, I've already encountered some troubles/problems using it. The most of them were easily solved but I can't get around this one:
public class ReportDocument : IDisposable
{
private MemoryStream stream;
private WordprocessingDocument document;
private MainDocumentPart mainPart;
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
byte[] documentData = this.stream.ToArray();
return documentData;
}
}
public ReportDocument()
{
byte[] template = DocumentTemplates.SingleReportTemplate;
this.stream = new MemoryStream();
stream.Write(template, 0, template.Length);
this.document = WordprocessingDocument.Open(stream, true);
this.mainPart = document.MainDocumentPart;
}
public void SetReport(Report report)
{
Body body = mainPart.Document.Body;
var placeholder = body.Descendants<SdtBlock>();
this.SetPlaceholderTextValue(placeholder, "Company", WebApplication.Service.Properties.Settings.Default.CompanyName);
this.SetPlaceholderTextValue(placeholder, "Title", String.Format("Status Report for {0} to {1}", report.StartDate.ToShortDateString(),
report.ReportingInterval.EndDate.ToShortDateString()));
//this.SetPlaceholderTextValue(placeholder, "Subtitle", String.Format("for {0}", report.ReportingInterval.Project.Name));
this.SetPlaceholderTextValue(placeholder, "Author", report.TeamMember.User.Username);
this.SetPlaceholderTextValue(placeholder, "Date", String.Format("for {0}", DateTime.Today.ToShortDateString()));
}
private void SetPlaceholderTextValue(IEnumerable<SdtBlock> sdts, string alias, string value)
{
SdtContentBlock contentBlock = this.GetContentBlock(sdts, alias);
Text text = contentBlock.Descendants<Text>().First();
text.Text = value;
}
private SdtContentBlock GetContentBlock(IEnumerable<SdtBlock> sdts, string alias)
{
return sdts.First(sdt => sdt.Descendants<SdtAlias>().First().Val.Value == alias).SdtContentBlock;
}
public void Dispose()
{
this.document.Close();
}
}
So i create a new document, based on a template it gains through the memory stream and want to write it back to a memory stream when the changes are made.
The big problem is, when i save the resulting byte array the data docx file is corrupted:
The document.xml in .\word is called document2.xml
The document.xml.rels in .\word_rels is called document2.xml.rels and it contains
I hope some of you can provide good solutions for it.
MFG SakeSushiBig
Change your DocumentData property to this and I think it should work. The important thing is to close the document before you read the memorystream.
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
this.document.MainDocumentPart.Document.Save();
this.document.Close();
byte[] documentData = this.stream.ToArray();
return documentData;
}
}

Categories

Resources