diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..80646b2b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Source/ThirdParty/c2pa-rs"] + path = Source/ThirdParty/c2pa-rs + url = https://github.com/contentauth/c2pa-rs.git diff --git a/Project/GNU/CLI/Makefile.am b/Project/GNU/CLI/Makefile.am index 0f2d6755..7544dfe3 100644 --- a/Project/GNU/CLI/Makefile.am +++ b/Project/GNU/CLI/Makefile.am @@ -30,6 +30,7 @@ bwfmetaedit_SOURCES = \ ../../../Source/Riff/Riff_Chunks_WAVE_adtl_note.cpp \ ../../../Source/Riff/Riff_Chunks_WAVE_adtl_ltxt.cpp \ ../../../Source/Riff/Riff_Chunks_WAVE_CSET.cpp \ + ../../../Source/Riff/Riff_Chunks_WAVE_C2PA.cpp \ ../../../Source/Riff/Riff_Handler.cpp \ ../../../Source/TinyXml2/tinyxml2.cpp \ ../../../Source/ZenLib/Conf.cpp \ @@ -50,6 +51,10 @@ bwfmetaedit_SOURCES = \ ../../../Source/ZenLib/ZtringListList.cpp \ ../../../Source/ZenLib/ZtringListListF.cpp +if ENABLE_C2PA + bwfmetaedit_SOURCES += ../../../Source/Riff/Riff_C2PA_Helpers.cpp +endif + #bwfmetaedit_LDFLAGS = -no-undefined -version-info 0:0:0 AM_CXXFLAGS = -std=c++11 diff --git a/Project/GNU/CLI/configure.ac b/Project/GNU/CLI/configure.ac index 10088447..b8272ed1 100644 --- a/Project/GNU/CLI/configure.ac +++ b/Project/GNU/CLI/configure.ac @@ -33,8 +33,8 @@ dnl ######################################################################### dnl ------------------------------------------------------------------------- dnl Arguments - Enable dnl -AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [Enable full debug]), , enable_debug=no) -AC_ARG_ENABLE(gprof, AC_HELP_STRING([--enable-gprof], [Enable gprof log]), , enable_gprof=no) +AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [Enable full debug]), , enable_debug=no) +AC_ARG_ENABLE(gprof, AC_HELP_STRING([--enable-gprof], [Enable gprof log]), , enable_gprof=no) AC_ARG_ENABLE(universal_binary, AC_HELP_STRING([--enable-universal_binary], [Create Mac x84_64/arm64 Universal binary]), , enable_universal_binary=no) AC_ARG_ENABLE(unicode, AS_HELP_STRING([--disable-unicode], [Disable unicode]), , enable_unicode=yes) AC_ARG_ENABLE(arch-ppc, AC_HELP_STRING([--with-arch-ppc], [Create Mac PPC Universal binary]), , enable_arch_ppc=no) @@ -42,6 +42,9 @@ AC_ARG_ENABLE(arch-i386, AC_HELP_STRING([--with-arch-i386], [Cre AC_ARG_ENABLE(arch-x86_64, AC_HELP_STRING([--enable-arch-x86_64], [Create Mac x86_64 architecture]), , enable_arch_x86_64=no) AC_ARG_ENABLE(arch-arm64, AC_HELP_STRING([--enable-arch-arm64], [Create Mac arm64 architecture]), , enable_arch_arm64=no) AC_ARG_ENABLE(large_files, AC_HELP_STRING([--disable-large_files], [Disable large files support]), , enable_large_files=yes) +AC_ARG_ENABLE(c2pa, AC_HELP_STRING([--enable-c2pa], [Enable C2PA validation and signing]), , enable_c2pa=no) + +AM_CONDITIONAL([ENABLE_C2PA], [test "$enable_c2pa" != "no"]) dnl ------------------------------------------------------------------------- dnl Arguments - With @@ -155,6 +158,22 @@ if test "x$with_macosx_version_min" != xno ; then LDFLAGS="-mmacosx-version-min=$with_macosx_version_min $LDFLAGS" fi +dnl ######################################################################### +dnl ### Libraries +dnl ######################################################################### + +dnl ------------------------------------------------------------------------- +dnl c2pa +dnl +if test "$enable_c2pa" = "yes"; then + CXXFLAGS="-DENABLE_C2PA $CXXFLAGS" + if test "x$host_os" = "xdarwin"; then + LIBS="$srcdir/../../../Source/ThirdParty/c2pa-rs/target/release/libc2pa_c.dylib $LIBS" + else + LIBS="$srcdir/../../../Source/ThirdParty/c2pa-rs/target/release/libc2pa_c.so $LIBS" + fi +fi + dnl ######################################################################### dnl ### Output dnl ######################################################################### diff --git a/Project/GNU/GUI/bwfmetaedit-gui.metainfo.xml b/Project/GNU/GUI/bwfmetaedit-gui.metainfo.xml index 7b351724..0dcb859f 100644 --- a/Project/GNU/GUI/bwfmetaedit-gui.metainfo.xml +++ b/Project/GNU/GUI/bwfmetaedit-gui.metainfo.xml @@ -1,5 +1,5 @@ - + bwfmetaedit-gui.desktop bwfmetaedit-gui.desktop @@ -8,8 +8,10 @@ BWF MetaEdit Embed, validate, and export BWF files metadata -

BWF MetaEdit was developed by the Federal Agencies Digitization Guidelines Initiative (FADGI) supported by AudioVisual Preservation Solutions.

-

This tool permits embedding, editing, and exporting of metadata in Broadcast WAVE Format (BWF) files. This tool can also enforce metadata guidelines developed by the Federal Agencies Audio-Visual Working Group, as well as recommendations and specifications from the European Broadcasting Union (EBU), Microsoft, and IBM.

+ BWF MetaEdit is a tool that supports embedding, validating, and exporting of metadata in Broadcast WAVE Format (BWF) files. It supports the FADGI Broadcast WAVE Metadata Embedding Guidelines.
+
+ Initially funded by the Library of Congress and FADGI (Federal Agencies Digital Guidelines Initiative); previously developed by MediaArea and led by AVPreserve; currently developed and led by MediaArea.
+ MediaArea provides support and binaries for all platforms.
metadata diff --git a/Project/MSVC2022/CLI/BWF_MetaEdit_CLI.vcxproj b/Project/MSVC2022/CLI/BWF_MetaEdit_CLI.vcxproj index 8c3491d1..4c72e4fd 100755 --- a/Project/MSVC2022/CLI/BWF_MetaEdit_CLI.vcxproj +++ b/Project/MSVC2022/CLI/BWF_MetaEdit_CLI.vcxproj @@ -207,6 +207,8 @@ + + @@ -238,6 +240,9 @@ + + + diff --git a/Project/Mac/mkdmg.sh b/Project/Mac/mkdmg.sh index 4a7f699b..ad3e72b7 100644 --- a/Project/Mac/mkdmg.sh +++ b/Project/Mac/mkdmg.sh @@ -65,6 +65,10 @@ if [ "$KIND" = "CLI" ]; then cp "../GNU/CLI/${APPNAME_lower}" "${FILES}-Root/usr/local/bin" codesign -f --deep --options=runtime -s "Developer ID Application: ${SIGNATURE}" --verbose "${FILES}-Root/usr/local/bin/${APPNAME_lower}" + mkdir -p "${FILES}-Root/usr/local/lib" + cp "../../Source/ThirdParty/c2pa/c2pa-rs/target/release/libc2pa_c.dylib" "${FILES}-Root/usr/local/lib" + codesign -f --deep --options=runtime -s "Developer ID Application: ${SIGNATURE}" --verbose "${FILES}-Root/usr/local/lib/libc2pa_c.dylib" + pkgbuild --root "${FILES}-Root" --identifier "net.mediaarea.${APPNAME_lower}.mac-${KIND_lower}" --sign "Developer ID Installer: ${SIGNATURE}" --version "${VERSION}" "${FILES}/${APPNAME_lower}.pkg" codesign -f --deep --options=runtime -s "Developer ID Application: ${SIGNATURE}" --verbose "${FILES}/${APPNAME_lower}.pkg" diff --git a/Project/QtCreator/bwfmetaedit-gui.pro b/Project/QtCreator/bwfmetaedit-gui.pro index bfd95a7c..c04f03d4 100644 --- a/Project/QtCreator/bwfmetaedit-gui.pro +++ b/Project/QtCreator/bwfmetaedit-gui.pro @@ -146,6 +146,7 @@ SOURCES = \ ../../Source/Riff/Riff_Chunks_WAVE_adtl_note.cpp \ ../../Source/Riff/Riff_Chunks_WAVE_adtl_ltxt.cpp \ ../../Source/Riff/Riff_Chunks_WAVE_CSET.cpp \ + ../../Source/Riff/Riff_Chunks_WAVE_C2PA.cpp \ ../../Source/Riff/Riff_Handler.cpp \ ../../Source/TinyXml2/tinyxml2.cpp \ ../../Source/ZenLib/Conf.cpp \ @@ -167,6 +168,27 @@ SOURCES = \ ../../Source/ZenLib/ZtringListList.cpp \ ../../Source/ZenLib/ZtringListListF.cpp +contains(ENABLE_C2PA, yes|1) { + SOURCES += ../../Source/Riff/Riff_C2PA_Helpers.cpp \ + ../../Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.cpp + + HEADERS += ../../Source/ThirdParty/c2pa-rs/target/release/c2pa.h \ + ../../Source/ThirdParty/json/json.h \ + ../../Source/Riff/Riff_C2PA_Helpers.h \ + ../../Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.h + + DEFINES += ENABLE_C2PA + + win32 { + LIBS += ../../Source/ThirdParty/c2pa-rs/target/release/c2pa_c.dll.lib + } + else:macx { + LIBS += ../../Source/ThirdParty/c2pa-rs/target/release/libc2pa_c.dylib + } + else { + LIBS += ../../Source/ThirdParty/c2pa-rs/target/release/libc2pa_c.so + } +} macx { HEADERS += ../../Source/Common/Mac_Helpers.h diff --git a/Source/CLI/CLI_Help.cpp b/Source/CLI/CLI_Help.cpp index b2891a53..6695c4d7 100644 --- a/Source/CLI/CLI_Help.cpp +++ b/Source/CLI/CLI_Help.cpp @@ -38,6 +38,7 @@ std::string Help() ToDisplay<<" Reject file if:"<, the file name and 'audio/x-wav' if not provided"< #include #include -#include #ifdef __BORLANDC__ #pragma hdrstop #endif @@ -22,6 +21,9 @@ #include "CLI/CommandLine_Parser.h" #include "CLI/CLI_Help.h" #include "Common/Common_About.h" +#if defined(ENABLE_C2PA) + #include "ZenLib/File.h" +#endif // defined(ENABLE_C2PA) #include "ZenLib/ZtringList.h" using namespace ZenLib; //--------------------------------------------------------------------------- @@ -40,6 +42,39 @@ ZtringList In_Core_File_List; else if (Argument.find(_TEXT)==0) return CL_##_TOLAUNCH(C, Argument); \ //--------------------------------------------------------------------------- +//*************************************************************************** +// Helpers +//*************************************************************************** + +//--------------------------------------------------------------------------- +#if defined(ENABLE_C2PA) +static bool ReadWholeFile(const string &FileName, string &Content) +{ + File F; + if (!F.Open(Ztring().From_UTF8(FileName))) + return false; + + int64u F_Size=F.Size_Get(); + if (F_Size>((size_t)-1)-1) + return false; + + int8u* Buffer=new int8u[(size_t)F_Size]; + size_t Buffer_Offset=0; + while (Buffer_OffsetDESCRIPTION" "" -"BWF MetaEdit was developed by the Federal Agencies Digital Guidelines Initiative (FADGI) supported by AudioVisual Preservation Solutions.
" -"
" -"This tool permits embedding, editing, and exporting of metadata in Broadcast WAVE Format (BWF) files. This tool can also enforce metadata guidelines developed by the Federal Agencies Audio-Visual Working Group, as well as format specifications from the European Broadcasting Union (EBU), Microsoft, and IBM." +"BWF MetaEdit is a tool that supports embedding, validating, and exporting of metadata in Broadcast WAVE Format (BWF) files. It supports the FADGI Broadcast WAVE Metadata Embedding Guidelines.
" +"
" +"Initially funded by the Library of Congress and FADGI (Federal Agencies Digital Guidelines Initiative); previously developed by MediaArea and led by AVPreserve; currently developed and led by MediaArea.
" +"MediaArea provides support and binaries for all platforms." ;} //--------------------------------------------------------------------------- const char* Description_Text() {return "DESCRIPTION\n" "\n" -"BWF MetaEdit was developed by the Federal Agencies Digital\n" -"Guidelines Initiative (FADGI) supported by AudioVisual Preservation\n" -"Solutions.\n" +"BWF MetaEdit is a tool that supports embedding, validating, and\n" +"exporting of metadata in Broadcast WAVE Format (BWF) files.\n" +"It supports the FADGI Broadcast WAVE Metadata Embedding Guidelines\n" "\n" -"This tool permits embedding, editing, and exporting of metadata in\n" -"Broadcast WAVE Format (BWF) files. This tool can also enforce metadata\n" -"guidelines developed by the Federal Agencies Audio-Visual Working Group,\n" -"as well as format specifications from the European Broadcasting\n" -"Union (EBU), Microsoft, and IBM.\n" +"Initially funded by the Library of Congress and FADGI\n" +"(Federal Agencies Digital Guidelines Initiative);\n" +"previously developed by MediaArea and led by AVPreserve;\n" +"currently developed and led by MediaArea.\n" +"MediaArea provides support and binaries for all platforms." "\n" ;} diff --git a/Source/Common/Core.cpp b/Source/Common/Core.cpp index 9608ed4b..1aa1b1ce 100644 --- a/Source/Common/Core.cpp +++ b/Source/Common/Core.cpp @@ -134,6 +134,7 @@ Core::Core() //Configuration riff2rf64_Reject=false; Overwrite_Reject=false; + C2PA_Reject=false; NoPadding_Accept=false; Errors_Continue=false; FileNotValid_Skip=false; @@ -181,6 +182,11 @@ Core::Core() Batch_IsBackuping=false; Trace_UseDec=false; + #if defined(ENABLE_C2PA) + Out_C2PA_JSON=false; + VerifyC2PA=false; + VerifyC2PA_Force=false; + #endif // defined(ENABLE_C2PA) RevertToRiff=false; @@ -1342,6 +1348,13 @@ const string& Core::Cout_Get () if (Handler->second.Riff) Text+=Handler->second.Riff->Get("cuexml")+Ztring(EOL).To_UTF8(); break; + #if defined(ENABLE_C2PA) + case Cout_C2PA: + for (handlers::iterator Handler=Handlers.begin(); Handler!=Handlers.end(); Handler++) + if (Handler->second.Riff) + Text+=Handler->second.Riff->Get("c2pajson")+Ztring(EOL).To_UTF8(); + break; + #endif // defined(ENABLE_C2PA) case Cout_XML: Text=Out_XML_Buf; break; @@ -1918,6 +1931,12 @@ void Core::Batch_Launch(handlers::iterator &Handler) if (Out_cue__XML || !Out_cue__FileName.empty() || Out_XML_Doc) Batch_Launch_cue_(Handler); + //C2PA chunk + #if defined(ENABLE_C2PA) + if (Out_C2PA_JSON || !Out_C2PA_FileName.empty() || Out_XML_Doc) + Batch_Launch_C2PA(Handler); + #endif // defined(ENABLE_C2PA) + //Write if (!Simulation_Enabled) Batch_Launch_Write(Handler); @@ -2335,6 +2354,71 @@ void Core::Batch_Launch_cue_(handlers::iterator &Handler) } } +#if defined(ENABLE_C2PA) +//--------------------------------------------------------------------------- +void Core::Batch_Launch_C2PA(handlers::iterator &Handler) +{ + Ztring Content=Ztring().From_UTF8(Handler->second.Riff->Get("c2pajson")); + + if (!Content.empty()) + { + if (Out_C2PA_JSON) + { + //Saving file + try + { + File F; + if (!F.Create(Ztring().From_UTF8(Handler->second.Riff->FileName_Get())+__T(".C2PA.json"))) + throw "--out-C2PA-json: error during file creation"; + if (!F.Write(Content)) + throw "--out-C2PA-json: error during file writing"; + } + catch (const char *Message) + { + StdErr(Message); + } + catch (...) {} + } + + if (!Out_C2PA_FileName.empty()) + { + try + { + File F; + if (!F.Create(Ztring().From_UTF8(Out_C2PA_FileName.c_str()))) + throw "--out-C2PA=: error during file creation"; + if (!F.Write(Content)) + throw "--out-C2PA=: error during file writing"; + } + catch (const char *Message) + { + StdErr(Message); + } + catch (...) {} + } + + if (Out_XML_Doc) + { + string Content_UTF8=Content.To_UTF8(); + for (size_t Pos=Content_UTF8.find("]]>" ); Pos!=string::npos; Pos=Content_UTF8.find("]]>", Pos)) + { + // Escape inner CDATA end token in payload before wrapping in CDATA. + Content_UTF8.replace(Pos, 3, "]]]]>"); + Pos+=15; + } + + tinyxml2::XMLElement* File=Out_XML_Doc->RootElement()->LastChildElement(); + if (File) + { + tinyxml2::XMLElement* Parent=File->InsertNewChildElement("C2PA"); + tinyxml2::XMLText* Text=Parent->InsertNewText(Content_UTF8.c_str()); + Text->SetCData(true); + } + } + } +} +#endif // defined(ENABLE_C2PA) + //--------------------------------------------------------------------------- void Core::Batch_Launch_Write(handlers::iterator &Handler) { @@ -2357,6 +2441,7 @@ void Core::Options_Update(handlers::iterator &Handler) //Options Handler->second.Riff->riff2rf64_Reject=riff2rf64_Reject; Handler->second.Riff->Overwrite_Reject=Overwrite_Reject; + Handler->second.Riff->C2PA_Reject=C2PA_Reject; Handler->second.Riff->NoPadding_Accept=NoPadding_Accept; Handler->second.Riff->NewChunksAtTheEnd=NewChunksAtTheEnd; Handler->second.Riff->GenerateMD5=GenerateMD5; @@ -2373,6 +2458,16 @@ void Core::Options_Update(handlers::iterator &Handler) Handler->second.Riff->Write_Encoding=Write_Encoding; Handler->second.Riff->Write_CodePage=Write_CodePage; Handler->second.Riff->Ignore_File_Encoding=Ignore_File_Encoding; + #if defined(ENABLE_C2PA) + Handler->second.Riff->VerifyC2PA=VerifyC2PA; + Handler->second.Riff->VerifyC2PA_Force=VerifyC2PA_Force; + Handler->second.Riff->C2PA_SignCertificate=C2PA_SignCertificate; + Handler->second.Riff->C2PA_SignPrivateKey=C2PA_SignPrivateKey; + Handler->second.Riff->C2PA_SignAlgorithm=C2PA_SignAlgorithm; + Handler->second.Riff->C2PA_SignTA_URL=C2PA_SignTA_URL; + if (!C2PA_SignManifestJson.empty()) + Handler->second.Riff->C2PA_SignManifestJson=C2PA_SignManifestJson; + #endif //defined(ENABLE_C2PA) bool IsModified_Old=Handler->second.Riff->IsModified_Get(); diff --git a/Source/Common/Core.h b/Source/Common/Core.h index 2d2306ad..c868eeff 100644 --- a/Source/Common/Core.h +++ b/Source/Common/Core.h @@ -43,6 +43,9 @@ class Core : public Thread Cout_aXML, Cout_iXML, Cout_cue_, + #if defined(ENABLE_C2PA) + Cout_C2PA, + #endif // defined(ENABLE_C2PA) Cout_XML, //All }; @@ -116,6 +119,7 @@ class Core : public Thread //Configuration bool riff2rf64_Reject; bool Overwrite_Reject; + bool C2PA_Reject; bool NoPadding_Accept; bool Errors_Continue; bool FileNotValid_Skip; @@ -174,6 +178,17 @@ class Core : public Thread bool Write_CodePage; bool Ignore_File_Encoding; bool In_CSET_Remove; + #if defined(ENABLE_C2PA) + string Out_C2PA_FileName; + bool Out_C2PA_JSON; + bool VerifyC2PA; + bool VerifyC2PA_Force; + string C2PA_SignManifestJson; + string C2PA_SignCertificate; + string C2PA_SignPrivateKey; + string C2PA_SignAlgorithm; + string C2PA_SignTA_URL; + #endif // defined(ENABLE_C2PA) bool RevertToRiff; //Status @@ -254,6 +269,9 @@ class Core : public Thread void Batch_Launch_aXML (handlers::iterator &Handler); void Batch_Launch_iXML (handlers::iterator &Handler); void Batch_Launch_cue_ (handlers::iterator &Handler); + #if defined(ENABLE_C2PA) + void Batch_Launch_C2PA (handlers::iterator &Handler); + #endif // defined(ENABLE_C2PA) void Batch_Launch_Write (handlers::iterator &Handler); void Options_Update (handlers::iterator &Handler); void Entry(); diff --git a/Source/GUI/Qt/GUI_Main.cpp b/Source/GUI/Qt/GUI_Main.cpp index ef0322ab..90be7155 100644 --- a/Source/GUI/Qt/GUI_Main.cpp +++ b/Source/GUI/Qt/GUI_Main.cpp @@ -551,6 +551,36 @@ void GUI_Main::LogFile_Set(const string &Value) C->LogFile=Ztring().From_UTF8(Value); } +#if defined(ENABLE_C2PA) +//--------------------------------------------------------------------------- +void GUI_Main::C2PA_SignCertificate_Set(const string &Value) +{ + C->C2PA_SignCertificate=Value; + C->Menu_File_Options_Update(); +} + +//--------------------------------------------------------------------------- +void GUI_Main::C2PA_SignPrivateKey_Set(const string &Value) +{ + C->C2PA_SignPrivateKey=Value; + C->Menu_File_Options_Update(); +} + +//--------------------------------------------------------------------------- +void GUI_Main::C2PA_SignAlgorithm_Set(const string &Value) +{ + C->C2PA_SignAlgorithm=Value; + C->Menu_File_Options_Update(); +} + +//--------------------------------------------------------------------------- +void GUI_Main::C2PA_SignTA_URL_Set(const string &Value) +{ + C->C2PA_SignTA_URL=Value; + C->Menu_File_Options_Update(); +} +#endif // defined(ENABLE_C2PA) + //--------------------------------------------------------------------------- void GUI_Main::OnOpen_Timer () { diff --git a/Source/GUI/Qt/GUI_Main.h b/Source/GUI/Qt/GUI_Main.h index 6300ef98..d8f26095 100644 --- a/Source/GUI/Qt/GUI_Main.h +++ b/Source/GUI/Qt/GUI_Main.h @@ -58,6 +58,12 @@ class GUI_Main : public QMainWindow void OpenSaveDirectory_Set(const string &Value); void BackupDirectory_Set(const string &Value); void LogFile_Set(const string &Value); + #if defined(ENABLE_C2PA) + void C2PA_SignCertificate_Set(const string &Value); + void C2PA_SignPrivateKey_Set(const string &Value); + void C2PA_SignAlgorithm_Set(const string &Value); + void C2PA_SignTA_URL_Set(const string &Value); + #endif // defined(ENABLE_C2PA) //Preferences GUI_Preferences* Preferences; @@ -106,6 +112,9 @@ class GUI_Main : public QMainWindow QAction* Menu_Export_aXML_XML_PerFile; QAction* Menu_Export_iXML_XML_PerFile; QAction* Menu_Export_cue__XML_PerFile; + #if defined(ENABLE_C2PA) + QAction* Menu_Export_C2PA_JSON_PerFile; + #endif // defined(ENABLE_C2PA) QMenu* Menu_Options; QAction* Menu_Options_ResetFieldSizes; QAction* Menu_Options_Preferences; @@ -149,6 +158,9 @@ public Q_SLOTS: void OnMenu_Export_aXML_XML_PerFile (); void OnMenu_Export_iXML_XML_PerFile (); void OnMenu_Export_cue__XML_PerFile (); + #if defined(ENABLE_C2PA) + void OnMenu_Export_C2PA_JSON_PerFile (); + #endif // defined(ENABLE_C2PA) void OnMenu_Rules_Tech3285_Req (bool); void OnMenu_Rules_Tech3285_Rec (bool); void OnMenu_Rules_CodingHistory_Rec (bool); @@ -180,6 +192,10 @@ public Q_SLOTS: void OnMenu_Options_TechCore (bool); void OnMenu_Options_riff2rf64_Reject (bool); void OnMenu_Options_Overwrite_Reject (bool); + void OnMenu_Options_C2PA_Reject (bool); + #if defined(ENABLE_C2PA) + void OnMenu_Options_VerifyC2PA (bool); + #endif // defined(ENABLE_C2PA) void OnMenu_Options_NoPadding_Accept (bool); void OnMenu_Options_FileNotValid_Skip (bool); void OnMenu_Options_WrongExtension_Skip (bool); diff --git a/Source/GUI/Qt/GUI_Main_Menu.cpp b/Source/GUI/Qt/GUI_Main_Menu.cpp index 451d7caf..b349dce2 100644 --- a/Source/GUI/Qt/GUI_Main_Menu.cpp +++ b/Source/GUI/Qt/GUI_Main_Menu.cpp @@ -259,6 +259,15 @@ void GUI_Main::Menu_Create() Menu_Export_cue__XML_PerFile->setStatusTip(tr("")); connect(Menu_Export_cue__XML_PerFile, SIGNAL(triggered()), this, SLOT(OnMenu_Export_cue__XML_PerFile())); + #if defined(ENABLE_C2PA) + Menu_Export_C2PA_JSON_PerFile = new QAction(tr("C2PA crJSON (one JSON file per WAV file)"), this); + #ifdef MACSTORE + Menu_Export_C2PA_JSON_PerFile->setVisible(false); + #endif + Menu_Export_C2PA_JSON_PerFile->setStatusTip(tr("")); + connect(Menu_Export_C2PA_JSON_PerFile, SIGNAL(triggered()), this, SLOT(OnMenu_Export_C2PA_JSON_PerFile())); + #endif // defined(ENABLE_C2PA) + Menu_Export = menuBar()->addMenu(tr("E&xport")); Menu_Export->addAction(Menu_Export_Unified_XML_Global); Menu_Export->addSeparator(); @@ -277,6 +286,10 @@ void GUI_Main::Menu_Create() Menu_Export->addAction(Menu_Export_iXML_XML_PerFile); Menu_Export->addSeparator(); Menu_Export->addAction(Menu_Export_cue__XML_PerFile); + #if defined(ENABLE_C2PA) + Menu_Export->addSeparator(); + Menu_Export->addAction(Menu_Export_C2PA_JSON_PerFile); + #endif // defined(ENABLE_C2PA) //Options (dynamic) Menu_Options_Preferences = new QAction(QIcon(":/Image/Menu/Options_Prefs.png"), tr("Preferences..."), this); @@ -339,6 +352,10 @@ void GUI_Main::Menu_Create() connect(Menu_Fields_CheckBoxes[Group_Rules*options::MaxCount+Option_Rules_EBU_ISRC_Rec ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Rules_EBU_ISRC_Rec(bool))); connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_Riff2Rf64_Reject ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_riff2rf64_Reject(bool))); connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_Overwrite_Reject ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_Overwrite_Reject(bool))); + connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_C2PA_Reject ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_C2PA_Reject(bool))); + #if defined(ENABLE_C2PA) + connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_C2PA_Verify ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_VerifyC2PA(bool))); + #endif // defined(ENABLE_C2PA) connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_NoPadding_Accept ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_NoPadding_Accept(bool))); connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_FileNotValid_Skip ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_FileNotValid_Skip(bool))); connect(Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_WrongExtension_Skip ], SIGNAL(toggled(bool)), this, SLOT(OnMenu_Options_WrongExtension_Skip(bool))); @@ -945,6 +962,23 @@ void GUI_Main::OnMenu_Export_cue__XML_PerFile() C->Out_cue__XML=false; } +#if defined(ENABLE_C2PA) +//--------------------------------------------------------------------------- +void GUI_Main::OnMenu_Export_C2PA_JSON_PerFile() +{ + //Configuring + C->Out_C2PA_JSON=true; + + //Running + C->Simulation_Enabled=true; + C->Batch_Launch(); + C->Simulation_Enabled=false; + + //Clearing + C->Out_C2PA_JSON=false; +} +#endif // defined(ENABLE_C2PA) + //--------------------------------------------------------------------------- void GUI_Main::OnMenu_Rules_Tech3285_Req(bool) { @@ -1316,6 +1350,41 @@ void GUI_Main::OnMenu_Options_Overwrite_Reject(bool) } } +//--------------------------------------------------------------------------- +void GUI_Main::OnMenu_Options_C2PA_Reject(bool) +{ + C->C2PA_Reject=Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_C2PA_Reject]->isChecked(); + C->Menu_File_Options_Update(); + + if (View==NULL) + return; + QEvent Event((QEvent::Type)(QEvent::User+2)); + QApplication::sendEvent(View, &Event); +} + +#if defined(ENABLE_C2PA) +//--------------------------------------------------------------------------- +void GUI_Main::OnMenu_Options_VerifyC2PA(bool) +{ + C->VerifyC2PA=Menu_Fields_CheckBoxes[Group_File*options::MaxCount+Option_File_C2PA_Verify]->isChecked(); + if (C->VerifyC2PA==true) + { + C->Menu_File_Options_Update(); + + //Showing + if (C->Text_stderr_Updated_Get()) + { + Menu_View_Output_stderr->setChecked(true); + View_Refresh(View_Output_stderr); + } + else + View_Refresh(); + } + else + C->Menu_File_Options_Update(); +} +#endif // defined(ENABLE_C2PA) + //--------------------------------------------------------------------------- void GUI_Main::OnMenu_Options_NoPadding_Accept(bool) { diff --git a/Source/GUI/Qt/GUI_Main_PerFile.cpp b/Source/GUI/Qt/GUI_Main_PerFile.cpp index 4e49af27..148050c1 100644 --- a/Source/GUI/Qt/GUI_Main_PerFile.cpp +++ b/Source/GUI/Qt/GUI_Main_PerFile.cpp @@ -20,6 +20,9 @@ #include "GUI/Qt/GUI_Main_xxxx_TimeReferenceDialog.h" #include "GUI/Qt/GUI_Main_xxxx_UmidDialog.h" #include "GUI/Qt/GUI_Main_xxxx_CueDialog.h" +#if defined(ENABLE_C2PA) +#include "GUI/Qt/GUI_Main_xxxx_C2PADialog.h" +#endif // defined(ENABLE_C2PA) #include "GUI/Qt/GUI_Main_xxxx_EditMenu.h" #include "Common/Core.h" #include "ZenLib/ZtringListList.h" @@ -277,6 +280,10 @@ Q_INVOKABLE bool PerFileModel::visible(const QString& FileName, const QString& F // if (Field=="MD5Stored" && (!C->Get(FileName.toStdString(), Field.toStdString()).empty() &&)) // toReturn=true; + //C2PA's Get() never returns an empty string (it returns "No"/"Yes"/"Valid"/"Invalid"), so treat "No" as the empty case here + if (Field=="C2PA" && !EditMode.value(FileName, false) && C->Get(FileName.toStdString(), Field.toStdString())=="No") + toReturn=false; + if (!EditMode.value(FileName, false) && C->Get(FileName.toStdString(), Field.toStdString()).empty() && C->IsValid(FileName.toStdString(), Field.toStdString(), "", true)) @@ -307,7 +314,20 @@ Q_INVOKABLE bool PerFileModel::readOnly(const QString& FileName, const QString& //--------------------------------------------------------------------------- Q_INVOKABLE bool PerFileModel::readOnly(const QString& FileName) const { - return C->IsReadOnly_Get(FileName.toStdString()); + return C->IsReadOnly_Get(FileName.toStdString()) || c2paLocked(FileName); +} + +//--------------------------------------------------------------------------- +Q_INVOKABLE bool PerFileModel::c2paLocked(const QString& FileName) const { + return C->C2PA_Reject && C->Get(FileName.toStdString(), "C2PA")!="Absent"; +} + +//--------------------------------------------------------------------------- +Q_INVOKABLE bool PerFileModel::c2paEnabled() const { + #if defined(ENABLE_C2PA) + return true; + #endif + return false; } //--------------------------------------------------------------------------- @@ -321,6 +341,20 @@ Q_INVOKABLE void PerFileModel::editField(const QString& FileName, const QString& string ModifiedContent=C->Get(FileName.toStdString(), Field.toStdString()); AdaptEOL(ModifiedContent, adapt_n); + if (Field=="C2PA") + { + #if defined(ENABLE_C2PA) + bool Writable=!readOnly(FileName, Field); + GUI_Main_xxxx_C2PADialog* Edit=new GUI_Main_xxxx_C2PADialog(C, FileName.toStdString(), NULL, Writable); + Edit->exec(); + delete Edit; //Edit=NULL; + + Q_EMIT dataChanged(index(FileNames.indexOf(FileName)), index(FileNames.indexOf(FileName))); + Main->Menu_Update(); + #endif // defined(ENABLE_C2PA) + return; + } + if (readOnly(FileName, Field)) { if ((Field=="MD5Generated" || Field=="MD5Stored") && Main->Preferences->Group_Option_Checked_Get(Group_MD5, Option_MD5_SwapEndian) && !ModifiedContent.empty()) ModifiedContent=Swap_MD5_Endianess(QString::fromUtf8(ModifiedContent.c_str())).toStdString(); @@ -494,7 +528,7 @@ void PerFileModel::Fill() QString FileName = QString::fromUtf8(List[Pos][0].To_UTF8().c_str()); FileNames.append(FileName); Expanded.insert(FileName, Expanded_Old.value(FileName, true)); - EditMode.insert(FileName, EditMode_Old.value(FileName, false)); + EditMode.insert(FileName, !readOnly(FileName) && EditMode_Old.value(FileName, false)); } C->Menu_File_Close_File_FileName_Clear(); @@ -540,6 +574,7 @@ QString PerFileModel::Technical_Info(const QString FileName) const QString SampleRate=Get_Technical_Field(FileName, "SampleRate"); QString BitRate=Get_Technical_Field(FileName, "BitRate"); QString BitPerSample=Get_Technical_Field(FileName, "BitPerSample"); + QString C2PA=Get_Technical_Field(FileName, "C2PA"); QString Encoding=Get_Technical_Field(FileName, "Encoding"); if (!FileSize.isEmpty()) @@ -594,6 +629,13 @@ QString PerFileModel::Technical_Info(const QString FileName) const toReturn+=" bit"; } + if (!C2PA.isEmpty()) + { + toReturn+=toReturn.isEmpty()?"":" "; + toReturn+="C2PA: "; + toReturn+=C2PA; + } + if (!Encoding.isEmpty()) { toReturn+=toReturn.isEmpty()?"":" "; diff --git a/Source/GUI/Qt/GUI_Main_PerFile.h b/Source/GUI/Qt/GUI_Main_PerFile.h index e00e3964..a137b1c8 100644 --- a/Source/GUI/Qt/GUI_Main_PerFile.h +++ b/Source/GUI/Qt/GUI_Main_PerFile.h @@ -77,6 +77,8 @@ class PerFileModel : public QAbstractListModel Q_INVOKABLE bool modified(const QString& FileName, const QString& Field) const; Q_INVOKABLE bool readOnly(const QString& FileName, const QString& Field) const; Q_INVOKABLE bool readOnly(const QString& FileName) const; + Q_INVOKABLE bool c2paLocked(const QString& FileName) const; + Q_INVOKABLE bool c2paEnabled() const; Q_INVOKABLE bool isWritable(const QString& FileName) const; Q_INVOKABLE bool visible(const QString& FileName, const QString& Field) const; Q_INVOKABLE void editField(const QString& FileName, const QString& Field); diff --git a/Source/GUI/Qt/GUI_Main_Technical_Table.cpp b/Source/GUI/Qt/GUI_Main_Technical_Table.cpp index 0a9b7cf4..55983fc8 100644 --- a/Source/GUI/Qt/GUI_Main_Technical_Table.cpp +++ b/Source/GUI/Qt/GUI_Main_Technical_Table.cpp @@ -14,6 +14,9 @@ #include "GUI/Qt/GUI_Main_xxxx_TextEditDialog.h" #include "GUI/Qt/GUI_Main_xxxx_CueDialog.h" #include "GUI/Qt/GUI_Main_xxxx_CodePageDialog.h" +#if defined(ENABLE_C2PA) +#include "GUI/Qt/GUI_Main_xxxx_C2PADialog.h" +#endif // defined(ENABLE_C2PA) #include "Common/Core.h" #include "ZenLib/ZtringListList.h" #include @@ -416,6 +419,18 @@ bool GUI_Main_Technical_Table::edit (const QModelIndex &index, EditTrigger trigg return false; } + //C2PA + if (Field=="C2PA") + { + #if defined(ENABLE_C2PA) + //User interaction + GUI_Main_xxxx_C2PADialog* Edit=new GUI_Main_xxxx_C2PADialog(C, FileName, NULL, !C->IsReadOnly_Get(FileName)); + Edit->exec(); + delete Edit; //Edit=NULL; + #endif // defined(ENABLE_C2PA) + return false; + } + return false; } diff --git a/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.cpp b/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.cpp new file mode 100644 index 00000000..ba95862f --- /dev/null +++ b/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.cpp @@ -0,0 +1,524 @@ +// BWF MetaEdit GUI - A GUI for BWF MetaEdit +// +// This code was created in 2010 for the Library of Congress and the +// other federal government agencies participating in the Federal Agencies +// Digital Guidelines Initiative and it is in the public domain. +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +#include "GUI/Qt/GUI_Main_xxxx_C2PADialog.h" +#include "Common/Core.h" +#include "ZenLib/Ztring.h" +#include "ZenLib/File.h" +#include "ThirdParty/json/json.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace ZenLib; +using namespace std; +//--------------------------------------------------------------------------- + +//*************************************************************************** +// JSON helpers +//*************************************************************************** + +//--------------------------------------------------------------------------- +static json_value_s* Json_Object_Find(json_value_s* Value, const string &Key) +{ + if (!Value || Value->type!=json_type_object) + return NULL; + + json_object_s* Object=json_value_as_object(Value); + if (!Object) + return NULL; + + for (json_object_element_s* Element=Object->start; Element; Element=Element->next) + if (Element->name && Element->name->string && Key==Element->name->string) + return Element->value; + + return NULL; +} + +//--------------------------------------------------------------------------- +static QString Json_String(json_value_s* Value) +{ + if (!Value) + return QString(); + + if (Value->type==json_type_string) + { + json_string_s* String=json_value_as_string(Value); + if (String && String->string) + return QString::fromUtf8(String->string, (int)String->string_size); + } + else if (Value->type==json_type_number) + { + json_number_s* Number=json_value_as_number(Value); + if (Number && Number->number) + return QString::fromUtf8(Number->number, (int)Number->number_size); + } + else if (Value->type==json_type_true) + return "true"; + else if (Value->type==json_type_false) + return "false"; + + return QString(); +} + +//--------------------------------------------------------------------------- +static QList Json_Members (json_value_s* Value) +{ + QList ToReturn; + + if (!Value) + return ToReturn; + + if (Value->type==json_type_array) + { + json_array_s* Array=json_value_as_array(Value); + if (Array) + for (json_array_element_s* Element=Array->start; Element; Element=Element->next) + ToReturn.append(Element->value); + } + else if (Value->type==json_type_object) + { + json_object_s* Object=json_value_as_object(Value); + if (Object) + for (json_object_element_s* Element=Object->start; Element; Element=Element->next) + ToReturn.append(Element->value); + } + + return ToReturn; +} + +//--------------------------------------------------------------------------- +static QString Json_CommonName (json_value_s* Value) +{ + QString CommonName=Json_String(Json_Object_Find(Value, "CN")); + if (!CommonName.isEmpty()) + return CommonName; + + return Json_String(Json_Object_Find(Value, "O")); +} + +//*************************************************************************** +// Constructor/Destructor +//*************************************************************************** + +//--------------------------------------------------------------------------- +GUI_Main_xxxx_C2PADialog::GUI_Main_xxxx_C2PADialog(Core* _C, const std::string &FileName_, QWidget* parent, bool Writable_) +: QDialog(parent) +{ + //Internal + C=_C; + FileName=FileName_; + Status=C->Get(FileName, "C2PA"); + Manifest=C->Get(FileName, "c2pajson"); + Writable=Writable_; + //SignManifestPath is only known for the lifetime of the dialog instance that just imported a file (set in + //OnMenu_Import); reopening the dialog on an already-staged file still detects the staged state (via + //Update_PendingBanner querying Core directly) but can no longer show the original file name + + //Configuration + setWindowFlags(windowFlags()&(~Qt::WindowContextHelpButtonHint)); + setWindowTitle("C2PA Content Credentials"); + setWindowIcon (QIcon(":/Image/Logo/Logo.png")); + + //Buttons + Export=new QPushButton("&Export manifest..."); + Import=new QPushButton("&Import signing manifest..."); + ClearImport=new QPushButton("&Clear staged manifest"); + Dialog=new QDialogButtonBox(QDialogButtonBox::Close); + if (!Manifest.empty()) + Dialog->addButton(Export, QDialogButtonBox::ResetRole); + if (Writable) + { + Dialog->addButton(Import, QDialogButtonBox::ActionRole); + bool HasSigningCredentials=!C->C2PA_SignCertificate.empty() && !C->C2PA_SignPrivateKey.empty(); + Import->setEnabled(HasSigningCredentials); + if (!HasSigningCredentials) + Import->setToolTip(tr("Configure a signing certificate and private key in Preferences → C2PA first.")); + Dialog->addButton(ClearImport, QDialogButtonBox::ActionRole); + } + connect(Dialog, SIGNAL(rejected()), this, SLOT(reject())); + connect(Export, SIGNAL(clicked()), this, SLOT(OnMenu_Export())); + connect(Import, SIGNAL(clicked()), this, SLOT(OnMenu_Import())); + connect(ClearImport, SIGNAL(clicked()), this, SLOT(OnMenu_ClearImport())); + + //Central - Summary + QScrollArea* Scroll=new QScrollArea(this); + Scroll->setWidgetResizable(true); + Scroll->setFrameShape(QFrame::NoFrame); + Scroll->setWidget(Build_Summary()); + Update_PendingBanner(); + + //Central - Raw JSON + QTextEdit* RawText=new QTextEdit(this); + RawText->setReadOnly(true); + static const int MaxSize=0x100000; + if (Manifest.empty()) + RawText->setPlainText(Status=="Absent"?tr("No C2PA manifest present in this file."):tr("A C2PA chunk is present, but its manifest could not be read.")); + else if (Manifest.size()setPlainText(QString::fromUtf8(Manifest.c_str())); + else + RawText->setPlainText(QString::fromUtf8(Manifest.substr(0, MaxSize).c_str())+"\n..."); + + Central=new QTabWidget(this); + Central->addTab(Scroll, tr("&Summary")); + Central->addTab(RawText, tr("&Raw JSON")); + + QVBoxLayout* L=new QVBoxLayout(); + L->addWidget(Central); + L->addWidget(Dialog); + + setLayout(L); + + QScreen* Screen=QApplication::screenAt(mapToGlobal(QPoint(0,0))); + if (Screen) + resize(Screen->availableGeometry().width()/2, Screen->availableGeometry().height()/2); +} + +//*************************************************************************** +// Menu actions +//*************************************************************************** + +//--------------------------------------------------------------------------- +void GUI_Main_xxxx_C2PADialog::OnMenu_Export () +{ + //User interaction + QString FileNamesQ=QFileDialog::getSaveFileName(this, tr("Export C2PA manifest..."), QString::fromUtf8(C->OpenSaveFolder.c_str()), "JSON files (*.json);;All files (*.*)"); + + if (FileNamesQ.isEmpty()) + return; + + File F; + if (!F.Create(ZenLib::Ztring().From_UTF8(FileNamesQ.toUtf8().data()))) + return; + + F.Write((const int8u*)Manifest.c_str(), Manifest.size()); +} + +//--------------------------------------------------------------------------- +void GUI_Main_xxxx_C2PADialog::OnMenu_Import () +{ + //User interaction + QString FileNameQ=QFileDialog::getOpenFileName(this, tr("Import C2PA signing manifest..."), QString::fromUtf8(C->OpenSaveFolder.c_str()), "JSON files (*.json);;All files (*.*)"); + + if (FileNameQ.isEmpty()) + return; + + //The manifest is staged as text (read now), not as a path: the file may not be reachable anymore by the + //time Save() actually signs (portal-style file access on Flatpak, macOS sandboxed bookmarks...) + File F; + string Content; + bool Ok=F.Open(ZenLib::Ztring().From_UTF8(FileNameQ.toUtf8().data())); + if (Ok) + { + int64u F_Size=F.Size_Get(); + if (F_Size && F_Size<=((size_t)-1)-1) + { + int8u* Buffer=new int8u[(size_t)F_Size]; + size_t Buffer_Offset=0; + while (Buffer_OffsetSet(FileName, "C2PASignManifest", Content); + SignManifestPath=FileNameQ.toStdString(); + Update_PendingBanner(); +} + +//--------------------------------------------------------------------------- +void GUI_Main_xxxx_C2PADialog::OnMenu_ClearImport () +{ + C->Set(FileName, "C2PASignManifest", string()); + SignManifestPath.clear(); + Update_PendingBanner(); +} + +//*************************************************************************** +// Helpers +//*************************************************************************** + +//--------------------------------------------------------------------------- +QLabel* GUI_Main_xxxx_C2PADialog::Build_StatusBanner () +{ + QString Text; + QString Color; + if (Status=="Absent") + { + Text=tr("No C2PA manifest found in this file."); + Color="gray"; + } + else if (Manifest.empty()) + { + //The chunk is present but its content could not be extracted (eg the C2PA library is missing or failed) + Text=QString(QChar(0x26A0))+" "+tr("A C2PA chunk is present, but its manifest could not be read."); + Color="#e67e22"; + } + else if (Status=="Valid") + { + Text=QString(QChar(0x2714))+" "+tr("Signature is valid"); + Color="#27ae60"; + } + else if (Status=="Invalid") + { + Text=QString(QChar(0x2716))+" "+tr("Signature is invalid"); + Color="#c0392b"; + } + else + { + Text=QString(QChar(0x2139))+" "+tr("C2PA manifest present (not verified)"); + Color="#2980b9"; + } + + QLabel* Banner=new QLabel(Text); + Banner->setStyleSheet("font-size: 13pt; font-weight: bold; color: "+Color+";"); + + return Banner; +} + +//--------------------------------------------------------------------------- +QLabel* GUI_Main_xxxx_C2PADialog::Build_ValidationItem (json_value_s* Item, const QString &Color, const QString &Glyph) +{ + QString Code=Json_String(Json_Object_Find(Item, "code")); + QString Explanation=Json_String(Json_Object_Find(Item, "explanation")); + QString Url=Json_String(Json_Object_Find(Item, "url")); + + QString Text=""+Glyph+" "+Code.toHtmlEscaped()+""; + if (!Explanation.isEmpty()) + Text+=" — "+Explanation.toHtmlEscaped(); + + QLabel* Label=new QLabel(); + Label->setTextFormat(Qt::RichText); + Label->setWordWrap(true); + Label->setText(Text); + if (!Url.isEmpty()) + Label->setToolTip(Url); + + return Label; +} + +//--------------------------------------------------------------------------- +QWidget* GUI_Main_xxxx_C2PADialog::Build_ValidationResults (json_value_s* ValidationResults) +{ + QList Success=Json_Members(Json_Object_Find(ValidationResults, "success")); + QList Informational=Json_Members(Json_Object_Find(ValidationResults, "informational")); + QList Failure=Json_Members(Json_Object_Find(ValidationResults, "failure")); + + QGroupBox* Group=new QGroupBox(tr("Validation results")); + QVBoxLayout* Layout=new QVBoxLayout(Group); + + QLabel* Summary=new QLabel(tr("%1 check(s) passed, %2 warning(s), %3 failure(s)").arg(Success.count()).arg(Informational.count()).arg(Failure.count())); + Summary->setStyleSheet(Failure.isEmpty()?"color: #27ae60; font-weight: bold;":"color: #c0392b; font-weight: bold;"); + Layout->addWidget(Summary); + + for (int Pos=0; PosaddWidget(Build_ValidationItem(Failure[Pos], "#c0392b", QString(QChar(0x2717)))); + + for (int Pos=0; PosaddWidget(Build_ValidationItem(Informational[Pos], "#e67e22", QString(QChar(0x2139)))); + + QString SpecVersion=Json_String(Json_Object_Find(ValidationResults, "specVersion")); + QString ValidationTime=Json_String(Json_Object_Find(ValidationResults, "validationTime")); + if (!SpecVersion.isEmpty() || !ValidationTime.isEmpty()) + { + QLabel* Footer=new QLabel(tr("C2PA spec %1, validated %2").arg(SpecVersion, ValidationTime)); + Footer->setStyleSheet("color: gray;"); + Layout->addWidget(Footer); + } + + return Group; +} + +//--------------------------------------------------------------------------- +QWidget* GUI_Main_xxxx_C2PADialog::Build_Manifest (json_value_s* ManifestValue, int Index, int Count) +{ + QGroupBox* Group=new QGroupBox(Count>1?tr("Manifest %1 of %2").arg(Index).arg(Count):tr("Manifest")); + QVBoxLayout* Layout=new QVBoxLayout(Group); + + json_value_s* Claim=Json_Object_Find(ManifestValue, "claim.v2"); + if (!Claim) + Claim=Json_Object_Find(ManifestValue, "claim.v1"); + + QFormLayout* Form=new QFormLayout(); + + QString Title=Json_String(Json_Object_Find(Claim, "dc:title")); + if (!Title.isEmpty()) + Form->addRow(tr("Title:"), new QLabel(Title)); + + QString Label=Json_String(Json_Object_Find(ManifestValue, "label")); + if (!Label.isEmpty()) + { + QLabel* LabelWidget=new QLabel(Label); + LabelWidget->setTextInteractionFlags(Qt::TextSelectableByMouse); + Form->addRow(tr("Manifest ID:"), LabelWidget); + } + + QString InstanceID=Json_String(Json_Object_Find(Claim, "instanceID")); + if (!InstanceID.isEmpty()) + { + QLabel* InstanceIDWidget=new QLabel(InstanceID); + InstanceIDWidget->setTextInteractionFlags(Qt::TextSelectableByMouse); + Form->addRow(tr("Instance ID:"), InstanceIDWidget); + } + + json_value_s* GeneratorInfo=Json_Object_Find(Claim, "claim_generator_info"); + QString Generator=(Json_String(Json_Object_Find(GeneratorInfo, "name"))+" "+Json_String(Json_Object_Find(GeneratorInfo, "version"))).trimmed(); + if (!Generator.isEmpty()) + Form->addRow(tr("Claim generator:"), new QLabel(Generator)); + + json_value_s* Assertions=Json_Object_Find(ManifestValue, "assertions"); + json_value_s* ActionsAssertion=Json_Object_Find(Assertions, "c2pa.actions.v2"); + if (!ActionsAssertion) + ActionsAssertion=Json_Object_Find(Assertions, "c2pa.actions"); + QList Actions=Json_Members(Json_Object_Find(ActionsAssertion, "actions")); + if (!Actions.isEmpty()) + { + QStringList ActionNames; + for (int Pos=0; PosaddRow(tr("Actions:"), new QLabel(ActionNames.join(", "))); + } + + json_value_s* Signature=Json_Object_Find(ManifestValue, "signature"); + QString Algorithm=Json_String(Json_Object_Find(Signature, "algorithm")); + if (!Algorithm.isEmpty()) + Form->addRow(tr("Signature algorithm:"), new QLabel(Algorithm.toUpper())); + + json_value_s* CertificateInfo=Json_Object_Find(Signature, "certificateInfo"); + QString Subject=Json_CommonName(Json_Object_Find(CertificateInfo, "subject")); + if (!Subject.isEmpty()) + Form->addRow(tr("Signed by:"), new QLabel(Subject)); + + QString Issuer=Json_CommonName(Json_Object_Find(CertificateInfo, "issuer")); + if (!Issuer.isEmpty()) + Form->addRow(tr("Issued by:"), new QLabel(Issuer)); + + json_value_s* Validity=Json_Object_Find(CertificateInfo, "validity"); + QString NotBefore=Json_String(Json_Object_Find(Validity, "notBefore")); + QString NotAfter=Json_String(Json_Object_Find(Validity, "notAfter")); + if (!NotBefore.isEmpty() || !NotAfter.isEmpty()) + Form->addRow(tr("Certificate validity:"), new QLabel(NotBefore+" - "+NotAfter)); + + Layout->addLayout(Form); + + json_value_s* ValidationResults=Json_Object_Find(ManifestValue, "validationResults"); + if (ValidationResults) + Layout->addWidget(Build_ValidationResults(ValidationResults)); + + return Group; +} + +//--------------------------------------------------------------------------- +void GUI_Main_xxxx_C2PADialog::Update_PendingBanner () +{ + bool HasStagedManifest=!C->Get(FileName, "C2PASignManifest").empty(); + ClearImport->setEnabled(HasStagedManifest); + + if (!HasStagedManifest) + { + PendingBanner->hide(); + return; + } + + if (SignManifestPath.empty()) + PendingBanner->setText(QString(QChar(0x270D))+" "+tr("A signing manifest is staged and will be applied when this file is saved.")); + else + { + QString Base=QFileInfo(QString::fromUtf8(SignManifestPath.c_str())).fileName(); + PendingBanner->setText(QString(QChar(0x270D))+" "+tr("A signing manifest is staged and will be applied when this file is saved: %1").arg(Base)); + } + PendingBanner->show(); +} + +//--------------------------------------------------------------------------- +QWidget* GUI_Main_xxxx_C2PADialog::Build_Summary () +{ + QWidget* Widget=new QWidget(); + QVBoxLayout* Layout=new QVBoxLayout(Widget); + Layout->setAlignment(Qt::AlignTop); + + Layout->addWidget(Build_StatusBanner()); + + PendingBanner=new QLabel(); + PendingBanner->setWordWrap(true); + PendingBanner->setStyleSheet("color: #8e44ad; font-weight: bold;"); + PendingBanner->hide(); + Layout->addWidget(PendingBanner); + + if (Manifest.empty()) + { + if (Status!="Absent") + { + QLabel* Hint=new QLabel(tr("Verify that the C2PA library (libc2pa_c) is installed and reachable by this application.")); + Hint->setWordWrap(true); + Hint->setStyleSheet("color: gray;"); + Layout->addWidget(Hint); + } + return Widget; + } + + json_value_s* Root=json_parse(Manifest.data(), Manifest.size()); + if (!Root) + { + Layout->addWidget(new QLabel(tr("The C2PA manifest could not be parsed."))); + return Widget; + } + + QList Manifests=Json_Members(Json_Object_Find(Root, "manifests")); + for (int Pos=0; PosaddWidget(Build_Manifest(Manifests[Pos], Pos+1, Manifests.count())); + + json_value_s* Generator=Json_Object_Find(Root, "jsonGenerator"); + QString GeneratorText=(Json_String(Json_Object_Find(Generator, "name"))+" "+Json_String(Json_Object_Find(Generator, "version"))).trimmed(); + if (!GeneratorText.isEmpty()) + { + QLabel* Footer=new QLabel(tr("Generated by %1").arg(GeneratorText)); + Footer->setStyleSheet("color: gray; font-style: italic;"); + Layout->addWidget(Footer); + } + + free(Root); + + return Widget; +} diff --git a/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.h b/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.h new file mode 100644 index 00000000..1dad3eb7 --- /dev/null +++ b/Source/GUI/Qt/GUI_Main_xxxx_C2PADialog.h @@ -0,0 +1,72 @@ +// BWF MetaEdit GUI - A GUI for BWF MetaEdit +// +// This code was created in 2010 for the Library of Congress and the +// other federal government agencies participating in the Federal Agencies +// Digital Guidelines Initiative and it is in the public domain. +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +#ifndef GUI_Main_xxxx_C2PADialogH +#define GUI_Main_xxxx_C2PADialogH +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +class Core; +#include +#include +class QWidget; +class QLabel; +class QTabWidget; +class QTextEdit; +class QPushButton; +class QDialogButtonBox; +struct json_value_s; +//--------------------------------------------------------------------------- + +//*************************************************************************** +// GUI_Main_xxxx_C2PADialog +//*************************************************************************** + +class GUI_Main_xxxx_C2PADialog : public QDialog +{ + Q_OBJECT + +public: + //Constructor/Destructor + GUI_Main_xxxx_C2PADialog(Core* C, const std::string &FileName_, QWidget* parent=NULL, bool Writable=false); + +private Q_SLOTS: + //Actions + void OnMenu_Export (); + void OnMenu_Import (); + void OnMenu_ClearImport (); + +private: + //Internal + Core* C; + std::string FileName; + std::string Status; + std::string Manifest; + bool Writable; + std::string SignManifestPath; + + //Widgets + QTabWidget* Central; + QDialogButtonBox* Dialog; + QPushButton* Export; + QPushButton* Import; + QPushButton* ClearImport; + QLabel* PendingBanner; + + //Helpers + QWidget* Build_Summary (); + QLabel* Build_StatusBanner (); + QWidget* Build_Manifest (json_value_s* ManifestValue, int Index, int Count); + QWidget* Build_ValidationResults (json_value_s* ValidationResults); + QLabel* Build_ValidationItem (json_value_s* Item, const QString &Color, const QString &Glyph); + void Update_PendingBanner (); +}; + +#endif diff --git a/Source/GUI/Qt/GUI_Main_xxxx__Common.cpp b/Source/GUI/Qt/GUI_Main_xxxx__Common.cpp index 6bb001ed..09e006c2 100644 --- a/Source/GUI/Qt/GUI_Main_xxxx__Common.cpp +++ b/Source/GUI/Qt/GUI_Main_xxxx__Common.cpp @@ -321,7 +321,10 @@ void GUI_Main_xxxx__Common::Colors_Update () //--------------------------------------------------------------------------- void GUI_Main_xxxx__Common::Colors_Update (QTableWidgetItem* Item, const string &FileName, const string &Field) { - if (!C->IsValid_Get(FileName) || C->IsReadOnly_Get(FileName) || !Fill_Enabled(FileName, Field, C->Get(FileName, Field=="Cue"?"cuexml":Field))) + bool C2PALocked=C->C2PA_Reject && C->Get(FileName, "C2PA")!="Absent"; + bool ReadOnly=C->IsReadOnly_Get(FileName) || C2PALocked; + + if (!C->IsValid_Get(FileName) || ReadOnly || !Fill_Enabled(FileName, Field, C->Get(FileName, Field=="Cue"?"cuexml":Field))) { const QPalette DefaultPalette; bool DarkMode=DefaultPalette.color(QPalette::WindowText).lightness()>DefaultPalette.color(QPalette::Window).lightness(); @@ -352,7 +355,7 @@ void GUI_Main_xxxx__Common::Colors_Update (QTableWidgetItem* Item, const string } } - if (C->IsReadOnly_Get(FileName)) + if (ReadOnly) { if(Field=="FileName" && Item->icon().isNull()) Item->setIcon(QIcon(":/Image/Menu/Warning.svg")); @@ -363,7 +366,7 @@ void GUI_Main_xxxx__Common::Colors_Update (QTableWidgetItem* Item, const string Message+=Item->toolTip(); Message+="\n\n"; } - Message+="Edit mode disabled, file is read only!"; + Message+=C2PALocked?"Edit mode disabled, editing would invalidate the C2PA signature!":"Edit mode disabled, file is read only!"; Item->setToolTip(Message); } } diff --git a/Source/GUI/Qt/GUI_Preferences.cpp b/Source/GUI/Qt/GUI_Preferences.cpp index ba6cdca7..b4a56636 100644 --- a/Source/GUI/Qt/GUI_Preferences.cpp +++ b/Source/GUI/Qt/GUI_Preferences.cpp @@ -47,13 +47,42 @@ #endif //_WIN32 using namespace std; using namespace ZenLib; + +//--------------------------------------------------------------------------- +#if defined(ENABLE_C2PA) +static bool C2PA_ReadWholeFile(const string& FileName, string& Content) +{ + File F; + if (!F.Open(Ztring().From_UTF8(FileName))) + return false; + + int64u F_Size=F.Size_Get(); + if (F_Size>((size_t)-1)-1) + return false; + + int8u* Buffer=new int8u[(size_t)F_Size]; + size_t Buffer_Offset=0; + while (Buffer_OffsetBext_MaxVersion_Set(Extra_Bext_MaxVersion->value()); Extra_Bext_Toggle->setChecked(Config("Extra_Bext_Toggle").To_int64u()?true:false); Main->Bext_Toggle_Set(Extra_Bext_Toggle->isChecked()); + + #if defined(ENABLE_C2PA) + //C2PA + //The certificate/key content itself is persisted (base64-encoded, the config file is line-based text) rather + //than only the file path, because the original file may no longer be reachable at next launch (portal-style + //file access on Linux, sandbox on macOS...); the path is kept only for on-screen reference. + C2PA_SignCertificate_Path->setText(Config("C2PA_SignCertificatePath").To_UTF8().c_str()); + if (!Config("C2PA_SignCertificateContent").empty()) + { + QByteArray Decoded=QByteArray::fromBase64(Config("C2PA_SignCertificateContent").To_UTF8().c_str()); + Main->C2PA_SignCertificate_Set(string(Decoded.constData(), (size_t)Decoded.size())); + } + else + Main->C2PA_SignCertificate_Set(string()); + C2PA_SignPrivateKey_Path->setText(Config("C2PA_SignPrivateKeyPath").To_UTF8().c_str()); + if (!Config("C2PA_SignPrivateKeyContent").empty()) + { + QByteArray Decoded=QByteArray::fromBase64(Config("C2PA_SignPrivateKeyContent").To_UTF8().c_str()); + Main->C2PA_SignPrivateKey_Set(string(Decoded.constData(), (size_t)Decoded.size())); + } + else + Main->C2PA_SignPrivateKey_Set(string()); + { + string Algorithm=Config("C2PA_SignAlgorithm").empty()?string("es256"):Config("C2PA_SignAlgorithm").To_UTF8(); + int Index=C2PA_SignAlgorithm_Combo->findData(QString::fromStdString(Algorithm)); + C2PA_SignAlgorithm_Combo->setCurrentIndex(Index>=0?Index:0); + Main->C2PA_SignAlgorithm_Set(C2PA_SignAlgorithm_Combo->currentData().toString().toStdString()); + } + C2PA_SignTA_URL_Edit->setText(Config("C2PA_SignTA_URL").To_UTF8().c_str()); + Main->C2PA_SignTA_URL_Set(C2PA_SignTA_URL_Edit->text().toUtf8().data()); + #endif // defined(ENABLE_C2PA) } //--------------------------------------------------------------------------- @@ -673,6 +738,62 @@ void GUI_Preferences::OnSave() Main->Bext_Toggle_Set(Data.To_int8u()?true:false); } + #if defined(ENABLE_C2PA) + //C2PA + //The path is saved only for on-screen reference; the content (already pushed live to Core when the file was + //selected) is what's actually persisted, base64-encoded since the config file is line-based text. + { + Ztring Content; + Content+=__T("C2PA_SignCertificatePath"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(C2PA_SignCertificate_Path->text().toUtf8().data()); + Content+=EOL; + Prefs.Write(Content); + } + { + Ztring Content; + Content+=__T("C2PA_SignCertificateContent"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(QString(QByteArray(C->C2PA_SignCertificate.data(), (int)C->C2PA_SignCertificate.size()).toBase64()).toStdString()); + Content+=EOL; + Prefs.Write(Content); + } + { + Ztring Content; + Content+=__T("C2PA_SignPrivateKeyPath"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(C2PA_SignPrivateKey_Path->text().toUtf8().data()); + Content+=EOL; + Prefs.Write(Content); + } + { + Ztring Content; + Content+=__T("C2PA_SignPrivateKeyContent"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(QString(QByteArray(C->C2PA_SignPrivateKey.data(), (int)C->C2PA_SignPrivateKey.size()).toBase64()).toStdString()); + Content+=EOL; + Prefs.Write(Content); + } + { + Ztring Content; + Content+=__T("C2PA_SignAlgorithm"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(C2PA_SignAlgorithm_Combo->currentData().toString().toStdString()); + Content+=EOL; + Prefs.Write(Content); + Main->C2PA_SignAlgorithm_Set(C2PA_SignAlgorithm_Combo->currentData().toString().toStdString()); + } + { + Ztring Content; + Content+=__T("C2PA_SignTA_URL"); + Content+=__T(" = "); + Content+=Ztring().From_UTF8(C2PA_SignTA_URL_Edit->text().toUtf8().data()); + Content+=EOL; + Prefs.Write(Content); + Main->C2PA_SignTA_URL_Set(C2PA_SignTA_URL_Edit->text().toUtf8().data()); + } + #endif // defined(ENABLE_C2PA) + //Menu Main->Menu_Update(); @@ -911,6 +1032,46 @@ void GUI_Preferences::OnExtra_LogFile_Activated_BrowseClicked (bool) Extra_LogFile_Activated->setText(File); } +#if defined(ENABLE_C2PA) +//--------------------------------------------------------------------------- +void GUI_Preferences::OnC2PA_SignCertificate_BrowseClicked (bool) +{ + QString FileName = QFileDialog::getOpenFileName(this, + tr("Open File..."), + C2PA_SignCertificate_Path->text(), + "Certificate files (*.pem *.crt *.cer);;All files (*.*)"); + + if (FileName.isEmpty()) + return; + + string Content; + if (!C2PA_ReadWholeFile(FileName.toUtf8().data(), Content)) + return; + + C2PA_SignCertificate_Path->setText(FileName); + Main->C2PA_SignCertificate_Set(Content); +} + +//--------------------------------------------------------------------------- +void GUI_Preferences::OnC2PA_SignPrivateKey_BrowseClicked (bool) +{ + QString FileName = QFileDialog::getOpenFileName(this, + tr("Open File..."), + C2PA_SignPrivateKey_Path->text(), + "Key files (*.pem *.key);;All files (*.*)"); + + if (FileName.isEmpty()) + return; + + string Content; + if (!C2PA_ReadWholeFile(FileName.toUtf8().data(), Content)) + return; + + C2PA_SignPrivateKey_Path->setText(FileName); + Main->C2PA_SignPrivateKey_Set(Content); +} +#endif // defined(ENABLE_C2PA) + //--------------------------------------------------------------------------- void GUI_Preferences::CreateCoreDefaults(QVBoxLayout* Columns) { @@ -1145,6 +1306,54 @@ void GUI_Preferences::Create() QWidget* Extra_Widget=new QWidget(); Extra_Widget->setLayout(Extra); Central->addTab(Extra_Widget, "Extra"); + + #if defined(ENABLE_C2PA) + //C2PA - Signing credentials + C2PA_SignCertificate_Path=new QLineEdit(); + C2PA_SignCertificate_Path->setReadOnly(true); + C2PA_SignCertificate_Browse=new QPushButton(QCommonStyle().standardIcon(QStyle::SP_FileIcon), "Browse..."); + connect(C2PA_SignCertificate_Browse, SIGNAL(clicked(bool)), this, SLOT(OnC2PA_SignCertificate_BrowseClicked(bool))); + + C2PA_SignPrivateKey_Path=new QLineEdit(); + C2PA_SignPrivateKey_Path->setReadOnly(true); + C2PA_SignPrivateKey_Browse=new QPushButton(QCommonStyle().standardIcon(QStyle::SP_FileIcon), "Browse..."); + connect(C2PA_SignPrivateKey_Browse, SIGNAL(clicked(bool)), this, SLOT(OnC2PA_SignPrivateKey_BrowseClicked(bool))); + + //C2PA - Algorithm and TA-URL + C2PA_SignAlgorithm_Combo=new QComboBox(); + C2PA_SignAlgorithm_Combo->addItem("ES256 (ECDSA P-256 / SHA-256)", "es256"); + C2PA_SignAlgorithm_Combo->addItem("ES384 (ECDSA P-384 / SHA-384)", "es384"); + C2PA_SignAlgorithm_Combo->addItem("ES512 (ECDSA P-521 / SHA-512)", "es512"); + C2PA_SignAlgorithm_Combo->addItem("PS256 (RSA-PSS / SHA-256)", "ps256"); + C2PA_SignAlgorithm_Combo->addItem("PS384 (RSA-PSS / SHA-384)", "ps384"); + C2PA_SignAlgorithm_Combo->addItem("PS512 (RSA-PSS / SHA-512)", "ps512"); + C2PA_SignAlgorithm_Combo->addItem("Ed25519", "ed25519"); + + C2PA_SignTA_URL_Edit=new QLineEdit(); + + QGridLayout* C2PA_SignCredentials_Layout=new QGridLayout(); + C2PA_SignCredentials_Layout->addWidget(new QLabel("Certificate file:"), 0, 0); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignCertificate_Path, 0, 1); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignCertificate_Browse, 0, 2); + C2PA_SignCredentials_Layout->addWidget(new QLabel("Private key file:"), 1, 0); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignPrivateKey_Path, 1, 1); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignPrivateKey_Browse, 1, 2); + C2PA_SignCredentials_Layout->addWidget(new QLabel("Algorithm:"), 2, 0); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignAlgorithm_Combo, 2, 1); + C2PA_SignCredentials_Layout->addWidget(new QLabel("Timestamp authority URL (optional):"), 3, 0); + C2PA_SignCredentials_Layout->addWidget(C2PA_SignTA_URL_Edit, 3, 1); + + QGroupBox* C2PA_SignCredentials=new QGroupBox("Signing certificate, private key and options"); + C2PA_SignCredentials->setLayout(C2PA_SignCredentials_Layout); + + //C2PA Tab + QVBoxLayout* C2PA=new QVBoxLayout(); + C2PA->addWidget(C2PA_SignCredentials); + C2PA->addStretch(); + QWidget* C2PA_Widget=new QWidget(); + C2PA_Widget->setLayout(C2PA); + Central->addTab(C2PA_Widget, "C2PA"); + #endif // defined(ENABLE_C2PA) } //--------------------------------------------------------------------------- @@ -1184,4 +1393,16 @@ void GUI_Preferences::LoadOriginalConfig() Extra_Bext_DefaultVersion->setValue(0); Extra_Bext_MaxVersion->setValue(2); Extra_Bext_Toggle->setChecked(false); + + #if defined(ENABLE_C2PA) + // Reset C2PA + C2PA_SignCertificate_Path->setText(QString()); + Main->C2PA_SignCertificate_Set(string()); + C2PA_SignPrivateKey_Path->setText(QString()); + Main->C2PA_SignPrivateKey_Set(string()); + C2PA_SignAlgorithm_Combo->setCurrentIndex(0); + Main->C2PA_SignAlgorithm_Set(C2PA_SignAlgorithm_Combo->currentData().toString().toStdString()); + C2PA_SignTA_URL_Edit->setText(QString()); + Main->C2PA_SignTA_URL_Set(string()); + #endif // defined(ENABLE_C2PA) } diff --git a/Source/GUI/Qt/GUI_Preferences.h b/Source/GUI/Qt/GUI_Preferences.h index 12effc42..3009a323 100644 --- a/Source/GUI/Qt/GUI_Preferences.h +++ b/Source/GUI/Qt/GUI_Preferences.h @@ -71,6 +71,10 @@ enum option_file { Option_File_Riff2Rf64_Reject, Option_File_Overwrite_Reject, + Option_File_C2PA_Reject, + #if defined(ENABLE_C2PA) + Option_File_C2PA_Verify, + #endif // defined(ENABLE_C2PA) Option_File_NoPadding_Accept, Option_File_FileNotValid_Skip, Option_File_WrongExtension_Skip, @@ -212,6 +216,10 @@ class GUI_Preferences : public QDialog void OnExtra_BackupDirectory_Specific_BrowseClicked(bool Checked); void OnExtra_LogFile_Activated_RadioToggled(bool Checked); void OnExtra_LogFile_Activated_BrowseClicked(bool Checked); + #if defined(ENABLE_C2PA) + void OnC2PA_SignCertificate_BrowseClicked(bool Checked); + void OnC2PA_SignPrivateKey_BrowseClicked(bool Checked); + #endif // defined(ENABLE_C2PA) private: void CreateCoreDefaults(QVBoxLayout *Columns); @@ -244,6 +252,15 @@ class GUI_Preferences : public QDialog QDoubleSpinBox* Extra_Bext_DefaultVersion; QDoubleSpinBox* Extra_Bext_MaxVersion; QCheckBox* Extra_Bext_Toggle; + + #if defined(ENABLE_C2PA) + QLineEdit* C2PA_SignCertificate_Path; + QPushButton* C2PA_SignCertificate_Browse; + QLineEdit* C2PA_SignPrivateKey_Path; + QPushButton* C2PA_SignPrivateKey_Browse; + QComboBox* C2PA_SignAlgorithm_Combo; + QLineEdit* C2PA_SignTA_URL_Edit; + #endif // defined(ENABLE_C2PA) }; #endif diff --git a/Source/GUI/Qt/PerFile.qml b/Source/GUI/Qt/PerFile.qml index ccf69397..f8e825fd 100644 --- a/Source/GUI/Qt/PerFile.qml +++ b/Source/GUI/Qt/PerFile.qml @@ -160,7 +160,7 @@ Control { visible: (parent.hovered || parent.activeFocus) && Model.valid(file) timeout: 5000 delay: 1000 - text: Model.readOnly(file) ? "Edit mode disabled, file is read only!" : (editMode ? "Display Mode" : "Edit Mode") + text: Model.c2paLocked(file) ? "Edit mode disabled, editing would invalidate the C2PA signature!" : (Model.readOnly(file) ? "Edit mode disabled, file is read only!" : (editMode ? "Display Mode" : "Edit Mode")) } Image { id: editmode_icon @@ -326,7 +326,7 @@ Control { Layout.fillWidth: true } Repeater { - model: ["Cue", "XMP", "aXML", "iXML"] + model: Model.c2paEnabled() ? ["Cue", "XMP", "aXML", "iXML", "C2PA"] : ["Cue", "XMP", "aXML", "iXML"] delegate: Button { function buttonColor(hovered) { var field = modelData==="Cue"?"cuexml":modelData diff --git a/Source/Resource/Documentation/index.html b/Source/Resource/Documentation/index.html index 2fd4ae54..e6f0d46e 100644 --- a/Source/Resource/Documentation/index.html +++ b/Source/Resource/Documentation/index.html @@ -1,46 +1,41 @@ - - - - BWF MetaEdit Help - - - -

BWF MetaEdit Help

- -

About this Software

- -BWF MetaEdit was developed by the Federal Agencies Digital -Guidelines Initiative (FADGI) supported by AudioVisual Preservation -Solutions.

- -This tool permits embedding, editing, and exporting of metadata in -Broadcast WAVE Format (BWF) files. This tool can also enforce metadata -guidelines developed by the Federal -Agencies Audio-Visual Working Group, as well as recommendations and -specifications from the European Broadcasting Union (EBU), Microsoft, and IBM. - -

Explanation of BWF MetaEdit Views and Documents

- - -

Metadata Standards

- - -

Use

- - - + + + + BWF MetaEdit Help + + + +

BWF MetaEdit Help

+ +

About this Software

+ +BWF MetaEdit is a tool that supports embedding, validating, and exporting of metadata in Broadcast WAVE Format (BWF) files. It supports the FADGI Broadcast WAVE Metadata Embedding Guidelines.
+
+Initially funded by the Library of Congress and FADGI (Federal Agencies Digital Guidelines Initiative); previously developed by MediaArea and led by AVPreserve; currently developed and led by MediaArea.
+MediaArea provides support and binaries for all platforms. + +

Explanation of BWF MetaEdit Views and Documents

+ + +

Metadata Standards

+ + +

Use

