Asked  7 Months ago    Answers:  5   Viewed   20 times

This is the output of print_r() run on a typical SimpleXMLElement object:

SimpleXMLElement Object
(
    [@attributes] => Array
        (

        )
)

What does the @ sign mean?

 Answers

23

This is a SimpleXMLElement object. The '@attributes' row is an internal representation of the attributes from the XML element. Use SimpleXML's functions to get data from this object rather than interacting with it directly.

Wednesday, March 31, 2021
 
KingCrunch
answered 7 Months ago
77

It's not really clear to me what you're asking for in the end, however if you want to find out if a SimpleXMLElement hast a Node-Value or not, you can make use of the strlen() function in PHP.

It will take the string context of the SimpleXMLElement which is it's node-value (at least for those leaf-nodes you have in your question) and therefore will return 0 when the node is empty and larger than zero if it contains text.

Accessing a children by an attribute value is not supported by SimpleXMLElement out of the box. The Array-Access works as documented to access attributes, not children; See Example #5 Using attributes.

However you can extend SimpleXMLElement to add that functionality, for example to get a children by the Type attribute value:

class MyXMLElement extends SimpleXMLElement
{
    public function byType($value) {
        list($result) = ((array)$this->getByAttribute('Type', $value)) + array(NULL);
        return $result[0];
    }

    public function getByAttribute($attribute, $value) {
        return $this->xpath(sprintf('.//*[@%s = "%s"]', $attribute, $value));
    }
}

This new variant can be used instead of the old one so that you can easily access what you're looking for:

$team = simplexml_load_string($buffer, 'MyXMLElement');

echo "Team ",  $team['uID'], " Player(s):n";
foreach($team->Player as $i => $player)
{
    printf(" %d. %s %sn", $i + 1, $player->byType('first_name'), $player->byType('last_name'));
}

This for example with the reduced example you've got in your question making <Team> the root-element outputs:

Team t684 Player(s):
 1. Manuel Neuer

You find accessing a children by attribute value in SimpleXML outline as well in the following question:

  • SimpleXML: Selecting Elements Which Have A Certain Attribute Value (Dec 2009)
  • Access value through parent attribute (Jun 2010)
  • Help accessing xml attribute in php (Aug 2010)
  • Implementing condition in XPath (Aug 2010)

Last time I extended SimpleXMLElement on Stackoverflow was in an answer to Displaying 5 latest thumbnails from public flickr api using atom_1 and php.

No, actually turned out that last time I extended SimpleXMLElement on Stackoverflow was explaining exactly the same access by attribute value thing in the question PHP/XML - how to read multible sub's

Wednesday, March 31, 2021
 
twk
answered 7 Months ago
twk
70

You have a network/DNS problem, not a PHP problem. Seems your machine cannot resolve the IP of api.flickr.com.

"su" to your web server user and try to resolve the name there, i.e.

$ sudo bash
  # get root
$ su - apache
  # we're the apache user now
$ ping api.flickr.com
Saturday, May 29, 2021
 
barden
answered 5 Months ago
55

As far as I know, you can't do it with SimpleXML because addChild doesn't make a deep copy of the element (being necessary to specify the tag name can easily be overcome by calling SimpleXMLElement::getName()).

One solution would be to use DOM instead:

With this function:

function sxml_append(SimpleXMLElement $to, SimpleXMLElement $from) {
    $toDom = dom_import_simplexml($to);
    $fromDom = dom_import_simplexml($from);
    $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
}

We have for

<?php
header("Content-type: text/plain");
$sxml = simplexml_load_string("<root></root>");

$n1 = simplexml_load_string("<child>one</child>");
$n2 = simplexml_load_string("<child><k>two</k></child>");

sxml_append($sxml, $n1);
sxml_append($sxml, $n2);

echo $sxml->asXML();

the output

<?xml version="1.0"?>
<root><child>one</child><child><k>two</k></child></root>

See also some user comments that use recursive functions and addChild, e.g. this one.

Friday, July 16, 2021
 
Null
answered 3 Months ago
48

A first quick look through your code revealed two major errors.

Hashing twice

You hash the document data twice (using different APIs for that... weird!):

        Stream data = appearance.GetRangeStream();

        byte[] hash = DigestAlgorithms.Digest(data, "SHA256");

        [...]

        _signatureHash = hash;// signatureHash;
    }
}

[...]
using (SHA256.Create())
{
    _signatureHash = SHA256.Create().ComputeHash(_signatureHash);
}

This is wrong, this makes no sense.

Injecting the wrong signature container

You say

Requesting Esign services give response in PKCS7(CMS) format.

But instead of using the CMS signature container from the result as such, you try to build an own CMS container, injecting the Esign response CMS container as if it was a mere signed hash:

XmlNodeList UserX509Certificate = xmlDoc.GetElementsByTagName("UserX509Certificate");
byte[] rawdat = Convert.FromBase64String(UserX509Certificate[0].InnerText);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
    Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(rawdat))
};
var signaturee = new PdfPKCS7(null, chain, "SHA256", false);
_signature = signaturee;

_signature.SetExternalDigest(Convert.FromBase64String(signature), null, "RSA");

byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);

According to your comments in the XML

    <DocSignature error="" id="1" sigHashAlgorithm="SHA256">--Signature in base 64 in PKCS7(CMS)---</DocSignature>

