how to remove seal When I sign many times - itext7

How I sign pdf by p12:
signer.setFieldName(fieldName);
signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
How I try to remove one sign:
PdfAcroForm.removeField(fieldName);
PdfAcroForm.flattenFields();

If one looks for an example for removing digital signatures on the iText site, one finds this example (as you did). This is a bit misleading as it strictly speaking is an example for flattening a signature, i.e. removing the whole signature field but making its visual appearance part of the page content. Actually the code is flattening not only the signature field but all fields.
You clarified in a comment to your question, though:
i want to remove the signature value and the visualization thereo by fieldName
So you in particular want to change only the signature field, you don't want to actually remove it but merely clear it, and you don't want to keep its appearance. Thus, the example you found does not match your requirements in multiple ways.
First of all, you can only clear the most recently signed signature field(s); otherwise you invalidate any more recent signatures. So we can assume that the signature field you want to clear is the outermost one.
To clear the signature field as you want to, you need to remove its value (the actual signature and some metadata) and the appearances of the field widgets (strictly speaking ISO 32000-2 allows only a single widget but there are a number of PDFs in the wild with signature fields with multiple visualizations), e.g. using this method:
String clearLastSignature(PdfDocument pdfDocument) {
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);
List<String> signatureNames = signatureUtil.getSignatureNames();
if (signatureNames != null && signatureNames.size() > 0) {
String lastSignatureName = signatureNames.get(signatureNames.size() - 1);
PdfFormField lastSignatureField = acroForm.getField(lastSignatureName);
if (null != lastSignatureField.getPdfObject().remove(PdfName.V))
lastSignatureField.getPdfObject().setModified();
for (PdfWidgetAnnotation pdfWidgetAnnotation : lastSignatureField.getWidgets()) {
if (pdfWidgetAnnotation.getPdfObject().remove(PdfName.AP) != null)
pdfWidgetAnnotation.getPdfObject().setModified();
}
return lastSignatureName;
}
return null;
}
(from ClearSignature.java)
This method additionally returns the name of the cleared field which can be helpful if you want to resign it soon after.
You can use this method like this:
try ( PdfReader pdfReader = new PdfReader(source);
PdfWriter pdfWriter = new PdfWriter(clearedFile);
PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode())) {
String clearedSignatureName = clearLastSignature(pdfDocument);
}
(ClearSignature test testClearLastSignatureIncrementally)
Applied to the output "step6_signed_by_alice_bob_carol_and_dave.pdf" of the iText signature whitepaper example C2_11_SignatureWorkflow
you get
and after resigning the signature field you get this
Beware: Depending on PDF permissions and restrictions this may fail for some PDF documents.

Related

Any ar js multimarkers learning tutorial?

I have been searching for ar.js multimarkers tutorial or anything that explains about it. But all I can find is 2 examples, but no tutorials or explanations.
So far, I understand that it requires to learn the pattern or order of the markers, then it stores it in localStorage. This data is used later to display the image.
What I don't understand, is how this "learner" is implemented. Also, the learning process is only used once by the "creator", right? The output file should be stored and then served later when needed, not created from scratch at each person's phone or computer.
Any help is appreciated.
Since the question is mostly about the learner page, I'll try to break it down as much as i can:
1) You need to have an array of {type, URL} objects.
A sample of creating the default array is shown below (source code):
var markersControlsParameters = [
{
type : 'pattern',
patternUrl : 'examples/marker-training/examples/pattern-files/pattern-hiro.patt',
},
{
type : 'pattern',
patternUrl : 'examples/marker-training/examples/pattern-files/pattern-kanji.patt',
}]
2) You need to feed this to the 'learner' object.
By default the above object is being encoded into the url (source) and then decoded by the learner site. What is important, happens on the site:
for each object in the array, an ArMarkerControls object is created and stored:
// array.forEach(function(markerParams){
var markerRoot = new THREE.Group()
scene.add(markerRoot)
// create markerControls for our markerRoot
var markerControls = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, markerParams)
subMarkersControls.push(markerControls)
The subMarkersControls is used to create the object used to do the learning. At long last:
var multiMarkerLearning = new THREEx.ArMultiMakersLearning(arToolkitContext, subMarkersControls)
The example learner site has multiple utility functions, but as far as i know, the most important here are the ArMultiMakersLearning members which can be used in the following order (or any other):
// this method resets previously collected statistics
multiMarkerLearning.resetStats()
// this member flag enables data collection
multiMarkerLearning.enabled = true
// this member flag stops data collection
multiMarkerLearning.enabled = false
// To obtain the 'learned' data, simply call .toJSON()
var jsonString = multiMarkerLearning.toJSON()
Thats all. If you store the jsonString as
localStorage.setItem('ARjsMultiMarkerFile', jsonString);
then it will be used as the default multimarker file later on. If you want a custom name or more areas - then you'll have to modify the name in the source code.
3) 2.1.4 debugUI
It seems that the debug UI is broken - the UI buttons do exist but are nowhere to be seen. A hot fix would be using the 'markersAreaEnabled' span style for the div
containing the buttons (see this source bit).
It's all in this glitch, you can find it under the phrase 'CHANGES HERE' in the arjs code.