+ + + \ No newline at end of file diff --git a/Source/Riff/Riff_Base.h b/Source/Riff/Riff_Base.h index b2862e9c..a8cdddba 100644 --- a/Source/Riff/Riff_Base.h +++ b/Source/Riff/Riff_Base.h @@ -344,6 +344,28 @@ class Riff_Base dialectCode=0; } }; + + struct chunk_C2PA + { + bool present; + #if defined(ENABLE_C2PA) + bool valid; + bool signatureValid; + string manifest; + vector validationErrors; + vector validationWarnings; + #endif // defined(ENABLE_C2PA) + + chunk_C2PA() + { + present=false; + #if defined(ENABLE_C2PA) + valid=false; + signatureValid=false; + #endif // defined(ENABLE_C2PA) + } + }; + struct chunk_strings { map Strings; @@ -395,6 +417,7 @@ class Riff_Base chunk_strings *MD5Generated; chunk_cue_ *cue_; chunk_adtl *adtl; + chunk_C2PA *C2PA; bool CSET_Present; bool NoPadding_Accept; bool NoPadding_IsCorrected; @@ -412,6 +435,10 @@ class Riff_Base bool Read_Only; bool BextDescriptionFromFileName; bool OriginatorReferenceFromFileName; + #if defined(ENABLE_C2PA) + bool VerifyC2PA; + bool VerifyC2PA_Force; + #endif // defined(ENABLE_C2PA) CriticalSection CS; float Progress; @@ -433,6 +460,7 @@ class Riff_Base cue_=NULL; adtl=NULL; cuexml=NULL; + C2PA=NULL; MD5Stored=NULL; MD5Generated=NULL; CSET_Present=false; @@ -453,6 +481,10 @@ class Riff_Base Read_Only=false; BextDescriptionFromFileName=false; OriginatorReferenceFromFileName=false; + #if defined(ENABLE_C2PA) + VerifyC2PA=false; + VerifyC2PA_Force=false; + #endif // defined(ENABLE_C2PA) Progress=0; Canceling=false; } @@ -470,6 +502,7 @@ class Riff_Base delete cue_; //cue_=NULL; delete adtl; //adtl=NULL; delete cuexml; //cuexml=NULL; + delete C2PA; //C2PA=NULL; } }; diff --git a/Source/Riff/Riff_C2PA_Helpers.cpp b/Source/Riff/Riff_C2PA_Helpers.cpp new file mode 100644 index 00000000..9bd8817d --- /dev/null +++ b/Source/Riff/Riff_C2PA_Helpers.cpp @@ -0,0 +1,532 @@ +//--------------------------------------------------------------------------- +#include "Riff/Riff_Base.h" +#include "Riff/Riff_C2PA_Helpers.h" + +//--------------------------------------------------------------------------- +#include "ThirdParty/c2pa-rs/target/release/c2pa.h" +#include "ThirdParty/json/json.h" +#include +#include + +#include "ZenLib/Ztring.h" +#include "ZenLib/File.h" +#include "ZenLib/FileName.h" +#include "Common/Common_About.h" + +using namespace ZenLib; +using namespace std; + + +struct C2PA_FileStreamContext +{ + FILE* F; +}; + +//*************************************************************************** +// Callbacks +//*************************************************************************** + +//--------------------------------------------------------------------------- +static intptr_t C2PA_ReadCallback(StreamContext* Context, uint8_t* Data, intptr_t Len) +{ + if (!Context || !Data || Len < 0) + return -1; + + C2PA_FileStreamContext* FileContext=reinterpret_cast(Context); + if (!FileContext->F) + return -1; + + size_t ReadSize=fread(Data, 1, (size_t)Len, FileContext->F); + if (ReadSize==0 && ferror(FileContext->F)) + return -1; + + return (intptr_t)ReadSize; +} + +//--------------------------------------------------------------------------- +static intptr_t C2PA_SeekCallback(StreamContext* Context, intptr_t Offset, C2paSeekMode Mode) +{ + if (!Context) + return -1; + + C2PA_FileStreamContext* FileContext=reinterpret_cast(Context); + if (!FileContext->F) + return -1; + +#if defined(_WIN32) + if (_fseeki64(FileContext->F, (__int64)Offset, Mode) != 0) + return -1; + + __int64 Position = _ftelli64(FileContext->F); + if (Position < 0) + return -1; + + return (intptr_t)Position; +#else + if (fseeko(FileContext->F, (off_t)Offset, Mode) != 0) + return -1; + + off_t Position = ftello(FileContext->F); + if (Position < 0) + return -1; + + return (intptr_t)Position; +#endif +} + +//--------------------------------------------------------------------------- +static intptr_t C2PA_WriteCallback(StreamContext* Context, const uint8_t* Data, intptr_t Len) +{ + if (!Context || !Data || Len < 0) + return -1; + + C2PA_FileStreamContext* FileContext=reinterpret_cast(Context); + if (!FileContext->F) + return -1; + + size_t WriteSize=fwrite(Data, 1, (size_t)Len, FileContext->F); + if (WriteSize<(size_t)Len) + return -1; + + return (intptr_t)WriteSize; +} + +//--------------------------------------------------------------------------- +static intptr_t C2PA_FlushCallback(StreamContext* Context) +{ + if (!Context) + return -1; + + C2PA_FileStreamContext* FileContext=reinterpret_cast(Context); + if (!FileContext->F || fflush(FileContext->F)!=0) + return -1; + + return 0; +} + +//*************************************************************************** +// Helpers +//*************************************************************************** + +//--------------------------------------------------------------------------- +static string Json_EscapeString(const string& Value) +{ + string Result; + Result.reserve(Value.size()+2); + for (size_t Pos=0; Pos& Messages) +{ + if (!StatusArray) + return; + + for (const json_array_element_s* StatusItem=StatusArray->start; StatusItem; StatusItem=StatusItem->next) + { + string StatusItemElementCode; + string StatusItemElementURL; + string StatusItemElementExplanation; + + json_object_s* StatusItemObject=json_value_as_object(StatusItem->value); + if (!StatusItemObject) + continue; + + for (const json_object_element_s* StatusItemElement=StatusItemObject->start; StatusItemElement; StatusItemElement=StatusItemElement->next) + { + string StatusItemElementName=string((StatusItemElement->name && StatusItemElement->name->string) ? StatusItemElement->name->string : ""); + if (StatusItemElementName=="code") + { + json_string_s* StatusCode=json_value_as_string(StatusItemElement->value); + if (StatusCode && StatusCode->string) + StatusItemElementCode=string(StatusCode->string); + } + else if (StatusItemElementName=="url") + { + json_string_s* StatusURL=json_value_as_string(StatusItemElement->value); + if (StatusURL && StatusURL->string) + StatusItemElementURL=string(StatusURL->string); + } + else if (StatusItemElementName=="explanation") + { + json_string_s* StatusExplanation=json_value_as_string(StatusItemElement->value); + if (StatusExplanation && StatusExplanation->string) + StatusItemElementExplanation=string(StatusExplanation->string); + } + } + + ostringstream Message; + Message << "code: " << StatusItemElementCode << " url: " << StatusItemElementURL << " explanation: " << StatusItemElementExplanation; + Messages.push_back(Message.str()); + } +} + +//--------------------------------------------------------------------------- +void C2PA_Validate(Riff_Handler* Handler, Riff_Base::global* Global) +{ + if (!Handler || !Global || !Global->C2PA) + return; + + //if (!c2pa_load()) + // return; + + string FileName=Global->File_Name.To_UTF8(); + if (FileName.empty()) + { + //c2pa_unload(); + return; + } + + C2PA_FileStreamContext FileContext; + FileContext.F=fopen(FileName.c_str(), "rb"); + if (!FileContext.F) + { + //c2pa_unload(); + return; + } + + C2paContext* Context=c2pa_context_new(); + if (!Context) + { + fclose(FileContext.F); + //c2pa_unload(); + return; + } + + C2paReader* Reader=c2pa_reader_from_context(Context); + if (Reader) + { + C2paStream* Stream=c2pa_create_stream(reinterpret_cast(&FileContext), C2PA_ReadCallback, C2PA_SeekCallback, NULL, NULL); + if (Stream) + { + Reader=c2pa_reader_with_stream(Reader, "audio/wav", Stream); + c2pa_release_stream(Stream); + } + else + { + c2pa_free(Reader); + Reader = NULL; + } + } + + if (Reader) + { + char* ManifestJson=c2pa_reader_crjson(Reader); + if (ManifestJson) + { + Global->C2PA->manifest=ManifestJson; + c2pa_free(ManifestJson); + } + + if (!Global->VerifyC2PA) + { + c2pa_free(Reader); + c2pa_free(Context); + fclose(FileContext.F); + //c2pa_unload(); + return; + } + + char* DetailedJson=c2pa_reader_detailed_json(Reader); + if (DetailedJson) + { + json_value_s* ParsedJson=json_parse(DetailedJson, strlen(DetailedJson)); + if (ParsedJson) + { + Global->C2PA->validationErrors.clear(); + Global->C2PA->validationWarnings.clear(); + + bool HasSuccess=false; + bool HasSignatureStatus=false; + bool HasSignatureValidated=false; + + if (ParsedJson->type==json_type_object) + { + const json_object_s* RootJsonObject=json_value_as_object(const_cast(ParsedJson)); + if (RootJsonObject) + { + for (const json_object_element_s* Element=RootJsonObject->start; Element; Element=Element->next) + { + string ElementName=string((Element->name && Element->name->string) ? Element->name->string : ""); + if (ElementName.empty()) + continue; + + if (ElementName=="validation_state") + { + json_string_s* ElementValue=json_value_as_string(Element->value); + if (ElementValue && string(ElementValue->string)!="Invalid") + { + HasSuccess=true; + HasSignatureStatus=true; + HasSignatureValidated=true; + } + } + else if (ElementName=="validation_results") + { + json_object_s* ValidationResults=json_value_as_object(Element->value); + if (!ValidationResults) + continue; + + for (const json_object_element_s* ValidationElement=ValidationResults->start; ValidationElement; ValidationElement=ValidationElement->next) + { + string ValidationElementName=string((ValidationElement->name && ValidationElement->name->string) ? ValidationElement->name->string : ""); + if (ValidationElementName!="activeManifest") + continue; + + json_object_s* ActiveManifest=json_value_as_object(ValidationElement->value); + if (!ActiveManifest) + continue; + + for (const json_object_element_s* StatusElement=ActiveManifest->start; StatusElement; StatusElement=StatusElement->next) + { + string StatusElementName=string((StatusElement->name && StatusElement->name->string) ? StatusElement->name->string : ""); + if (StatusElementName=="failure") + C2PA_ParseStatusArray(json_value_as_array(StatusElement->value), Global->C2PA->validationErrors); + else if (StatusElementName=="informational") + C2PA_ParseStatusArray(json_value_as_array(StatusElement->value), Global->C2PA->validationWarnings); + } + } + } + } + } + } + + if (HasSuccess) + Global->C2PA->valid=true; + + if (HasSignatureStatus) + Global->C2PA->signatureValid=HasSignatureValidated; + + for (size_t Pos=0; PosC2PA->validationErrors.size(); Pos++) + { + Handler->Errors << Global->File_Name.To_UTF8() << ": C2PA validation failure, " << Global->C2PA->validationErrors[Pos] << endl; + Handler->PerFile_Error << "C2PA validation failure, " << Global->C2PA->validationErrors[Pos] << endl; + } + for (size_t Pos=0; PosC2PA->validationWarnings.size(); Pos++) + { + Handler->Warnings << Global->File_Name.To_UTF8() << ": C2PA validation warning, " << Global->C2PA->validationWarnings[Pos] << endl; + Handler->PerFile_Warning << "C2PA validation warning, " << Global->C2PA->validationWarnings[Pos] << endl; + } + + free(ParsedJson); + } + + c2pa_free(DetailedJson); + } + + c2pa_free(Reader); + } + + c2pa_free(Context); + fclose(FileContext.F); + //c2pa_unload(); +} + +//--------------------------------------------------------------------------- +void C2PA_Sign(Riff_Handler* Handler, Riff_Base::global* Global) +{ + if (!Handler || !Global || Handler->C2PA_SignManifestJson.empty()) + return; + + string FileName=Global->File_Name.To_UTF8(); + if (FileName.empty()) + return; + + if (Handler->C2PA_SignAlgorithm.empty() || Handler->C2PA_SignCertificate.empty() || Handler->C2PA_SignPrivateKey.empty()) + { + Handler->Errors<PerFile_Error<<"C2PA signing, missing algorithm, certificate or private key (see --C2PA-Sign-Algorithm=, --C2PA-Sign-Certificate= and --C2PA-Sign-Key=)"<C2PA_SignManifestJson.data(), Handler->C2PA_SignManifestJson.size()); + if (!ManifestRoot || ManifestRoot->type!=json_type_object) + { + Handler->Errors<PerFile_Error<<"C2PA signing, the signing manifest is not a valid JSON object"<start; Element; Element=Element->next) + { + string Name=(Element->name && Element->name->string)?Element->name->string:""; + if (Name=="claim_generator_info") + HasClaimGeneratorInfo=true; + else if (Name=="title") + HasTitle=true; + else if (Name=="format") + HasFormat=true; + } + bool ManifestWasEmpty=ManifestObject->length==0; + free(ManifestRoot); + + //Defaulting fields not provided by the caller, inserted as text right after the manifest's opening brace + string Defaults; + if (!HasClaimGeneratorInfo) + Defaults+="\"claim_generator_info\":"+C2PA_DefaultClaimGeneratorInfo()+","; + if (!HasTitle) + Defaults+="\"title\":\""+Json_EscapeString(C2PA_BaseFileName(FileName))+"\","; + if (!HasFormat) + Defaults+="\"format\":\"audio/x-wav\","; + + string ManifestJson=Handler->C2PA_SignManifestJson; + if (!Defaults.empty()) + { + if (ManifestWasEmpty) + Defaults.resize(Defaults.size()-1); //Removing the trailing comma, nothing follows in the object + ManifestJson.insert(ManifestJson.find('{')+1, Defaults); + } + +// if (!c2pa_load()) + // { + // Handler->Errors<PerFile_Error<<"C2PA signing, unable to load the C2PA library"<C2PA_SignAlgorithm.c_str(); + SignerInfo.sign_cert=Handler->C2PA_SignCertificate.c_str(); + SignerInfo.private_key=Handler->C2PA_SignPrivateKey.c_str(); + SignerInfo.ta_url=Handler->C2PA_SignTA_URL.empty()?NULL:Handler->C2PA_SignTA_URL.c_str(); + + C2paSigner* Signer=c2pa_signer_from_info(&SignerInfo); + if (!Signer) + { + char* Error=c2pa_error(); + Handler->Errors<PerFile_Error<<"C2PA signing, unable to create the signer: "<<(Error?Error:"")<Errors<PerFile_Error<<"C2PA signing, unable to configure the manifest builder: "<<(Error?Error:"")<Errors<PerFile_Error<<"C2PA signing, unable to open the source or destination file"<(&SourceContext), C2PA_ReadCallback, C2PA_SeekCallback, NULL, NULL); + C2paStream* DestStream=c2pa_create_stream(reinterpret_cast(&DestContext), C2PA_ReadCallback, C2PA_SeekCallback, C2PA_WriteCallback, C2PA_FlushCallback); + + const unsigned char* ManifestBytes=NULL; + int64_t Result=(SourceStream && DestStream)?c2pa_builder_sign(Builder, "audio/wav", SourceStream, DestStream, Signer, &ManifestBytes):-1; + if (ManifestBytes) + c2pa_free(ManifestBytes); + + if (SourceStream) + c2pa_release_stream(SourceStream); + if (DestStream) + c2pa_release_stream(DestStream); + fclose(SourceContext.F); + fclose(DestContext.F); + + c2pa_free(Builder); + c2pa_free(Signer); + c2pa_free(Context); + + if (Result<0) + { + char* Error=c2pa_error(); + Handler->Errors<PerFile_Error<<"C2PA signing failed: "<<(Error?Error:"")<File_Name) || !File::Move(Ztring().From_UTF8(TempFileName), Global->File_Name)) + { + Handler->Errors<PerFile_Error<<"C2PA signing, unable to replace the original file with the signed file"<Information<PerFile_Information<<"C2PA, signed"<C2PA) + throw exception_valid("2 C2PA chunks"); + + //Reading + Read_Internal_ReadAllInBuffer(); + + //Filling + Global->C2PA=new Riff_Base::global::chunk_C2PA; + Global->C2PA->present=true; +} \ No newline at end of file diff --git a/Source/Riff/Riff_Handler.cpp b/Source/Riff/Riff_Handler.cpp index 1fa536ec..d9e803c0 100644 --- a/Source/Riff/Riff_Handler.cpp +++ b/Source/Riff/Riff_Handler.cpp @@ -10,6 +10,9 @@ //--------------------------------------------------------------------------- #include "Riff/Riff_Handler.h" #include "Riff/Riff_Chunks.h" +#if defined(ENABLE_C2PA) +#include "Riff/Riff_C2PA_Helpers.h" +#endif // defined(ENABLE_C2PA) #include "Common/Codes.h" #include #include @@ -285,6 +288,7 @@ Riff_Handler::Riff_Handler () //Configuration riff2rf64_Reject=false; Overwrite_Reject=false; + C2PA_Reject=false; NoPadding_Accept=false; NewChunksAtTheEnd=false; GenerateMD5=false; @@ -299,6 +303,10 @@ Riff_Handler::Riff_Handler () Write_Encoding=Encoding_Max; Write_CodePage=false; Ignore_File_Encoding=false; + #if defined(ENABLE_C2PA) + VerifyC2PA=false; + VerifyC2PA_Force=false; + #endif // defined(ENABLE_C2PA) Bext_DefaultVersion=0; Bext_MaxVersion=2; @@ -899,12 +907,29 @@ bool Riff_Handler::Open_Internal(const string &FileName) PerFile_Information.str(string()); PerFile_Information<<"MD5, verified"<Global->MD5Generated && !Chunks->Global->MD5Generated->Strings["md5generated"].empty() && (!(Chunks->Global->MD5Stored && !Chunks->Global->MD5Stored->Strings["md5stored"].empty()) || EmbedMD5_AuthorizeOverWritting)) Set_Internal("MD5Stored", Chunks->Global->MD5Generated->Strings["md5generated"], rules()); + + #if defined(ENABLE_C2PA) + if (Chunks->Global->C2PA) + C2PA_Validate(this, Chunks->Global); + else if (VerifyC2PA_Force) + { + Errors<Global->File_Name.To_UTF8()<<": C2PA, no existing C2PA chunk"<Global->File_Name.To_UTF8()<<": C2PA, no existing C2PA chunk"<Global->CS); @@ -1078,13 +1103,25 @@ bool Riff_Handler::Save() } } - //Write only if modified - if (!IsModified_Get_Internal()) + //Write only if modified (or if C2PA signing was requested, even without any other change) + bool WasModified=IsModified_Get_Internal(); + if (!WasModified + #if defined(ENABLE_C2PA) + && C2PA_SignManifestJson.empty() + #endif // defined(ENABLE_C2PA) + ) { Information<Global->File_Name.To_UTF8()<<": Nothing to do"<Global->C2PA && C2PA_Reject) + { + Errors<Global->File_Name.To_UTF8()<<": C2PA signature is present (and --reject-c2pa option)"<Global->File_Name.To_UTF8():"")<<": Is modified"<Global->File_Name.To_UTF8():"")<<": Is modified"<Global->In.Close(); + C2PA_Sign(this, Chunks->Global); + C2PA_SignManifestJson.clear(); //One-shot: the manifest is consumed by this Save(), whatever the outcome + } + #endif // defined(ENABLE_C2PA) //Loading the new file (we are verifying the integraty of the generated file) string FileName=Chunks->Global->File_Name.To_UTF8(); @@ -1212,6 +1260,9 @@ bool Riff_Handler::BackToLastSave() } Chunks->IsModified_Clear(); + #if defined(ENABLE_C2PA) + C2PA_SignManifestJson.clear(); + #endif // defined(ENABLE_C2PA) return true; } @@ -1282,11 +1333,34 @@ string Riff_Handler::Get_Internal(const string &Field) } } } + else if (Field=="C2PA") + { + if (Chunks->Global->C2PA) + { + #if defined(ENABLE_C2PA) + if (VerifyC2PA) + return (Chunks->Global->C2PA->valid && Chunks->Global->C2PA->signatureValid)?"Valid":"Invalid"; + #endif // defined(ENABLE_C2PA) + return "Yes"; + } + else + return "No"; + } //Special case - CueXml if (Field=="cuexml") return Cue_Xml_Get(); + #if defined(ENABLE_C2PA) + //Special case - C2PAJson + if (Field=="c2pajson") + return Chunks->Global->C2PA?Chunks->Global->C2PA->manifest:string(); + + //Special case - C2PA signing manifest staged for the next Save() + if (Field=="C2PASignManifest") + return C2PA_SignManifestJson; + #endif // defined(ENABLE_C2PA) + Riff_Base::global::chunk_strings** Chunk_Strings=chunk_strings_Get(Field); if (!Chunk_Strings || !*Chunk_Strings) return string(); @@ -1325,6 +1399,15 @@ bool Riff_Handler::Set_Internal(const string &Field_, const string &Value_, rule string Field=Field_Get(Field_); + #if defined(ENABLE_C2PA) + //Special case - C2PA signing manifest, stage it for the next Save() + if (Field=="c2pasignmanifest") + { + C2PA_SignManifestJson=Value_; + return true; + } + #endif // defined(ENABLE_C2PA) + //Testing if useful if (Field=="filename" || Field=="version" @@ -1859,6 +1942,12 @@ bool Riff_Handler::IsModified_Internal(const string &Field) else return false; } + if (Field_Get(Field)=="C2PA") + #if defined(ENABLE_C2PA) + return !C2PA_SignManifestJson.empty(); + #else + return false; + #endif // defined(ENABLE_C2PA) Riff_Base::global::chunk_strings** Chunk_Strings=chunk_strings_Get(Field); if (!Chunk_Strings || !*Chunk_Strings) @@ -3083,7 +3172,26 @@ bool Riff_Handler::IsValid_Internal(const string &Field_, const string &Value_, IsValid_Errors<<"Dialect must be a valid CSET dialect code for the given language (FADGI recommandations)"; } } - + #if defined(ENABLE_C2PA) + else if (Field=="C2PA") + { + if (Chunks && Chunks->Global->C2PA) + { + for (size_t Pos=0; PosGlobal->C2PA->validationErrors.size(); Pos++) + { + if (Pos) + IsValid_Errors<<"; "; + IsValid_Errors<Global->C2PA->validationErrors[Pos]; + } + for (size_t Pos=0; PosGlobal->C2PA->validationWarnings.size(); Pos++) + { + if (Pos) + IsValid_Warnings<<"; "; + IsValid_Warnings<Global->C2PA->validationWarnings[Pos]; + } + } + } + #endif // defined(ENABLE_C2PA) else if (Field=="errors") { return PerFile_Error.str().empty(); @@ -3210,6 +3318,7 @@ string Riff_Handler::Technical_Header() ToReturn<<"iXML"<<','; ToReturn<<"MD5Stored"<<','; ToReturn<<"MD5Generated"<<','; + ToReturn<<"C2PA"<<','; ToReturn<<"Encoding"<<','; ToReturn<<"Errors"<<','; ToReturn<<"Warnings"<<','; @@ -3253,6 +3362,7 @@ string Riff_Handler::Technical_Get() List.push_back(Get_Internal("iXML").empty()?__T("No"):__T("Yes")); List.push_back(Ztring().From_UTF8(Get_Internal("MD5Stored"))); List.push_back(Ztring().From_UTF8(Get_Internal("MD5Generated"))); + List.push_back(Ztring().From_UTF8(Get_Internal("C2PA"))); List.push_back(Ztring().From_UTF8(Get_Internal("Encoding"))); string Errors_Temp=PerFile_Error.str(); if (!Errors_Temp.empty()) @@ -3358,6 +3468,11 @@ bool Riff_Handler::IsModified_Get_Internal() if (IsModified_Internal("Encoding")) ToReturn=true; + #if defined(ENABLE_C2PA) + if (!C2PA_SignManifestJson.empty()) + ToReturn=true; + #endif //defined(ENABLE_C2PA) + if (!ToReturn && Chunks) ToReturn=Chunks->IsModified(); @@ -3496,6 +3611,13 @@ bool Riff_Handler::Set(const string &Field, const string &Value, Riff_Base::glob return false; } + if (C2PA_Reject && Chunks->Global->C2PA) + { + Errors<Global->File_Name.To_UTF8()<<": C2PA signature is present (and invalidation is not authorized)"<Global->EmbedMD5=EmbedMD5; Chunks->Global->EmbedMD5_AuthorizeOverWritting=EmbedMD5_AuthorizeOverWritting; Chunks->Global->Trace_UseDec=Trace_UseDec; + #if defined(ENABLE_C2PA) + Chunks->Global->VerifyC2PA=VerifyC2PA; + Chunks->Global->VerifyC2PA_Force=VerifyC2PA_Force; + #endif //defined(ENABLE_C2PA) //MD5 if (Update && (Chunks->Global->VerifyMD5 || Chunks->Global->VerifyMD5_Force)) @@ -3693,6 +3819,12 @@ void Riff_Handler::Options_Update_Internal(bool Update) && (!(Chunks->Global->MD5Stored && !Chunks->Global->MD5Stored->Strings["md5stored"].empty()) || EmbedMD5_AuthorizeOverWritting)) Set_Internal("MD5Stored", Chunks->Global->MD5Generated->Strings["md5generated"], rules()); + + //C2PA + #if defined(ENABLE_C2PA) + if (Update && Chunks->Global->C2PA && (Chunks->Global->VerifyC2PA || Chunks->Global->VerifyC2PA_Force)) + C2PA_Validate(this, Chunks->Global); + #endif //defined(ENABLE_C2PA) } //*************************************************************************** diff --git a/Source/Riff/Riff_Handler.h b/Source/Riff/Riff_Handler.h index 3d2ed819..d34107ee 100644 --- a/Source/Riff/Riff_Handler.h +++ b/Source/Riff/Riff_Handler.h @@ -128,6 +128,7 @@ class Riff_Handler //Configuration bool riff2rf64_Reject; bool Overwrite_Reject; + bool C2PA_Reject; bool NoPadding_Accept; bool NewChunksAtTheEnd; bool GenerateMD5; @@ -142,6 +143,15 @@ class Riff_Handler Riff_Encoding Write_Encoding; bool Write_CodePage; bool Ignore_File_Encoding; + #if defined(ENABLE_C2PA) + bool VerifyC2PA; + bool VerifyC2PA_Force; + string C2PA_SignManifestJson; + string C2PA_SignCertificate; + string C2PA_SignPrivateKey; + string C2PA_SignAlgorithm; + string C2PA_SignTA_URL; + #endif // defined(ENABLE_C2PA) unsigned short Bext_DefaultVersion; unsigned short Bext_MaxVersion; void Options_Update(); diff --git a/Source/ThirdParty/c2pa-rs b/Source/ThirdParty/c2pa-rs new file mode 160000 index 00000000..12f9ac6b --- /dev/null +++ b/Source/ThirdParty/c2pa-rs @@ -0,0 +1 @@ +Subproject commit 12f9ac6b6661871c03e16756310af09511d56e26 diff --git a/Source/ThirdParty/c2pa/c2pa.h b/Source/ThirdParty/c2pa/c2pa.h new file mode 100644 index 00000000..cba2f819 --- /dev/null +++ b/Source/ThirdParty/c2pa/c2pa.h @@ -0,0 +1,2260 @@ +// Copyright 2025 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +// This file is generated by cbindgen. Do not edit by hand. +// Version: 0.91.0-dev + + +#ifndef c2pa_bindings_h +#define c2pa_bindings_h + +#include +#include +#include +#include + +#if defined(C2PA_DYNAMIC_LOADING) && C2PA_DYNAMIC_LOADING +#define C2PA_API + +#if defined (_WIN32) || defined (WIN32) + #ifndef _UNICODE + #define C2PADLL_NAME "c2pa_c.dll" + #else //_UNICODE + #define C2PADLL_NAME L"c2pa_c.dll" + #endif +#elif defined(__APPLE__) && defined(__MACH__) + #define C2PADLL_NAME "libc2pa_c.dylib" +#else + #define C2PADLL_NAME "libc2pa_c.so" +#endif //!defined(_WIN32) || defined (WIN32) + +#if defined (_WIN32) || defined (WIN32) + #undef __TEXT + #include + static HMODULE c2pa_module=NULL; +#else + #include + static void* c2pa_module=NULL; +#endif + +#if defined (_WIN32) || defined (WIN32) + #define ASSIGN(_Module, _Name) \ + _Name=(_Module##_##_Name)GetProcAddress(_Module, #_Name); \ + if (_Name==NULL) Error=true; + #define DLOPEN(_Module, _Name) \ + _Module=LoadLibrary(_Name); + #define DLCLOSE(_Module) \ + FreeLibrary(_Module); +#else + #define ASSIGN(_Module, _Name) \ + _Name=(_Module##_##_Name)dlsym(_Module, #_Name); \ + if (_Name==NULL) Error=true; + #define DLOPEN(_Module, _Name) \ + _Module=dlopen(_Name, RTLD_LAZY); \ + if (!_Module) \ + _Module=dlopen("./" _Name, RTLD_LAZY); \ + if (!_Module) \ + _Module=dlopen("/usr/local/lib/" _Name, RTLD_LAZY); \ + if (!_Module) \ + _Module=dlopen("/usr/local/lib64/" _Name, RTLD_LAZY); \ + if (!_Module) \ + _Module=dlopen("/usr/lib/" _Name, RTLD_LAZY); \ + if (!_Module) \ + _Module=dlopen("/usr/lib64/" _Name, RTLD_LAZY); + #define DLCLOSE(_Module) \ + dlclose(_Module); +#endif //defined (_WIN32) || defined (WIN32) + +#else + #if defined(_WIN32) || defined(_WIN64) + #if defined(C2PA_DLL) && C2PA_DLL + #if defined(__GNUC__) && __GNUC__ + #define C2PA_API __attribute__((dllexport)) + #else + #define C2PA_API __declspec(dllexport) + #endif + #else + #if defined(__GNUC__) && __GNUC__ + #define C2PA_API __attribute__((dllimport)) + #else + #define C2PA_API __declspec(dllimport) + #endif + #endif + #else + #if defined(__GNUC__) && __GNUC__ + #define C2PA_API __attribute__((visibility("default"))) + #else + #define C2PA_API + #endif + #endif +#endif + +/* Deprecation macros: produce compiler warnings when deprecated C2PA functions are called. + * GCC/Clang __attribute__ is checked first because it is position-flexible + * (works after 'extern'), whereas C++ [[deprecated]] is only valid at the + * very start of a declaration and cbindgen emits the annotation after the + * 'extern' keyword. */ +#if defined(__GNUC__) || defined(__clang__) + #define C2PA_DEPRECATED __attribute__((deprecated)) + #define C2PA_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) +#elif defined(__cplusplus) && __cplusplus >= 201402L + #define C2PA_DEPRECATED [[deprecated]] + #define C2PA_DEPRECATED_MSG(msg) [[deprecated(msg)]] +#elif defined(_MSC_VER) + #define C2PA_DEPRECATED __declspec(deprecated) + #define C2PA_DEPRECATED_MSG(msg) __declspec(deprecated(msg)) +#else + #define C2PA_DEPRECATED + #define C2PA_DEPRECATED_MSG(msg) +#endif + + + + +/** + * Maximum length for C strings when using bounded conversion (64KB) + */ +#define MAX_CSTRING_LEN 1048576 + +/** + * Maximum number of entries accepted from a NULL-terminated C string array. + * Guards against runaway iteration when a caller omits the NULL terminator. + */ +#define MAX_STRING_ARRAY_LEN 256 + +/** + * Defines the seek mode for the seek callback. + */ +typedef enum C2paSeekMode { + /** + * Seeks from the start of the stream. + */ + Start = 0, + /** + * Seeks from the current position in the stream. + */ + Current = 1, + /** + * Seeks from the end of the stream. + */ + End = 2, +} C2paSeekMode; + +/** + * Progress phase constants passed to C progress callbacks. + * These mirror [`c2pa::ProgressPhase`] variants. + */ +typedef enum C2paProgressPhase { + Reading = 0, + VerifyingManifest = 1, + VerifyingSignature = 2, + VerifyingIngredient = 3, + VerifyingAssetHash = 4, + AddingIngredient = 5, + Thumbnail = 6, + Hashing = 7, + Signing = 8, + Embedding = 9, + FetchingRemoteManifest = 10, + Writing = 11, + FetchingOCSP = 12, + FetchingTimestamp = 13, +} C2paProgressPhase; + +/** + * Builder intent enumeration. + * This specifies what kind of manifest to create. + */ +typedef enum C2paBuilderIntent { + /** + * This is a new digital creation with the specified digital source type. + * The Manifest must not have a parent ingredient. + * A `c2pa.created` action will be added if not provided. + */ + Create, + /** + * This is an edit of a pre-existing parent asset. + * The Manifest must have a parent ingredient. + * A parent ingredient will be generated from the source stream if not otherwise provided. + * A `c2pa.opened` action will be tied to the parent ingredient. + */ + Edit, + /** + * A restricted version of Edit for non-editorial changes. + * There must be only one ingredient, as a parent. + * No changes can be made to the hashed content of the parent. + */ + Update, +} C2paBuilderIntent; + +/** + * List of possible digital source types. + */ +typedef enum C2paDigitalSourceType { + Empty, + TrainedAlgorithmicData, + DigitalCapture, + ComputationalCapture, + NegativeFilm, + PositiveFilm, + Print, + HumanEdits, + CompositeWithTrainedAlgorithmicMedia, + AlgorithmicallyEnhanced, + DigitalCreation, + DataDrivenMedia, + TrainedAlgorithmicMedia, + AlgorithmicMedia, + ScreenCapture, + VirtualRecording, + Composite, + CompositeCapture, + CompositeSynthetic, +} C2paDigitalSourceType; + +/** + * Hash binding type for embeddable signing workflows. + */ +typedef enum C2paHashType { + /** + * Placeholder + exclusions + hash + sign (JPEG, PNG, etc.). + */ + DataHash = 0, + /** + * Placeholder + hash + sign (MP4, AVIF, HEIF/HEIC). + */ + BmffHash = 1, + /** + * Hash + sign, no placeholder needed. + */ + BoxHash = 2, +} C2paHashType; + +/** + * List of supported signing algorithms. + */ +typedef enum C2paSigningAlg { + Es256, + Es384, + Es512, + Ps256, + Ps384, + Ps512, + Ed25519, +} C2paSigningAlg; + +typedef struct C2paSigner C2paSigner; + +/** + * An opaque struct to hold a context value for the stream callbacks. + */ +typedef struct StreamContext { + +} StreamContext; + +/** + * Defines a callback to read from a stream. + * + * The return value is the number of bytes read, or a negative number for an error. + */ +typedef intptr_t (*ReadCallback)(struct StreamContext *context, uint8_t *data, intptr_t len); + +/** + * Defines a callback to seek to an offset in a stream. + * + * The return value is the new position in the stream, or a negative number for an error. + */ +typedef intptr_t (*SeekCallback)(struct StreamContext *context, + intptr_t offset, + enum C2paSeekMode mode); + +/** + * Defines a callback to write to a stream. + * + * The return value is the number of bytes written, or a negative number for an error. + */ +typedef intptr_t (*WriteCallback)(struct StreamContext *context, const uint8_t *data, intptr_t len); + +/** + * Defines a callback to flush a stream. + * + * The return value is 0 for success, or a negative number for an error. + */ +typedef intptr_t (*FlushCallback)(struct StreamContext *context); + +/** + * A C2paStream is a Rust Read/Write/Seek stream that can be created and used in C. + */ +typedef struct C2paStream { + struct StreamContext *context; + ReadCallback reader; + SeekCallback seeker; + WriteCallback writer; + FlushCallback flusher; +} C2paStream; + +typedef struct C2paSettings { + +} C2paSettings; + +typedef struct C2paContextBuilder { + +} C2paContextBuilder; + +/** + * C-callable progress callback function type. + * + * # Parameters + * * `context` – the opaque `user_data` pointer passed to + * `c2pa_context_builder_set_progress_callback`. + * * `phase` – a [`C2paProgressPhase`] value identifying the current operation. + * Callers should derive any user-visible text from this value in the appropriate language. + * * `step` – monotonically increasing counter within the current phase, starting at + * `1`. Resets to `1` at the start of each new phase. Use as a liveness heartbeat: + * a rising `step` means the SDK is making forward progress. The unit is + * phase-specific and should otherwise be treated as opaque. + * * `total` – `0` = indeterminate (show a spinner, use `step` as liveness signal); + * `1` = single-shot phase (the callback itself is the notification); + * `> 1` = determinate (`step / total` gives a completion fraction for a progress bar). + * + * # Return value + * Return non-zero to continue the operation, zero to cancel. + */ +typedef int (*ProgressCCallback)(const void *context, + enum C2paProgressPhase phase, + uint32_t step, + uint32_t total); + +typedef struct C2paHttpResolver { + +} C2paHttpResolver; + +/** + * HTTP request passed to the resolver callback. + * + * All string fields are NULL-terminated UTF-8. The struct and all + * pointed-to data remain valid for the duration of the callback. + */ +typedef struct C2paHttpRequest { + /** + * URL (e.g. `https://example.com/manifest`) + */ + const char *url; + /** + * HTTP method (e.g. "GET", "POST") + */ + const char *method; + /** + * Newline-delimited "Name: Value\n" pairs, or NULL if none + */ + const char *headers; + /** + * Request body bytes, or NULL if none + */ + const unsigned char *body; + /** + * Length of `body` in bytes + */ + uintptr_t body_len; +} C2paHttpRequest; + +/** + * HTTP response filled in by the resolver callback. + * + * The callback must set `status`, `body`, and `body_len`. + * `body` must be allocated with `malloc()`. Rust will call `free()` on it + * after copying the data. + */ +typedef struct C2paHttpResponse { + /** + * HTTP status code (e.g. 200, 404) + */ + int32_t status; + /** + * Response body bytes, allocated by the callback with `malloc()`. + * Rust takes ownership and will call `free()`. + */ + unsigned char *body; + /** + * Length of `body` in bytes + */ + uintptr_t body_len; +} C2paHttpResponse; + +/** + * Callback type for custom HTTP request resolution. + * + * Called synchronously by Rust when an HTTP request is needed + * (remote manifest fetch, OCSP, timestamp, etc.). + * + * Returns 0 on success, non-zero on error. On error, call + * `c2pa_error_set_last()` before returning. + */ +typedef int (*C2paHttpResolverCallback)(void *context, + const struct C2paHttpRequest *request, + struct C2paHttpResponse *response); + +typedef struct C2paContext { + +} C2paContext; + +typedef struct C2paReader { + +} C2paReader; + +typedef struct C2paBuilder { + +} C2paBuilder; + +/** + * Defines a callback to read from a stream. + * + * # Parameters + * * context: A generic context value to used by the C code, often a file or stream reference. + */ +typedef intptr_t (*SignerCallback)(const void *context, + const unsigned char *data, + uintptr_t len, + unsigned char *signed_bytes, + uintptr_t signed_len); + +/** + * Defines the configuration for a Signer. + * + * The signer is created from the sign_cert and private_key fields. + * an optional url to an RFC 3161 compliant time server will ensure the signature is timestamped. + */ +typedef struct C2paSignerInfo { + /** + * The signing algorithm. + */ + const char *alg; + /** + * The public certificate chain in PEM format. + */ + const char *sign_cert; + /** + * The private key in PEM format. + */ + const char *private_key; + /** + * The timestamp authority URL or NULL. + */ + const char *ta_url; +} C2paSignerInfo; + +#if defined(C2PA_DYNAMIC_LOADING) && C2PA_DYNAMIC_LOADING + +//--------------------------------------------------------------------------- +typedef int (*c2pa_module_c2pa_free)(const void*); static c2pa_module_c2pa_free c2pa_free=NULL; +typedef struct C2paContext* (*c2pa_module_c2pa_context_new)(void); static c2pa_module_c2pa_context_new c2pa_context_new=NULL; +typedef struct C2paReader* (*c2pa_module_c2pa_reader_from_context)(C2paContext*); static c2pa_module_c2pa_reader_from_context c2pa_reader_from_context=NULL; +typedef struct C2paStream* (*c2pa_module_c2pa_create_stream)(struct StreamContext*, ReadCallback, SeekCallback, WriteCallback, FlushCallback); static c2pa_module_c2pa_create_stream c2pa_create_stream=NULL; +typedef void (*c2pa_module_c2pa_release_stream)(struct C2paStream*); static c2pa_module_c2pa_release_stream c2pa_release_stream=NULL; +typedef struct C2paReader* (*c2pa_module_c2pa_reader_with_stream)(struct C2paReader*, const char*, struct C2paStream*); static c2pa_module_c2pa_reader_with_stream c2pa_reader_with_stream=NULL; +typedef char* (*c2pa_module_c2pa_reader_detailed_json)(struct C2paReader*); static c2pa_module_c2pa_reader_detailed_json c2pa_reader_detailed_json=NULL; +typedef char* (*c2pa_module_c2pa_reader_crjson)(struct C2paReader*); static c2pa_module_c2pa_reader_crjson c2pa_reader_crjson=NULL; +typedef char* (*c2pa_module_c2pa_error)(void); static c2pa_module_c2pa_error c2pa_error=NULL; +typedef struct C2paBuilder* (*c2pa_module_c2pa_builder_from_context)(struct C2paContext*); static c2pa_module_c2pa_builder_from_context c2pa_builder_from_context=NULL; +typedef struct C2paBuilder* (*c2pa_module_c2pa_builder_with_definition)(struct C2paBuilder*, const char*); static c2pa_module_c2pa_builder_with_definition c2pa_builder_with_definition=NULL; +typedef struct C2paSigner* (*c2pa_module_c2pa_signer_from_info)(const struct C2paSignerInfo*); static c2pa_module_c2pa_signer_from_info c2pa_signer_from_info=NULL; +typedef int64_t (*c2pa_module_c2pa_builder_sign)(struct C2paBuilder*, const char*, struct C2paStream*, struct C2paStream*, struct C2paSigner*, const unsigned char**); static c2pa_module_c2pa_builder_sign c2pa_builder_sign=NULL; + +//--------------------------------------------------------------------------- +static inline bool c2pa_load(void) +{ + bool Error=false; + + if (c2pa_module) + return true; + + DLOPEN(c2pa_module, C2PADLL_NAME); + if (!c2pa_module) + return false; + + ASSIGN(c2pa_module, c2pa_free); + ASSIGN(c2pa_module, c2pa_context_new); + ASSIGN(c2pa_module, c2pa_reader_from_context); + ASSIGN(c2pa_module, c2pa_create_stream); + ASSIGN(c2pa_module, c2pa_release_stream); + ASSIGN(c2pa_module, c2pa_reader_with_stream); + ASSIGN(c2pa_module, c2pa_reader_detailed_json); + ASSIGN(c2pa_module, c2pa_reader_crjson); + ASSIGN(c2pa_module, c2pa_error); + ASSIGN(c2pa_module, c2pa_builder_from_context); + ASSIGN(c2pa_module, c2pa_builder_with_definition); + ASSIGN(c2pa_module, c2pa_signer_from_info); + ASSIGN(c2pa_module, c2pa_builder_sign); + + if (Error) + { + DLCLOSE(c2pa_module); + c2pa_module=NULL; + c2pa_free=NULL; + c2pa_context_new=NULL; + c2pa_reader_from_context=NULL; + c2pa_create_stream=NULL; + c2pa_release_stream=NULL; + c2pa_reader_with_stream=NULL; + c2pa_reader_detailed_json=NULL; + c2pa_reader_crjson=NULL; + c2pa_error=NULL; + c2pa_builder_from_context=NULL; + c2pa_builder_with_definition=NULL; + c2pa_signer_from_info=NULL; + c2pa_builder_sign=NULL; + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +static inline void c2pa_unload(void) +{ + if (c2pa_module) + { + DLCLOSE(c2pa_module); + c2pa_module=NULL; + } + + c2pa_free=NULL; + c2pa_context_new=NULL; + c2pa_reader_from_context=NULL; + c2pa_create_stream=NULL; + c2pa_release_stream=NULL; + c2pa_reader_with_stream=NULL; + c2pa_reader_detailed_json=NULL; + c2pa_reader_crjson=NULL; + c2pa_error=NULL; + c2pa_builder_from_context=NULL; + c2pa_builder_with_definition=NULL; + c2pa_signer_from_info=NULL; + c2pa_builder_sign=NULL; +} +#else +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Creates a new C2paStream from context with callbacks. + * + * This allows implementing streams in other languages. + * + * # Arguments + * * `context` - a pointer to a StreamContext + * * `read` - a ReadCallback to read from the stream + * * `seek` - a SeekCallback to seek in the stream + * * `write` - a WriteCallback to write to the stream + * + * # Safety + * The context must remain valid for the lifetime of the C2paStream. + * + * The resulting C2paStream must be released by calling c2pa_release_stream. + */ +C2PA_API extern +struct C2paStream *c2pa_create_stream(struct StreamContext *context, + ReadCallback reader, + SeekCallback seeker, + WriteCallback writer, + FlushCallback flusher); + +/** + * Releases a C2paStream allocated by Rust. + * + * # Safety + * Can only be released once and is invalid after this call. + */ +C2PA_API extern void c2pa_release_stream(struct C2paStream *stream); + +/** + * Returns a version string for logging. + * + * # Safety + * The returned value MUST be released by calling release_string + * and it is no longer valid after that call. + */ +C2PA_API extern char *c2pa_version(void); + +/** + * Returns the last error message. + * + * # Safety + * The returned value MUST be released by calling release_string + * and it is no longer valid after that call. + */ +C2PA_API extern char *c2pa_error(void); + +/** + * Sets the last error message. + * This is used by callbacks so they can set a return error message. + * THe error should be in the form of "ErrorType: ErrorMessage". + * If ErrorType is missing or invalid, it will be set to "Other". + * and the message will include the original error string. + * Can return -1 if the error string is NULL. + * + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern int c2pa_error_set_last(const char *error_str); + +/** + * Load Settings from a string. + * Sets thread-local settings. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_settings_new() and c2pa_context_builder_set_settings() to configure a context explicitly.") +int c2pa_load_settings(const char *settings, + const char *format); + +/** + * Creates a new C2PA settings object with default values. + * + * # Safety + * + * The returned pointer must be freed with `c2pa_free()`. + * + * # Returns + * + * A pointer to a newly allocated C2paSettings object, or NULL on allocation failure. + */ +C2PA_API extern struct C2paSettings *c2pa_settings_new(void); + +/** + * Updates settings from a JSON or TOML string. + * + * # Safety + * + * * `settings` must be a valid pointer to a C2paSettings object previously + * created by `c2pa_settings_new()` and not yet freed. + * * `settings_str` must be a valid pointer to a null-terminated UTF-8 string. + * * `format` must be a valid pointer to a null-terminated string containing + * either "json" or "toml". + * * The pointers must remain valid for the duration of this call. + * * This function is not thread-safe - do not call concurrently on the same `settings`. + * + * # Returns + * + * 0 on success, negative value on error. + */ +C2PA_API extern +int c2pa_settings_update_from_string(struct C2paSettings *settings, + const char *settings_str, + const char *format); + +/** + * Sets a specific configuration value in the settings using dot notation. + * + * # Safety + * + * * `settings` must be a valid pointer to a C2paSettings object previously + * created by `c2pa_settings_new()` and not yet freed. + * * `path` must be a valid pointer to a null-terminated UTF-8 string containing + * a dot-separated path (e.g., "verify.verify_after_sign"). + * * `value` must be a valid pointer to a null-terminated UTF-8 string containing + * a JSON value (e.g., "true", "\"ps256\"", "42"). + * * The pointers must remain valid for the duration of this call. + * * This function is not thread-safe - do not call concurrently on the same `settings`. + * + * # Returns + * + * 0 on success, negative value on error. + */ +C2PA_API extern +int c2pa_settings_set_value(struct C2paSettings *settings, + const char *path, + const char *value); + +/** + * Creates a new context builder with default settings. + * + * Use this to construct a context with custom configuration. After setting up + * the builder, call `c2pa_context_builder_build()` to create an immutable context. + * + * # Safety + * + * The returned pointer must be freed by calling + * `c2pa_free()` or converted to a context with `c2pa_context_builder_build()`. + * + * # Returns + * + * A pointer to a newly allocated C2paContextBuilder object, or NULL on allocation failure. + * + * # Example + * + * ```c + * // Create and configure a builder + * C2paContextBuilder* builder = c2pa_context_builder_new(); + * C2paSettings* settings = c2pa_settings_new(); + * c2pa_settings_set_value(settings, "verify.verify_after_sign", "true"); + * c2pa_context_builder_set_settings(builder, settings); + * c2pa_free(settings); + * + * // Build immutable context + * C2paContext* ctx = c2pa_context_builder_build(builder); + * // builder is now invalid, ctx can be shared + * ``` + */ +C2PA_API extern struct C2paContextBuilder *c2pa_context_builder_new(void); + +/** + * Updates the builder with settings. + * + * This configures the builder with the provided settings. The settings are cloned + * internally, so the caller retains ownership. If this call fails, the builder + * remains in its previous valid state. + * + * # Safety + * + * * `builder` must be a valid pointer to a C2paContextBuilder object previously + * created by `c2pa_context_builder_new()` and not yet built. + * * `settings` must be a valid pointer to a C2paSettings object. + * * The pointers must remain valid for the duration of this call. + * * This function is not thread-safe - do not call concurrently on the same `builder`. + * + * # Returns + * + * 0 on success, negative value on error. + */ +C2PA_API extern +int c2pa_context_builder_set_settings(struct C2paContextBuilder *builder, + struct C2paSettings *settings); + +/** + * Set a Signer into the Builder's context. + * (The context will own the Signer from that point on). + * The signer will be available via `context.signer()` after + * building the context. If a signer is also configured in settings, + * the programmatic signer takes priority regardless of call order. + * + * Works with any C2paSigner pointer, whether created by + * `c2pa_signer_from_info` or `c2pa_signer_create`. + * + * # Safety + * + * * `builder` must be a valid C2paContextBuilder pointer (not yet built). + * * `signer_ptr` must be a valid C2paSigner pointer. It is consumed by this + * call and must not be used or freed afterward. + * + * # Returns + * + * 0 on success, negative value on error. + */ +C2PA_API extern +int c2pa_context_builder_set_signer(struct C2paContextBuilder *builder, + struct C2paSigner *signer_ptr); + +/** + * Attaches a C progress callback to a context builder. + * + * The `callback` is invoked at key checkpoints during signing and reading + * operations. Returning `0` from the callback requests cancellation; the SDK + * will return an error at the next safe stopping point. + * + * # Parameters + * * `builder` – a valid `C2paContextBuilder` pointer. + * * `user_data` – opaque `void*` captured by the closure and passed as the first argument + * of every `callback` invocation. Pass `NULL` if the callback does not need user data. + * * `callback` – C function pointer matching [`ProgressCCallback`]. + * + * # Returns + * `0` on success, non-zero on error (check `c2pa_error()`). + * + * # Safety + * * `builder` must be valid and not yet built. + * * `user_data` must remain valid for the entire lifetime of the built context. + */ +C2PA_API extern +int c2pa_context_builder_set_progress_callback(struct C2paContextBuilder *builder, + const void *user_data, + ProgressCCallback callback); + +/** + * Creates a new HTTP resolver backed by a C callback. + * + * The `context` pointer is passed unmodified to every callback invocation and + * must remain valid for the lifetime of the resolver and any context built from it. + * + * # Safety + * + * * `callback` must be a valid function pointer that remains valid for the + * lifetime of the resolver. + * * `context` must remain valid for the lifetime of the resolver and any + * context that uses it. + * * `context` must be safe to use from any thread (i.e. the caller upholds + * `Send + Sync` semantics for the pointed-to data). + * + * # Returns + * + * A new `C2paHttpResolver*`, or NULL on error. Must be freed with `c2pa_free()` + * OR consumed by `c2pa_context_builder_set_http_resolver()`. + */ +C2PA_API extern +struct C2paHttpResolver *c2pa_http_resolver_create(const void *context, + C2paHttpResolverCallback callback); + +/** + * Sets a custom HTTP resolver on the context builder. + * + * The builder takes ownership of the resolver; the caller must NOT free it afterward. + * + * # Safety + * + * * `builder` must be a valid C2paContextBuilder pointer (not yet built). + * * `resolver_ptr` is consumed and must not be used or freed afterward. + * + * # Returns + * + * 0 on success, -1 on error. + */ +C2PA_API extern +int c2pa_context_builder_set_http_resolver(struct C2paContextBuilder *builder, + struct C2paHttpResolver *resolver_ptr); + +/** + * Builds an immutable, shareable context from the builder. + * + * The builder is consumed by this operation and becomes invalid. + * The returned context is immutable and can be shared between multiple + * Reader and Builder instances. + * + * # Safety + * + * * `builder` must be a valid pointer to a C2paContextBuilder. + * * After calling this function, the builder pointer is INVALID and must not be used again. + * * The returned context must be freed with `c2pa_free()`. + * + * # Returns + * + * A pointer to an immutable C2paContext that can be shared, or NULL on error. + */ +C2PA_API extern struct C2paContext *c2pa_context_builder_build(struct C2paContextBuilder *builder); + +/** + * Creates a new immutable context with default settings. + * + * This is a convenience function equivalent to: + * ```c + * builder = c2pa_context_builder_new(); + * ctx = c2pa_context_builder_build(builder); + * ``` + * + * Use `c2pa_context_builder_new()` if you need to configure the context + * before building it. + * + * # Safety + * + * The returned pointer must be freed with `c2pa_free()`. + * + * # Returns + * + * A pointer to a newly allocated immutable C2paContext object, or NULL on allocation failure. + */ +C2PA_API extern struct C2paContext *c2pa_context_new(void); + +/** + * Requests cancellation of any in-progress operation on this context. + * + * Thread-safe — may be called from any thread that holds a valid `C2paContext` + * pointer. The SDK will return an `OperationCancelled` error at the next safe + * checkpoint inside the running operation. + * + * # Parameters + * * `ctx` – a valid, non-null `C2paContext` pointer obtained from + * `c2pa_context_builder_build()` or `c2pa_context_new()`. + * + * # Returns + * `0` on success, non-zero if `ctx` is null or invalid. + * + * # Safety + * `ctx` must be a valid pointer and must not be freed concurrently with this call. + */ +C2PA_API extern int c2pa_context_cancel(struct C2paContext *ctx); + +/** + * Frees a string allocated by Rust. + * + * # Safety + * The string must not have been modified in C. + * The string can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_release_string(char *s); + +/** + * Frees any pointer allocated by this library. + * + * This is the **recommended** free function for all C2PA objects. It replaces the + * type-specific free functions (like `c2pa_string_free`, `c2pa_reader_free`, etc.) + * which are maintained only for backward compatibility. + * + * ## Why use c2pa_free? + * + * - **Simpler API**: One function to remember instead of multiple type-specific functions + * - **Less error-prone**: No need to match the correct free function to each type + * - **Consistent**: Works uniformly across all pointer types + * - **Returns error codes**: Returns 0 on success, -1 on error for better error handling + * + * ## Supported Types + * + * This function works for all C2PA objects including: + * - C2paContext + * - C2paSettings + * - C2paBuilder + * - C2paReader + * - C2paSigner + * - strings (c_char*) + * - byte arrays (c_uchar*) + * - manifest bytes + * - signatures + * - and any other objects created by this library + * + * # Safety + * + * * The pointer must have been allocated by this library (e.g., from c2pa_context_new(), + * c2pa_settings_new(), c2pa_builder_from_json(), etc.) + * * The pointer must not have been modified in C. + * * The pointer can only be freed once and is invalid after this call. + * * Do not mix this with type-specific free functions for the same pointer. + * + * # Returns + * + * 0 on success, -1 on error (e.g., if the pointer was not allocated by this library). + * + * # Example (C) + * + * ```c + * // Create various objects + * C2paReader* reader = c2pa_reader_from_file("image.jpg", "json"); + * char* json = c2pa_reader_json(reader); + * C2paBuilder* builder = c2pa_builder_from_json(json); + * + * // Free everything with the same function + * c2pa_free(json); + * c2pa_free(reader); + * c2pa_free(builder); + * ``` + */ +C2PA_API extern int c2pa_free(const void *ptr); + +/** + * Frees a string allocated by Rust. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * Reads from NULL-terminated C strings. + * The string must not have been modified in C. + * The string can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_string_free(char *s); + +/** + * Frees an array of char* pointers created by Rust. + * + * # Parameters + * * ptr: pointer to the array of char* pointers. + * * count: number of elements in the array. + * + * # Safety + * * The ptr passed into this function must point to memory that was allocated + * by our library. + * * The array and its strings must not have been modified in C. + * * The array and its contents can only be freed once and is invalid after this call. + */ +C2PA_API extern void c2pa_free_string_array(const char *const *ptr, uintptr_t count); + +/** + * Creates a new C2paReader from a default context. + * + * # Safety + * + * This function is safe to call with no preconditions. + * + * # Returns + * A pointer to a newly allocated C2paReader, or NULL on error. + * The returned pointer must be freed with `c2pa_free()`. + */ +C2PA_API extern struct C2paReader *c2pa_reader_new(void); + +/** + * Creates a new C2paReader from a shared context. + * + * The context can be reused to create multiple readers and builders. + * + * # Safety + * + * * `context` must be a valid pointer to a C2paContext object. + * * The context pointer remains valid after this call and can be reused. + * + * # Returns + * + * A pointer to a newly allocated C2paReader, or NULL on error. + */ +C2PA_API extern struct C2paReader *c2pa_reader_from_context(struct C2paContext *context); + +/** + * # Example + * ```c + * auto result = c2pa_reader_from_stream("image/jpeg", stream); + * if (result == NULL) { + * let error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + * + * # Safety + * format must be a valid NULL-terminated C string pointer. + * stream must be a valid pointer to a C2paStream. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_reader_from_context() with an explicit context instead of relying on thread-local settings.") +struct C2paReader *c2pa_reader_from_stream(const char *format, + struct C2paStream *stream); + +/** + * Configures an existing reader with a stream. + * + * This method consumes the original reader and returns a new configured reader. + * The original reader pointer becomes invalid after this call. + * + * # Safety + * + * * `reader` must be a valid pointer to a C2paReader. + * * `format` must be a valid null-terminated string with the MIME type. + * * `stream` must be a valid pointer to a C2paStream. + * * After calling this function, the `reader` pointer is INVALID. + * + * # Returns + * + * A pointer to a newly configured C2paReader, or NULL on error. + */ +C2PA_API extern +struct C2paReader *c2pa_reader_with_stream(struct C2paReader *reader, + const char *format, + struct C2paStream *stream); + +/** + * Configures an existing passed in Reader with manifest data and a stream. + * This covers the case when a Reader needs to be able to re-read signed + * manifest bytes. This method consumes the original Reader and returns a + * new configured Reader. The original Reader pointer becomes invalid after + * this call and should not be reused. + * + * # Safety + * + * * `reader` must be a valid pointer to a configured C2paReader + * (usually with a Context). + * * `format` must be a valid null-terminated string with the MIME type. + * * `stream` must be a valid pointer to a C2paStream. + * * `manifest_data` must be a valid pointer to manifest bytes. + * * `manifest_size` must be the length of the manifest_data buffer. + * * After calling this function, the `reader` pointer becomes invalid. + * + * # Returns + * + * A pointer to a newly configured C2paReader, or NULL on error. + */ +C2PA_API extern +struct C2paReader *c2pa_reader_with_manifest_data_and_stream(struct C2paReader *reader, + const char *format, + struct C2paStream *stream, + const unsigned char *manifest_data, + uintptr_t manifest_size); + +/** + * Configures an existing reader with a fragment stream. + * + * This is used for fragmented BMFF media formats where manifests are stored + * in separate fragments. This method consumes the original reader and returns + * a new configured reader. The original reader pointer becomes invalid after this call. + * + * # Safety + * + * * `reader` must be a valid pointer to a C2paReader. + * * `format` must be a valid null-terminated string with the MIME type. + * * `stream` must be a valid pointer to a C2paStream (the main asset stream). + * * `fragment` must be a valid pointer to a C2paStream (the fragment stream). + * * After calling this function, the `reader` pointer is INVALID. + * + * # Returns + * + * A pointer to a newly configured C2paReader, or NULL on error. + * + * # Example + * + * ```c + * C2paReader* reader = c2pa_reader_from_context(ctx); + * C2paReader* new_reader = c2pa_reader_with_fragment(reader, "video/mp4", main_stream, fragment_stream); + * // reader is now invalid, use new_reader + * ``` + */ +C2PA_API extern +struct C2paReader *c2pa_reader_with_fragment(struct C2paReader *reader, + const char *format, + struct C2paStream *stream, + struct C2paStream *fragment); + +/** + * Creates and verifies a C2paReader from an asset stream with the given format and manifest data. + * + * Parameters + * * format: pointer to a C string with the mime type or extension. + * * stream: pointer to a C2paStream. + * * manifest_data: pointer to the manifest data bytes. + * * manifest_size: size of the manifest data bytes. + * + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a ManifestStore. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_reader_from_context() then c2pa_reader_with_manifest_data_and_stream() instead.") +struct C2paReader *c2pa_reader_from_manifest_data_and_stream(const char *format, + struct C2paStream *stream, + const unsigned char *manifest_data, + uintptr_t manifest_size); + +/** + * Frees a C2paReader allocated by Rust. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * The C2paReader can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_reader_free(struct C2paReader *reader_ptr); + +/** + * Returns a JSON string generated from a C2paReader. + * + * # Safety + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern char *c2pa_reader_json(struct C2paReader *reader_ptr); + +/** + * Returns a detailed JSON string generated from a C2paReader. + * + * # Safety + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern char *c2pa_reader_detailed_json(struct C2paReader *reader_ptr); + +/** + * Returns a crJSON string generated from a C2paReader. + * + * # Safety + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern char *c2pa_reader_crjson(struct C2paReader *reader_ptr); + +/** + * Returns the remote url of the manifest if it was obtained remotely. + * + * # Parameters + * * reader_ptr: pointer to a C2paReader. + * + * # Safety + * reader_ptr must be a valid pointer to a C2paReader. + */ +C2PA_API extern const char *c2pa_reader_remote_url(struct C2paReader *reader_ptr); + +/** + * Returns if the reader was created from an embedded manifest. + * + * # Parameters + * * reader_ptr: pointer to a C2paReader. + * + * # Safety + * reader_ptr must be a valid pointer to a C2paReader. + */ +C2PA_API extern bool c2pa_reader_is_embedded(struct C2paReader *reader_ptr); + +/** + * Writes a C2paReader resource to a stream given a URI. + * + * The resource uri should match an identifier in the the manifest store. + * + * # Parameters + * * reader_ptr: pointer to a Reader. + * * uri: pointer to a C string with the URI to identify the resource. + * * stream: pointer to a writable C2paStream. + * + * # Errors + * Returns -1 if there were errors, otherwise returns size of stream written. + * + * # Safety + * Reads from NULL-terminated C strings. + * + * # Example + * ```c + * auto result = c2pa_reader_resource_to_stream(store, "uri", stream); + * if (result < 0) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +int64_t c2pa_reader_resource_to_stream(struct C2paReader *reader_ptr, + const char *uri, + struct C2paStream *stream); + +/** + * Returns an array of char* pointers to c2pa::Reader's supported mime types. + * The caller is responsible for freeing the array. + * + * # Parameters + * * count: pointer to a usize to return the number of mime types. + * + * # Safety + * The returned value MUST be released by calling [c2pa_free_string_array]. + * The array and its contents are no longer valid after that call. + */ +C2PA_API extern const char *const *c2pa_reader_supported_mime_types(uintptr_t *count); + +/** + * Creates a C2paBuilder from a JSON manifest definition string. + * + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a Builder. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + * + * # Example + * ```c + * auto result = c2pa_builder_from_json(manifest_json); + * if (result == NULL) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_builder_from_context() then c2pa_builder_set_definition() instead.") +struct C2paBuilder *c2pa_builder_from_json(const char *manifest_json); + +/** + * Creates a C2paBuilder from a shared Context. + * + * The context can be reused to create multiple builders and readers. + * The builder will inherit the context's settings, signers, and resolvers. + * + * # Safety + * + * * `context` must be a valid pointer to a C2paContext object. + * * The context pointer remains valid after this call and can be reused. + * + * # Returns + * + * A pointer to a newly allocated C2paBuilder, or NULL on error. + * + * # Example + * + * ```c + * C2paContext* ctx = c2pa_context_new(); + * C2paBuilder* builder = c2pa_builder_from_context(ctx); + * // context can still be used + * C2paReader* reader = c2pa_reader_from_context(ctx); + * ``` + */ +C2PA_API extern struct C2paBuilder *c2pa_builder_from_context(struct C2paContext *context); + +/** + * Create a C2paBuilder from an archive stream. + * + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a Builder. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + * + * # Example + * ```c + * auto result = c2pa_builder_from_archive(stream); + * if (result == NULL) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_builder_from_context() then c2pa_builder_with_archive() instead.") +struct C2paBuilder *c2pa_builder_from_archive(struct C2paStream *stream); + +/** + * Returns an array of char* pointers to the supported mime types. + * The caller is responsible for freeing the array. + * + * # Parameters + * * count: pointer to a usize to return the number of mime types. + * + * # Safety + * The returned value MUST be released by calling [c2pa_free_string_array]. + * The array and its contents are no longer valid after that call. + */ +C2PA_API extern const char *const *c2pa_builder_supported_mime_types(uintptr_t *count); + +/** + * Frees a C2paBuilder allocated by Rust. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * The C2paBuilder can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_builder_free(struct C2paBuilder *builder_ptr); + +/** + * Updates the builder with a new manifest definition. + * + * This consumes the original builder and returns a new configured builder. + * The original builder pointer becomes invalid after this call. + * + * # Safety + * + * * `builder` must be a valid pointer to a C2paBuilder. + * * `manifest_json` must be a valid null-terminated JSON string. + * * After calling this function, the `builder` pointer is INVALID. + * + * # Returns + * + * A pointer to a newly configured C2paBuilder, or NULL on error. + * + * # Example + * + * ```c + * C2paBuilder* builder = c2pa_builder_from_context(ctx); + * const char* json = "{\"title\": \"Updated Title\"}"; + * C2paBuilder* new_builder = c2pa_builder_with_definition(builder, json); + * // builder is now invalid, use new_builder + * ``` + */ +C2PA_API extern +struct C2paBuilder *c2pa_builder_with_definition(struct C2paBuilder *builder, + const char *manifest_json); + +/** + * Configures an existing builder with an archive stream. + * + * This consumes the original builder and returns a new configured builder. + * The original builder pointer becomes invalid after this call. + * + * # Safety + * + * * `builder` must be a valid pointer to a C2paBuilder. + * * `stream` must be a valid pointer to a C2paStream. + * * After calling this function, the `builder` pointer is INVALID. + * + * # Returns + * + * A pointer to a newly configured C2paBuilder, or NULL on error. + * + * # Example + * + * ```c + * C2paBuilder* builder = c2pa_builder_from_context(ctx); + * C2paBuilder* new_builder = c2pa_builder_with_archive(builder, archive_stream); + * // builder is now invalid, use new_builder + * ``` + */ +C2PA_API extern +struct C2paBuilder *c2pa_builder_with_archive(struct C2paBuilder *builder, + struct C2paStream *stream); + +/** + * Sets the builder intent on the Builder. + * + * An intent lets the API know what kind of manifest to create. + * Intents are `Create`, `Edit`, or `Update`. + * + * Create requires a `DigitalSourceType`. It is used for assets without a parent ingredient. + * Edit requires a parent ingredient and is used for most assets that are being edited. + * Update is a special case with many restrictions but is more compact than Edit. + * + * For the `Create` intent, a valid `digital_source_type` must be provided. + * For `Edit` and `Update` intents, `digital_source_type` will be ignored (any value is allowed). + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * intent: the builder intent (Create, Edit, or Update). + * * digital_source_type: the digital source type (required for Create intent). + * + * # Errors + * Returns -1 if there were errors (null pointer for builder_ptr), otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * builder_ptr must be a valid pointer to a Builder. + */ +C2PA_API extern +int c2pa_builder_set_intent(struct C2paBuilder *builder_ptr, + enum C2paBuilderIntent intent, + enum C2paDigitalSourceType digital_source_type); + +/** + * Sets the no-embed flag on the Builder. + * When set, the builder will not embed a C2PA manifest store into the asset when signing. + * This is useful when creating cloud or sidecar manifests. + * # Parameters + * * builder_ptr: pointer to a Builder. + * # Safety + * builder_ptr must be a valid pointer to a Builder. + */ +C2PA_API extern void c2pa_builder_set_no_embed(struct C2paBuilder *builder_ptr); + +/** + * Sets the remote URL on the Builder. + * When set, the builder will embed a remote URL into the asset when signing. + * This is useful when creating cloud based Manifests. + * # Parameters + * * builder_ptr: pointer to a Builder. + * * remote_url: pointer to a C string with the remote URL. + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern +int c2pa_builder_set_remote_url(struct C2paBuilder *builder_ptr, + const char *remote_url); + +/** + * ⚠️ **Deprecated Soon** + * This method is planned to be deprecated in a future release. + * Usage should be limited and temporary. + * + * Sets the resource directory on the Builder. + * When set, resources that are not found in memory will be searched for in the given directory. + * # Parameters + * * builder_ptr: pointer to a Builder. + * * base_path: pointer to a C string with the resource directory. + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern +int c2pa_builder_set_base_path(struct C2paBuilder *builder_ptr, + const char *base_path); + +/** + * Adds a resource to the C2paBuilder. + * + * The resource uri should match an identifier in the manifest definition. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * uri: pointer to a C string with the URI to identify the resource. + * * stream: pointer to a C2paStream. + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings + */ +C2PA_API extern +int c2pa_builder_add_resource(struct C2paBuilder *builder_ptr, + const char *uri, + struct C2paStream *stream); + +/** + * Adds an ingredient to the C2paBuilder. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * ingredient_json: pointer to a C string with the JSON ingredient definition. + * * format: pointer to a C string with the mime type or extension. + * * source: pointer to a C2paStream. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern +int c2pa_builder_add_ingredient_from_stream(struct C2paBuilder *builder_ptr, + const char *ingredient_json, + const char *format, + struct C2paStream *source); + +/** + * Adds an action to the manifest the Builder is constructing. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * action_json: JSON string containing the action data. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling [c2pa_error]. + * + * # Safety + * Reads from NULL-terminated C strings. + * + * # Example + * ```C + * const char* manifest_def = "{}"; + * C2paBuilder* builder = c2pa_builder_from_json(manifest_def); + * + * const char* action_json = "{\n" + * " \"action\": \"com.example.test-action\",\n" + * " \"parameters\": {\n" + * " \"key1\": \"value1\",\n" + * " \"key2\": \"value2\"\n" + * " }\n" + * "}"; + * + * int result = c2pa_builder_add_action(builder, action_json); + * ``` + * + * This creates a manifest with an actions assertion + * containing the added action (excerpt of the full manifest): + * ```json + * "assertions": [ + * { + * "label": "c2pa.actions.v2", + * "data": { + * "actions": [ + * { + * "action": "c2pa.created", + * "digitalSourceType": "http://c2pa.org/digitalsourcetype/empty" + * }, + * { + * "action": "com.example.test-action", + * "parameters": { + * "key2": "value2", + * "key1": "value1" + * } + * } + * ], + * } + * } + * ] + * ``` + */ +C2PA_API extern +int c2pa_builder_add_action(struct C2paBuilder *builder_ptr, + const char *action_json); + +/** + * Writes an Archive of the Builder to the destination stream. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * stream: pointer to a writable C2paStream. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * + * # Example + * ```c + * auto result = c2pa_builder_to_archive(builder, stream); + * if (result < 0) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +int c2pa_builder_to_archive(struct C2paBuilder *builder_ptr, + struct C2paStream *stream); + +/** + * Adds an ingredient to the C2paBuilder from a C2PA ingredient archive stream. + * + * The stream must contain a C2PA ingredient archive produced by + * `c2pa_builder_write_ingredient_archive`. Use + * `c2pa_builder_add_ingredient_from_stream` for regular asset streams. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * stream: pointer to a readable, seekable C2paStream containing the ingredient archive. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Pointers must be valid and non-NULL. + * + * # Example + * ```c + * // Write the ingredient archive first + * C2paStream* archive = c2pa_create_stream(...); + * int result = c2pa_builder_write_ingredient_archive(ingredient_builder, "ingredient-id", archive); + * + * // Rewind and add it to the parent builder + * c2pa_stream_seek(archive, 0, C2PA_SEEK_START); + * result = c2pa_builder_add_ingredient_from_archive(parent_builder, archive); + * ``` + */ +C2PA_API extern +int c2pa_builder_add_ingredient_from_archive(struct C2paBuilder *builder_ptr, + struct C2paStream *stream); + +/** + * Writes a single-ingredient C2PA archive to the destination stream. + * + * The archive can later be loaded with `c2pa_builder_add_ingredient_from_archive`. + * This requires the `generate_c2pa_archive` builder setting to be enabled via + * `c2pa_builder_with_settings` / `c2pa_context_with_settings` before calling. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * ingredient_id: pointer to a C string identifying the ingredient within the builder. + * * stream: pointer to a writable C2paStream. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. Pointers must be valid and non-NULL. + * + * # Example + * ```c + * C2paStream* archive = c2pa_create_stream(...); + * int result = c2pa_builder_write_ingredient_archive(builder, "my-ingredient", archive); + * if (result < 0) { + * char* error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +int c2pa_builder_write_ingredient_archive(struct C2paBuilder *builder_ptr, + const char *ingredient_id, + struct C2paStream *stream); + +/** + * Creates and writes signed manifest from the C2paBuilder to the destination stream. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: pointer to a C string with the mime type or extension. + * * source: pointer to a C2paStream. + * * dest: pointer to a writable C2paStream. + * * signer: pointer to a C2paSigner. + * * c2pa_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes (optional, can be NULL). + * + * # Errors + * Returns -1 if there were errors, otherwise returns the size of the c2pa data. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings + * If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +int64_t c2pa_builder_sign(struct C2paBuilder *builder_ptr, + const char *format, + struct C2paStream *source, + struct C2paStream *dest, + struct C2paSigner *signer_ptr, + const unsigned char **manifest_bytes_ptr); + +/** + * Sign using the Signer from the Context. + * + * Equivalent to `c2pa_builder_sign` but the signer comes from the Builder's + * context instead of being passed explicitly. + * + * If the context has no signer (neither programmatic via + * `c2pa_context_builder_set_signer` nor from settings), an error + * will be returned. + * + * # Parameters + * + * * `builder_ptr` - pointer to a Builder whose context has a signer set. + * * `format` - MIME type or file extension (null-terminated C string). + * * `source` - pointer to a readable C2paStream. + * * `dest` - pointer to a read+write+seek C2paStream. + * * `manifest_bytes_ptr` - out-pointer for the manifest bytes. + * + * # Safety + * + * Reads from NULL-terminated C strings. + * The returned bytes MUST be released by calling `c2pa_free`. + * + * # Returns + * + * The length of the manifest bytes on success, or -1 on error. + */ +C2PA_API extern +int64_t c2pa_builder_sign_context(struct C2paBuilder *builder_ptr, + const char *format, + struct C2paStream *source, + struct C2paStream *dest, + const unsigned char **manifest_bytes_ptr); + +/** + * Frees a C2PA manifest returned by c2pa_builder_sign. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * The bytes can only be freed once and are invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_manifest_bytes_free(const unsigned char *manifest_bytes_ptr); + +/** + * Creates a hashed placeholder from a Builder. + * The placeholder is used to reserve size in an asset for later signing. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * reserved_size: the size required for a signature from the intended signer. + * * format: pointer to a C string with the mime type or extension. + * * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes. + * + * # Errors + * Returns -1 if there were errors, otherwise returns the size of the manifest_bytes. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +int64_t c2pa_builder_data_hashed_placeholder(struct C2paBuilder *builder_ptr, + uintptr_t reserved_size, + const char *format, + const unsigned char **manifest_bytes_ptr); + +/** + * Sign a Builder using the specified signer and data hash. + * The data hash is a JSON string containing DataHash information for the asset. + * This is a low-level method for advanced use cases where the caller handles embedding the manifest. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * signer: pointer to a C2paSigner. + * * data_hash: pointer to a C string with the JSON data hash. + * * format: pointer to a C string with the mime type or extension. + * * asset: pointer to a C2paStream (may be NULL to use pre calculated hashes). + * * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes (optional, can be NULL). + * + * # Errors + * Returns -1 if there were errors, otherwise returns the size of the manifest_bytes. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +int64_t c2pa_builder_sign_data_hashed_embeddable(struct C2paBuilder *builder_ptr, + struct C2paSigner *signer_ptr, + const char *data_hash, + const char *format, + struct C2paStream *asset, + const unsigned char **manifest_bytes_ptr); + +/** + * Returns whether a placeholder manifest is required for the given format. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: pointer to a C string with the mime type or extension. + * + * # Returns + * Returns 1 if a placeholder is required, 0 if not, or -1 on error. + * Use [`c2pa_error`] to retrieve the error message when -1 is returned. + * + * # Safety + * Reads from NULL-terminated C strings. + */ +C2PA_API extern +int c2pa_builder_needs_placeholder(struct C2paBuilder *builder_ptr, + const char *format); + +/** + * Returns the hash binding type that the builder will use for the given format. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: pointer to a C string with the MIME type or extension. + * * out_hash_type: pointer to a C2paHashType that receives the result on success. + * + * # Returns + * 0 on success, -1 on error (null pointer or invalid string). + * + * # Safety + * Reads from NULL-terminated C strings. Writes to `out_hash_type` only on success. + */ +C2PA_API extern +int c2pa_builder_hash_type(struct C2paBuilder *builder_ptr, + const char *format, + enum C2paHashType *out_hash_type); + +/** + * Creates a composed placeholder manifest from a Builder. + * + * The placeholder is a format-specific (e.g. C2PA UUID box for MP4, APP11 for JPEG) + * byte sequence that can be embedded directly into an asset to reserve space for the + * final signed manifest. The placeholder JUMBF length is stored internally in the + * Builder so that [`c2pa_builder_sign_embeddable`] returns bytes of the identical size. + * + * The signer (including its reserve size) is obtained from the Builder's Context. + * For BMFF assets, if `core.merkle_tree_chunk_size_in_kb` is set in the Context settings, + * the placeholder will include pre-allocated Merkle map slots for up to 4 mdat boxes. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: pointer to a C string with the mime type or extension. + * * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return the composed placeholder bytes. + * * (the pointer may be NULL if the caller does not want to receive the bytes) + * + * # Errors + * Returns -1 on error (call c2pa_error() for the message). + * On success, returns the byte length of the composed placeholder. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned bytes MUST be released by calling c2pa_free. + */ +C2PA_API extern +int64_t c2pa_builder_placeholder(struct C2paBuilder *builder_ptr, + const char *format, + const unsigned char **manifest_bytes_ptr); + +/** + * Signs the manifest and returns composed bytes ready for embedding. + * + * Operates in two modes: + * + * **Placeholder mode** (after calling [`c2pa_builder_placeholder`]): The Builder knows + * the pre-committed size of the composed placeholder. The returned bytes are + * zero-padded to be exactly the same size, enabling in-place patching of the asset. + * + * **Direct mode** (no placeholder): The Builder must already contain a valid hard + * binding assertion (DataHash, BmffHash, or BoxHash with a real hash value), set + * either by [`c2pa_builder_update_hash_from_stream`] or directly via the assertion + * API. The returned bytes reflect the actual manifest size. + * + * The signer is obtained from the Builder's Context. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: pointer to a C string with the mime type or extension. + * * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return the signed manifest bytes. + * + * # Errors + * Returns -1 on error (call c2pa_error() for the message). + * In direct mode, also returns -1 if no valid hard binding assertion exists. + * On success, returns the byte length of the signed manifest. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned bytes MUST be released by calling c2pa_free. + */ +C2PA_API extern +int64_t c2pa_builder_sign_embeddable(struct C2paBuilder *builder_ptr, + const char *format, + const unsigned char **manifest_bytes_ptr); + +/** + * Sets the byte exclusion ranges on the DataHash assertion in a Builder. + * + * Call this after [`c2pa_builder_placeholder`] to register the exact byte region + * where the composed placeholder was embedded in the asset. This step is required + * before [`c2pa_builder_update_hash_from_stream`] so the hash covers all asset bytes + * except the manifest slot. + * + * Exclusions are provided as a flat array of `(start, length)` pairs, each a `uint64_t`. + * The layout is: `[start0, length0, start1, length1, …]`, so `exclusions_ptr` must + * point to `exclusion_count * 2` consecutive `uint64_t` values. + * + * The existing DataHash's name and algorithm are preserved; only its exclusion list + * is replaced. + * + * # Parameters + * * builder_ptr: pointer to a Builder (must have called [`c2pa_builder_placeholder`] first). + * * exclusions_ptr: pointer to a flat array of `(start, length)` uint64_t pairs. + * * exclusion_count: number of exclusion ranges (not the number of uint64_t values). + * + * # Errors + * Returns 0 on success, -1 on error (call c2pa_error() for the message). + * Fails if no DataHash assertion exists on the Builder. + * + * # Safety + * `exclusions_ptr` must point to at least `exclusion_count * 2` valid `uint64_t` values. + */ +C2PA_API extern +int c2pa_builder_set_data_hash_exclusions(struct C2paBuilder *builder_ptr, + const uint64_t *exclusions_ptr, + uintptr_t exclusion_count); + +/** + * If set the hasher will hash fixed size chunks of data, padding the final block as needed. + * This will produce a Merkle tree for each mdat with fixed size leaves, which can be used + * for efficient hashing of large assets. + * + * #Parameters + * * builder_ptr: pointer to a Builder (must have called [`c2pa_builder_placeholder`] first). + * * fixed_size_kb: length of fixed size blocks. The units are KB. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * builder_ptr must not be NULL. + */ +C2PA_API extern +int c2pa_builder_set_fixed_size_merkle(struct C2paBuilder *builder_ptr, + uintptr_t fixed_size_kb); + +/** + * Generate the mdat leaf hashes for the asset, the C2paHasher will accumulate the hash values for + * each mdat_id. The data_ptr should be supplied in the order the chunks are written to the mdat. + * The mdat_id should be begin with 0 and increment for each mdat in the asset. For assets with a s + * ingle mdat, the mdat_id should be 0. If fixed size Merkle is enabled, the data will be accumulated + * and hashed in fixed size chunks and the final chunk will be padded. Otherwise the data will be hashed + * as a single leaf for each mdat chunk supplied to this call. + * + * #Parameters + * * builder_ptr: pointer to the C2paBuilder. + * * mdat_id: specifies which mdat this hash leaf belongs. + * * data_ptr: pointer to data to hash. + * * data_len: length of data to hash. + * + * # Errors + * Returns -1 if there were errors, otherwise returns 0. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * builder_ptr must not be NULL.. + * + * # Example + * ```c + * auto data = std::vector buffer(1024); + * + * c2pa_builder_hash_mdat_bytes(builder, 1, (const uint8_t*)data.data(), 1024, true); + * ``` + */ +C2PA_API extern +int c2pa_builder_hash_mdat_bytes(struct C2paBuilder *builder_ptr, + uintptr_t mdat_id, + const unsigned char *data_ptr, + uintptr_t data_len, + bool large_size); + +/** + * Updates the hard binding assertion in a Builder by hashing an asset stream. + * + * Automatically detects the type of hard binding on the Builder: + * - **BmffHash**: uses the assertion's own path-based exclusions (UUID box, mdat when + * Merkle hashing is enabled). The hash algorithm is read from the assertion itself. + * - **BoxHash**: uses the format's box-hash handler to enumerate chunks, hashes each + * one individually, and stores the result. Triggered when a BoxHash assertion is + * already present or when `builder.prefer_box_hash` is enabled and the format + * supports it. + * - **DataHash**: reads any exclusion ranges already on the existing DataHash assertion, + * hashes the stream excluding those ranges, and stores the result. If no DataHash + * exists, creates one with no exclusions (hashes the entire stream — sidecar case). + * + * The hash algorithm is resolved in this order: + * 1. The `alg` field of the existing hard binding assertion + * 2. The `alg` field on the ManifestDefinition (set via JSON or builder settings) + * 3. `"sha256"` (the C2PA default) + * + * For DataHash workflows, call [`c2pa_builder_set_data_hash_exclusions`] before this + * function to register where the composed placeholder was embedded. + * + * # Parameters + * * builder_ptr: pointer to a Builder. + * * format: MIME type or file extension of the asset (e.g. `"image/jpeg"`). + * * stream: pointer to a C2paStream of the asset to hash. + * + * # Errors + * Returns 0 on success, -1 on error (call c2pa_error() for the message). + * + * # Safety + * The stream must remain valid for the duration of the call. + */ +C2PA_API extern +int c2pa_builder_update_hash_from_stream(struct C2paBuilder *builder_ptr, + const char *format, + struct C2paStream *stream); + +/** + * Convert a binary c2pa manifest into an embeddable version for the given format. + * A raw manifest (in application/c2pa format) can be uploaded to the cloud but + * it cannot be embedded directly into an asset without extra processing. + * This method converts the raw manifest into an embeddable version that can be + * embedded into an asset. + * + * # Parameters + * * format: pointer to a C string with the mime type or extension. + * * manifest_bytes_ptr: pointer to a c_uchar with the raw manifest bytes. + * * manifest_bytes_size: the size of the manifest_bytes. + * * result_bytes_ptr: pointer to a pointer to a c_uchar to return the embeddable manifest bytes. + * + * # Errors + * Returns -1 if there were errors, otherwise returns the size of the result_bytes. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +int64_t c2pa_format_embeddable(const char *format, + const unsigned char *manifest_bytes_ptr, + uintptr_t manifest_bytes_size, + const unsigned char **result_bytes_ptr); + +/** + * Creates a C2paSigner from a callback and configuration. + * + * # Parameters + * * callback: a callback function to sign data. + * * alg: the signing algorithm. + * * certs: a pointer to a NULL-terminated string containing the certificate chain in PEM format. + * * tsa_url: a pointer to a NULL-terminated string containing the RFC 3161 compliant timestamp authority URL. + * + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + * When binding through the C API to other languages, the callback must live long + * enough, possibly being re-used and called multiple times. The callback is logically + * owned by the host/caller. + * + * # Example + * ```c + * auto result = c2pa_signer_create(callback, alg, certs, tsa_url); + * if (result == NULL) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +struct C2paSigner *c2pa_signer_create(const void *context, + SignerCallback callback, + enum C2paSigningAlg alg, + const char *certs, + const char *tsa_url); + +/** + * Creates a C2paSigner that handles both C2PA claim signing and X.509 identity assertion signing + * by combining two existing [`C2paSigner`] instances. + * + * The resulting signer will embed one X.509 identity assertion (using `cawg.x509.cose`) + * into every manifest it signs. + * + * Both input signers are **consumed** by this call — ownership transfers to the returned + * signer and the caller MUST NOT free them afterward. + * + * # Parameters + * * `c2pa_signer`: A `C2paSigner` used to sign the C2PA claim. Consumed by this call. + * * `identity_signer`: A `C2paSigner` used to sign the X.509 identity assertion. Consumed by + * this call. + * * `referenced_assertions`: A NULL-terminated array of NULL-terminated UTF-8 strings naming + * assertions to reference in the identity assertion, or NULL if none. + * * `roles`: A NULL-terminated array of NULL-terminated UTF-8 strings specifying the named + * actor's roles, or NULL if none. + * + * # Errors + * Returns NULL if either signer pointer is NULL; call `c2pa_error` to retrieve the error string. + * + * # Safety + * Both signer pointers must have been created by a `c2pa_signer_*` function and not yet freed. + * After this call they are invalid — do NOT pass them to `c2pa_free`. + * The returned value MUST be released by calling `c2pa_free`. + * `referenced_assertions` and `roles`, if non-NULL, must each point to a NULL-terminated array + * of NULL-terminated UTF-8 strings that remain valid for the duration of this call. + * + * # Example + * ```c + * C2paSigner* c2pa = c2pa_signer_create(c2pa_ctx, c2pa_sign_cb, C2PA_SIGNING_ALG_ES256, c2pa_certs, NULL); + * C2paSigner* identity = c2pa_signer_create(id_ctx, id_sign_cb, C2PA_SIGNING_ALG_ES256, id_certs, NULL); + * const char* refs[] = { "c2pa.actions", NULL }; + * C2paSigner* signer = c2pa_identity_signer_create(c2pa, identity, refs, NULL); + * if (signer == NULL) { + * char* error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern +struct C2paSigner *c2pa_identity_signer_create(struct C2paSigner *c2pa_signer_ptr, + struct C2paSigner *identity_signer_ptr, + const char *const *referenced_assertions, + const char *const *roles); + +/** + * Creates a C2paSigner from a SignerInfo. + * The signer is created from the sign_cert and private_key fields. + * an optional url to an RFC 3161 compliant time server will ensure the signature is timestamped. + * + * # Parameters + * * signer_info: pointer to a C2paSignerInfo. + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner. + * The error string can be retrieved by calling c2pa_error. + * # Safety + * Reads from NULL-terminated C strings. + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + * # Example + * ```c + * auto result = c2pa_signer_from_info(signer_info); + * if (result == NULL) { + * auto error = c2pa_error(); + * printf("Error: %s\n", error); + * c2pa_string_free(error); + * } + * ``` + */ +C2PA_API extern struct C2paSigner *c2pa_signer_from_info(const struct C2paSignerInfo *signer_info); + +/** + * Creates a C2paSigner from the settings. + * The signer is created from the settings defined in the c2pa_settings.json file. + * + * # Errors + * Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner. + * The error string can be retrieved by calling c2pa_error. + * # Safety + * The returned value MUST be released by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_context_builder_set_signer() to configure a signer on a context instead.") +struct C2paSigner *c2pa_signer_from_settings(void); + +/** + * Returns the size to reserve for the signature for this signer. + * + * # Parameters + * * signer_ptr: pointer to a C2paSigner. + * + * # Errors + * Returns -1 if there were errors, otherwise returns the size to reserve. + * The error string can be retrieved by calling c2pa_error. + * + * # Safety + * The signer_ptr must be a valid pointer to a C2paSigner. + */ +C2PA_API extern int64_t c2pa_signer_reserve_size(struct C2paSigner *signer_ptr); + +/** + * Frees a C2paSigner allocated by Rust. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * The C2paSigner can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_signer_free(const struct C2paSigner *signer_ptr); + +/** + * Signs a byte array using the Ed25519 algorithm. + * # Safety + * The returned value MUST be freed by calling c2pa_free + * and it is no longer valid after that call. + */ +C2PA_API extern +const unsigned char *c2pa_ed25519_sign(const unsigned char *bytes, + uintptr_t len, + const char *private_key); + +/** + * Frees a signature allocated by Rust. + * + * **Note**: This function is maintained for backward compatibility. New code should + * use [`c2pa_free`] instead, which works for all pointer types. + * + * # Safety + * The signature can only be freed once and is invalid after this call. + */ +C2PA_API extern +C2PA_DEPRECATED_MSG("Use c2pa_free() instead, which works for all pointer types.") +void c2pa_signature_free(const uint8_t *signature_ptr); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus +#endif /* C2PA_DYNAMIC_LOADING */ + +#endif /* c2pa_bindings_h */ + diff --git a/Source/ThirdParty/json/LICENSE b/Source/ThirdParty/json/LICENSE new file mode 100644 index 00000000..68a49daa --- /dev/null +++ b/Source/ThirdParty/json/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Source/ThirdParty/json/json.h b/Source/ThirdParty/json/json.h new file mode 100644 index 00000000..427e59d4 --- /dev/null +++ b/Source/ThirdParty/json/json.h @@ -0,0 +1,3516 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/json.h. +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to . +*/ + +#ifndef SHEREDOM_JSON_H_INCLUDED +#define SHEREDOM_JSON_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: 'bytes padding added after construct' */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(__TINYC__) +#define JSON_ATTRIBUTE(a) __attribute((a)) +#else +#define JSON_ATTRIBUTE(a) __attribute__((a)) +#endif + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define json_weak __inline +#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__) +#define json_weak JSON_ATTRIBUTE(weak) +#else +#error Non clang, non gcc, non MSVC, non tcc, non WATCOM compiler found! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_value_s; +struct json_parse_result_s; + +enum json_parse_flags_e { + json_parse_flags_default = 0, + + /* allow trailing commas in objects and arrays. For example, both [true,] and + {"a" : null,} would be allowed with this option on. */ + json_parse_flags_allow_trailing_comma = 0x1, + + /* allow unquoted keys for objects. For example, {a : null} would be allowed + with this option on. */ + json_parse_flags_allow_unquoted_keys = 0x2, + + /* allow a global unbracketed object. For example, a : null, b : true, c : {} + would be allowed with this option on. */ + json_parse_flags_allow_global_object = 0x4, + + /* allow objects to use '=' instead of ':' between key/value pairs. For + example, a = null, b : true would be allowed with this option on. */ + json_parse_flags_allow_equals_in_object = 0x8, + + /* allow that objects don't have to have comma separators between key/value + pairs. */ + json_parse_flags_allow_no_commas = 0x10, + + /* allow c-style comments (either variants) to be ignored in the input JSON + file. */ + json_parse_flags_allow_c_style_comments = 0x20, + + /* deprecated flag, unused. */ + json_parse_flags_deprecated = 0x40, + + /* record location information for each value. */ + json_parse_flags_allow_location_information = 0x80, + + /* allow strings to be 'single quoted'. */ + json_parse_flags_allow_single_quoted_strings = 0x100, + + /* allow numbers to be hexadecimal. */ + json_parse_flags_allow_hexadecimal_numbers = 0x200, + + /* allow numbers like +123 to be parsed. */ + json_parse_flags_allow_leading_plus_sign = 0x400, + + /* allow numbers like .0123 or 123. to be parsed. */ + json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800, + + /* allow Infinity, -Infinity, NaN, -NaN. */ + json_parse_flags_allow_inf_and_nan = 0x1000, + + /* allow multi line string values. */ + json_parse_flags_allow_multi_line_strings = 0x2000, + + /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set + of other parsing options. */ + json_parse_flags_allow_simplified_json = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_global_object | + json_parse_flags_allow_equals_in_object | + json_parse_flags_allow_no_commas), + + /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing + options. */ + json_parse_flags_allow_json5 = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_c_style_comments | + json_parse_flags_allow_single_quoted_strings | + json_parse_flags_allow_hexadecimal_numbers | + json_parse_flags_allow_leading_plus_sign | + json_parse_flags_allow_leading_or_trailing_decimal_point | + json_parse_flags_allow_inf_and_nan | + json_parse_flags_allow_multi_line_strings) +}; + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to malloc for the entire encoding. + * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */ +json_weak struct json_value_s *json_parse(const void *src, size_t src_size); + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to alloc_func_ptr for the entire + * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc + * failed). If an error occurred, the result struct (if not NULL) will explain + * the type of error, and the location in the input it occurred. If + * alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *, size_t), void *user_data, + struct json_parse_result_s *result); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to malloc for the entire encoding. + */ +json_weak struct json_value_s * +json_extract_value(const struct json_value_s *value); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to alloc_func_ptr for the entire + * encoding. If alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, size_t), void *user_data); + +/* Write out a minified JSON utf-8 string. This string is an encoding of the + * minimal string characters required to still encode the same data. + * json_write_minified performs 1 call to malloc for the entire encoding. Return + * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_minified(const struct json_value_s *value, + size_t *out_size); + +/* Write out a pretty JSON utf-8 string. This string is encoded such that the + * resultant JSON is pretty in that it is easily human readable. The indent and + * newline parameters allow a user to specify what kind of indentation and + * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both + * indent and newline can be NULL, indent defaults to two spaces (" "), and + * newline defaults to linux newlines ('\n' as the newline character). + * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0 + * if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_pretty(const struct json_value_s *value, + const char *indent, const char *newline, + size_t *out_size); + +/* Reinterpret a JSON value as a string. Returns null is the value was not a + * string. */ +json_weak struct json_string_s * +json_value_as_string(struct json_value_s *const value); + +/* Reinterpret a JSON value as a number. Returns null is the value was not a + * number. */ +json_weak struct json_number_s * +json_value_as_number(struct json_value_s *const value); + +/* Reinterpret a JSON value as an object. Returns null is the value was not an + * object. */ +json_weak struct json_object_s * +json_value_as_object(struct json_value_s *const value); + +/* Reinterpret a JSON value as an array. Returns null is the value was not an + * array. */ +json_weak struct json_array_s * +json_value_as_array(struct json_value_s *const value); + +/* Whether the value is true. */ +json_weak int json_value_is_true(const struct json_value_s *const value); + +/* Whether the value is false. */ +json_weak int json_value_is_false(const struct json_value_s *const value); + +/* Whether the value is null. */ +json_weak int json_value_is_null(const struct json_value_s *const value); + +/* The various types JSON values can be. Used to identify what a value is. */ +typedef enum json_type_e { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null + +} json_type_t; + +/* A JSON string value. */ +typedef struct json_string_s { + /* utf-8 string */ + const char *string; + /* The size (in bytes) of the string */ + size_t string_size; + +} json_string_t; + +/* A JSON string value (extended). */ +typedef struct json_string_ex_s { + /* The JSON string this extends. */ + struct json_string_s string; + + /* The character offset for the value in the JSON input. */ + size_t offset; + + /* The line number for the value in the JSON input. */ + size_t line_no; + + /* The row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_string_ex_t; + +/* A JSON number value. */ +typedef struct json_number_s { + /* ASCII string containing representation of the number. */ + const char *number; + /* the size (in bytes) of the number. */ + size_t number_size; + +} json_number_t; + +/* an element of a JSON object. */ +typedef struct json_object_element_s { + /* the name of this element. */ + struct json_string_s *name; + /* the value of this element. */ + struct json_value_s *value; + /* the next object element (can be NULL if the last element in the object). */ + struct json_object_element_s *next; + +} json_object_element_t; + +/* a JSON object value. */ +typedef struct json_object_s { + /* a linked list of the elements in the object. */ + struct json_object_element_s *start; + /* the number of elements in the object. */ + size_t length; + +} json_object_t; + +/* an element of a JSON array. */ +typedef struct json_array_element_s { + /* the value of this element. */ + struct json_value_s *value; + /* the next array element (can be NULL if the last element in the array). */ + struct json_array_element_s *next; + +} json_array_element_t; + +/* a JSON array value. */ +typedef struct json_array_s { + /* a linked list of the elements in the array. */ + struct json_array_element_s *start; + /* the number of elements in the array. */ + size_t length; + +} json_array_t; + +/* a JSON value. */ +typedef struct json_value_s { + /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */ + /* json_array_s. Should be cast to the appropriate struct type based on what. + */ + /* the type of this value is. */ + void *payload; + /* must be one of json_type_e. If type is json_type_true, json_type_false, or. + */ + /* json_type_null, payload will be NULL. */ + size_t type; + +} json_value_t; + +/* a JSON value (extended). */ +typedef struct json_value_ex_s { + /* the JSON value this extends. */ + struct json_value_s value; + + /* the character offset for the value in the JSON input. */ + size_t offset; + + /* the line number for the value in the JSON input. */ + size_t line_no; + + /* the row number for the value in the JSON input, in bytes. */ + size_t row_no; + +} json_value_ex_t; + +/* a parsing error code. */ +enum json_parse_error_e { + /* no error occurred (huzzah!). */ + json_parse_error_none = 0, + + /* expected either a comma or a closing '}' or ']' to close an object or. */ + /* array! */ + json_parse_error_expected_comma_or_closing_bracket, + + /* colon separating name/value pair was missing! */ + json_parse_error_expected_colon, + + /* expected string to begin with '"'! */ + json_parse_error_expected_opening_quote, + + /* invalid escaped sequence in string! */ + json_parse_error_invalid_string_escape_sequence, + + /* invalid number format! */ + json_parse_error_invalid_number_format, + + /* invalid value! */ + json_parse_error_invalid_value, + + /* reached end of buffer before object/array was complete! */ + json_parse_error_premature_end_of_buffer, + + /* string was malformed! */ + json_parse_error_invalid_string, + + /* a call to malloc, or a user provider allocator, failed. */ + json_parse_error_allocator_failed, + + /* the JSON input had unexpected trailing characters that weren't part of the. + JSON value. */ + json_parse_error_unexpected_trailing_characters, + + /* the JSON has too many nested objects & arrays - to prevent a stack + overflow, the library only supports recursion up to JSON_MAX_RECURSION */ + json_parse_error_recursion, + + /* catch-all error for everything else that exploded (real bad chi!). */ + json_parse_error_unknown +}; + +/* error report from json_parse_ex(). */ +typedef struct json_parse_result_s { + /* the error code (one of json_parse_error_e). */ + size_t error; + + /* the character offset for the error in the JSON input. */ + size_t error_offset; + + /* the line number for the error in the JSON input. */ + size_t error_line_no; + + /* the row number for the error, in bytes. */ + size_t error_row_no; + +} json_parse_result_t; + +#ifdef __cplusplus +} /* extern "C". */ +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define json_uintmax_t unsigned __int64 +#else +#include +#define json_uintmax_t uintmax_t +#endif + +#if defined(_MSC_VER) +#define json_strtoumax _strtoui64 +#else +#define json_strtoumax strtoumax +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define json_null nullptr +#else +#define json_null 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + +/* we do one big allocation via malloc, then cast aligned slices of this for. */ +/* our structures - we don't have a way to tell the compiler we know what we. */ +/* are doing, so disable the warning instead! */ +#pragma clang diagnostic ignored "-Wcast-align" + +/* We use C style casts everywhere. */ +#pragma clang diagnostic ignored "-Wold-style-cast" + +/* We need long long for strtoull. */ +#pragma clang diagnostic ignored "-Wc++11-long-long" + +/* Who cares if nullptr doesn't work with C++98, we don't use it there! */ +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#elif defined(_MSC_VER) +#pragma warning(push) + +/* disable 'function not inlined' warning. */ +#pragma warning(disable : 4710) + +/* disable 'function selected for inline expansion' warning. */ +#pragma warning(disable : 4711) + +/* disable '#pragma warning: there is no warning number' warning. */ +#pragma warning(disable : 4619) + +/* disable 'warning number not a valid compiler warning' warning. */ +#pragma warning(disable : 4616) + +/* disable 'Compiler will insert Spectre mitigation for memory load if + * /Qspectre. */ +/* switch specified' warning. */ +#pragma warning(disable : 5045) +#endif + +/* set recursion limit for recursing nested arrays & objects */ +#ifndef JSON_MAX_RECURSION +#define JSON_MAX_RECURSION 1000 +#endif + +struct json_parse_state_s { + const char *src; + size_t size; + size_t offset; + size_t flags_bitset; + char *data; + char *dom; + size_t dom_size; + size_t data_size; + size_t line_no; /* line counter for error reporting. */ + size_t line_offset; /* (offset-line_offset) is the character number (in + bytes). */ + size_t error; + size_t recursion; +}; + +json_weak int json_hexadecimal_digit(const char c); +int json_hexadecimal_digit(const char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +json_weak int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result); +int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result) { + const char *p; + int digit; + + if (size > sizeof(unsigned long) * 2) { + return 0; + } + + *result = 0; + for (p = c; (unsigned long)(p - c) < size; ++p) { + *result <<= 4; + digit = json_hexadecimal_digit(*p); + if (digit < 0 || digit > 15) { + return 0; + } + *result |= (unsigned char)digit; + } + return 1; +} + +json_weak int json_skip_whitespace(struct json_parse_state_s *state); +int json_skip_whitespace(struct json_parse_state_s *state) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + + if (offset >= state->size) { + return 0; + } + + /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and + * '\t'. */ + switch (src[offset]) { + default: + return 0; + case ' ': + case '\r': + case '\t': + case '\n': + break; + } + + do { + switch (src[offset]) { + default: + /* Update offset. */ + state->offset = offset; + return 1; + case ' ': + case '\r': + case '\t': + break; + case '\n': + state->line_no++; + state->line_offset = offset; + break; + } + + offset++; + } while (offset < size); + + /* Update offset. */ + state->offset = offset; + return 1; +} + +json_weak int json_skip_c_style_comments(struct json_parse_state_s *state); +int json_skip_c_style_comments(struct json_parse_state_s *state) { + /* to have a C-style comment we need at least 2 characters of space */ + if ((state->offset + 2) > state->size) { + return 0; + } + + /* do we have a comment? */ + if ('/' == state->src[state->offset]) { + if ('/' == state->src[state->offset + 1]) { + /* we had a comment of the form // */ + + /* skip first '/' */ + state->offset++; + + /* skip second '/' */ + state->offset++; + + while (state->offset < state->size) { + switch (state->src[state->offset]) { + default: + /* skip the character in the comment */ + state->offset++; + break; + case '\n': + /* if we have a newline, our comment has ended! Skip the newline */ + state->offset++; + + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + return 1; + } + } + + /* we reached the end of the JSON file! */ + return 1; + } else if ('*' == state->src[state->offset + 1]) { + /* we had a comment in the C-style long form */ + + /* skip '/' */ + state->offset++; + + /* skip '*' */ + state->offset++; + + while (state->offset + 1 < state->size) { + if (('*' == state->src[state->offset]) && + ('/' == state->src[state->offset + 1])) { + /* we reached the end of our comment! */ + state->offset += 2; + return 1; + } else if ('\n' == state->src[state->offset]) { + /* we entered a newline, so move our line info forward */ + state->line_no++; + state->line_offset = state->offset; + } + + /* skip character within comment */ + state->offset++; + } + + /* comment wasn't ended correctly which is a failure */ + return 1; + } + } + + /* we didn't have any comment, which is ok too! */ + return 0; +} + +json_weak int json_skip_all_skippables(struct json_parse_state_s *state); +int json_skip_all_skippables(struct json_parse_state_s *state) { + /* skip all whitespace and other skippables until there are none left. note + * that the previous version suffered from read past errors should. the + * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag. + */ + + int did_consume = 0; + const size_t size = state->size; + + if (json_parse_flags_allow_c_style_comments & state->flags_bitset) { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + + /* This should really be checked on access, not in front of every call. + */ + if (state->offset >= size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume |= json_skip_c_style_comments(state); + } while (0 != did_consume); + } else { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + } while (0 != did_consume); + } + + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); + +json_weak int json_get_string_size(struct json_parse_state_s *state, + size_t is_key); +int json_get_string_size(struct json_parse_state_s *state, size_t is_key) { + size_t offset = state->offset; + const size_t size = state->size; + size_t data_size = 0; + const char *const src = state->src; + const int is_single_quote = '\'' == src[offset]; + const char quote_to_use = is_single_quote ? '\'' : '"'; + const size_t flags_bitset = state->flags_bitset; + unsigned long codepoint; + unsigned long high_surrogate = 0; + + if ((json_parse_flags_allow_location_information & flags_bitset) != 0 && + is_key != 0) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + if ('"' != src[offset]) { + /* if we are allowed single quoted strings check for that too. */ + if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + is_single_quote)) { + state->error = json_parse_error_expected_opening_quote; + state->offset = offset; + return 1; + } + } + + /* skip leading '"' or '\''. */ + offset++; + + while ((offset < size) && (quote_to_use != src[offset])) { + /* add space for the character. */ + data_size++; + + switch (src[offset]) { + default: + break; + case '\0': + case '\t': + state->error = json_parse_error_invalid_string; + state->offset = offset; + return 1; + } + + if ('\\' == src[offset]) { + /* skip reverse solidus character. */ + offset++; + + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset; + return 1; + } + + switch (src[offset]) { + default: + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + /* all valid characters! */ + offset++; + break; + case 'u': + if (!(offset + 5 < size)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + codepoint = 0; + if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) { + /* escaped unicode sequences must contain 4 hexadecimal digits! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + /* Valid sequence! + * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points. + * 1 7 U + 0000 U + 007F 0xxxxxxx. + * 2 11 U + 0080 U + 07FF 110xxxxx + * 10xxxxxx. + * 3 16 U + 0800 U + FFFF 1110xxxx + * 10xxxxxx 10xxxxxx. + * 4 21 U + 10000 U + 10FFFF 11110xxx + * 10xxxxxx 10xxxxxx 10xxxxxx. + * Note: the high and low surrogate halves used by UTF-16 (U+D800 + * through U+DFFF) and code points not encodable by UTF-16 (those after + * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must + * be treated as an invalid byte sequence. */ + + if (high_surrogate != 0) { + /* we previously read the high half of the \uxxxx\uxxxx pair, so now + * we expect the low half. */ + if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate range. */ + data_size += 3; + high_surrogate = 0; + } else { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + } else if (codepoint <= 0x7f) { + data_size += 0; + } else if (codepoint <= 0x7ff) { + data_size += 1; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate range. */ + /* The codepoint is the first half of a "utf-16 surrogate pair". so we + * need the other half for it to be valid: \uHHHH\uLLLL. */ + if (offset + 11 > size || '\\' != src[offset + 5] || + 'u' != src[offset + 6]) { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + high_surrogate = codepoint; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdfff) { /* low surrogate range. */ + /* we did not read the other half before. */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } else { + data_size += 2; + } + /* escaped codepoints after 0xffff are supported in json through utf-16 + * surrogate pairs: \uD83D\uDD25 for U+1F525. */ + + offset += 5; + break; + } + } else if (('\r' == src[offset]) || ('\n' == src[offset])) { + if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + offset++; + } else { + /* skip character (valid part of sequence). */ + offset++; + } + } + + /* If the offset is equal to the size, we had a non-terminated string! */ + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset - 1; + return 1; + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* add enough space to store the string. */ + state->data_size += data_size; + + /* one more byte for null terminator ending the string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int is_valid_unquoted_key_char(const char c); +int is_valid_unquoted_key_char(const char c) { + return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || ('_' == c) || ('$' == c)); +} + +json_weak int json_get_key_size(struct json_parse_state_s *state); +int json_get_key_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + + if (json_parse_flags_allow_unquoted_keys & flags_bitset) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + size_t data_size = state->data_size; + + /* if we are allowing unquoted keys, first grok for a quote... */ + if ('"' == src[offset]) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + ('\'' == src[offset])) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else { + while ((offset < size) && is_valid_unquoted_key_char(src[offset])) { + offset++; + data_size++; + } + + /* one more byte for null terminator ending the string! */ + data_size++; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + /* update offset. */ + state->offset = offset; + + /* update data_size. */ + state->data_size = data_size; + + return 0; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + return json_get_string_size(state, 1); + } +} + +json_weak int json_get_object_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_object_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + int found_closing_brace = 0; + + if (++state->recursion > JSON_MAX_RECURSION) { + /* recursion error */ + state->error = json_parse_error_recursion; + return 1; + } + + if (is_global_object) { + /* if we found an opening '{' of an object, we actually have a normal JSON + * object at the root of the DOM... */ + if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + if ('{' != src[state->offset]) { + state->error = json_parse_error_unknown; + --state->recursion; + return 1; + } + + /* skip leading '{'. */ + state->offset++; + } + + state->dom_size += sizeof(struct json_object_s); + + if ((state->offset == size) && !is_global_object) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + do { + if (!is_global_object) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + found_closing_brace = 1; + + /* finished the object! */ + break; + } + } else { + /* we don't require brackets, so that means the object ends when the input + * stream ends! */ + if (json_skip_all_skippables(state)) { + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (json_parse_flags_allow_no_commas & flags_bitset) { + /* we don't require a comma, and we didn't find one, which is ok! */ + allow_comma = 0; + } else { + /* otherwise we are required to have a comma, and we found none. */ + state->error = json_parse_error_expected_comma_or_closing_bracket; + --state->recursion; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + } + } + + if (json_get_key_size(state)) { + /* key parsing failed! */ + state->error = json_parse_error_invalid_string; + --state->recursion; + return 1; + } + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + const char current = src[state->offset]; + if ((':' != current) && ('=' != current)) { + state->error = json_parse_error_expected_colon; + --state->recursion; + return 1; + } + } else { + if (':' != src[state->offset]) { + state->error = json_parse_error_expected_colon; + --state->recursion; + return 1; + } + } + + /* skip colon. */ + state->offset++; + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + --state->recursion; + return 1; + } + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + if ((state->offset == size) && !is_global_object && !found_closing_brace) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + state->dom_size += sizeof(struct json_object_element_s) * elements; + --state->recursion; + + return 0; +} + +json_weak int json_get_array_size(struct json_parse_state_s *state); +int json_get_array_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t elements = 0; + int allow_comma = 0; + const char *const src = state->src; + const size_t size = state->size; + + if (++state->recursion > JSON_MAX_RECURSION) { + /* recursion error */ + state->error = json_parse_error_recursion; + return 1; + } + + if ('[' != src[state->offset]) { + /* expected array to begin with leading '['. */ + state->error = json_parse_error_unknown; + --state->recursion; + return 1; + } + + /* skip leading '['. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_s); + + while (state->offset < size) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_element_s) * elements; + + /* finished the object! */ + --state->recursion; + return 0; + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) { + state->error = json_parse_error_expected_comma_or_closing_bracket; + --state->recursion; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + allow_comma = 0; + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; + } + } + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + --state->recursion; + return 1; + } + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } + + /* we consumed the entire input before finding the closing ']' of the array! + */ + state->error = json_parse_error_premature_end_of_buffer; + --state->recursion; + return 1; +} + +json_weak int json_get_number_size(struct json_parse_state_s *state); +int json_get_number_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + int had_leading_digits = 0; + const char *const src = state->src; + + state->dom_size += sizeof(struct json_number_s); + + if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) && + (offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* skip the leading 0x that identifies a hexadecimal number. */ + offset += 2; + + /* consume hexadecimal digits. */ + while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F'))) { + offset++; + } + } else { + int found_sign = 0; + int inf_or_nan = 0; + + if ((offset < size) && + (('-' == src[offset]) || + ((json_parse_flags_allow_leading_plus_sign & flags_bitset) && + ('+' == src[offset])))) { + /* skip valid leading '-' or '+'. */ + offset++; + + found_sign = 1; + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const char inf[9] = "Infinity"; + const size_t inf_strlen = sizeof(inf) - 1; + const char nan[4] = "NaN"; + const size_t nan_strlen = sizeof(nan) - 1; + + if (offset + inf_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < inf_strlen; i++) { + if (inf[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'Infinity' keyword! */ + offset += inf_strlen; + + inf_or_nan = 1; + } + } + + if (offset + nan_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < nan_strlen; i++) { + if (nan[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'NaN' keyword! */ + offset += nan_strlen; + + inf_or_nan = 1; + } + } + + if (inf_or_nan) { + if (offset < size) { + switch (src[offset]) { + default: + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'e': + case 'E': + /* cannot follow an inf or nan with digits! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + } + } + + if (found_sign && !inf_or_nan && (offset < size) && + !('0' <= src[offset] && src[offset] <= '9')) { + /* check if we are allowing leading '.'. */ + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + ('.' != src[offset])) { + /* a leading '-' must be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + if ((offset < size) && ('0' == src[offset])) { + /* skip valid '0'. */ + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + + if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + /* a leading '0' must not be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* the main digits of our number next. */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + } + + if ((offset < size) && ('.' == src[offset])) { + offset++; + + if ((offset >= size) || !('0' <= src[offset] && src[offset] <= '9')) { + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + !had_leading_digits) { + /* a decimal point must be followed by at least one digit. */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* a decimal point can be followed by more digits of course! */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + } + } + + if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) { + /* our number has an exponent! Skip 'e' or 'E'. */ + offset++; + + if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) { + /* skip optional '-' or '+'. */ + offset++; + } + + if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) { + /* an exponent must have at least one digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + + /* consume exponent digits. */ + do { + offset++; + } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')); + } + } + + if (offset < size) { + switch (src[offset]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ',': + case ']': + /* all of the above are ok. */ + break; + case '=': + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + break; + } + + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + default: + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + state->data_size += offset - state->offset; + + /* one more byte for null terminator ending the number string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_value_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + size_t offset; + const size_t size = state->size; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_value_ex_s); + } else { + state->dom_size += sizeof(struct json_value_s); + } + + if (is_global_object) { + return json_get_object_size(state, /* is_global_object = */ 1); + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + /* can cache offset now. */ + offset = state->offset; + + switch (src[offset]) { + case '"': + return json_get_string_size(state, 0); + case '\'': + if (json_parse_flags_allow_single_quoted_strings & flags_bitset) { + return json_get_string_size(state, 0); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + case '{': + return json_get_object_size(state, /* is_global_object = */ 0); + case '[': + return json_get_array_size(state); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_get_number_size(state); + case '+': + if (json_parse_flags_allow_leading_plus_sign & flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + case '.': + if (json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + state->offset += 5; + return 0; + } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] && + 'u' == state->src[offset + 1] && + 'l' == state->src[offset + 2] && + 'l' == state->src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + return json_get_number_size(state); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + return json_get_number_size(state); + } + + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + } +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); + +json_weak void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string) { + size_t offset = state->offset; + size_t bytes_written = 0; + const char *const src = state->src; + const char quote_to_use = '\'' == src[offset] ? '\'' : '"'; + char *data = state->data; + unsigned long high_surrogate = 0; + unsigned long codepoint; + + string->string = data; + + /* skip leading '"' or '\''. */ + offset++; + + while (quote_to_use != src[offset]) { + if ('\\' == src[offset]) { + /* skip the reverse solidus. */ + offset++; + + switch (src[offset++]) { + default: + return; /* we cannot ever reach here. */ + case 'u': { + codepoint = 0; + if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) { + return; /* this shouldn't happen as the value was already validated. + */ + } + + offset += 4; + + if (codepoint <= 0x7fu) { + data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */ + } else if (codepoint <= 0x7ffu) { + data[bytes_written++] = + (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate. */ + high_surrogate = codepoint; + continue; /* we need the low half to form a complete codepoint. */ + } else if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate. */ + /* combine with the previously read half to obtain the complete + * codepoint. */ + const unsigned long surrogate_offset = + 0x10000u - (0xD800u << 10) - 0xDC00u; + codepoint = (high_surrogate << 10) + codepoint + surrogate_offset; + high_surrogate = 0; + data[bytes_written++] = + (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else { + /* we assume the value was validated and thus is within the valid + * range. */ + data[bytes_written++] = + (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } + } break; + case '"': + data[bytes_written++] = '"'; + break; + case '\\': + data[bytes_written++] = '\\'; + break; + case '/': + data[bytes_written++] = '/'; + break; + case 'b': + data[bytes_written++] = '\b'; + break; + case 'f': + data[bytes_written++] = '\f'; + break; + case 'n': + data[bytes_written++] = '\n'; + break; + case 'r': + data[bytes_written++] = '\r'; + break; + case 't': + data[bytes_written++] = '\t'; + break; + case '\r': + data[bytes_written++] = '\r'; + + /* check if we have a "\r\n" sequence. */ + if ('\n' == src[offset]) { + data[bytes_written++] = '\n'; + offset++; + } + + break; + case '\n': + data[bytes_written++] = '\n'; + break; + } + } else { + /* copy the character. */ + data[bytes_written++] = src[offset++]; + } + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* record the size of the string. */ + string->string_size = bytes_written; + + /* add null terminator to string. */ + data[bytes_written++] = '\0'; + + /* move data along. */ + state->data += bytes_written; + + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string) { + if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) { + const char *const src = state->src; + char *const data = state->data; + size_t offset = state->offset; + + /* if we are allowing unquoted keys, check for quoted anyway... */ + if (('"' == src[offset]) || ('\'' == src[offset])) { + /* ... if we got a quote, just parse the key as a string as normal. */ + json_parse_string(state, string); + } else { + size_t size = 0; + + string->string = state->data; + + while (is_valid_unquoted_key_char(src[offset])) { + data[size++] = src[offset++]; + } + + /* add null terminator to string. */ + data[size] = '\0'; + + /* record the size of the string. */ + string->string_size = size++; + + /* move data along. */ + state->data += size; + + /* update offset. */ + state->offset = offset; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + json_parse_string(state, string); + } +} + +json_weak void json_parse_object(struct json_parse_state_s *state, + int is_global_object, + struct json_object_s *object); +void json_parse_object(struct json_parse_state_s *state, int is_global_object, + struct json_object_s *object) { + const size_t flags_bitset = state->flags_bitset; + const size_t size = state->size; + const char *const src = state->src; + size_t elements = 0; + int allow_comma = 0; + struct json_object_element_s *previous = json_null; + + if (is_global_object) { + /* if we skipped some whitespace, and then found an opening '{' of an. */ + /* object, we actually have a normal JSON object at the root of the DOM... + */ + if ('{' == src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + /* skip leading '{'. */ + state->offset++; + } + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + while (state->offset < size) { + struct json_object_element_s *element = json_null; + struct json_string_s *string = json_null; + struct json_value_s *value = json_null; + + if (!is_global_object) { + (void)json_skip_all_skippables(state); + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + /* finished the object! */ + break; + } + } else { + if (json_skip_all_skippables(state)) { + /* global object ends when the file ends! */ + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_object_element_s *)state->dom; + + state->dom += sizeof(struct json_object_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our object. */ + object->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_string_ex_s *string_ex = + (struct json_string_ex_s *)state->dom; + state->dom += sizeof(struct json_string_ex_s); + + string_ex->offset = state->offset; + string_ex->line_no = state->line_no; + string_ex->row_no = state->offset - state->line_offset; + + string = &(string_ex->string); + } else { + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + } + + element->name = string; + + (void)json_parse_key(state, string); + + (void)json_skip_all_skippables(state); + + /* skip colon or equals. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } + + /* if we had at least one element, end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + object->start = json_null; + } + + object->length = elements; +} + +json_weak void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array); +void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array) { + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + struct json_array_element_s *previous = json_null; + + /* skip leading '['. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + do { + struct json_array_element_s *element = json_null; + struct json_value_s *value = json_null; + + (void)json_skip_all_skippables(state); + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + /* finished the array! */ + break; + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_array_element_s *)state->dom; + + state->dom += sizeof(struct json_array_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our array. */ + array->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & state->flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + /* end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + array->start = json_null; + } + + array->length = elements; +} + +json_weak void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number); +void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + size_t bytes_written = 0; + const char *const src = state->src; + char *data = state->data; + + number->number = data; + + if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) { + if ((offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* consume hexadecimal digits. */ + while ((offset < size) && + (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F') || + ('x' == src[offset]) || ('X' == src[offset]))) { + data[bytes_written++] = src[offset++]; + } + } + } + + while (offset < size) { + int end = 0; + + switch (src[offset]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'e': + case 'E': + case '+': + case '-': + data[bytes_written++] = src[offset++]; + break; + default: + end = 1; + break; + } + + if (0 != end) { + break; + } + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const size_t inf_strlen = 8; /* = strlen("Infinity");. */ + const size_t nan_strlen = 3; /* = strlen("NaN");. */ + + if (offset + inf_strlen < size) { + if ('I' == src[offset]) { + size_t i; + /* We found our special 'Infinity' keyword! */ + for (i = 0; i < inf_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + + if (offset + nan_strlen < size) { + if ('N' == src[offset]) { + size_t i; + /* We found our special 'NaN' keyword! */ + for (i = 0; i < nan_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + } + + /* record the size of the number. */ + number->number_size = bytes_written; + /* add null terminator to number string. */ + data[bytes_written++] = '\0'; + /* move data along. */ + state->data += bytes_written; + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); +void json_parse_value(struct json_parse_state_s *state, int is_global_object, + struct json_value_s *value) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t offset; + + (void)json_skip_all_skippables(state); + + /* cache offset now. */ + offset = state->offset; + + if (is_global_object) { + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 1, + (struct json_object_s *)value->payload); + } else { + switch (src[offset]) { + case '"': + case '\'': + value->type = json_type_string; + value->payload = state->dom; + state->dom += sizeof(struct json_string_s); + json_parse_string(state, (struct json_string_s *)value->payload); + break; + case '{': + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 0, + (struct json_object_s *)value->payload); + break; + case '[': + value->type = json_type_array; + value->payload = state->dom; + state->dom += sizeof(struct json_array_s); + json_parse_array(state, (struct json_array_s *)value->payload); + break; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + break; + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + value->type = json_type_true; + value->payload = json_null; + state->offset += 4; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + value->type = json_type_false; + value->payload = json_null; + state->offset += 5; + } else if ((offset + 4) <= size && 'n' == src[offset + 0] && + 'u' == src[offset + 1] && 'l' == src[offset + 2] && + 'l' == src[offset + 3]) { + value->type = json_type_null; + value->payload = json_null; + state->offset += 4; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } + break; + } + } +} + +struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *user_data, size_t size), + void *user_data, struct json_parse_result_s *result) { + struct json_parse_state_s state; + void *allocation; + struct json_value_s *value; + size_t total_size; + int input_error; + + if (result) { + result->error = json_parse_error_none; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + if (json_null == src) { + /* invalid src pointer was null! */ + return json_null; + } + + state.src = (const char *)src; + state.size = src_size; + state.offset = 0; + state.line_no = 1; + state.line_offset = 0; + state.error = json_parse_error_none; + state.dom_size = 0; + state.data_size = 0; + state.flags_bitset = flags_bitset; + state.recursion = 0; + + input_error = json_get_value_size( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset)); + + if (0 == input_error) { + json_skip_all_skippables(&state); + + if (state.offset != state.size) { + /* our parsing didn't have an error, but there are characters remaining in + * the input that weren't part of the JSON! */ + + state.error = json_parse_error_unexpected_trailing_characters; + input_error = 1; + } + } + + if (input_error) { + /* parsing value's size failed (most likely an invalid JSON DOM!). */ + if (result) { + result->error = state.error; + result->error_offset = state.offset; + result->error_line_no = state.line_no; + result->error_row_no = state.offset - state.line_offset; + } + return json_null; + } + + /* our total allocation is the combination of the dom and data sizes (we. */ + /* first encode the structure of the JSON, and then the data referenced by. */ + /* the JSON values). */ + total_size = state.dom_size + state.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + if (json_null == allocation) { + /* malloc failed! */ + if (result) { + result->error = json_parse_error_allocator_failed; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + return json_null; + } + + /* reset offset so we can reuse it. */ + state.offset = 0; + + /* reset the line information so we can reuse it. */ + state.line_no = 1; + state.line_offset = 0; + + state.dom = (char *)allocation; + state.data = state.dom + state.dom_size; + + if (json_parse_flags_allow_location_information & state.flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom; + state.dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state.offset; + value_ex->line_no = state.line_no; + value_ex->row_no = state.offset - state.line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state.dom; + state.dom += sizeof(struct json_value_s); + } + + json_parse_value( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset), + value); + + return (struct json_value_s *)allocation; +} + +struct json_value_s *json_parse(const void *src, size_t src_size) { + return json_parse_ex(src, src_size, json_parse_flags_default, json_null, + json_null, json_null); +} + +struct json_extract_result_s { + size_t dom_size; + size_t data_size; +}; + +struct json_value_s *json_extract_value(const struct json_value_s *value) { + return json_extract_value_ex(value, json_null, json_null); +} + +json_weak struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number); +json_weak struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string); +json_weak struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object); +json_weak struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array); +json_weak struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value); + +struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_number_s); + result.data_size = number->number_size; + return result; +} + +struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_string_s); + result.data_size = string->string_size + 1; + return result; +} + +struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object) { + struct json_extract_result_s result; + size_t i; + const struct json_object_element_s *element = object->start; + + result.dom_size = sizeof(struct json_object_s) + + (sizeof(struct json_object_element_s) * object->length); + result.data_size = 0; + + for (i = 0; i < object->length; i++) { + const struct json_extract_result_s string_result = + json_extract_get_string_size(element->name); + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += string_result.dom_size; + result.data_size += string_result.data_size; + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array) { + struct json_extract_result_s result; + size_t i; + const struct json_array_element_s *element = array->start; + + result.dom_size = sizeof(struct json_array_s) + + (sizeof(struct json_array_element_s) * array->length); + result.data_size = 0; + + for (i = 0; i < array->length; i++) { + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value) { + struct json_extract_result_s result = {0, 0}; + + switch (value->type) { + default: + break; + case json_type_object: + result = json_extract_get_object_size( + (const struct json_object_s *)value->payload); + break; + case json_type_array: + result = json_extract_get_array_size( + (const struct json_array_s *)value->payload); + break; + case json_type_number: + result = json_extract_get_number_size( + (const struct json_number_s *)value->payload); + break; + case json_type_string: + result = json_extract_get_string_size( + (const struct json_string_s *)value->payload); + break; + } + + result.dom_size += sizeof(struct json_value_s); + + return result; +} + +struct json_extract_state_s { + char *dom; + char *data; +}; + +json_weak void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value); +void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value) { + struct json_string_s *string; + struct json_number_s *number; + struct json_object_s *object; + struct json_array_s *array; + struct json_value_s *new_value; + + memcpy(state->dom, value, sizeof(struct json_value_s)); + new_value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + new_value->payload = state->dom; + + if (json_type_string == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + } else if (json_type_number == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_number_s)); + number = (struct json_number_s *)state->dom; + state->dom += sizeof(struct json_number_s); + + memcpy(state->data, number->number, number->number_size); + number->number = state->data; + state->data += number->number_size; + } else if (json_type_object == value->type) { + struct json_object_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_object_s)); + object = (struct json_object_s *)state->dom; + state->dom += sizeof(struct json_object_s); + + element = object->start; + object->start = json_null; + + if (0 < object->length) { + object->start = (struct json_object_element_s *)state->dom; + } + + for (i = 0; i < object->length; i++) { + struct json_value_s *previous_value; + struct json_object_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_object_element_s)); + element = (struct json_object_element_s *)state->dom; + state->dom += sizeof(struct json_object_element_s); + + string = element->name; + memcpy(state->dom, string, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + element->name = string; + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_object_element_s *)state->dom; + } + } + } else if (json_type_array == value->type) { + struct json_array_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_array_s)); + array = (struct json_array_s *)state->dom; + state->dom += sizeof(struct json_array_s); + + element = array->start; + array->start = json_null; + + if (0 < array->length) { + array->start = (struct json_array_element_s *)state->dom; + } + + for (i = 0; i < array->length; i++) { + struct json_value_s *previous_value; + struct json_array_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_array_element_s)); + element = (struct json_array_element_s *)state->dom; + state->dom += sizeof(struct json_array_element_s); + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_array_element_s *)state->dom; + } + } + } +} + +struct json_value_s *json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, + size_t), + void *user_data) { + void *allocation; + struct json_extract_result_s result; + struct json_extract_state_s state; + size_t total_size; + + if (json_null == value) { + /* invalid value was null! */ + return json_null; + } + + result = json_extract_get_value_size(value); + total_size = result.dom_size + result.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + state.dom = (char *)allocation; + state.data = state.dom + result.dom_size; + + json_extract_copy_value(&state, value); + + return (struct json_value_s *)allocation; +} + +struct json_string_s *json_value_as_string(struct json_value_s *const value) { + if (value->type != json_type_string) { + return json_null; + } + + return (struct json_string_s *)value->payload; +} + +struct json_number_s *json_value_as_number(struct json_value_s *const value) { + if (value->type != json_type_number) { + return json_null; + } + + return (struct json_number_s *)value->payload; +} + +struct json_object_s *json_value_as_object(struct json_value_s *const value) { + if (value->type != json_type_object) { + return json_null; + } + + return (struct json_object_s *)value->payload; +} + +struct json_array_s *json_value_as_array(struct json_value_s *const value) { + if (value->type != json_type_array) { + return json_null; + } + + return (struct json_array_s *)value->payload; +} + +int json_value_is_true(const struct json_value_s *const value) { + return value->type == json_type_true; +} + +int json_value_is_false(const struct json_value_s *const value) { + return value->type == json_type_false; +} + +int json_value_is_null(const struct json_value_s *const value) { + return value->type == json_type_null; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); + +json_weak int json_write_get_number_size(const struct json_number_s *number, + size_t *size); +int json_write_get_number_size(const struct json_number_s *number, + size_t *size) { + json_uintmax_t parsed_number; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + i = 0; + + do { + parsed_number /= 10; + i++; + } while (0 != parsed_number); + + *size += i; + return 0; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '+' or '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf) { + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + *size += 22; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *size += 1; + } + } + + return 0; + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan) { + /* NaN becomes 1 because JSON can't support it. */ + *size += 1; + + return 0; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a leading decimal point. */ + *size += 1; + goto cleanup; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a trailing decimal point. */ + *size += 1; + goto cleanup; + } + +cleanup: + *size += number->number_size; /* the actual string of the number. */ + + /* if we had a leading '+' we don't record it in the JSON output. */ + if ('+' == number->number[0]) { + *size -= 1; + } + + return 0; +} + +json_weak int json_write_get_string_size(const struct json_string_s *string, + size_t *size); +int json_write_get_string_size(const struct json_string_s *string, + size_t *size) { + size_t i; + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + *size += 2; + break; + default: + *size += 1; + break; + } + } + + *size += 2; /* need to encode the surrounding '"' characters. */ + + return 0; +} + +json_weak int +json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size); +int json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size) { + struct json_array_element_s *element; + + *size += 2; /* '[' and ']'. */ + + if (1 < array->length) { + *size += array->length - 1; /* ','s seperate each element. */ + } + + for (element = array->start; json_null != element; element = element->next) { + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size); +int json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size) { + struct json_object_element_s *element; + + *size += 2; /* '{' and '}'. */ + + *size += object->length; /* ':'s seperate each name/value pair. */ + + if (1 < object->length) { + *size += object->length - 1; /* ','s seperate each element. */ + } + + for (element = object->start; json_null != element; element = element->next) { + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); +int json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_minified_get_array_size( + (struct json_array_s *)value->payload, size); + case json_type_object: + return json_write_minified_get_object_size( + (struct json_object_s *)value->payload, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); + +json_weak char *json_write_number(const struct json_number_s *number, + char *data); +char *json_write_number(const struct json_number_s *number, char *data) { + json_uintmax_t parsed_number, backup; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + /* We need a copy of parsed number twice, so take a backup of it. */ + backup = parsed_number; + + i = 0; + + do { + parsed_number /= 10; + i++; + } while (0 != parsed_number); + + /* Restore parsed_number to its original value stored in the backup. */ + parsed_number = backup; + + /* Now use backup to take a copy of i, or the length of the string. */ + backup = i; + + do { + *(data + i - 1) = '0' + (char)(parsed_number % 10); + parsed_number /= 10; + i--; + } while (0 != parsed_number); + + data += backup; + + return data; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf++) { + const char *dbl_max; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *data++ = '-'; + } + + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) { + *data++ = *dbl_max; + } + + return data; + } + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan++) { + /* NaN becomes 0 because JSON can't support it. */ + *data++ = '0'; + return data; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* insert a '0' to fix the leading decimal point for JSON output. */ + *data++ = '0'; + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + /* insert a '0' to fix the trailing decimal point for JSON output. */ + *data++ = '0'; + + return data; + } + + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; +} + +json_weak char *json_write_string(const struct json_string_s *string, + char *data); +char *json_write_string(const struct json_string_s *string, char *data) { + size_t i; + + *data++ = '"'; /* open the string. */ + + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + *data++ = '\\'; /* escape the control character. */ + *data++ = '"'; + break; + case '\\': + *data++ = '\\'; /* escape the control character. */ + *data++ = '\\'; + break; + case '\b': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'b'; + break; + case '\f': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'f'; + break; + case '\n': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'n'; + break; + case '\r': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'r'; + break; + case '\t': + *data++ = '\\'; /* escape the control character. */ + *data++ = 't'; + break; + default: + *data++ = string->string[i]; + break; + } + } + + *data++ = '"'; /* close the string. */ + + return data; +} + +json_weak char *json_write_minified_array(const struct json_array_s *array, + char *data); +char *json_write_minified_array(const struct json_array_s *array, char *data) { + struct json_array_element_s *element = json_null; + + *data++ = '['; /* open the array. */ + + for (element = array->start; json_null != element; element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_minified_object(const struct json_object_s *object, + char *data); +char *json_write_minified_object(const struct json_object_s *object, + char *data) { + struct json_object_element_s *element = json_null; + + *data++ = '{'; /* open the object. */ + + for (element = object->start; json_null != element; element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + *data++ = ':'; /* ':'s seperate each name/value pair. */ + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); +char *json_write_minified_value(const struct json_value_s *value, char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_minified_array((struct json_array_s *)value->payload, + data); + case json_type_object: + return json_write_minified_object((struct json_object_s *)value->payload, + data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_minified(const struct json_value_s *value, size_t *out_size) { + size_t size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_write_minified_get_value_size(value, &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_minified_value(value, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); + +json_weak int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_array_element_s *element; + + *size += 1; /* '['. */ + + if (0 < array->length) { + /* if we have any elements we need to add a newline after our '['. */ + *size += newline_size; + + *size += array->length - 1; /* ','s seperate each element. */ + + for (element = array->start; json_null != element; + element = element->next) { + /* each element gets an indent. */ + *size += (depth + 1) * indent_size; + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + + /* each element gets a newline too. */ + *size += newline_size; + } + + /* since we wrote out some elements, need to add a newline and indentation. + */ + /* to the trailing ']'. */ + *size += depth * indent_size; + } + + *size += 1; /* ']'. */ + + return 0; +} + +json_weak int +json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size); +int json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_object_element_s *element; + + *size += 1; /* '{'. */ + + if (0 < object->length) { + *size += newline_size; /* need a newline next. */ + + *size += object->length - 1; /* ','s seperate each element. */ + + for (element = object->start; json_null != element; + element = element->next) { + /* each element gets an indent and newline. */ + *size += (depth + 1) * indent_size; + *size += newline_size; + + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + *size += 3; /* seperate each name/value pair with " : ". */ + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + } + + *size += depth * indent_size; + } + + *size += 1; /* '}'. */ + + return 0; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_pretty_get_array_size( + (struct json_array_s *)value->payload, depth, indent_size, newline_size, + size); + case json_type_object: + return json_write_pretty_get_object_size( + (struct json_object_s *)value->payload, depth, indent_size, + newline_size, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); + +json_weak char *json_write_pretty_array(const struct json_array_s *array, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_array(const struct json_array_s *array, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_array_element_s *element; + + *data++ = '['; /* open the array. */ + + if (0 < array->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = array->start; json_null != element; + element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_pretty_object(const struct json_object_s *object, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_object(const struct json_object_s *object, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_object_element_s *element; + + *data++ = '{'; /* open the object. */ + + if (0 < object->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = object->start; json_null != element; + element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + /* " : "s seperate each name/value pair. */ + *data++ = ' '; + *data++ = ':'; + *data++ = ' '; + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_value(const struct json_value_s *value, size_t depth, + const char *indent, const char *newline, + char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_pretty_array((struct json_array_s *)value->payload, depth, + indent, newline, data); + case json_type_object: + return json_write_pretty_object((struct json_object_s *)value->payload, + depth, indent, newline, data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_pretty(const struct json_value_s *value, const char *indent, + const char *newline, size_t *out_size) { + size_t size = 0; + size_t indent_size = 0; + size_t newline_size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_null == indent) { + indent = " "; /* default to two spaces. */ + } + + if (json_null == newline) { + newline = "\n"; /* default to linux newlines. */ + } + + while ('\0' != indent[indent_size]) { + ++indent_size; /* skip non-null terminating characters. */ + } + + while ('\0' != newline[newline_size]) { + ++newline_size; /* skip non-null terminating characters. */ + } + + if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size, + &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_pretty_value(value, 0, indent, newline, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* SHEREDOM_JSON_H_INCLUDED. */ diff --git a/Website/index.html b/Website/index.html index 6e4c2943..4f62bdba 100644 --- a/Website/index.html +++ b/Website/index.html @@ -1,96 +1,91 @@ - - - - - BWF MetaEdit - - - - -

BWF MetaEdit

- -

About this Software

- -BWF MetaEdit was developed by the Federal Agencies Digital -Guidelines Initiative (FADGI) supported by AudioVisual Preservation -Solutions.

- -This tool permits embedding, editing, and exporting of metadata in -Broadcast WAVE Format (BWF) files. This tool can also enforce metadata -guidelines developed by the Federal -Agencies Audio-Visual Working Group, as well as recommendations and -specifications from the European Broadcasting Union (EBU), Microsoft, and IBM. - -

Explanation of BWF MetaEdit Views and Documents

- - -

Metadata Standards

- - -

Use

- - -

Download

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Operating systemLink
Microsoft WindowsOfficial releases
Mac OSOfficial releases
DebianOfficial releases
UbuntuOfficial releases
RedHat Entreprise LinuxOfficial releases
CentOSOfficial releases
FedoraOfficial releases
openSUSEOfficial releases
MandrivaOfficial releases
From source
(for other distributions or/and CPU)
Official releases
- - + + + + + BWF MetaEdit + + + + +

BWF MetaEdit

+ +

About this Software

+ +BWF MetaEdit is a tool that supports embedding, validating, and exporting of metadata in Broadcast WAVE Format (BWF) files. It supports the FADGI Broadcast WAVE Metadata Embedding Guidelines.
+
+Initially funded by the Library of Congress and FADGI (Federal Agencies Digital Guidelines Initiative); previously developed by MediaArea and led by AVPreserve; currently developed and led by MediaArea.
+MediaArea provides support and binaries for all platforms. + +

Explanation of BWF MetaEdit Views and Documents

+ + +

Metadata Standards

+ + +

Use

+ + +

Download

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Operating systemLink
Microsoft WindowsOfficial releases
Mac OSOfficial releases
DebianOfficial releases
UbuntuOfficial releases
RedHat Entreprise LinuxOfficial releases
CentOSOfficial releases
FedoraOfficial releases
openSUSEOfficial releases
MandrivaOfficial releases
From source
(for other distributions or/and CPU)
Official releases
+ + \ No newline at end of file