this DocSignature element contains the CMS signature container.

Thus, remove the code segment above and instead put the content of the DocSignature element (don't forget to base64 decode) into the byte[] encodedSignature. Now you can inject it into the prepared signature as before:

IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);

MakeSignature.SignDeferred(reader, "sign1", os, external);

After you fixed the issues above, two more became apparent:

Using the wrong file mode

You open the stream to write to like this:

using (FileStream os = System.IO.File.OpenWrite(signedPdf))

File.OpenWrite is documented on docs.microsoft.com to be

equivalent to the FileStream(String, FileMode, FileAccess, FileShare) constructor overload with file mode set to OpenOrCreate, the access set to Write, and the share mode set to None.

The file mode OpenOrCreate in turn is documented to specify

that the operating system should open a file if it exists; otherwise, a new file should be created.

Thus, if there already is a file at the given location, that file remains and you start writing into it.

If the new file you create is longer than the old one, this is no problem, you eventually overwrite all the old file content and then the file grows to house the additional new content.

But if the new file you create is shorter than the old one, you have a problem: After the end of the new file there still is data from the old, longer file. Thus, your result is a hodgepodge of two files.

This happened in case of the example files you shared, your new content of "signedPdf.pdf" is only 175982 bytes long but there appears to have been some older file with that name which was 811986 bytes long. Thus, the "signedPdf.pdf" file you shared is 811986 bytes long, the first 175982 bytes containing the result of your operation, the rest data from some other file.

If you cut down your shared "signedPdf.pdf" file to its first 175982 bytes, the result looks much better!

To solve this issue you should use the file mode Create which is documented to be

equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate.

using (FileStream os = new FileStream(signedPdf, FileMode.Create, FileAccess.Write, FileShare.None))

An issue with your signing service - identity not yet valid

As mentioned above, if you cut down your shared "signedPdf.pdf" file to its first 175982 bytes, the result looks much better! Unfortunately merely better, not yet good:

Signature Panel

The reason for your "identity has expired or is not yet valid" becomes clearer by looking at the details:

Signature Properties

I.e. the signing time claimed by the PDF is 09:47:59 UTC+1.

But looking at the certificate:

Certificate Viewer

I.e. your certificate is valid not before 09:48:40 UTC+1.

Thus, the claimed signing time is more than half a minute before your user certificate became valid! This obviously cannot be accepted by a validator...

Apparently your signing service creates a short-time certificate for you on demand, valid from just then for half an hour. And the time at which you started creating the PDF signature is not in that interval.

I doubt they will change the design of the signing service for your requirements. Thus, you'll have to cheat a bit and use a signing time slightly in the future.

By default the signing time is set to the current by the PdfSignatureAppearance constructor, i.e. when this line executes:

PdfSignatureAppearance appearance = stamper.SignatureAppearance;

Fortunately you can change this claimed signing time if you immediately use

appearance.SignDate = [some other date time];

The date time you should use here has to be shortly (I'd propose not more than 5 minutes) after the time you will call your signing service.

This of course implies that you cannot arbitrarily wait until executing that service call. As soon as you assigned the claimed signing time above, you are committed to have successfully called your signing service shortly before that claimed time!

Furthermore, if that signing service turns out to react only slowly or only after some retries, your software should definitively check the certificate in the signature container you retrieve from it and compare its validity interval with your claimed signing time. If the claimed signing time is not in that interval, start signing again!


Now it became apparent that the AllPagesSignatureContainer you used was designed for a very special use case and still had to be adapted to your use case.

Adapting the AllPagesSignatureContainer for append mode

The AllPagesSignatureContainer implementation essentially copied from this answer worked fine when not signing in append mode but when signing in append mode it failed.

This at first was plausible because that class has to predict the object number that will be used for the signature value. This prediction depends on the exact use case, and switching on append mode changes this use case considerably. Thus, my advice in a comment was

If you need append mode, try to replace the

PdfLiteral PRefLiteral = ...

line in the AllPagesSignatureContainer by

PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");

In my tests that worked but in your tests it still didn't. An analysis of your signed file turned up the cause: My test file was using cross reference tables while yours was using cross reference streams.

Adapting the AllPagesSignatureContainer for append mode and object streams

iText in append mode uses the compression features of the original file, i.e. in case of your file it creates an object stream as soon as storing an indirect object that allows storage in an object stream.

In case of your file iText reserved an object number for the object stream, and it did so between the time the AllPagesSignatureContainer predicted the signature value object number and the time the signature value actually was generated. Thus, in your file the actual signature value object number was higher than the predicted number by 1.

To solve this for PDFs with cross reference streams, therefore, one can simply replace the PdfLiteral PRefLiteral = ... line by

PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages + 1) + " 0 R");

i.e. by adding 1 to the originally predicted value. Unfortunately now the prediction is wrong for PDFs with cross reference tables...

A better way to fix this is to force iText to reserve an object number for the object stream for cross reference stream PDFs before predicting the signature value object number and then use the original prediction code. One way to do this is by creating and writing an indirect object right before the prediction, e.g. like this:

stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);

PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");

The answer the AllPagesSignatureContainer implementation essentially was copied from has been updated accordingly.

Wednesday, August 18, 2021
 
Kingpin2k
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :