There are a couple of questions about binary data and GWT already. After reading them I am still not sure if the following is possible or not (I am a complete GWT beginner though!):
I have some very complicated data-files with only exist in binary form and I cannot convert them to something like XML or JSON. I have a closed source library though that accepts a byte[] and returns a Java object I can use. To get my GWT-app running I 'printed out' one of those binary data files and hard-coded the resulting byte[] in a .java file I access from my GWT-app code. Everything works fine. Obviously this is only a test and in the deployed app I cannot hard-code those data-files. I want to place them in the directory my GWT-app resides and the 'load' them with my GWT app.
I take it I can 'load' text files from my server with GWT, right? Why can't I read binary data with GWT? Or can I read the binary-data-files as text and the String into a byte[]? I read a lot about base64 encoding and that GWT can read it, although I don't really understand what they are talking about. Can I configure my server to serve those binary-data-files as base64 encoded and then read them with GWT?
Or is there some other solution? I wouldn't like to touch any JS code if I can help it. That's why I started using GWT ;)
Thanks for your help :)
Let's presume we are on HTML 4.
GWT client cannot "read" files. GWT client is javascript running on a browser. Browser security does not allow you to read local files. You have to get the servlet to proxy read the file for you on the server.
You set the mime type for a file because you want the browser to download a file and invoke the local PC to invoke the appropriate software - for example, pdf to invoke pdf reader or xls to invoke ms excel. Nothing to do with GWT Java or Javascript (except to enable the download).
Why do you need GWT client to read the binary file? If you do, your architecture is probably wrong. "Wrong" is an unkind word. Perhaps, misaligned is a better word. Your concept of AJAX thin client-server is misaligned. Drop your desktop processing concepts and habits at the door when you enter the door of GWT.
GWT is Java but not Java
I keep having to remind people that GWT Java is merely a more coherent representation of Javascript. When you code in GWT Java, always remember you are actually coding in Javascript not Java. All Java source is translated to Javascript
Therefore, the GWT compiler needs all Java classes to be supplied in source code. The GWT compiler has no ability to translate Java bytecode jar/class files into Javascript. If your library is in bytecode or your source library calls a bytecode library anywhere down the calling chain, the compilation will fail.
Confusion between server side and client side GWT
GWT RPC is sometimes source of confusion for GWT newbies. They don't seem to realise that the remote servlet is the only part that is compiled into bytecode because it is running on the server. Especially so, if you are using Vaadin - because they have so intentionally blurred the line between server and browser. And so the GWT newbie goes off wondering, "why do my bytecode libraries work at certain parts of the app only?"
The ajax client server architecture
GWT is merely a web enabled UI. Why can't you do whatever you want to do on the server and let the server reflect what it is doing or has done to the UI? Why must it be done on the browser?
Just imagine your GWT interface as a souped up JSP. Imagine you are writing a JSP. Do you get your JSP to suck your binary data into the browser and get the JSP to generate Javascript to analyse the binary data there?
I have written complex statistical analyses and I merely used the browser as a reflection of what is being done on the server. The engineer thinks he/she is running the analysis on his/her PC. Charts/reports are generated. But it's all done on the server by calling SAS.
The service oriented pattern/architecture
Your server will present services. Your browser GWT client will request for those services.
Open a file, read the file, analyse the file, generate a visual/mime representation of analysis and pass it to the browser. Simply think of the GWT browser client as the display monitor for your server based manipulation. GWT is a magician's trick to help me conjure the illusion to let the engineers feel they are performing analysis on the local PC. Being engineers, of course, most of them know the browser is not actually doing the work.
When your user is satisfied with the analysis, get your service to generate a mime-representation of the results so that it could be downloaded by the browser to invoke the appropriate local PC software as mapped by the mime.
Do it on the server and reflect it on the browser.
Further Edits: Concerning binary data ...
The motivation behind base64 encoding being used in web apps: transmission of auth tokens, picture, audio files - so that their binary representation and sequencing would not be messed up by architectural nuances like endianness.
e.g., do not attempt writing a browser app to read a raw binary spreadsheet - always have the server translate it into XML or JSON (preferably JSON) where any binary element should be base64 encoded, before sending it to the browser app. Or if the purpose of your life is to climb Mt Everest, invent an architecture-agnostic encoding in place of base64 to transmit binary data.
Use only binary info if it was for the browser's OS processing (like audio, pictures, pdfs). No point in sending binary data to be processed solely by a javascript routine. The javascript routine would have to use extraneous processing time to translate it (unless again, if the purpose in your life is to climb ... ).
Yes it is possible.
Two solutions depending on the type of data.
Dynamic (if the binary data is dynamic and might change):
Just Base64 encode your binary data on your backend and serve them (i.e GET request).
Then you can use any of GWT's communication protocols (see here for more details) to retrieve the data from the backend.
You then have to base64 decode the data and work with it (as you already have solved it).
Static (if the binary data won't change and is known during compile time):
You can use ClientBundle (i.e.: DataResource) to generate those binary files during compile time and they can then be automatically retrieved on the client side without manually setting up transferring them.
Client Side:
#Override
public void onModuleLoad()
{
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, "/test");
try
{
rb.sendRequest(null, new RequestCallback()
{
#Override
public void onResponseReceived( Request request, Response response )
{
String encoded = response.getText();
byte[] data = decode(encoded);
System.out.println(Arrays.toString(data));
}
#Override
public void onError( Request request, Throwable exception )
{
}
});
}
catch( RequestException e )
{
e.printStackTrace();
}
}
private final static String base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
public static byte[] decode( String s )
{
// remove/ignore any characters not in the base64 characters list
// or the pad character -- particularly newlines
s = s.replaceAll("[^" + base64chars + "=]", "");
// replace any incoming padding with a zero pad (the 'A' character is
// zero)
String p = (s.charAt(s.length() - 1) == '=' ? (s.charAt(s.length() - 2) == '=' ? "AA" : "A") : "");
s = s.substring(0, s.length() - p.length()) + p;
int resLength = (int) Math.ceil(((s.length()) / 4f) * 3f);
byte[] bufIn = new byte[resLength];
int bufIn_i = 0;
// increment over the length of this encrypted string, four characters
// at a time
for( int c = 0; c < s.length(); c += 4 )
{
// each of these four characters represents a 6-bit index in the
// base64 characters list which, when concatenated, will give the
// 24-bit number for the original 3 characters
int n = (base64chars.indexOf(s.charAt(c)) << 18) + (base64chars.indexOf(s.charAt(c + 1)) << 12)
+ (base64chars.indexOf(s.charAt(c + 2)) << 6) + base64chars.indexOf(s.charAt(c + 3));
// split the 24-bit number into the original three 8-bit (ASCII)
// characters
char c1 = (char) ((n >>> 16) & 0xFF);
char c2 = (char) ((n >>> 8) & 0xFF);
char c3 = (char) (n & 0xFF);
bufIn[bufIn_i++] = (byte) c1;
bufIn[bufIn_i++] = (byte) c2;
bufIn[bufIn_i++] = (byte) c3;
}
byte[] out = new byte[bufIn.length - p.length()];
System.arraycopy(bufIn, 0, out, 0, out.length);
return out;
}
Server Side(Java):
#Override
public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException
{
byte[] binaryData = new byte[1000];
for( int i = 0; i < 1000; i++ )
binaryData[i] = (byte) (Byte.MIN_VALUE + (i % (Math.pow(2, Byte.SIZE))));
System.out.println("Sending: " + Arrays.toString(binaryData));
byte[] base64Encoded = org.apache.commons.codec.binary.Base64.encodeBase64(binaryData);
response.setContentType("application/octet-stream");
PrintWriter out = response.getWriter();
out.write(new String(base64Encoded));
}
Here's a solution that lets you easily read bytes from any URL:
XMLHttpRequest request = XMLHttpRequest.create();
request.open("GET", "http://127.0.0.1:8888/sample/index.bin");
request.setResponseType(ResponseType.ArrayBuffer);
request.setOnReadyStateChange(new ReadyStateChangeHandler() {
#Override
public void onReadyStateChange(XMLHttpRequest xhr) {
if (xhr.getReadyState() == XMLHttpRequest.DONE) {
if (xhr.getStatus() == 200) {
ArrayBuffer buffer = xhr.getResponseArrayBuffer();
Uint8Array array = TypedArrays.createUint8Array(buffer);
System.out.println("got " + array.length() + " bytes: ");
for (int i = 0; i < array.length(); i++) {
System.out.println(array.get(i));
}
} else {
System.out.println("response status: " + xhr.getStatus() + " " + xhr.getStatusText());
}
}
}
});
request.send();
Related
#GET
#Path("/{id}/content")
#Produces({ "application/octet-stream" })
public Response getDocumentContentById(#PathParam("id") String docId) {
InputStream is = getDocumentStream(); // some method which gives stream
ResponseBuilder responseBuilder = Response.ok(is);
responseBuilder.header("Content-Disposition", "attachment; filename=" + fileName);
return responseBuilder.build();
}
Here how can I close the InputStream is ? If something(jax.rs) closes automatically. Please give me some information. Thank you.
When you're wanting to stream a custom response, the most reliable way I've found is to return an object that contains the InputStream (or which can obtain the stream in some other way at some point), and to define a MessageBodyWriter provider that will do the actual streaming at the right time.
For example, this code is part of Apache Taverna, and it streams back the zipped contents of a directory. All that the main code needs to do to use it is to return a ZipStream as the response (which can be packaged in a Response or not) and to ensure that it is dealing with returning the application/zip content type. The final point to note is that since this is dealing with CXF, you need to manually register the provider; unlike with Glassfish, they are not automatically picked up. This is a good thing in sophisticated scenarios, but it does mean that you need to do the registration.
Read through the following references:
iText Digital signature white paper, and C# examples. (specifically chapter 4) For those interested, another great and concise summary of the PDF signing process.
CAPICOM documentation.
Online examples / questions here and on iText mailing list archives, such as here and here.
Hashing code:
BouncyCastle.X509Certificate[] chain = Utils.GetSignerCertChain();
reader = Utils.GetReader();
MemoryStream stream = new MemoryStream();
using (var stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.SetVisibleSignature(
new Rectangle(36, 740, 144, 770),
reader.NumberOfPages,
"SignatureField"
);
sap.Certificate = chain[0];
sap.SignDate = DateTime.Now;
sap.Reason = "testing web context signatures";
PdfSignature pdfSignature = new PdfSignature(
PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED
);
pdfSignature.Date = new PdfDate(sap.SignDate);
pdfSignature.Reason = sap.Reason;
sap.CryptoDictionary = pdfSignature;
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, SIG_BUFFER * 2 + 2);
sap.PreClose(exclusionSizes);
Stream sapStream = sap.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(
sapStream,
DigestAlgorithms.SHA256
);
// is this needed?
PdfPKCS7 sgn = new PdfPKCS7(
null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
hash, sap.SignDate, null, null, CryptoStandard.CMS
);
var hashedValue = Convert.ToBase64String(preSigned);
}
Just a simple test - a dummy Pdf document is created on initial page request, hash is calculated, and put in a hidden input field Base64 encoded. (the hashedValue above)
Then use CAPICOM on client-side to POST the form and get user's signed response:
PdfSignatureAppearance sap = (PdfSignatureAppearance)TempData[TEMPDATA_SAP];
PdfPKCS7 sgn = (PdfPKCS7)TempData[TEMPDATA_PKCS7];
stream = (MemoryStream)TempData[TEMPDATA_STREAM];
byte[] hash = (byte[])TempData[TEMPDATA_HASH];
byte[] originalText = (Encoding.Unicode.GetBytes(hashValue));
// Oid algorithm verified on client side
ContentInfo content = new ContentInfo(new Oid("RSA"), originalText);
SignedCms cms = new SignedCms(content, true);
cms.Decode(Convert.FromBase64String(signedValue));
// CheckSignature does not throw exception
cms.CheckSignature(true);
var encodedSignature = cms.Encode();
/* tried this too, but no effect on result
sgn.SetExternalDigest(
Convert.FromBase64String(signedValue),
null,
"RSA"
);
byte[] encodedSignature = sgn.GetEncodedPKCS7(
hash, sap.SignDate, null, null, null, CryptoStandard.CMS
);
*/
byte[] paddedSignature = new byte[SIG_BUFFER];
Array.Copy(encodedSignature, 0, paddedSignature, 0, encodedSignature.Length);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(
PdfName.CONTENTS,
new PdfString(paddedSignature).SetHexWriting(true)
);
sap.Close(pdfDictionary);
So right now I'm not sure if I'm messing up hashing part, signature part, or both. In signature code snippet above and in client code (not shown) I'm calling what I think is signature verification code, but that may be wrong too, since this is a first for me. Get the infamous "Document has been altered or corrupted since it was signed" invalid signature message when opening the PDF.
Client-side code (not authored by me) can be found here. Source has a variable naming error, which was corrected. For reference, CAPICOM documentation says signed response is in PKCS#7 format.
EDIT 2015-03-12:
After some nice pointers from #mkl and more research, it seems CAPICOM is practicably unusable in this scenario. Although not documented clearly, (what else is new?) according to here and here, CAPICOM expects a utf16 string (Encoding.Unicode in .NET) as input to create a digital signature. From there it either pads or truncates (depending which source in previous sentence in correct) whatever data it receives if the length is an odd number. I.e. signature creation will ALWAYS FAIL if the Stream returned by PdfSignatureAppearance.GetRangeStream() has a length that is an odd number. Maybe I should create an I'm lucky option: sign if ranged stream length is even, and throw an InvalidOperationException if odd. (sad attempt at humor)
For reference, here's the test project.
EDIT 2015-03-25:
To close the loop on this, here's a link to a VS 2013 ASP.NET MVC project. May not the be best way, but it does provide a fully working solution to the problem. Because of CAPICOM's strange and inflexible signing implementation, as described above, knew a possible solution would potentially require a second pass and a way to inject an extra byte if the return value of PdfSignatureAppearance.GetRangeStream() (again, Stream.Length) is an odd number. I was going to try the long and hard way by padding the PDF content, but luckily a co-worker found it was much easier to pad PdfSignatureAppearance.Reason. Requiring a second pass to do something with iText[Sharp], is not unprecedented - e.g. adding page x of y for a document page header/footer.
Use of PdfPkcs7
The server-side code contains this block after the calculation of the range stream digest and before forwarding data to the web page:
PdfPKCS7 sgn = new PdfPKCS7(
null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
hash, sap.SignDate, null, null, CryptoStandard.CMS
);
var hashedValue = Convert.ToBase64String(preSigned);
In the case at hand this is not necessary. It is needed only if the external signing API you use merely returns a signed digest; in that case the PdfPKCS7 instance builds the CMS/PKCS#7 signature container. You, on the other hand, use an API for which you know
CAPICOM documentation says signed response is in PKCS#7 format.
Thus, you don't need and (more to the point) must not use the PdfPKCS7 instance.
What does sign.js sign
The content of the server-side hash variable already is the hash digest value of the data to sign. Thus, the frontend, i.e. the sign.js used there, must not hash it again to get the message digest attribute value to put into the signature.
But sign.js signing methods for IE eventually execute
var signedData = new ActiveXObject("CAPICOM.SignedData");
// Set the data that we want to sign
signedData.Content = src;
SignedData.Content, on the other hand, is documented as
Content Read/write Data to be signed.
(msdn: "SignedData object")
So the hash from the backend is used as data to be signed and not as hash of the data to be signed, you indeed hash twice and so have the wrong hash value there.
Thus, it looks like you have to transmit the whole ranged stream which is not really practical...
"But there used to be signing samples using CAPICOM..."
Indeed some old iTextSharp (version 4.x) signing example used CAPICOM. But that code only worked because it created signatures of PDF signature type adbe.pkcs7.sha1 for which a SHA1 hash of the ranged stream indeed is the data embedded in and signed by the PKCS#7 signature.
This is no real option anymore because
it requires the use of SHA1 which in serious contexts is invalid, and
its use has been discouraged at least since ISO 32000-1 (2008) and will be officially deprecated in ISO 32000-2 (under development).
I have this scenario.
I have an application that generates a PDF, and that needs to be signed.
We have not the certificates to sign the document, because they're in a HSM, and the only way we could make use of the certificates is using a webservice.
This webservice, offers two options, send the PDF document, and it returns a signed pdf, or send a hash that will be signed.
The first option, is not viable, because the PDF is signed without a timestamp (this is a very important requisite), so the second option is chosen.
This is our code, first, we get the signature appearance, and calculate the hash:
PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0', null, true);
appearance = stamper.getSignatureAppearance();
appearance.setCrypto(null, chain, null, PdfSignatureAppearance.SELF_SIGNED);
appearance.setVisibleSignature("Representant");
cal = Calendar.getInstance();
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.TYPE, PdfName.SIG);
dic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
dic.put(PdfName.SUBFILTER, new PdfName("adbe.pkcs7.detached"));
dic.put(PdfName.M, new PdfDate(cal));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, Integer.valueOf(reservedSpace.intValue() * 2 + 2));
appearance.setCertificationLevel(1);
appearance.preClose(exc);
AbstractChecksum checksum = JacksumAPI.getChecksumInstance("sha1");
checksum.reset();
checksum.update(Utils.streamToByteArray(appearance.getRangeStream()));
hash = checksum.getByteArray();
In this point, we have the hash code of the document. Then we send the hash to the webservice, and we get the signed hash code.
Finally, we put the signed hash to the PDF:
byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(signedHash, 0, paddedSig, 0, signedHash.length);
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic);
byte[] pdf = baos.toByteArray();
In this point, we get a PDF signed, but with an invalid signature. Adobe says that "Document has been altered or corrupted since it was signed".
I think that we make something wrong in the process, and we don't know exactly what could be.
We appreciate help on this, or an alternative way to do that.
Thanks.
EDITED
As suggested by mkl, I have followed the 4.3.3 section of this book Digital Signatures for PDF documents, and my code now what that follows:
The first part, when we calculate the hash:
PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');
appearance = stamper.getSignatureAppearance();
appearance.setReason("Test");
appearance.setLocation("A casa de la caputeta");
appearance.setVisibleSignature("TMAQ-TSR[0].Pagina1[0].DadesSignatura[0].Representant[0]");
appearance.setCertificate(chain[0]);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(reservedSpace.intValue() * 2 + 2));
appearance.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest()
{
public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
{
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
hashPdf = new String(Base64.encode(sh));
And in the second part, we get the signed hash, and we put that into the PDF:
sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(encodedSign, 0, paddedSig, 0, encodedSign.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
byte[] pdf = baos.toByteArray();
Now, Adobe raises a Internal Cryptographic library error. Error Code: 0x2726, when we try to validate the signature.
If the web service returned a mere signed hash
In this point, we have the hash code of the document. Then we send the hash to the webservice, and we get the signed hash code.
Finally, we put the signed hash to the PDF:
If the webservice merely returns a signed hash, then your PDF signature is incorrect: You set the signature SubFilter to adbe.pkcs7.detached. This implies that the signature Contents have to contain a full-blown PKCS#7 signature container, not merely a signed hash.
You might want to download Digital Signatures for PDF documents, A White Paper by Bruno Lowagie (iText Software) on creating and verifying digital PDF signatures using iText. It especially contains a section "4.3 Client/server architectures for signing" which should encompass your use cases.
But the web service returns a full-fledged CMS signature container
Following to the explanation above, the OP started using code from section 4.3.3 of the above-mentioned white paper which is intended for signing using externally generated signed hashes. As this also resulted in a signed document Adobe Reader was not happy with, he provided a sample document created with this new code.
Analysis of the sample showed that the CMS signature container embedded in the document contained another CMS signature container where there should have been the signature bytes (the signed hash) for the signed attributes:
2417 13: SEQUENCE {
2419 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
2430 0: NULL
: }
2432 5387: OCTET STRING, encapsulates {
2436 NDEF: SEQUENCE {
2438 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
2449 NDEF: [0] {
2451 NDEF: SEQUENCE {
(The OCTET STRING following the signature algorithm should contain the signature bytes and not embed another SignedData structure.)
This indicates that the web service indeed already returns a full-fledged CMS container.
For such a scenario the original code looked quite ok. The issue might be due to a detail like the use of a wrong hashing algorithm (the original code hashed using SHA1).
A possible issue: BER encoding
The CMS signature container from the web service embedded in the CMS container generated by iText from the first sample provided by the OP hints at a possible issue: Looking at the ASN.1 dump above the sizes of the outer structures in the embedded CMS container are often NDEF.
This indicates that these outer structures are created using the less strict BER (Basic encoding Rules), not the more strict DER (Distinguished Encoding Rules) because the BER option to start a structure without stating its size is forbidden in DER.
The CMS specification (RFC 3852) referenced from the PDF specification does allow any BER encoding for the outer structures of the container, the PDF specification on the other hand requires:
the value of Contents shall be a DER-encoded PKCS#7 binary data
object containing the signature. The PKCS#7 object shall conform to RFC3852 Cryptographic Message Syntax.
Strictly speaking, therefore, signature containers embedded in PDFs are required to be DER encoded all over.
As far as I know no PDF signature validator rejects such signatures as long as the signature container DER-encodes certain pivotal elements. Concerning future tools such signatures are a possible point of failure, though.
After much debugging, we finally found the problem.
For some mysterious reason, the method that generates the hash of the document, was executed twice, invalidating the first hash (which we use to send to the service).
After a refactoring work of the code, the original code worked correctly.
Very thanks to all people that help me, especially mkl.
i mode development in eclipse. the fileupload works just fine. but i will make directory to /var/wms/year/month/file.jpg on linux. this my source code from client:
add component to form
fileUpload = new SingleUploader(FileInputType.LABEL);
fileUpload.setFileInputPrefix("PJ");
fileUpload.addOnFinishUploadHandler(onFinishUploaderHandler);
layoutContainerItemRight.add(fileUpload, formData);
method is addOnFinishUploadHandler
private IUploader.OnFinishUploaderHandler onFinishUploaderHandler = new IUploader.OnFinishUploaderHandler() {
public void onFinish(IUploader uploader) {
if (uploader.getStatus() == gwtupload.client.IUploadStatus.Status.SUBMITING) {
String month = VisionProperties.getBulan();
String year = DateTimeFormat.getFormat( "d-M-yyyy" ).format( new Date() ).split( "-")[2];
String strDirectoy = "/var/wms/" + year + "/" + month + "/";
File file = new File(strDirectoy);
if (!file.exists()) {
file.mkdirs();
}
}
if (uploader.getStatus() == gwtupload.client.IUploadStatus.Status.SUCCESS) {
String msg = uploader.getServerInfo().message;
fileName = msg.toString();
if(selectWindow != 2){
img.setUrl("servlet.gupld?show=&fieldname=" + fileName);
itemPanel.render(img.getElement());
}else{
tb.setVisible(true);
tb.setText("Download File "+uploader.getFileName());
}
}
}
};
how to make directory file when upload file process?
You are trying to use to use java.io.File in the client side which is not supported by the set of packages in the GWT jre emulation.
If you want to do this in client side you have to use the javascript File Api which is not supported by old browsers, and is not implemented in gwt-core. Using elemental you could use the Api only with Chrome, but I'm not positive. So it is better to wrap it via jsni, it is planned in gwtupload, but there is no a timeframe yet. Be aware that using js File Api, you dont have access to your real filesystem, but a virtual one inside your browser. To save created files in the local filesystem you have to download it using and iframe so as it asks the user where to save it.
Otherwise, If you wanted to do this work at server side, do it overriding the executeAction in your servlet if you are extending UploadAction.
You cannot do this on client side. You can perform this on server side in the following ways
before you upload the files to server by another rpc/http call.
after you upload the files to server when the file upload servlet is being executed on the srever side.
HTML5 FILE API are restricted to readonly behavior in even modern browser.
Reference -
1. Basic File upload in GWT
2. How to retrieve file from GWT FileUpload component?
What I'm Trying To Do
I'm trying to create a solution of any kind that will run nightly on a Windows server, authenticate to a website, check a web page on the site for new links indicating a new version of a zip file, use new links (if present) to download a zip file, unzip the downloaded file to an existing folder on the server, use the unzipped contents (sql scripts, etc.) to build an instance of a database, and log everything that happens to a text file.
Forms App: The Part That Sorta Works
I created a Windows Forms app that uses a couple of WebBrowser controls, a couple of threads, and a few timers to do all that except the running nightly. It works great as a Form when I'm logged in and run it, but I need to get it (or something like it) to run on it's own like a Service or scheduled task.
My Service Attempt
So, I created a Windows Service that ticks every hour and, if the System.DateTime.Now.Hour >= 22, attempts to launch the Windows Forms app to do it's thing. When the Service attempts to launch the Form, this error occurs:
ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
which I researched and tried to resolve by either placing the [STAThread] attribute on the Main method of the Service's Program class or using some code like this in a few places including the Form constructor:
webBrowseThread = new Thread(new ThreadStart(InitializeComponent));
webBrowseThread.SetApartmentState(ApartmentState.STA);
webBrowseThread.Start();
I couldn't get either approach to work. In the latter approach, the controls on the Form (which would get initialized inside IntializeComponent) don't get initialized and I get null reference exceptions.
My Scheduled Task Attempt
So, I tried creating a nightly scheduled task using my own credentials to run the Form locally on my dev machine (just testing). It gets farther than the Service did, but gets hung up at the File Download Dialog.
Related Note: To send the key sequences to get through the File Download and File Save As dialogs, my Form actually runs a couple of vbscript files that use WScript.Shell.SendKeys. Ok, that's embarassing to admit, but I tried a few different things including SendMessage in Win32 API and referencing IWshRuntimeLibrary to use SendKeys inside my C# code. When I was researching how to get through the dialogs, the Win32 API seemed to be the recommended way to go, but I couldn't figure it out. The vbscript files was the only thing I could get to work, but I'm worried now that this may be the reason why a scheduled task won't work.
Regarding My Choice of WebBrowser Control
I have read about the System.WebClient class as an alternative to the WebBrowser control, but at a glance, it doesn't look like it has what I need to get this done. For example, I needed (or I think I needed) the WebBrowser's DocumentCompleted and FileDownload events to handle the delays in pages loading, files downloading, etc. Is there more to WebClient that I'm not seeing? Is there another class besides WebBrowser that is more Service-friendly and would do the trick?
In Summary
Geez, this is long. Sorry! It would help to even have a high level recommendation for a better way to do what I'm trying to do, because nothing I've tried has worked.
Update 10/22/09
Well, I think I'm closer, but I'm stuck again. I should end up with a decent-sized zip file with several files in it, but the zip file resulting from my code is empty. Here's my code:
// build post request
string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(targetHref);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
// encoding to use
Encoding enc = Encoding.GetEncoding(1252);
// build post string containing authentication information and add to post request
string poststring = "returnUrl=" + fixCharacters(targetDownloadFileUrl);
poststring += getUsernameAndPasswordString();
poststring += "&login2.x=0&login2.y=0";
// convert to required byte array
byte[] postBytes = enc.GetBytes(poststring);
request.ContentLength = postBytes.Length;
// write post to request
Stream postStream = request.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length);
postStream.Close();
// get response as stream
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
// writes stream to zip file
FileStream writeStream = new FileStream(fullZipFileName, FileMode.Create, FileAccess.Write);
ReadWriteStream(responseStream, writeStream);
response.Close();
responseStream.Close();
The code for ReadWriteStream looks like this.
private void ReadWriteStream(Stream readStream, Stream writeStream)
{
// taken verbatum from http://www.developerfusion.com/code/4669/save-a-stream-to-a-file/
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
The building of the post string is taken from my previous forms app that works. I compared the resulting values in poststring for both sets of code (my working forms app and this one) and they're identical.
I'm not even sure how to troubleshoot this further. Anyone see anything obvious as to why this isn't working?
Conclusion 10/23/09
I finally have this working. A couple of important hurdles I had to get over. I had some problems with the ReadWriteStream method code that I got online. I don't know why, but it wasn't working for me. A guy named JB in Claudio Lassala's Virtual Brown Bag meeting helped me to come up with this code which worked much better for my purposes:
private void WriteResponseStreamToFile(Stream responseStreamToRead, string zipFileFullName)
{
// responseStreamToRead will contain a zip file, write it to a file in
// the target location at zipFileFullName
FileStream fileStreamToWrite = new FileStream(zipFileFullName, FileMode.Create);
int readByte = responseStreamToRead.ReadByte();
while (readByte != -1)
{
fileStreamToWrite.WriteByte((byte)readByte);
readByte = responseStreamToRead.ReadByte();
}
fileStreamToWrite.Flush();
fileStreamToWrite.Close();
}
As Will suggested below, I did have trouble with the authentication. The following code is what worked to get around that issue. A few comments inserted addressing key issues I ran into.
string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm";
HttpWebRequest firstRequest = (HttpWebRequest)WebRequest.Create(targetHref);
firstRequest.AllowAutoRedirect = false; // this is critical, without this, NLM redirects and the whole thing breaks
// firstRequest.Proxy = new WebProxy("127.0.0.1", 8888); // not needed for production, but this helped in order to debug the http traffic using Fiddler
firstRequest.Method = "POST";
firstRequest.ContentType = "application/x-www-form-urlencoded";
// build post string containing authentication information and add to post request
StringBuilder poststring = new StringBuilder("returnUrl=" + fixCharacters(targetDownloadFileUrl));
poststring.Append(getUsernameAndPasswordString());
poststring.Append("&login2.x=0&login2.y=0");
// convert to required byte array
byte[] postBytes = Encoding.UTF8.GetBytes(poststring.ToString());
firstRequest.ContentLength = postBytes.Length;
// write post to request
Stream postStream = firstRequest.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length); // Fiddler shows that post and response happen on this line
postStream.Close();
// get response as stream
HttpWebResponse firstResponse = (HttpWebResponse)firstRequest.GetResponse();
// create new request for new location and cookies
HttpWebRequest secondRequest = (HttpWebRequest)WebRequest.Create(firstResponse.GetResponseHeader("location"));
secondRequest.AllowAutoRedirect = false;
secondRequest.Headers.Add(HttpRequestHeader.Cookie, firstResponse.GetResponseHeader("Set-Cookie"));
// get response to second request
HttpWebResponse secondResponse = (HttpWebResponse)secondRequest.GetResponse();
// write stream to zip file
Stream responseStreamToRead = secondResponse.GetResponseStream();
WriteResponseStreamToFile(responseStreamToRead, fullZipFileName);
responseStreamToRead.Close();
sl.logScriptActivity("Downloading update.");
firstResponse.Close();
I want to underscore that setting AllowAutoRedirect to false on the first HttpWebRequest instance was critical to the whole thing working. Fiddler showed two additional requests that occurred when this was not set, and it broke the rest of the script.
You're trying to use UI controls to do something in a windows service. This will never work.
What you need to do is just use the WebRequest and WebResponse classes to download the contents of the webpage.
var request = WebRequest.Create("http://www.google.com");
var response = request.GetResponse();
var stream = response.GetResponseStream();
You can dump the contents of the stream, parse the text looking for updates, and then construct a new request for the URL of the file you want to download. That response stream will then have the file, which you can dump on the filesystem and etc etc.
Before you wonder, GetResponse will block until the response returns, and the stream will block as data is being received, so you don't need to worry about events firing when everything has been downloaded.
You definitely need to re-think your approach (as you've already begun to do) to eliminate the Forms-based application approach. The service you're describing needs to operate with no UI at all.
I'm not familiar with the details of System.WebClient, but since it
provides common methods for sending
data to and receiving data from a
resource identified by a URI,
it will probably be your answer.
At first glance, WebClient.DownloadFile(...) or WebClient.DownloadFileAsync(...) will do what you need.
The only thing I can add is that once you have scraped your screen and have the fully qualified name of the file you want to download, you could pass it along to the Windows/DOS command 'get' which will fetch files via HTTP. You can also script a command-line FTP client if desired. It's been a long time since I tried something like this in Windows, but I think you're almost there. Once you have fetched the correct file, building a batch file to do everything else should be pretty easy. If you are more comfortable with Unix, google "unix services for windows" just keep an eye on the services they start running (DHCP, etc). There are some nice utilities which will let your treat dos as a unix-like shell (ls -l, grep, etc) Finally, you could try another language like Perl or Python but I don't think that's the kind of advice you were looking for. :)