How to save an image in a subdirectory on android Q whilst remaining backwards compatible

I'm creating a simple image editor app and therefore need to load and save image files. I'd like the saved files to appear in the gallery in a separate album. From Android API 28 to 29, there have been drastic changes to what extent an app is able to access storage. I'm able to do what I want in Android Q (API 29) but that way is not backwards compatible.
When I want to achieve the same result in lower API versions, I have so far only found way's, which require the use of deprecated code (as of API 29).
These include:
the use of the MediaStore.Images.Media.DATA column
getting the file path to the external storage via Environment.getExternalStoragePublicDirectory(...)
inserting the image directly via MediaStore.Images.Media.insertImage(...)
My question is: is it possible to implement it in such a way, so it's backwards compatible, but doesn't require deprecated code? If not, is it okay to use deprecated code in this situation or will these methods soon be deleted from the sdk? In any case it feels very bad to use deprecated methods so I'd rather not :)
This is the way I found which works with API 29:
ContentValues values = new ContentValues();
String filename = System.currentTimeMillis() + ".jpg";
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "PATH/TO/ALBUM");
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);
I then use the URI returned by the insert method to save the bitmap. The Problem is that the field RELATIVE_PATH was introduced in API 29 so when I run the code on a lower version, the image is put into the "Pictures" folder and not the "PATH/TO/ALBUM" folder.
is it okay to use deprecated code in this situation or will these methods soon be deleted from the sdk?
The DATA option will not work on Android Q, as that data is not included in query() results, even if you ask for it you cannot use the paths returned by it, even if they get returned.
The Environment.getExternalStoragePublicDirectory(...) option will not work by default on Android Q, though you can add a manifest entry to re-enable it. However, that manifest entry may be removed in Android R, so unless you are short on time, I would not go this route.
AFAIK, MediaStore.Images.Media.insertImage(...) still works, even though it is deprecated.
is it possible to implement it in such a way, so it's backwards compatible, but doesn't require deprecated code?
My guess is that you will need to use two different storage strategies, one for API Level 29+ and one for older devices. I took that approach in this sample app, though there I am working with video content, not images, so insertImage() was not an option.
This is the code that works for me. This code saves an image to a subdirectory folder on your phone. It checks the android version of the phone, if its above android q, it runs the required codes and if its below, it runs the code in the else statement.
Source: https://androidnoon.com/save-file-in-android-10-and-below-using-scoped-storage-in-android-studio/
private void saveImageToStorage(Bitmap bitmap) throws IOException {
OutputStream imageOutStream;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME,
"image_screenshot.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.pathSeparator + "AppName");
Uri uri =
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values);
imageOutStream = getContentResolver().openOutputStream(uri);
} else {
String imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES). toString() + "/AppName";
File image = new File(imagesDir, "image_screenshot.jpg");
imageOutStream = new FileOutputStream(image);
}
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
imageOutStream.close();
}
For old API (<29) I place an image into the external media directory and scan it via MediaScannerConnection.
Let's see my code.
This function creates an image file. Pay attention to an appName variable - it's is a name of an album in which the image will be displayed.
override fun createImageFile(appName: String): File {
val dir = File(appContext.externalMediaDirs[0], appName)
if(!dir.exists()) {
ir.mkdir()
}
return File(dir, createFileName())
}
Then, I place an image into the file, and, at last, I run a media scanner like this:
private suspend fun scanNewFile(shot: File): Uri? {
return suspendCancellableCoroutine { continuation ->
MediaScannerConnection.scanFile(
appContext,
arrayOf<String>(shot.absolutePath),
arrayOf(imageMimeType)) { _, uri -> continuation.resume(uri)
}
}
}
After some trial and error, I discovered that it is possible to use MediaStore in a backwards compatible way, such that as much code as possible is shared between the implementations for different versions. The only trick is to remember that if you use MediaColumns.DATA, you need to create the file yourself.
Let's look at the code from my project (Kotlin). This example is for saving audio, not images, but you only need to substitute MIME_TYPE and DIRECTORY_MUSIC for whatever you require.
private fun newFile(): FileDescriptor? {
// Create a file descriptor for a new recording.
val date = DateFormat.getDateTimeInstance().format(Calendar.getInstance().time)
val filename = "$date.mp3"
val values = ContentValues().apply {
put(MediaColumns.TITLE, date)
put(MediaColumns.MIME_TYPE, "audio/mp3")
// store the file in a subdirectory
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaColumns.DISPLAY_NAME, filename)
put(MediaColumns.RELATIVE_PATH, saveTo)
} else {
// RELATIVE_PATH was added in Q, so work around it by using DATA and creating the file manually
#Suppress("DEPRECATION")
val music = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).path
with(File("$music/P2oggle/$filename")) {
#Suppress("DEPRECATION")
put(MediaColumns.DATA, path)
parentFile!!.mkdir()
createNewFile()
}
}
}
val uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)!!
return contentResolver.openFileDescriptor(uri, "w")?.fileDescriptor
}
On Android 10 and above, we use DISPLAY_NAME to set the filename and RELATIVE_PATH to set the subdirectory. On older versions, we use DATA and create the file (and its directory) manually. After this, the implementation for both is the same: we simply extract the file descriptor from MediaStore and return it for use.

