diff --git a/uppsrc/DocTypes/Report.h b/uppsrc/DocTypes/Report.h index 87f538d4e..72571d191 100644 --- a/uppsrc/DocTypes/Report.h +++ b/uppsrc/DocTypes/Report.h @@ -111,7 +111,7 @@ public: virtual ~DocReport(); }; -String Pdf(const DocReport& report); +String Pdf(const DocReport& report, bool pdfa = false); class ReportCtrl : public Ctrl { public: diff --git a/uppsrc/DocTypes/ShowReport.cpp b/uppsrc/DocTypes/ShowReport.cpp index 65c0f5a56..0c9470bc8 100644 --- a/uppsrc/DocTypes/ShowReport.cpp +++ b/uppsrc/DocTypes/ShowReport.cpp @@ -172,10 +172,10 @@ ReportDlg::ReportDlg() pdf <<= THISBACK(Pdf); } -String Pdf(const DocReport& report) +String Pdf(const DocReport& report, bool pdfa) { return GetDrawingToPdfFn() ? (*GetDrawingToPdfFn())(report.GetPages(), report.GetPageSize(), - 200) + 200, pdfa) : String(); } diff --git a/uppsrc/Draw/Draw.h b/uppsrc/Draw/Draw.h index abf0906a4..0f30d62e6 100644 --- a/uppsrc/Draw/Draw.h +++ b/uppsrc/Draw/Draw.h @@ -865,7 +865,7 @@ void DrawTLText(Draw& draw, int x, int y, int cx, const wchar *text, Font font = Color ink = SColorText(), int accesskey = 0); -typedef String (*DrawingToPdfFnType)(const Array& report, Size pagesize, int margin); +typedef String (*DrawingToPdfFnType)(const Array& report, Size pagesize, int margin, bool pdfa); void SetDrawingToPdfFn(DrawingToPdfFnType Pdf); DrawingToPdfFnType GetDrawingToPdfFn(); diff --git a/uppsrc/PdfDraw/PdfDraw.cpp b/uppsrc/PdfDraw/PdfDraw.cpp index 6b50ad685..f6a44d1da 100644 --- a/uppsrc/PdfDraw/PdfDraw.cpp +++ b/uppsrc/PdfDraw/PdfDraw.cpp @@ -8,6 +8,100 @@ NAMESPACE_UPP #define PDF_COMPRESS #define USE_TTF +static const char ICC_ColorSpaceInfo[] = +"H\211\234\226yTSw\026\307\177o\311\236\220\225\260\303c\r[\200\260\006\2205la\221" +"\035\004Q\bI\b\001\022BH\330\005AD\005\024ED\204\252\2252\326mtFOE\235.\256c\255\016" +"\326}\352\322\003\3650\352\3508\264\026\327\216\235\0278G\235Ng\246\323\357\037\357" +"\3679\367w\357\357\335\337\275\367\235\363\000\240'\245\252\265\3250\v\000\215\326" +"\240\317J\214\305\026\025\024b\244\t\000\003\n \002\021\0002y\255.-;!\a\340\222\306" +"K\260Z\334\t\374\213\236^\a\220i\275\"L\312\3000\360\377\211-\327\351\r\000@\0318" +"\a(\224\265r\234;q\256\2527\350L\366\031\234y\245\225&\206Q\023\353\361\004q\2664" +"\261j\236\275\347|\3469\332\304\n\215V\201\263)g\235B\2430\361i\234W\327\031\2258" +"#\2518w\325\251\225\3658_\305\331\245\312\250Q\343\374\334\024\253Q\312j\001@\351" +"&\273A)/\307\331\017g\272>'K\202\363\002\000\310t\325;\\\372\016\033\224\r\006\323" +"\245$\325\272F\275ZUn\300\334\345\036\230(4T\214%)\353\253\224\006\2030C&\257\224" +"\351\025\230\244Z\243\223i\033\001\230\277\363\2348\246\332bx\221\203E\241\301\301" +"B\177\037\321;\205\372\257\233\277P\246\336\316\323\223\314\271\236A\374\vom?\347" +"W=\n\200x\026\257\315\372\267\266\322-\000\214\257\004\300\362\346[\233\313\373\000" +"0\361\276\035\276\370\316}\370\246y)7\030ta\276\276\365\365\365>j\245\334\307T\320" +"7\372\237\016\277@\357\274\317\307t\334\233\362`q\3122\231\261\312\200\231\352&\257" +"\256\2526\352\261Z\235L\256\304\204?\035\342_\035\370\363yxg)\313\224z\245\026\217" +"\310\303\247L\255U\341\355\326*\324\006u\265\026Sk\377S\023\177e\330O4?\327\270\270" +"c\257\001\257\330\a\260.\362\000\362\267\v\000\345\322\000R\264\r\337\201\336\364" +"-\225\222\a2\3605\337\341\336\374\334\317\t\372\367S\341>\323\243V\255\232\213\223" +"d\345`r\243\276n~\317\364Y\002\002\240\002&\340\001+`\017\234\201;\020\002\177\020" +"\002\302A4\210\a\311 \035\344\200\002\260\024\310A9\320\000=\250\a-\240\035t\201\036" +"\260\036l\002\303`;\030\003\273\301~p\020\214\203\217\301\t\360Gp\036|\t\256\201[" +"`\022L\203\207`\006<\005\257 \b\"A\f\210\vYA\016\220+\344\005\371Cb(\022\212\207R" +"\241,\250\000*\201T\220\0262B-\320\n\250\a\352\207\206\241\035\320n\350\367\320Q\350" +"\004t\016\272\004}\005MA\017\240\357\240\2270\002\323a\036l\a\273\301\276\260\030" +"\216\201S\340\034x\t\254\202k\340&\270\023^\a\017\301\243\360>\3700|\002>\017_\203" +"'\341\207\360,\002\020\032\302G\034\021!\"F$H:R\210\224!z\244\025\351F\006\221Qd?" +"r\f9\213\\A&\221G\310\v\224\210rQ\f\025\242\341h\022\232\213\312\321\032\264\025\355" +"E\207\321]\350a\3644z\005\235Bg\320\327\004\006\301\226\340E\b#H\t\213\b*B=\241\213" +"0H\330I\370\210p\206p\2150MxJ$\022\371D\0011\204\230D, V\020\233\211\275\304\255\304" +"\003\304\343\304K\304\273\304Y\022\211dE\362\"E\220\322I2\222\201\324E\332B\332G\372" +"\214t\2314MzN\246\221\035\310\376\344\004r!YK\356 \017\222\367\220?%_&\337#\277\242" +"\260(\256\2240J:EAi\244\364Q\306(\307(\027)\323\224WT6U@\215\240\346P+\250\355\324" +"!\352~\352\031\352m\352\023\032\215\346D\v\245e\322\324\264\345\264!\332\357h\237" +"\323\246h/\350\034\272']B/\242\033\351\353\350\037\322\217\323\277\242?a0\030n\214" +"hF!\303\300X\307\330\3158\305\370\232\361\334\214k\346c&5S\230\265\231\215\230\035" +"6\273l\366\230Ia\2722c\230K\231M\314A\346!\346E\346#\026\205\345\306\222\260d\254" +"V\326\b\353(\353\006k\226\315e\213\330\351l\r\273\227\275\207}\216}\237C\342\270q" +"\3429\nN'\347\003\316)\316].\302u\346J\270r\356\n\356\030\367\fw\232G\344\txR^\005" +"\257\207\367[\336\004o\306\234c\036h\236g\336`>b\376\211\371$\037\341\273\361\245" +"\374*~\037\377 \377:\377\245\205\235E\214\205\322b\215\305~\213\313\026\317,m,\243" +"-\225\226\335\226\a,\257Y\276\264\302\254\342\255*\2556X\215[\335\261F\255=\2553\255" +"\353\255\267Y\237\261~d\303\263\t\267\221\333t\333\034\264\271i\v\333z\332f\3316\333" +"~`{\301v\326\316\336.\321Ng\267\305\356\224\335#{\276}\264}\205\375\200\375\247\366" +"\017\034\270\016\221\016j\207\001\207\317\034\376\212\231c1X\0256\204\235\306f\034" +"m\035\223\034\215\216;\034'\034_9\t\234r\235:\234\0168\335q\246:\213\235\313\234\a" +"\234O:\317\2708\270\244\271\264\270\354u\271\351Jq\025\273\226\273nv=\353\372\314" +"M\340\226\357\266\312m\334\355\276\300R \0254\t\366\nn\2733\334\243\334k\334G\335" +"\257z\020=\304\036\225\036[=\276\364\204=\203<\313=GTB(\311/\331S\362\203,]6*\233-\225\226\276W:#\227\3107\313\037*\242\025" +"\003\212\a\312\be\277\362^YDY\177\331}U\204j\243\352AyT\371`\371#\265D=\254\376\266" +"\"\251b{\305\263\312\364\312\017+\177\254\312\257:\240!kJ4G\265\034m\245\366t\265" +"}uC\365%\235\227\256K7Y\023V\263\251fF\237\242\337Y\v\325.\251=b\340\341?S\027\214" +"\356\306\225\306\251\272\310\272\221\272\347\365y\365\207\032\330\r\332\206\v\215" +"\236\215k\032\3575%4\375\246\031m\2267\237lqlio\231Z\026\263lG+\324Z\332z\262\315" +"\271\255\263mzy\342\362]\355\324\366\312\366?u\370u\364w|\277\"\177\305\261N\273\316" +"\345\235wW&\256\334\333e\326\245\357\272\261*|\325\366\325\350j\365\352\2115\001k" +"\266\254y\335\255\350\376\242\307\257g\260\347\207^y\357\027kEk\207\326\376\270\256" +"l\335D_p\337\266\365\304\365\332\365\3277Dm\330\325\317\356o\352\277\2731m\343\341" +"\001l\240{\340\373M\305\233\316\r\006\016n\337L\335l\334<9\224\372O\000\244\001[\376" +"\230\270\231$\231\220\231\374\232h\232\325\233B\233\257\234\034\234\211\234\367\235" +"d\235\322\236@\236\256\237\035\237\213\237\372\240i\240\330\241G\241\266\242&\242" +"\226\243\006\243v\243\346\244V\244\307\2458\245\251\246\032\246\213\246\375\247n\247" +"\340\250R\250\304\2517\251\251\252\034\252\217\253\002\253u\253\351\254\\\254\320" +"\255D\255\270\256-\256\241\257\026\257\213\260\000\260u\260\352\261`\261\326\262K" +"\262\302\2638\263\256\264%\264\234\265\023\265\212\266\001\266y\266\360\267h\267\340" +"\270Y\270\321\271J\271\302\272;\272\265\273.\273\247\274!\274\233\275\025\275\217" +"\276\n\276\204\276\377\277z\277\365\300p\300\354\301g\301\343\302_\302\333\303X\303" +"\324\304Q\304\316\305K\305\310\306F\306\303\307A\307\277\310=\310\274\311:\311\271" +"\3128\312\267\3136\313\266\3145\314\265\3155\315\265\3166\316\266\3177\317\270\320" +"9\320\272\321<\321\276\322?\322\301\323D\323\306\324I\324\313\325N\325\321\326U\326" +"\330\327\\\327\340\330d\330\350\331l\331\361\332v\332\373\333\200\334\005\334\212" +"\335\020\335\226\336\034\336\242\337)\337\257\3406\340\275\341D\341\314\342S\342\333" +"\343c\343\353\344s\344\374\345\204\346\r\346\226\347\037\347\251\3502\350\274\351" +"F\351\320\352[\352\345\353p\353\373\354\206\355\021\355\234\356(\356\264\357@\357" +"\314\360X\360\345\361r\361\377\362\214\363\031\363\247\3644\364\302\365P\365\336\366" +"m\366\373\367\212\370\031\370\250\3718\371\307\372W\372\347\373w\374\a\374\230\375" +")\375\272\376K\376\334\377m\377\377\002\f\000\367\204\363\373"; + dword PdfDraw::GetInfo() const { return DOTS; @@ -18,10 +112,11 @@ Size PdfDraw::GetPageSize() const return pgsz; } -void PdfDraw::Init(int pagecx, int pagecy, int _margin) +void PdfDraw::Init(int pagecx, int pagecy, int _margin, bool _pdfa) { Clear(); margin = _margin; + pdfa = _pdfa; pgsz.cx = pagecx; pgsz.cy = pagecy; pgsz += margin; @@ -55,20 +150,22 @@ void PdfDraw::PutRect(const Rect& rc) << Pt(rc.Width()) << ' ' << Pt(rc.Height()) << " re\n"; } -int PdfDraw::PutStream(const String& data, const String& keys) +int PdfDraw::PutStream(const String& data, const String& keys, bool compress) { #ifdef PDF_COMPRESS - String c = ZCompress(data); - if(c.GetLength() < data.GetLength()) { - BeginObj(); - out << "<< " << keys - << "/Length " << c.GetLength() << " " - << "/Length1 " << data.GetLength() << " " - << "/Filter /FlateDecode " - << " >>\n" - << "stream\r\n" << c << "\r\nendstream\n"; - EndObj(); - return offset.GetCount(); + if(compress) { + String c = ZCompress(data); + if(c.GetLength() < data.GetLength()) { + BeginObj(); + out << "<< " << keys + << "/Length " << c.GetLength() << " " + << "/Length1 " << data.GetLength() << " " + << "/Filter /FlateDecode " + << " >>\n" + << "stream\r\n" << c << "\r\nendstream\n"; + EndObj(); + return offset.GetCount(); + } } #endif BeginObj(); @@ -669,7 +766,7 @@ String PdfDraw::Finish() } } imgobj << "/Type /XObject /Subtype /Image" << wh - << " /BitsPerComponent 8 /ColorSpace /DeviceRGB"; + << " /BitsPerComponent 8 /ColorSpace /DeviceRGB /Intent /Perceptual"; } } if(mask >= 0) @@ -965,6 +1062,21 @@ String PdfDraw::Finish() out << ">>\n"; EndObj(); + int dflt_rgb_def = -1; + if(pdfa) { + int icc_info = BeginObj(); + static const int data_len = __countof(ICC_ColorSpaceInfo) - 1; + out << "<>\n" + "stream\n"; + out.Cat(ICC_ColorSpaceInfo, data_len); + out << "\nendstream\n"; + EndObj(); + + dflt_rgb_def = BeginObj(); + out << "[/ICCBased " << icc_info << " 0 R]\n"; + EndObj(); + } + int resources = BeginObj(); out << "<< /Font " << fonts << " 0 R\n" "/ProcSet [ /PDF /Text /ImageB /ImageC ]\n"; @@ -982,6 +1094,9 @@ String PdfDraw::Finish() out << "/Pat" << (i + 1) << ' ' << patternobj[i] << " 0 R "; out << ">>\n"; } + if(pdfa) { + out << "/ColorSpace <>\n"; + } out << ">>\n"; EndObj(); @@ -1009,11 +1124,66 @@ String PdfDraw::Finish() "/Count 0\n" ">>\n"; EndObj(); + int pdfa_metadata = -1; + if(pdfa) { + StringBuffer metadata; + metadata << + "\n" + "\n" + "\n" + "\n" + "0000-01-01\n" + "0000-01-01\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "1\n" + "B\n" + "\n" + "\n" + "\n"; + + StringBuffer meta_head; + meta_head << "/Type/Metadata/Subtype/XML"; + + pdfa_metadata = PutStream(metadata, meta_head, false); + } + int catalog = BeginObj(); out << "<< /Type /Catalog\n" << "/Outlines " << outlines << " 0 R\n" - << "/Pages " << pages << " 0 R\n" - << ">>\n"; + << "/Pages " << pages << " 0 R\n"; + + if(pdfa_metadata >= 0) { + out << "/Metadata " << pdfa_metadata << " 0 R\n"; + } + + out << ">>\n"; EndObj(); int startxref = out.GetCount(); out << "xref\n" diff --git a/uppsrc/PdfDraw/PdfDraw.h b/uppsrc/PdfDraw/PdfDraw.h index 304fad269..b058b5fc3 100644 --- a/uppsrc/PdfDraw/PdfDraw.h +++ b/uppsrc/PdfDraw/PdfDraw.h @@ -284,13 +284,14 @@ private: double linewidth; int margin; uint64 patternid; + bool pdfa; inline double Pt(double dot) { return 0.12 * dot; } int Pos() { return offset.GetCount() + 1; } int BeginObj(); void EndObj(); - int PutStream(const String& data, const String& keys = Null); + int PutStream(const String& data, const String& keys = Null, bool compress = true); void PutRect(const Rect& rc); void PutrgColor(Color rg, uint64 pattern = 0); @@ -320,7 +321,7 @@ private: M22() : a(1), b(0), c(0), d(1) {} }; - void Init(int pagecx, int pagecy, int margin); + void Init(int pagecx, int pagecy, int margin, bool pdfa); struct RGlyph : Moveable { String data; @@ -334,8 +335,8 @@ public: String Finish(); void Clear(); - PdfDraw(int pagecx, int pagecy) { Init(pagecx, pagecy, 0); } - PdfDraw(Size pgsz = Size(5100, 6600)) { Init(pgsz.cx, pgsz.cy, 0); } + PdfDraw(int pagecx, int pagecy, bool pdfa = false) { Init(pagecx, pagecy, 0, pdfa); } + PdfDraw(Size pgsz = Size(5100, 6600), bool pdfa = false) { Init(pgsz.cx, pgsz.cy, 0, pdfa); } }; String Pdf(const Array& report, Size pagesize, int margin); diff --git a/uppsrc/PdfDraw/PdfReport.icpp b/uppsrc/PdfDraw/PdfReport.icpp index d20aa0ecd..6cec31237 100644 --- a/uppsrc/PdfDraw/PdfReport.icpp +++ b/uppsrc/PdfDraw/PdfReport.icpp @@ -2,9 +2,9 @@ NAMESPACE_UPP -String Pdf(const Array& report, Size sz, int margin) +String Pdf(const Array& report, Size sz, int margin, bool pdfa) { - PdfDraw w(sz + 2 * margin); + PdfDraw w(sz + 2 * margin, pdfa); for(int i = 0; i < report.GetCount(); i++) { w.StartPage(); w.DrawDrawing(margin, margin, sz.cx, sz.cy, report[i]); diff --git a/uppsrc/Report/Report.h b/uppsrc/Report/Report.h index 82b7b05ba..3916aa7b5 100644 --- a/uppsrc/Report/Report.h +++ b/uppsrc/Report/Report.h @@ -141,7 +141,7 @@ public: ReportWindow(); }; -String Pdf(Report& report); +String Pdf(Report& report, bool pdfa = false); void Print(Report& r, PrinterJob& pd, bool center = true); bool DefaultPrint(Report& r, int i, const char *_name = t_("Report")); bool Print(Report& r, int i, const char *name = t_("Report")); diff --git a/uppsrc/Report/ReportDlg.cpp b/uppsrc/Report/ReportDlg.cpp index b52f63798..5d07b1595 100644 --- a/uppsrc/Report/ReportDlg.cpp +++ b/uppsrc/Report/ReportDlg.cpp @@ -214,10 +214,10 @@ ReportWindow::ReportWindow() pdf <<= THISBACK(Pdf); } -String Pdf(Report& report) +String Pdf(Report& report, bool pdfa) { return GetDrawingToPdfFn() && report.GetCount() ? - (*GetDrawingToPdfFn())(report.GetPages(), report.GetPage(0).GetSize(), 200) + (*GetDrawingToPdfFn())(report.GetPages(), report.GetPage(0).GetSize(), 200, pdfa) : String(); } diff --git a/uppsrc/plugin/wincert/init b/uppsrc/plugin/wincert/init new file mode 100644 index 000000000..e09445492 --- /dev/null +++ b/uppsrc/plugin/wincert/init @@ -0,0 +1,4 @@ +#ifndef _plugin_wincert_icpp_init_stub +#define _plugin_wincert_icpp_init_stub +#include "Web/init" +#endif diff --git a/uppsrc/plugin/wincert/pdf.cpp b/uppsrc/plugin/wincert/pdf.cpp new file mode 100644 index 000000000..9d7129d0e --- /dev/null +++ b/uppsrc/plugin/wincert/pdf.cpp @@ -0,0 +1,363 @@ +#include "wincert.h" + +class PDFInfo { +public: + struct Reference : Moveable { + Reference(const Nuller& = Null) : index(0) {} + Reference(int index_, word revision_) : index(index_), revision(revision_) {} + + String ToString() const; + + bool IsNullInstance() const { return index == 0; } + + int index; + word revision; + }; + + PDFInfo(); + + void Clear(); + + bool Open(const String& pdf_data); + +public: + bool SkipWhite(); + bool Check(const char *text); + int ScanInteger(); + Reference ScanReference(); + + bool ParseXRef(int startxref); + bool ParseRoot(); + bool ParsePageTree(Reference root); + + bool Seek(Reference ref); + +public: + enum { MAX_PDF_OBJECTS = 1000000 }; + + String pdf_data; + Vector objects; + Vector revisions; + Vector pages; + int startxref; + Reference trailer_root; + Reference trailer_info; + Reference page_tree; + String root_object; + const char *term; +}; + +String PDFInfo::Reference::ToString() const +{ + StringBuffer out; + out << index << ' ' << (int)revision << " R"; + return out; +} + +PDFInfo::PDFInfo() +{ + Clear(); +} + +void PDFInfo::Clear() +{ + pdf_data = Null; + objects.Clear(); + revisions.Clear(); + startxref = -1; + trailer_root = Null; + trailer_info = Null; + page_tree = Null; + root_object = Null; + term = NULL; +} + +bool PDFInfo::Open(const String& pdf_data_) +{ + Clear(); + pdf_data = pdf_data_; + const char *begin = pdf_data.Begin(); + term = pdf_data.End(); + while(term > begin && (byte)term[-1] <= ' ') + term--; + if(term - begin <= 5 || memcmp(term - 5, "%%EOF", 5)) + return false; + term -= 5; + while(term > begin && !IsDigit(*--term)) + ; + while(term > begin && IsDigit(term[-1])) + term--; + startxref = ScanInt(term); + if(!ParseXRef(startxref)) + return false; + if(!ParseRoot() || !ParsePageTree(page_tree)) + return false; + return true; +} + +bool PDFInfo::ParseXRef(int start) +{ + if(start < 0 || start >= pdf_data.GetLength() - 20) + return false; + term = pdf_data.GetIter(start); + if(!Check("xref")) + return false; + while(!Check("trailer")) { + if(!SkipWhite()) + return false; + int start = ScanInteger(); + int count = ScanInteger(); + if(start < 0 || count <= 0 || start + count > MAX_PDF_OBJECTS || !SkipWhite()) + return false; + int end = start + count; + if(objects.GetCount() < end) { + objects.SetCountR(end, -1); + revisions.SetCountR(end, 0xffffu); + } + for(int i = 0; i < count; i++) { + int offset = ScanInteger(); + int rev = ScanInteger(); + if(!SkipWhite()) + return false; + if(*term != 'f' && *term != 'n') + return false; + if(*term++ == 'f') { + offset = 0; + rev = 0xffffu; + } + if(objects[start + i] == -1) { + objects[start + i] = offset; + revisions[start + i] = rev; + } + } + } + if(!Check("<<")) + return false; + while(!Check(">>")) { + if(!SkipWhite()) + return false; + if(Check("/Size")) { + int sz = ScanInteger(); + if(sz < objects.GetCount()) + return false; + objects.SetCount(sz, -1); + revisions.SetCount(sz, 0xffffu); + } + else if(Check("/Root")) { + Reference root = ScanReference(); + if(IsNull(trailer_root)) + trailer_root = root; + } + else if(Check("/Info")) { + Reference info = ScanReference(); + if(IsNull(trailer_info)) + trailer_info = info; + } + else if(Check("/Prev")) { + int prev = ScanInteger(); + const char *store_term = term; + if(!ParseXRef(prev)) + return false; + term = store_term; + } + else { + while(*++term && *term != '/' && !(*term == '>' && term[1] == '>')) + ; + } + } + return true; +} + +bool PDFInfo::Seek(Reference r) +{ + if(r.index <= 0 || r.index >= objects.GetCount() || objects[r.index] == -1) + return false; + term = pdf_data.GetIter(objects[r.index]); + int index = ScanInteger(); + if(index != r.index) + return false; + int rev = ScanInteger(); + if(rev != r.revision) + return false; + if(!Check("obj") || !Check("<<")) + return false; + return true; +} + +bool PDFInfo::ParseRoot() +{ + if(!Seek(trailer_root)) + return false; + const char *begin = term; + while(*term) { + if(Check("/Pages")) { + page_tree = ScanReference(); + } + else + if(*term == '>' && term[1] == '>') + break; + else + term++; + } + root_object = String(begin, term); + return true; +} + +bool PDFInfo::ParsePageTree(Reference node) +{ + if(!Seek(node)) + return false; + if(!Check("/Type")) + return false; + if(Check("/Pages")) { + if(!Check("/Kids") || !Check("[")) + return false; + while(*term && !Check("]")) { + Reference kid = ScanReference(); + const char *stack = term; + if(!ParsePageTree(kid)) + return false; + term = stack; + } + return true; + } + else if(Check("/Page")) { + pages.Add(node); + return true; + } + else + return false; +} + +bool PDFInfo::SkipWhite() +{ + while(*term && (byte)*term <= ' ') + term++; + return !!*term; +} + +bool PDFInfo::Check(const char *text) +{ + if(!SkipWhite()) + return false; + int l = strlen(text); + if(memcmp(term, text, l)) + return false; + term += l; + return true; +} + +int PDFInfo::ScanInteger() +{ + if(!SkipWhite()) + return Null; + return ScanInt(term, &term); +} + +PDFInfo::Reference PDFInfo::ScanReference() +{ + int obj = ScanInteger(); + if(IsNull(obj)) + return Reference(); + int rev = ScanInteger(); + if(IsNull(rev)) + return Reference(); + if(SkipWhite() && *term == 'R') { + term++; + return Reference(obj, rev); + } + return Reference(); +} + +String CertSignPDF(PCCERT_CONTEXT cert_context, const String& pdf_data) +{ + PDFInfo pdfinfo; + if(!pdfinfo.Open(pdf_data) || pdfinfo.pages.IsEmpty()) + return String::GetVoid(); + StringBuffer out; + out.Cat(pdf_data); + int obj_index = pdfinfo.objects.GetCount(); + int sign_object = obj_index++; + int sign_dict = obj_index++; + int new_root = obj_index++; + int sign_off = out.GetLength(); + out << sign_object << " 0 obj\n" + "<>\n" + "endobj\n"; + int dict_off = out.GetLength(); + Time tm = GetSysTime(); + out << sign_dict << " 0 obj\n" + "<>>\n" + "endobj\n"; + int root_off = out.GetLength(); + out + << new_root << " 0 obj\n" + "<<" << pdfinfo.root_object + << "/AcroForm<>>>\n" + "endobj\n"; + int xref_offset = out.GetLength(); + out << "xref\n" + << sign_object << " 3\n" + << FormatIntDec(sign_off, 10, '0') << " 00000 n\r\n" + << FormatIntDec(dict_off, 10, '0') << " 00000 n\r\n" + << FormatIntDec(root_off, 10, '0') << " 00000 n\r\n" + "trailer\n" + "< <" << Uuid::Create() << "> ]\n" + "/Prev " << pdfinfo.startxref + << ">>\n" + "startxref\n" + << xref_offset + << "\n" + "%%EOF\n"; + int end_offset = out.GetLength(); + + String rcl_fmt = NFormat("%d %d %d ]", cert_offset, + post_offset, end_offset - post_offset); + ASSERT(rcl_fmt.GetLength() <= RCL_CONT); + memcpy(out.Begin() + br_offset, rcl_fmt, rcl_fmt.GetLength()); + + Vector sections; + Vector sizes; + sections.SetCount(2); + sizes.SetCount(2); + sections[0] = (const BYTE *)out.Begin(); + sizes[0] = cert_offset; + sections[1] = (const BYTE *)out.Begin() + post_offset; + sizes[1] = end_offset - post_offset; + int sig_len = 2 * CertSignLength(cert_context, sections, sizes); + if(sig_len <= cert_length) { + String signature = BinHexEncode(CertSign(cert_context, sections, sizes)); + ASSERT(signature.GetLength() <= cert_length); + memcpy(out.Begin() + cert_offset + 1, signature, signature.GetLength()); + return out; + } + cert_length = sig_len; + } +} diff --git a/uppsrc/plugin/wincert/util.cpp b/uppsrc/plugin/wincert/util.cpp new file mode 100644 index 000000000..997e8e6d3 --- /dev/null +++ b/uppsrc/plugin/wincert/util.cpp @@ -0,0 +1,163 @@ +#include "wincert.h" + +String CertGetProperty(PCCERT_CONTEXT cert_context, DWORD propertyID) +{ + dword len = 0; + if(!CertGetCertificateContextProperty(cert_context, propertyID, NULL, &len)) + return String::GetVoid(); + Buffer prop(len); + if(!CertGetCertificateContextProperty(cert_context, propertyID, prop, &len)) + return String::GetVoid(); + return String(~prop, len); +} + +String CertGetName(PCCERT_CONTEXT cert_context, DWORD dwType, DWORD flags) +{ + dword len = CertGetNameString(cert_context, dwType, flags, NULL, NULL, NULL); + if(!len) + return String::GetVoid(); + Buffer name(len); + if(!(len = CertGetNameString(cert_context, dwType, flags, NULL, name, len))) + return String::GetVoid(); + return String(name, len - 1); +} + +String CertGetNameAttr(PCCERT_CONTEXT cert_context, const char *attrOID) +{ + dword len = CertGetNameString(cert_context, CERT_NAME_ATTR_TYPE, 0, (void *)attrOID, NULL, NULL); + if(!len) + return String::GetVoid(); + Buffer name(len); + if(!(len = CertGetNameString(cert_context, CERT_NAME_ATTR_TYPE, 0, (void *)attrOID, name, len))) + return String::GetVoid(); + return String(name, len - 1); +} + +/* +String CertGetHash(PCCERT_CONTEXT cert_context) +{ + dword hash_len = 0; + CertGetCertificateContextProperty(cert_context, CERT_HASH_PROP_ID, NULL, &hash_len); + Buffer hash(hash_len); + CertGetCertificateContextProperty(cert_context, CERT_HASH_PROP_ID, hash, &hash_len); + String cert_hash(~hash, hash_len); + return cert_hash; +} +*/ + +/* +String CertGetName(PCCERT_CONTEXT cert_context) +{ + dword type; + dword len = CertGetNameString(cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, &type, NULL, NULL); + Buffer name(len); + CertGetNameString(cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, &type, name, len); + String cert_name(name, len ? len - 1 : 0); + return cert_name; +} +*/ + +CertInfo CertGetInfo(PCCERT_CONTEXT cert_context) +{ + CertInfo out; + out.hash = CertGetProperty(cert_context, CERT_HASH_PROP_ID); + out.common_name = CertGetNameAttr(cert_context, szOID_COMMON_NAME); + out.organization = CertGetNameAttr(cert_context, szOID_ORGANIZATION_NAME); + out.organizational_unit = CertGetNameAttr(cert_context, szOID_ORGANIZATIONAL_UNIT_NAME); + out.email = CertGetNameAttr(cert_context, szOID_RSA_emailAddr); + out.issuer = CertGetName(cert_context, CERT_NAME_FRIENDLY_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG); + out.valid_from = Time(cert_context->pCertInfo->NotBefore); + out.valid_until = Time(cert_context->pCertInfo->NotAfter); +// out.valid_until = Cert + return out; +} + +static void _sCertSignSet(PCCERT_CONTEXT cert_context, CRYPT_SIGN_MESSAGE_PARA& sign) +{ + Zero(sign); + sign.cbSize = sizeof(sign); + sign.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; + sign.pSigningCert = cert_context; + sign.HashAlgorithm.pszObjId = szOID_RSA_SHA1RSA; + sign.HashAlgorithm.Parameters.cbData = NULL; + sign.cMsgCert = 1; + sign.rgpMsgCert = &sign.pSigningCert; + sign.cAuthAttr = 0; + sign.dwInnerContentType = 0; + sign.cMsgCrl = 0; + sign.cUnauthAttr = 0; + sign.dwFlags = 0; + sign.pvHashAuxInfo = NULL; + sign.rgAuthAttr = NULL; +} + +int CertSignLength(PCCERT_CONTEXT cert_context, Vector& sections, Vector& sizes) +{ + ASSERT(sections.GetCount() == sizes.GetCount()); + + CRYPT_SIGN_MESSAGE_PARA sign; + + _sCertSignSet(cert_context, sign); + + const BYTE **psect = sections.Begin(); + DWORD *psize = sizes.Begin(); + + DWORD out_size = 0; + if(!CryptSignMessage(&sign, TRUE, sections.GetCount(), psect, psize, NULL, &out_size)) + return 0; + return out_size; +} + +String CertSign(PCCERT_CONTEXT cert_context, Vector& sections, Vector& sizes) +{ + ASSERT(sections.GetCount() == sizes.GetCount()); + + CRYPT_SIGN_MESSAGE_PARA sign; + + _sCertSignSet(cert_context, sign); + + const BYTE **psect = sections.Begin(); + DWORD *psize = sizes.Begin(); + + DWORD out_size = 0; + if(!CryptSignMessage(&sign, TRUE, sections.GetCount(), psect, psize, NULL, &out_size)) + return String::GetVoid(); + Buffer out_data(out_size); + if(!CryptSignMessage(&sign, TRUE, sections.GetCount(), psect, psize, out_data, &out_size)) + return String::GetVoid(); + + return String(~out_data, out_size); +} + +String CertSign(PCCERT_CONTEXT context, const String& message) +{ + Vector sections; + Vector sizes; + sections.SetCount(1, (const BYTE *)message.Begin()); + sizes.SetCount(1, message.GetLength()); + return CertSign(context, sections, sizes); +} + +Array CertEnum(HCERTSTORE store) +{ + Array out_list; + + RLOG("store = " << FormatIntHex(store)); + + PCCERT_CONTEXT cert_context = NULL; + while(cert_context = CertEnumCertificatesInStore(store, cert_context)) { + out_list.Add() = CertGetInfo(cert_context); + } + + return out_list; +} + +PCCERT_CONTEXT CertFind(HCERTSTORE store, const String& hash) +{ + CRYPT_HASH_BLOB hash_blob; + hash_blob.pbData = (BYTE *)hash.Begin(); + hash_blob.cbData = hash.GetLength(); + + return CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, CERT_FIND_HASH, &hash_blob, NULL); +} diff --git a/uppsrc/plugin/wincert/wincert.h b/uppsrc/plugin/wincert/wincert.h new file mode 100644 index 000000000..ec9f9b9f3 --- /dev/null +++ b/uppsrc/plugin/wincert/wincert.h @@ -0,0 +1,34 @@ +#ifndef _wincert_wincert_h +#define _wincert_wincert_h + +#include +#include + +using namespace Upp; + +struct CertInfo { + String hash; + String common_name; + String organization; + String organizational_unit; + String email; + String issuer; + Time valid_from; + Time valid_until; +}; + +String CertGetProperty(PCCERT_CONTEXT cert_context, DWORD propertyID); +String CertGetName(PCCERT_CONTEXT cert_context, DWORD dwType, DWORD flags = 0); +String CertGetNameAttr(PCCERT_CONTEXT cert_context, const char *attrOID); +CertInfo CertGetInfo(PCCERT_CONTEXT cert_context); + +Array CertEnum(HCERTSTORE store); +PCCERT_CONTEXT CertFind(HCERTSTORE store, const String& hash); + +int CertSignLength(PCCERT_CONTEXT cert_context, Vector& sections, Vector& sizes); +String CertSign(PCCERT_CONTEXT cert_context, Vector& sections, Vector& sizes); +String CertSign(PCCERT_CONTEXT context, const String& message); + +String CertSignPDF(PCCERT_CONTEXT cert_context, const String& pdf_data); + +#endif diff --git a/uppsrc/plugin/wincert/wincert.upp b/uppsrc/plugin/wincert/wincert.upp new file mode 100644 index 000000000..44a073327 --- /dev/null +++ b/uppsrc/plugin/wincert/wincert.upp @@ -0,0 +1,13 @@ +description "Minimum Win32 certificate support and PDF digital signing\377"; + +uses + Web; + +library + Crypt32.lib; + +file + wincert.h, + util.cpp, + pdf.cpp; +