Validate file extensions in Apex

End user can upload files in a Visualforce page. In the backend I need to create a list of allowed file extensions, and restrict the user to that list. How do I create the extensions list and validate it? Any help is very much appreciated.
You don't need apex for that?
<apex:inputFile> has accept parameter which you can use. Bear in mind this will check contentType, not extension (which is bit more proper way to do it).
If you still want the validation in apex - probably something like this?
String fileName = 'foobar.xls';
Set<String> acceptedExtensions = new Set<String> {'.doc','.txt', '.jpg'};
Boolean found = false;
for(String s : acceptedExtensions){
if(found = fileName.endsWith(s)){ // yes, there's only one "=", I do want assignment here
break;
}
}
if(!found){ // after the whole loop it's still false?
ApexPages.addMessage(...);
}

Read image IPTC data

I'm having some trouble with reading out the IPTC data of some images, the reason why I want to do this, is because my client has all the keywords already in the IPTC data and doesn't want to re-enter them on the site.
So I created this simple script to read them out:
$size = getimagesize($image, $info);
if(isset($info['APP13'])) {
$iptc = iptcparse($info['APP13']);
print '<pre>';
var_dump($iptc['2#025']);
print '</pre>';
}
This works perfectly in most cases, but it's having trouble with some images.
Notice: Undefined index: 2#025
While I can clearly see the keywords in photoshop.
Are there any decent small libraries that could read the keywords in every image? Or am I doing something wrong here?
I've seen a lot of weird IPTC problems. Could be that you have 2 APP13 segments. I noticed that, for some reasons, some JPEGs have multiple IPTC blocks. It's possibly the problem with using several photo-editing programs or some manual file manipulation.
Could be that PHP is trying to read the empty APP13 or even embedded "thumbnail metadata".
Could be also problem with segments lenght - APP13 or 8BIM have lenght marker bytes that might have wrong values.
Try HEX editor and check the file "manually".
I have found that IPTC is almost always embedded as xml using the XMP format, and is often not in the APP13 slot. You can sometimes get the IPTC info by using iptcparse($info['APP1']), but the most reliable way to get it without a third party library is to simply search through the image file from the relevant xml string (I got this from another answer, but I haven't been able to find it, otherwise I would link!):
The xml for the keywords always has the form "<dc:subject>...<rdf:Seq><rdf:li>Keyword 1</rdf:li><rdf:li>Keyword 2</rdf:li>...<rdf:li>Keyword N</rdf:li></rdf:Seq>...</dc:subject>"
So you can just get the file as a string using file_get_contents(get_attached_file($attachment_id)), use strpos() to find each opening (<rdf:li>) and closing (</rdf:li>) XML tag, and grab the keyword between them using substr().
The following snippet works for all jpegs I have tested it on. It will fill the array $keys with IPTC tags taken from an image on wordpress with id $attachment_id:
$content = file_get_contents(get_attached_file($attachment_id));
// Look for xmp data: xml tag "dc:subject" is where keywords are stored
$xmp_data_start = strpos($content, '<dc:subject>') + 12;
// Only proceed if able to find dc:subject tag
if ($xmp_data_start != FALSE) {
$xmp_data_end = strpos($content, '</dc:subject>');
$xmp_data_length = $xmp_data_end - $xmp_data_start;
$xmp_data = substr($content, $xmp_data_start, $xmp_data_length);
// Look for tag "rdf:Seq" where individual keywords are listed
$key_data_start = strpos($xmp_data, '<rdf:Seq>') + 9;
// Only proceed if able to find rdf:Seq tag
if ($key_data_start != FALSE) {
$key_data_end = strpos($xmp_data, '</rdf:Seq>');
$key_data_length = $key_data_end - $key_data_start;
$key_data = substr($xmp_data, $key_data_start, $key_data_length);
// $ctr will track position of each <rdf:li> tag, starting with first
$ctr = strpos($key_data, '<rdf:li>');
// Initialize empty array to store keywords
$keys = Array();
// While loop stores each keyword and searches for next xml keyword tag
while($ctr != FALSE && $ctr < $key_data_length) {
// Skip past the tag to get the keyword itself
$key_begin = $ctr + 8;
// Keyword ends where closing tag begins
$key_end = strpos($key_data, '</rdf:li>', $key_begin);
// Make sure keyword has a closing tag
if ($key_end == FALSE) break;
// Make sure keyword is not too long (not sure what WP can handle)
$key_length = $key_end - $key_begin;
$key_length = (100 < $key_length ? 100 : $key_length);
// Add keyword to keyword array
array_push($keys, substr($key_data, $key_begin, $key_length));
// Find next keyword open tag
$ctr = strpos($key_data, '<rdf:li>', $key_end);
}
}
}
I have this implemented in a plugin to put IPTC keywords into WP's "Description" field, which you can find here.
ExifTool is very robust if you can shell out to that (from PHP it looks like?)

Core Data - can't set empty string as default value for attribute

I have an entity in my datamodel with a string attribute that is currently optional, and I'd like to convert this to a required attribute with a default value of the empty string.
As others have discovered, leaving the default value blank in the Xcode Core Data data modeler results in validation errors (since the designer interprets this as NULL), but trying '', "", or #"" as the default value results in those literal characters being interpreted as the default, rather than the empty zero-length string, as desired.
I did find this thread on Google, however, apart from the solution being really ugly (model definition split between the .xcdatamodel and objc source), it also doesn't work for lightweight migrations because those migrations are done solely based on the .xcdatamodel files and the objc logic from your entity implementations isn't loaded.
Is there any way to achieve this in the data model designer?
This is a very interesting question. After some testing I don't think this is possible because of the way the text field in the data model is configured.
In principle, you could use the unicode empty-set character of \u2205 to represent a default empty string but the text field does not seem to accept any escapes so it converts any attempt to escape a unicode character code to the literal string of the code characters themselves e.g. entering '\u2205' ends up as the literal text '\u2205'.
In theory you could write a utility app to read in the graphically generated managed object model file and then programmatically set the attribute default to equal an empty string and then save the file back to disk. I say "in theory" because there is no documented way to way to save a managed object model file from code. You can read one and modify it in memory but not persist the changes.
Bit of an oversight, I think.
I don't think you have any choice but to set the default empty string pragmatically when the model first loads. That is simple to do but it's ugly and you'll have to remember you did (especially if you migrate versions) but I think right now that is the only choice.
Whip out your favorite XML editor (I just used Emacs) and dive down to the contents file inside the .xcdatamodel bundle inside the .xcdatamodeld bundle. Then just add a defaultValueString="" XML attribute to the <attribute>...</attribute> element inside the <entity>...</entity> brackets.
Here's an example:
<attribute name="email" attributeType="String" defaultValueString="" syncable="YES"/>
I can't speak to whether this survives migration since I haven't had to do that yet.
I resolved this by overriding the getter for my field - if it contains null, I return an empty string instead:
-(NSString *)unit {
if ([self primitiveValueForKey:#"unit"] == NULL) {
return #"";
} else {
return [self primitiveValueForKey:#"unit"];
}
}
So far it seems to be doing the trick, and I would imagine it wouldn't impact migrations (although I don't know enough about them to say for sure). I don't really care whether there's a null or an empty string in the db, after all - so long as I get "" instead of null when I ask for the field.
My approach to resolving this issue was to create an NSManagedObject subclass and handle the substitution of empty strings for NULL values in awakeFromInsert. I then set all entities as children of this subclass rather than children of NSManagedObject. The assumption here is that I want every string attribute within a given entity to be set to an empty string by default (it wouldn't work, or would at least require extra logic, if you wanted some to remain NULL within the same entity).
There's probably a more efficient way of doing this, but since it's only called upon entity creation, I don't think it is too much of a performance hit.
- (void)awakeFromInsert {
[super awakeFromInsert];
NSDictionary *allAttributes = [[self entity] attributesByName];
NSAttributeDescription *oneAttribute;
for (NSString *oneAttributeKey in allAttributes) {
oneAttribute = [allAttributes objectForKey:oneAttributeKey];
if ([oneAttribute attributeType] == NSStringAttributeType) {
if (![self valueForKey:[oneAttribute name]]) {
[self setValue:#"" forKey:[oneAttribute name]];
}
}
}
}
You can do it manually.
In your model class, override awakeFromInsert and set your strings to empty string
Swift:
override func awakeFromInsert()
{
super.awakeFromInsert()
self.stringProperty = ""
}
Objective-C
- (void) awakeFromInsert
{
[super awakeFromInsert];
self.stringProperty = #"";
}
A simpler solution based on Scott Marks answer to avoid syntax errors:
First, temporarily set the default value to be easy to find, something like here you are. Open with any text editor the contents file inside the .xcdatamodel bundle inside the .xcdatamodeld bundle. Then just do a search with replacing the string "here you are" with the "" in this file.
The migration took place without problems.
Here is the Swift solution based on David Ravetti's answer and edelaney05's comment. In addition, I added optionality check.
This solution works fine in my projects.
class ExampleEntity: NSManagedObject {
...
override func awakeFromInsert() {
super.awakeFromInsert()
for (key, attr) in self.entity.attributesByName {
if attr.attributeType == .stringAttributeType && !attr.isOptional {
if self.value(forKey: key) == nil {
self.setPrimitiveValue("", forKey: key)
}
}
}
}
...
}
Maybe I'm late with this answer, but I was Googling and found this forum.
The solution is very simple:
When you click on the xcdatamodelId (On the left screen)
Change the Entity View to Graph
Double Click on any Attribute you want and the menu will appear on the right.
All changes are easy.
Part 2
Part 3
This appears to have been fixed at some point. Using Xcode 13:
Null String, unchecked Default Value:
<attribute name="myAttributeName" optional="YES" attributeType="String"/>
Empty String, now shown in Xcode interface:
<attribute name="myAttributeName" defaultValueString="" optional="YES" attributeType="String"/>
Entering "" into the field seems wrong and produces """" in the XML:
<attribute name="myAttributeName" defaultValueString="""" optional="YES" attributeType="String"/>

Resources