diff --git a/attic/webview/.clang-format b/attic/webview/.clang-format new file mode 100644 index 000000000..5dad5a60a --- /dev/null +++ b/attic/webview/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/attic/webview/.clang-tidy b/attic/webview/.clang-tidy new file mode 100644 index 000000000..cc8f6eb3d --- /dev/null +++ b/attic/webview/.clang-tidy @@ -0,0 +1,256 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,*' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +User: serge +CheckOptions: + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: '0' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: bugprone-string-constructor.WarnOnLargeLength + value: '1' + - key: cert-dcl59-cpp.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: cert-err09-cpp.CheckThrowTemporaries + value: '1' + - key: cert-err61-cpp.CheckThrowTemporaries + value: '1' + - key: cert-oop11-cpp.IncludeStyle + value: llvm + - key: cppcoreguidelines-no-malloc.Allocations + value: '::malloc;::calloc' + - key: cppcoreguidelines-no-malloc.Deallocations + value: '::free' + - key: cppcoreguidelines-no-malloc.Reallocations + value: '::realloc' + - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers + value: '::free;::realloc;::freopen;::fclose' + - key: cppcoreguidelines-owning-memory.LegacyResourceProducers + value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader + value: '' + - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle + value: '0' + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: '0' + - key: google-build-namespaces.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-global-names-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.BranchThreshold + value: '4294967295' + - key: google-readability-function-size.LineThreshold + value: '4294967295' + - key: google-readability-function-size.NestingThreshold + value: '4294967295' + - key: google-readability-function-size.ParameterThreshold + value: '4294967295' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: google-runtime-int.SignedTypePrefix + value: int + - key: google-runtime-int.TypeSuffix + value: '' + - key: google-runtime-int.UnsignedTypePrefix + value: uint + - key: google-runtime-references.WhiteListTypes + value: '' + - key: hicpp-braces-around-statements.ShortStatementLines + value: '0' + - key: hicpp-function-size.BranchThreshold + value: '4294967295' + - key: hicpp-function-size.LineThreshold + value: '4294967295' + - key: hicpp-function-size.NestingThreshold + value: '4294967295' + - key: hicpp-function-size.ParameterThreshold + value: '4294967295' + - key: hicpp-function-size.StatementThreshold + value: '800' + - key: hicpp-member-init.IgnoreArrays + value: '0' + - key: hicpp-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: hicpp-named-parameter.IgnoreFailedSplit + value: '0' + - key: hicpp-no-malloc.Allocations + value: '::malloc;::calloc' + - key: hicpp-no-malloc.Deallocations + value: '::free' + - key: hicpp-no-malloc.Reallocations + value: '::realloc' + - key: hicpp-special-member-functions.AllowMissingMoveFunctions + value: '0' + - key: hicpp-special-member-functions.AllowSoleDefaultDtor + value: '0' + - key: hicpp-use-auto.RemoveStars + value: '0' + - key: hicpp-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: hicpp-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: hicpp-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: hicpp-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: hicpp-use-equals-default.IgnoreMacros + value: '1' + - key: hicpp-use-noexcept.ReplacementString + value: '' + - key: hicpp-use-noexcept.UseNoexceptFalse + value: '1' + - key: hicpp-use-nullptr.NullMacros + value: '' + - key: llvm-namespace-comment.ShortNamespaceLines + value: '1' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '1' + - key: misc-definitions-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: misc-definitions-in-headers.UseHeaderFileExtension + value: '1' + - key: misc-misplaced-widening-cast.CheckImplicitCasts + value: '0' + - key: misc-sizeof-expression.WarnOnSizeOfCompareToConstant + value: '1' + - key: misc-sizeof-expression.WarnOnSizeOfConstant + value: '1' + - key: misc-sizeof-expression.WarnOnSizeOfThis + value: '1' + - key: misc-suspicious-enum-usage.StrictMode + value: '0' + - key: misc-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: misc-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: misc-suspicious-missing-comma.SizeThreshold + value: '5' + - key: misc-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: misc-suspicious-string-compare.WarnOnImplicitComparison + value: '1' + - key: misc-suspicious-string-compare.WarnOnLogicalNotComparison + value: '0' + - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries + value: '1' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-make-shared.IgnoreMacros + value: '1' + - key: modernize-make-shared.IncludeStyle + value: '0' + - key: modernize-make-shared.MakeSmartPtrFunction + value: 'std::make_shared' + - key: modernize-make-shared.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-make-unique.IgnoreMacros + value: '1' + - key: modernize-make-unique.IncludeStyle + value: '0' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'std::make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-pass-by-value.ValuesOnly + value: '0' + - key: modernize-raw-string-literal.ReplaceShorterLiterals + value: '0' + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-replace-random-shuffle.IncludeStyle + value: llvm + - key: modernize-use-auto.RemoveStars + value: '0' + - key: modernize-use-default-member-init.IgnoreMacros + value: '1' + - key: modernize-use-default-member-init.UseAssignment + value: '0' + - key: modernize-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: modernize-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: modernize-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: modernize-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: modernize-use-equals-default.IgnoreMacros + value: '1' + - key: modernize-use-noexcept.ReplacementString + value: '' + - key: modernize-use-noexcept.UseNoexceptFalse + value: '1' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-use-transparent-functors.SafeMode + value: '0' + - key: modernize-use-using.IgnoreMacros + value: '1' + - key: objc-forbidden-subclassing.ForbiddenSuperClassNames + value: 'ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView' + - key: objc-property-declaration.Acronyms + value: 'ASCII;PDF;XML;HTML;URL;RTF;HTTP;TIFF;JPG;PNG;GIF;LZW;ROM;RGB;CMYK;MIDI;FTP' + - key: performance-faster-string-find.StringLikeClasses + value: 'std::basic_string' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: '0' + - key: performance-inefficient-string-concatenation.StrictMode + value: '0' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: readability-braces-around-statements.ShortStatementLines + value: '0' + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: readability-function-size.StatementThreshold + value: '800' + - key: readability-identifier-naming.IgnoreFailedSplit + value: '0' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: '0' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: '0' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: '0' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: '0' + - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold + value: '3' +... + diff --git a/attic/webview/.gitattributes b/attic/webview/.gitattributes new file mode 100644 index 000000000..5170675f3 --- /dev/null +++ b/attic/webview/.gitattributes @@ -0,0 +1 @@ +*.h linguist-language=c diff --git a/attic/webview/.gitignore b/attic/webview/.gitignore new file mode 100644 index 000000000..6307e1aab --- /dev/null +++ b/attic/webview/.gitignore @@ -0,0 +1,7 @@ +# Build atrifacts +/build +/examples/minimal-go/minimal-go +/examples/minimal/minimal +/examples/minimal/minimal.exe +/examples/minimal/build +/examples/timer-cxx/build diff --git a/attic/webview/.travis.yml b/attic/webview/.travis.yml new file mode 100644 index 000000000..e94a7765e --- /dev/null +++ b/attic/webview/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: + - 1.x + +matrix: + include: + - os: linux + before_install: + - sudo add-apt-repository ppa:webkit-team/ppa -y + - sudo apt-get update + - sudo apt-get install libwebkit2gtk-4.0-dev -y + env: WEBVIEW=gtk + - os: osx + osx_image: xcode8.3 + env: WEBVIEW=cocoa + +script: + - make example diff --git a/attic/webview/LICENSE b/attic/webview/LICENSE new file mode 100644 index 000000000..b18604bf4 --- /dev/null +++ b/attic/webview/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/attic/webview/Makefile b/attic/webview/Makefile new file mode 100644 index 000000000..71cdf57db --- /dev/null +++ b/attic/webview/Makefile @@ -0,0 +1,28 @@ +WEBVIEW_gtk_FLAGS = -DWEBVIEW_GTK -std=c++14 -Wall -Wextra -pedantic $(shell pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0) +WEBVIEW_cocoa_FLAGS = -DWEBVIEW_COCOA -std=c++14 -Wall -Wextra -pedantic -framework WebKit -mmacosx-version-min=10.11 -DOBJC_OLD_DISPATCH_PROTOTYPES +WEBVIEW_mshtml_FLAGS := -DWEBVIEW_MSHTML -std=c++14 -luser32 -lole32 -loleaut32 -lcomctl32 -luuid -static +WEBVIEW_edge_FLAGS := -DWEBVIEW_EDGE + +all: + @echo "make WEBVIEW=... test - build and run tests" + @echo "make WEBVIEW=... lint - run clang-tidy checkers" + @echo "make WEBVIEW=... fmt - run clang-format for all sources" + +fmt: webview.h + clang-format -i $^ + +check-env: +ifndef WEBVIEW_$(WEBVIEW)_FLAGS + $(error "Unknown WEBVIEW value, use WEBVIEW=gtk|cocoa|mshtml|edge") +endif + +lint: check-env + clang-tidy example.cc -- $(WEBVIEW_$(WEBVIEW)_FLAGS) + +example: check-env example.cc webview.h + $(CXX) example.cc $(WEBVIEW_$(WEBVIEW)_FLAGS) -o example + +test: check-env + $(CXX) webview_test.cc $(WEBVIEW_$(WEBVIEW)_FLAGS) -o webview_test + ./webview_test + rm webview_test diff --git a/attic/webview/example.cc b/attic/webview/example.cc new file mode 100644 index 000000000..7afcc5794 --- /dev/null +++ b/attic/webview/example.cc @@ -0,0 +1,39 @@ +// +build ignore + +#include "webview.h" + +#ifdef _WIN32 +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +#else +int main() +#endif +{ + webview::webview w(true, nullptr); + w.set_title("Example"); + w.set_size(480, 320, true); + w.bind("noop", [](std::string s) -> std::string { printf("%s\n", s.c_str());return s; }); + w.bind("add", [](std::string s) -> std::string { + auto a = std::stoi(webview::json_parse(s, "", 0)); + auto b = std::stoi(webview::json_parse(s, "", 1)); + return std::to_string(a + b); + }); + w.navigate(R"(data:text/html, + + + hello + + + )"); + w.run(); + return 0; +} diff --git a/attic/webview/example/example.go b/attic/webview/example/example.go new file mode 100644 index 000000000..d536a07cb --- /dev/null +++ b/attic/webview/example/example.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/zserge/webview" +) + +func main() { + w := webview.New(true) + w.Navigate("https://github.com") + w.SetTitle("Hello") + w.Dispatch(func() { + println("Hello dispatch") + }) + w.Run() +} diff --git a/attic/webview/go.mod b/attic/webview/go.mod new file mode 100644 index 000000000..487f240b9 --- /dev/null +++ b/attic/webview/go.mod @@ -0,0 +1,3 @@ +module github.com/zserge/webview + +go 1.13 diff --git a/attic/webview/webview.cc b/attic/webview/webview.cc new file mode 100644 index 000000000..0d861df50 --- /dev/null +++ b/attic/webview/webview.cc @@ -0,0 +1 @@ +#include "webview.h" diff --git a/attic/webview/webview.go b/attic/webview/webview.go new file mode 100644 index 000000000..32595f26a --- /dev/null +++ b/attic/webview/webview.go @@ -0,0 +1,138 @@ +package webview + +/* +#cgo linux openbsd freebsd CXXFLAGS: -DWEBVIEW_GTK -std=c++14 +#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0 + +#cgo darwin CXXFLAGS: -DWEBVIEW_COCOA -std=c++14 -DOBJC_OLD_DISPATCH_PROTOTYPES +#cgo darwin LDFLAGS: -framework WebKit + +#cgo windows CXXFLAGS: -DWEBVIEW_MSHTML +#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32 + +#define WEBVIEW_HEADER +#include "webview.h" + +#include +#include + +extern void _webviewDispatchGoCallback(void *); +static inline void _webview_dispatch_cb(webview_t w, void *arg) { + _webviewDispatchGoCallback(arg); +} +static inline void CgoWebViewDispatch(webview_t w, uintptr_t arg) { + webview_dispatch(w, _webview_dispatch_cb, (void *)arg); +} +*/ +import "C" +import ( + "runtime" + "sync" + "unsafe" +) + +func init() { + // Ensure that main.main is called from the main thread + runtime.LockOSThread() +} + +type WebView interface { + Run() + Terminate() + Dispatch(f func()) + Navigate(url string) + SetTitle(title string) + Window() unsafe.Pointer + Init(js string) + Eval(js string) + Destroy() + /* + SetBounds(x, y, width, height int) + Bounds() (x, y, width, height int) + Bind(name string, v interface{}) + */ +} + +type webview struct { + w C.webview_t +} + +var ( + m sync.Mutex + index uintptr + dispatch = map[uintptr]func(){} +) + +func boolToInt(b bool) C.int { + if b { + return 1 + } + return 0 +} + +func New(debug bool) WebView { return NewWindow(debug, nil) } + +func NewWindow(debug bool, window unsafe.Pointer) WebView { + w := &webview{} +q + return w +} + +func (w *webview) Destroy() { + C.webview_destroy(w.w) +} + +func (w *webview) Run() { + C.webview_run(w.w) +} + +func (w *webview) Terminate() { + C.webview_terminate(w.w) +} + +func (w *webview) Window() unsafe.Pointer { + return C.webview_get_window(w.w) +} + +func (w *webview) Navigate(url string) { + s := C.CString(url) + defer C.free(unsafe.Pointer(s)) + C.webview_navigate(w.w, s) +} + +func (w *webview) SetTitle(title string) { + s := C.CString(title) + defer C.free(unsafe.Pointer(s)) + C.webview_set_title(w.w, s) +} + +func (w *webview) Init(js string) { + s := C.CString(js) + defer C.free(unsafe.Pointer(s)) + C.webview_init(w.w, s) +} + +func (w *webview) Eval(js string) { + s := C.CString(js) + defer C.free(unsafe.Pointer(s)) + C.webview_eval(w.w, s) +} + +func (w *webview) Dispatch(f func()) { + m.Lock() + for ; dispatch[index] != nil; index++ { + } + dispatch[index] = f + m.Unlock() + C.CgoWebViewDispatch(w.w, C.uintptr_t(index)) +} + +//export _webviewDispatchGoCallback +func _webviewDispatchGoCallback(index unsafe.Pointer) { + var f func() + m.Lock() + f = dispatch[uintptr(index)] + delete(dispatch, uintptr(index)) + m.Unlock() + f() +} diff --git a/attic/webview/webview.h b/attic/webview/webview.h new file mode 100644 index 000000000..7a20b9541 --- /dev/null +++ b/attic/webview/webview.h @@ -0,0 +1,1248 @@ +/* + * MIT License + * + * Copyright (c) 2017 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 OR COPYRIGHT HOLDERS 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. + */ +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#ifndef WEBVIEW_API +#define WEBVIEW_API extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *webview_t; + +// Create a new webview instance +WEBVIEW_API webview_t webview_create(int debug, void *wnd); + +// Destroy a webview +WEBVIEW_API void webview_destroy(webview_t w); + +// Run the main loop +WEBVIEW_API void webview_run(webview_t w); + +// Stop the main loop +WEBVIEW_API void webview_terminate(webview_t w); + +// Post a function to be executed on the main thread +WEBVIEW_API void +webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg); + +WEBVIEW_API void *webview_get_window(webview_t w); + +WEBVIEW_API void webview_set_title(webview_t w, const char *title); + +WEBVIEW_API void webview_set_bounds(webview_t w, int x, int y, int width, + int height, int flags); +WEBVIEW_API void webview_get_bounds(webview_t w, int *x, int *y, int *width, + int *height, int *flags); + +WEBVIEW_API void webview_navigate(webview_t w, const char *url); +WEBVIEW_API void webview_init(webview_t w, const char *js); +WEBVIEW_API void webview_eval(webview_t w, const char *js); + +#ifdef __cplusplus +} +#endif + +#ifndef WEBVIEW_HEADER + +#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && \ + !defined(WEBVIEW_MSHTML) && !defined(WEBVIEW_EDGE) +#error "please, specify webview backend" +#endif + +#include +#include +#include +#include +#include +#include + +#include + +namespace webview { +using dispatch_fn_t = std::function; +using msg_cb_t = std::function; + +inline std::string url_encode(std::string s) { + std::string encoded; + for (unsigned int i = 0; i < s.length(); i++) { + auto c = s[i]; + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + encoded = encoded + c; + } else { + char hex[4]; + snprintf(hex, sizeof(hex), "%%%02x", c); + encoded = encoded + hex; + } + } + return encoded; +} + +inline std::string url_decode(std::string s) { + std::string decoded; + for (unsigned int i = 0; i < s.length(); i++) { + if (s[i] == '%') { + int n; + sscanf(s.substr(i + 1, 2).c_str(), "%x", &n); + decoded = decoded + static_cast(n); + i = i + 2; + } else if (s[i] == '+') { + decoded = decoded + ' '; + } else { + decoded = decoded + s[i]; + } + } + return decoded; +} + +inline std::string html_from_uri(std::string s) { + if (s.substr(0, 15) == "data:text/html,") { + return url_decode(s.substr(15)); + } + return ""; +} + +inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz, + const char **value, size_t *valuesz) { + enum { + JSON_STATE_VALUE, + JSON_STATE_LITERAL, + JSON_STATE_STRING, + JSON_STATE_ESCAPE, + JSON_STATE_UTF8 + } state = JSON_STATE_VALUE; + const char *k = NULL; + int index = 1; + int depth = 0; + int utf8_bytes = 0; + + if (key == NULL) { + index = keysz; + keysz = 0; + } + + *value = NULL; + *valuesz = 0; + + for (; sz > 0; s++, sz--) { + enum { + JSON_ACTION_NONE, + JSON_ACTION_START, + JSON_ACTION_END, + JSON_ACTION_START_STRUCT, + JSON_ACTION_END_STRUCT + } action = JSON_ACTION_NONE; + unsigned char c = *s; + switch (state) { + case JSON_STATE_VALUE: + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || + c == ':') { + continue; + } else if (c == '"') { + action = JSON_ACTION_START; + state = JSON_STATE_STRING; + } else if (c == '{' || c == '[') { + action = JSON_ACTION_START_STRUCT; + } else if (c == '}' || c == ']') { + action = JSON_ACTION_END_STRUCT; + } else if (c == 't' || c == 'f' || c == 'n' || c == '-' || + (c >= '0' && c <= '9')) { + action = JSON_ACTION_START; + state = JSON_STATE_LITERAL; + } else { + return -1; + } + break; + case JSON_STATE_LITERAL: + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || + c == ']' || c == '}' || c == ':') { + state = JSON_STATE_VALUE; + s--; + sz++; + action = JSON_ACTION_END; + } else if (c < 32 || c > 126) { + return -1; + } // fallthrough + case JSON_STATE_STRING: + if (c < 32 || (c > 126 && c < 192)) { + return -1; + } else if (c == '"') { + action = JSON_ACTION_END; + state = JSON_STATE_VALUE; + } else if (c == '\\') { + state = JSON_STATE_ESCAPE; + } else if (c >= 192 && c < 224) { + utf8_bytes = 1; + state = JSON_STATE_UTF8; + } else if (c >= 224 && c < 240) { + utf8_bytes = 2; + state = JSON_STATE_UTF8; + } else if (c >= 240 && c < 247) { + utf8_bytes = 3; + state = JSON_STATE_UTF8; + } else if (c >= 128 && c < 192) { + return -1; + } + break; + case JSON_STATE_ESCAPE: + if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || + c == 'n' || c == 'r' || c == 't' || c == 'u') { + state = JSON_STATE_STRING; + } else { + return -1; + } + break; + case JSON_STATE_UTF8: + if (c < 128 || c > 191) { + return -1; + } + utf8_bytes--; + if (utf8_bytes == 0) { + state = JSON_STATE_STRING; + } + break; + default: + return -1; + } + + if (action == JSON_ACTION_END_STRUCT) { + depth--; + } + + if (depth == 1) { + if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) { + if (index == 0) { + *value = s; + } else if (keysz > 0 && index == 1) { + k = s; + } else { + index--; + } + } else if (action == JSON_ACTION_END || + action == JSON_ACTION_END_STRUCT) { + if (*value != NULL && index == 0) { + *valuesz = (size_t)(s + 1 - *value); + return 0; + } else if (keysz > 0 && k != NULL) { + if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) { + index = 0; + } else { + index = 2; + } + k = NULL; + } + } + } + + if (action == JSON_ACTION_START_STRUCT) { + depth++; + } + } + return -1; +} + +inline std::string json_escape(std::string s) { + // TODO: implement + return '"' + s + '"'; +} + +inline int json_unescape(const char *s, size_t n, char *out) { + int r = 0; + if (*s++ != '"') { + return -1; + } + while (n > 2) { + char c = *s; + if (c == '\\') { + s++; + n--; + switch (*s) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\\': + c = '\\'; + break; + case '/': + c = '/'; + break; + case '\"': + c = '\"'; + break; + default: // TODO: support unicode decoding + return -1; + } + } + if (out != NULL) { + *out++ = c; + } + s++; + n--; + r++; + } + if (*s != '"') { + return -1; + } + if (out != NULL) { + *out = '\0'; + } + return r; +} + +inline std::string json_parse(std::string s, std::string key, int index) { + const char *value; + size_t value_sz; + if (key == "") { + json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz); + } else { + json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, + &value_sz); + } + if (value != nullptr) { + if (value[0] != '"') { + return std::string(value, value_sz); + } + int n = json_unescape(value, value_sz, nullptr); + if (n > 0) { + char *decoded = new char[n]; + json_unescape(value, value_sz, decoded); + auto result = std::string(decoded, n); + delete[] decoded; + return result; + } + } + return ""; +} + +} // namespace webview + +#if defined(WEBVIEW_GTK) +// +// ==================================================================== +// +// This implementation uses webkit2gtk backend. It requires gtk+3.0 and +// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: +// +// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 +// +// ==================================================================== +// +#include +#include +#include + +namespace webview { + +class browser_engine { +public: + browser_engine(msg_cb_t cb, bool debug, void *window) + : m_cb(cb), m_window(static_cast(window)) { + gtk_init_check(0, NULL); + m_window = static_cast(window); + if (m_window == nullptr) { + m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + } + g_signal_connect(G_OBJECT(m_window), "destroy", + G_CALLBACK(+[](GtkWidget *w, gpointer arg) { + static_cast(arg)->terminate(); + }), + this); + // Initialize webview widget + m_webview = webkit_web_view_new(); + WebKitUserContentManager *manager = + webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); + g_signal_connect(manager, "script-message-received::external", + G_CALLBACK(+[](WebKitUserContentManager *m, + WebKitJavascriptResult *r, gpointer arg) { + auto *w = static_cast(arg); +#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 + JSCValue *value = + webkit_javascript_result_get_js_value(r); + char *s = jsc_value_to_string(value); +#else + JSGlobalContextRef ctx = + webkit_javascript_result_get_global_context(r); + JSValueRef value = webkit_javascript_result_get_value(r); + JSStringRef js = JSValueToStringCopy(ctx, value, NULL); + size_t n = JSStringGetMaximumUTF8CStringSize(js); + char *s = g_new(char, n); + JSStringGetUTF8CString(js, s, n); + JSStringRelease(js); +#endif + w->m_cb(s); + g_free(s); + }), + this); + webkit_user_content_manager_register_script_message_handler(manager, + "external"); + init("window.external={invoke:function(s){window.webkit.messageHandlers." + "external.postMessage(s);}}"); + + gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview)); + gtk_widget_grab_focus(GTK_WIDGET(m_webview)); + + if (debug) { + WebKitSettings *settings = + webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview)); + webkit_settings_set_enable_write_console_messages_to_stdout(settings, + true); + webkit_settings_set_enable_developer_extras(settings, true); + } + + gtk_widget_show_all(m_window); + } + void run() { gtk_main(); } + void terminate() { gtk_main_quit(); } + void dispatch(std::function f) { + g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int { + (*static_cast(f))(); + return G_SOURCE_REMOVE; + }), + new std::function(f), + [](void *f) { delete static_cast(f); }); + } + + void set_title(const char *title) { + gtk_window_set_title(GTK_WINDOW(m_window), title); + } + + void set_size(int width, int height, bool resizable) { + gtk_window_set_resizable(GTK_WINDOW(m_window), !!resizable); + if (resizable) { + gtk_window_set_default_size(GTK_WINDOW(m_window), width, height); + } else { + gtk_widget_set_size_request(m_window, width, height); + } + } + + void navigate(const char *url) { + webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url); + } + + void init(const char *js) { + WebKitUserContentManager *manager = + webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); + webkit_user_content_manager_add_script( + manager, webkit_user_script_new( + js, WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL)); + } + + void eval(const char *js) { + webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js, NULL, NULL, + NULL); + } + +protected: + std::function m_cb; + GtkWidget *m_window; + GtkWidget *m_webview; +}; + +} // namespace webview + +#elif defined(WEBVIEW_COCOA) + +// +// ==================================================================== +// +// This implementation uses Cocoa WKWebView backend on macOS. It is +// written using ObjC runtime and uses WKWebView class as a browser runtime. +// You should pass "-framework Webkit" flag to the compiler. +// +// ==================================================================== +// + +#include +#include + +#define NSBackingStoreBuffered 2 + +#define NSWindowStyleMaskResizable 8 +#define NSWindowStyleMaskMiniaturizable 4 +#define NSWindowStyleMaskTitled 1 +#define NSWindowStyleMaskClosable 2 + +#define NSApplicationActivationPolicyRegular 0 + +#define WKUserScriptInjectionTimeAtDocumentStart 0 + +namespace webview { + +id operator"" _cls(const char *s, std::size_t sz) { + return (id)objc_getClass(s); +} +SEL operator"" _sel(const char *s, std::size_t sz) { + return sel_registerName(s); +} +id operator"" _str(const char *s, std::size_t sz) { + return objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, s); +} + +class browser_engine { +public: + browser_engine(msg_cb_t cb, bool debug, void *window) : m_cb(cb) { + // Application + id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); + objc_msgSend(app, "setActivationPolicy:"_sel, + NSApplicationActivationPolicyRegular); + + // Delegate + auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "AppDelegate", 0); + class_addProtocol(cls, objc_getProtocol("NSApplicationDelegate")); + class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler")); + class_addMethod( + cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel, + (IMP)(+[](id self, SEL cmd, id notification) -> BOOL { return 1; }), + "c@:@"); + class_addMethod( + cls, "userContentController:didReceiveScriptMessage:"_sel, + (IMP)(+[](id self, SEL cmd, id notification, id msg) { + auto w = (browser_engine *)objc_getAssociatedObject(self, "webview"); + w->m_cb((const char *)objc_msgSend(objc_msgSend(msg, "body"_sel), + "UTF8String"_sel)); + }), + "v@:@@"); + objc_registerClassPair(cls); + + auto delegate = objc_msgSend((id)cls, "new"_sel); + objc_setAssociatedObject(delegate, "webview", (id)this, + OBJC_ASSOCIATION_ASSIGN); + objc_msgSend(app, sel_registerName("setDelegate:"), delegate); + + // Main window + if (window == nullptr) { + m_window = objc_msgSend("NSWindow"_cls, "alloc"_sel); + m_window = objc_msgSend( + m_window, "initWithContentRect:styleMask:backing:defer:"_sel, + CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0); + set_size(480, 320, true); + } else { + m_window = (id)window; + } + + // Webview + auto config = objc_msgSend("WKWebViewConfiguration"_cls, "new"_sel); + m_manager = objc_msgSend(config, "userContentController"_sel); + m_webview = objc_msgSend("WKWebView"_cls, "alloc"_sel); + objc_msgSend(m_webview, "initWithFrame:configuration:"_sel, + CGRectMake(0, 0, 0, 0), config); + objc_msgSend(m_manager, "addScriptMessageHandler:name:"_sel, delegate, + "external"_str); + init(R"script( + window.external = { + invoke: function(s) { + window.webkit.messageHandlers.external.postMessage(s); + }, + }; + )script"); + if (debug) { + objc_msgSend(objc_msgSend(config, "preferences"_sel), + "setValue:forKey:"_sel, 1, "developerExtrasEnabled"_str); + } + objc_msgSend(m_window, "setContentView:"_sel, m_webview); + objc_msgSend(m_window, "makeKeyAndOrderFront:"_sel, nullptr); + } + ~browser_engine() { objc_msgSend(m_window, "close"_sel); } + void terminate() { objc_msgSend("NSApp"_cls, "terminate:"_sel, nullptr); } + void run() { + id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); + dispatch([&]() { objc_msgSend(app, "activateIgnoringOtherApps:"_sel, 1); }); + objc_msgSend(app, "run"_sel); + } + void dispatch(std::function f) { + dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), + (dispatch_function_t)([](void *arg) { + auto f = static_cast(arg); + (*f)(); + delete f; + })); + } + void set_title(const char *title) { + objc_msgSend( + m_window, "setTitle:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, title)); + } + void set_size(int width, int height, bool resizable) { + auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable; + if (resizable) { + style = style | NSWindowStyleMaskResizable; + } + objc_msgSend(m_window, "setStyleMask:"_sel, style); + objc_msgSend(m_window, "setFrame:display:animate:"_sel, + CGRectMake(0, 0, width, height), 1, 0); + } + void navigate(const char *url) { + auto nsurl = objc_msgSend( + "NSURL"_cls, "URLWithString:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, url)); + objc_msgSend( + m_webview, "loadRequest:"_sel, + objc_msgSend("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); + } + void init(const char *js) { + objc_msgSend( + m_manager, "addUserScript:"_sel, + objc_msgSend( + objc_msgSend("WKUserScript"_cls, "alloc"_sel), + "initWithSource:injectionTime:forMainFrameOnly:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js), + WKUserScriptInjectionTimeAtDocumentStart, 1)); + } + void eval(const char *js) { + objc_msgSend(m_webview, "evaluateJavaScript:completionHandler:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js), + nullptr); + } + +protected: + id m_window; + id m_webview; + id m_manager; + msg_cb_t m_cb; +}; + +} // namespace webview + +#elif defined(WEBVIEW_MSHTML) || defined(WEBVIEW_EDGE) + +// +// ==================================================================== +// +// This implementation uses Win32 API to create a native window. It can +// use either MSHTML or EdgeHTML backend as a browser engine. +// +// ==================================================================== +// + +#define WIN32_LEAN_AND_MEAN +#include + +#pragma comment(lib, "user32.lib") +namespace webview { +class browser_window { +public: + browser_window(msg_cb_t cb, void *window) : m_cb(cb) { + if (window == nullptr) { + WNDCLASSEX wc; + ZeroMemory(&wc, sizeof(WNDCLASSEX)); + wc.cbSize = sizeof(WNDCLASSEX); + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = "webview"; + wc.lpfnWndProc = + (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int { + auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) { + case WM_SIZE: + w->resize(); + break; + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_DESTROY: + w->terminate(); + break; + default: + return DefWindowProc(hwnd, msg, wp, lp); + } + return 0; + }); + RegisterClassEx(&wc); + m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, 640, 480, nullptr, nullptr, + GetModuleHandle(nullptr), nullptr); + SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); + } else { + m_window = *(static_cast(window)); + } + + ShowWindow(m_window, SW_SHOW); + UpdateWindow(m_window); + SetFocus(m_window); + } + + void run() { + MSG msg; + BOOL res; + while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) { + if (msg.hwnd) { + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } + if (msg.message == WM_APP) { + auto f = (dispatch_fn_t *)(msg.lParam); + (*f)(); + delete f; + } else if (msg.message == WM_QUIT) { + return; + } + } + } + + void terminate() { PostQuitMessage(0); } + void dispatch(dispatch_fn_t f) { + PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); + } + + void set_title(const char *title) { SetWindowText(m_window, title); } + + void set_size(int width, int height, bool resizable) { + RECT r; + r.left = 50; + r.top = 50; + r.right = width; + r.bottom = height; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); + SetWindowPos(m_window, NULL, r.left, r.top, r.right - r.left, + r.bottom - r.top, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } + +protected: + virtual void resize() {} + HWND m_window; + DWORD m_main_thread = GetCurrentThreadId(); + msg_cb_t m_cb; +}; +} // namespace webview + +#if defined(WEBVIEW_MSHTML) +#include +#include +#include +#include +#include +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "oleaut32.lib") + +#define DISPID_EXTERNAL_INVOKE 0x1000 + +namespace webview { +class browser_engine : public browser_window, + public IOleClientSite, + public IOleInPlaceSite, + public IOleInPlaceFrame, + public IDocHostUIHandler, + public DWebBrowserEvents2 { +public: + browser_engine(msg_cb_t cb, bool debug, void *window) + : browser_window(cb, window) { + RECT rect; + LPCLASSFACTORY cf = nullptr; + IOleObject *obj = nullptr; + + fix_ie_compat_mode(); + + OleInitialize(nullptr); + CoGetClassObject(CLSID_WebBrowser, + CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, nullptr, + IID_IClassFactory, (void **)&cf); + cf->CreateInstance(nullptr, IID_IOleObject, (void **)&obj); + cf->Release(); + + obj->SetClientSite(this); + OleSetContainedObject(obj, TRUE); + GetWindowRect(m_window, &rect); + obj->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, -1, m_window, &rect); + obj->QueryInterface(IID_IWebBrowser2, (void **)&m_webview); + + IConnectionPointContainer *cpc; + IConnectionPoint *cp; + DWORD cookie; + m_webview->QueryInterface(IID_IConnectionPointContainer, (void **)&cpc); + cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp); + cpc->Release(); + cp->Advise(static_cast(this), &cookie); + + resize(); + navigate("about:blank"); + } + + ~browser_engine() { OleUninitialize(); } + + void navigate(const char *url) { + VARIANT v; + DWORD size = MultiByteToWideChar(CP_UTF8, 0, url, -1, 0, 0); + WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size); + MultiByteToWideChar(CP_UTF8, 0, url, -1, ws, size); + VariantInit(&v); + v.vt = VT_BSTR; + v.bstrVal = SysAllocString(ws); + m_webview->Navigate2(&v, nullptr, nullptr, nullptr, nullptr); + VariantClear(&v); + } + + void eval(const char *js) { + // TODO + } + +private: + IWebBrowser2 *m_webview; + + int fix_ie_compat_mode() { + const char *WEBVIEW_KEY_FEATURE_BROWSER_EMULATION = + "Software\\Microsoft\\Internet " + "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION"; + HKEY hKey; + DWORD ie_version = 11000; + TCHAR appname[MAX_PATH + 1]; + TCHAR *p; + if (GetModuleFileName(NULL, appname, MAX_PATH + 1) == 0) { + return -1; + } + for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) { + } + p++; + if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION, + &hKey) != ERROR_SUCCESS) { + return -1; + } + if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version, + sizeof(ie_version)) != ERROR_SUCCESS) { + RegCloseKey(hKey); + return -1; + } + RegCloseKey(hKey); + return 0; + } + + // Inheruted via browser_window + void resize() override { + RECT rect; + GetClientRect(m_window, &rect); + m_webview->put_Left(0); + m_webview->put_Top(0); + m_webview->put_Width(rect.right); + m_webview->put_Height(rect.bottom); + m_webview->put_Visible(VARIANT_TRUE); + } + + // Inherited via IUnknown + ULONG __stdcall AddRef(void) override { return 1; } + ULONG __stdcall Release(void) override { return 1; } + HRESULT __stdcall QueryInterface(REFIID riid, void **obj) override { + if (riid == IID_IUnknown || riid == IID_IOleClientSite) { + *obj = static_cast(this); + return S_OK; + } + if (riid == IID_IOleInPlaceSite) { + *obj = static_cast(this); + return S_OK; + } + if (riid == IID_IDocHostUIHandler) { + *obj = static_cast(this); + return S_OK; + } + if (riid == IID_IDispatch || riid == DIID_DWebBrowserEvents2) { + *obj = static_cast(this); + return S_OK; + } + *obj = nullptr; + return E_NOINTERFACE; + } + + // Inherited via IOleClientSite + HRESULT __stdcall SaveObject(void) override { return E_NOTIMPL; } + HRESULT __stdcall GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, + IMoniker **ppmk) override { + return E_NOTIMPL; + } + HRESULT __stdcall GetContainer(IOleContainer **ppContainer) override { + *ppContainer = nullptr; + return E_NOINTERFACE; + } + HRESULT __stdcall ShowObject(void) override { return S_OK; } + HRESULT __stdcall OnShowWindow(BOOL fShow) override { return S_OK; } + HRESULT __stdcall RequestNewObjectLayout(void) override { return E_NOTIMPL; } + + // Inherited via IOleInPlaceSite + HRESULT __stdcall GetWindow(HWND *phwnd) override { + *phwnd = m_window; + return S_OK; + } + HRESULT __stdcall ContextSensitiveHelp(BOOL fEnterMode) override { + return E_NOTIMPL; + } + HRESULT __stdcall CanInPlaceActivate(void) override { return S_OK; } + HRESULT __stdcall OnInPlaceActivate(void) override { return S_OK; } + HRESULT __stdcall OnUIActivate(void) override { return S_OK; } + HRESULT __stdcall GetWindowContext( + IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, + LPRECT lprcPosRect, LPRECT lprcClipRect, + LPOLEINPLACEFRAMEINFO lpFrameInfo) override { + *ppFrame = static_cast(this); + *ppDoc = nullptr; + lpFrameInfo->fMDIApp = FALSE; + lpFrameInfo->hwndFrame = m_window; + lpFrameInfo->haccel = 0; + lpFrameInfo->cAccelEntries = 0; + return S_OK; + } + HRESULT __stdcall Scroll(SIZE scrollExtant) override { return E_NOTIMPL; } + HRESULT __stdcall OnUIDeactivate(BOOL fUndoable) override { return S_OK; } + HRESULT __stdcall OnInPlaceDeactivate(void) override { return S_OK; } + HRESULT __stdcall DiscardUndoState(void) override { return E_NOTIMPL; } + HRESULT __stdcall DeactivateAndUndo(void) override { return E_NOTIMPL; } + HRESULT __stdcall OnPosRectChange(LPCRECT lprcPosRect) override { + IOleInPlaceObject *inplace; + m_webview->QueryInterface(IID_IOleInPlaceObject, (void **)&inplace); + inplace->SetObjectRects(lprcPosRect, lprcPosRect); + return S_OK; + } + + // Inherited via IDocHostUIHandler + HRESULT __stdcall ShowContextMenu(DWORD dwID, POINT *ppt, + IUnknown *pcmdtReserved, + IDispatch *pdispReserved) override { + return S_OK; + } + HRESULT __stdcall GetHostInfo(DOCHOSTUIINFO *pInfo) override { + pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; + pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; + return S_OK; + } + HRESULT __stdcall ShowUI(DWORD dwID, IOleInPlaceActiveObject *pActiveObject, + IOleCommandTarget *pCommandTarget, + IOleInPlaceFrame *pFrame, + IOleInPlaceUIWindow *pDoc) override { + return S_OK; + } + HRESULT __stdcall HideUI(void) override { return S_OK; } + HRESULT __stdcall UpdateUI(void) override { return S_OK; } + HRESULT __stdcall EnableModeless(BOOL fEnable) override { return S_OK; } + HRESULT __stdcall OnDocWindowActivate(BOOL fActivate) override { + return S_OK; + } + HRESULT __stdcall OnFrameWindowActivate(BOOL fActivate) override { + return S_OK; + } + HRESULT __stdcall ResizeBorder(LPCRECT prcBorder, + IOleInPlaceUIWindow *pUIWindow, + BOOL fRameWindow) override { + return S_OK; + } + HRESULT __stdcall GetOptionKeyPath(LPOLESTR *pchKey, DWORD dw) override { + return S_FALSE; + } + HRESULT __stdcall GetDropTarget(IDropTarget *pDropTarget, + IDropTarget **ppDropTarget) override { + return E_NOTIMPL; + } + HRESULT __stdcall GetExternal(IDispatch **ppDispatch) override { + *ppDispatch = static_cast(this); + return S_OK; + } + HRESULT __stdcall TranslateUrl(DWORD dwTranslate, LPWSTR pchURLIn, + LPWSTR *ppchURLOut) override { + *ppchURLOut = nullptr; + return S_FALSE; + } + HRESULT __stdcall FilterDataObject(IDataObject *pDO, + IDataObject **ppDORet) override { + *ppDORet = nullptr; + return S_FALSE; + } + HRESULT __stdcall TranslateAcceleratorA(LPMSG lpMsg, + const GUID *pguidCmdGroup, + DWORD nCmdID) { + return S_FALSE; + } + + // Inherited via IOleInPlaceFrame + HRESULT __stdcall GetBorder(LPRECT lprectBorder) override { return S_OK; } + HRESULT __stdcall RequestBorderSpace(LPCBORDERWIDTHS pborderwidths) override { + return S_OK; + } + HRESULT __stdcall SetBorderSpace(LPCBORDERWIDTHS pborderwidths) override { + return S_OK; + } + HRESULT __stdcall SetActiveObject(IOleInPlaceActiveObject *pActiveObject, + LPCOLESTR pszObjName) override { + return S_OK; + } + HRESULT __stdcall InsertMenus(HMENU hmenuShared, + LPOLEMENUGROUPWIDTHS lpMenuWidths) override { + return S_OK; + } + HRESULT __stdcall SetMenu(HMENU hmenuShared, HOLEMENU holemenu, + HWND hwndActiveObject) override { + return S_OK; + } + HRESULT __stdcall RemoveMenus(HMENU hmenuShared) override { return S_OK; } + HRESULT __stdcall SetStatusText(LPCOLESTR pszStatusText) override { + return S_OK; + } + HRESULT __stdcall TranslateAcceleratorA(LPMSG lpmsg, WORD wID) { + return S_OK; + } + + // Inherited via IDispatch + HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo) override { return S_OK; } + HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo **ppTInfo) override { + return S_OK; + } + HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, + LCID lcid, DISPID *rgDispId) override { + *rgDispId = DISPID_EXTERNAL_INVOKE; + return S_OK; + } + HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS *pDispParams, + VARIANT *pVarResult, EXCEPINFO *pExcepInfo, + UINT *puArgErr) override { + if (dispIdMember == DISPID_NAVIGATECOMPLETE2) { + } else if (dispIdMember == DISPID_DOCUMENTCOMPLETE) { + } else if (dispIdMember == DISPID_EXTERNAL_INVOKE) { + } + return S_OK; + } +}; +} // namespace webview + +#elif defined(WEBVIEW_EDGE) +#include +#include +#include + +#pragma comment(lib, "windowsapp") + +namespace webview { + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Web::UI; +using namespace Windows::Web::UI::Interop; + +class browser_engine : public browser_window { +public: + browser_engine(msg_cb_t cb, bool debug, void *window) + : browser_window(cb, window) { + init_apartment(winrt::apartment_type::single_threaded); + m_process = WebViewControlProcess(); + auto op = m_process.CreateWebViewControlAsync( + reinterpret_cast(m_window), Rect()); + if (op.Status() != AsyncStatus::Completed) { + handle h(CreateEvent(nullptr, false, false, nullptr)); + op.Completed([h = h.get()](auto, auto) { SetEvent(h); }); + HANDLE hs[] = {h.get()}; + DWORD i; + CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | + COWAIT_DISPATCH_CALLS | + COWAIT_INPUTAVAILABLE, + INFINITE, 1, hs, &i); + } + m_webview = op.GetResults(); + m_webview.Settings().IsScriptNotifyAllowed(true); + m_webview.IsVisible(true); + m_webview.ScriptNotify([=](auto const &sender, auto const &args) { + std::string s = winrt::to_string(args.Value()); + m_cb(s.c_str()); + }); + m_webview.NavigationStarting([=](auto const &sender, auto const &args) { + m_webview.AddInitializeScript(winrt::to_hstring(init_js)); + }); + init("window.external.invoke = s => window.external.notify(s)"); + resize(); + } + + void navigate(const char *url) { + Uri uri(winrt::to_hstring(url)); + // TODO: if url starts with 'data:text/html,' prefix then use it as a string + m_webview.Navigate(uri); + // m_webview.NavigateToString(winrt::to_hstring(url)); + } + void init(const char *js) { + init_js = init_js + "(function(){" + js + "})();"; + } + void eval(const char *js) { + m_webview.InvokeScriptAsync( + L"eval", single_threaded_vector({winrt::to_hstring(js)})); + } + +private: + void resize() { + RECT r; + GetClientRect(m_window, &r); + Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top); + m_webview.Bounds(bounds); + } + WebViewControlProcess m_process; + WebViewControl m_webview = nullptr; + std::string init_js = ""; +}; +} // namespace webview +#endif + +#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_MSHTML, WEBVIEW_MSHTML */ + +namespace webview { + +class webview : public browser_engine { +public: + webview(bool debug = false, void *wnd = nullptr) + : browser_engine( + std::bind(&webview::on_message, this, std::placeholders::_1), debug, + wnd) {} + + void *window() { return (void *)m_window; } + + void navigate(const char *url) { + std::string html = html_from_uri(url); + if (html != "") { + browser_engine::navigate(("data:text/html," + url_encode(html)).c_str()); + } else { + browser_engine::navigate(url); + } + } + + using binding_t = std::function; + + void bind(const char *name, binding_t f) { + auto js = "(function() { var name = '" + std::string(name) + "';" + R"( + window[name] = function() { + var me = window[name]; + var errors = me['errors']; + var callbacks = me['callbacks']; + if (!callbacks) { + callbacks = {}; + me['callbacks'] = callbacks; + } + if (!errors) { + errors = {}; + me['errors'] = errors; + } + var seq = (me['lastSeq'] || 0) + 1; + me['lastSeq'] = seq; + var promise = new Promise(function(resolve, reject) { + callbacks[seq] = resolve; + errors[seq] = reject; + }); + window.external.invoke(JSON.stringify({ + name: name, + seq:seq, + args: Array.prototype.slice.call(arguments), + })); + return promise; + } + })())"; + init(js.c_str()); + bindings[name] = new binding_t(f); + } + +private: + void on_message(const char *msg) { + auto seq = json_parse(msg, "seq", 0); + auto name = json_parse(msg, "name", 0); + auto args = json_parse(msg, "args", 0); + auto fn = bindings[name]; + if (fn == nullptr) { + return; + } + std::async(std::launch::async, [=]() { + auto result = (*fn)(args); + dispatch([=]() { + eval(("var b = window['" + name + "'];b['callbacks'][" + seq + "](" + + result + ");b['callbacks'][" + seq + + "] = undefined;b['errors'][" + seq + "] = undefined;") + .c_str()); + }); + }); + } + std::map bindings; +}; +} // namespace webview + +WEBVIEW_API webview_t webview_create(int debug, void *wnd) { + return new webview::webview(debug, wnd); +} + +WEBVIEW_API void webview_destroy(webview_t w) { + delete static_cast(w); +} + +WEBVIEW_API void webview_run(webview_t w) { + static_cast(w)->run(); +} + +WEBVIEW_API void webview_terminate(webview_t w) { + static_cast(w)->terminate(); +} + +WEBVIEW_API void +webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg) { + static_cast(w)->dispatch([=]() { fn(w, arg); }); +} + +WEBVIEW_API void *webview_get_window(webview_t w) { + return static_cast(w)->window(); +} + +WEBVIEW_API void webview_set_title(webview_t w, const char *title) { + static_cast(w)->set_title(title); +} + +WEBVIEW_API void webview_set_bounds(webview_t w, int x, int y, int width, + int height, int flags) { + // TODO: x, y, flags + static_cast(w)->set_size(width, height, true); +} + +WEBVIEW_API void webview_get_bounds(webview_t w, int *x, int *y, int *width, + int *height, int *flags) { + // TODO +} + +WEBVIEW_API void webview_navigate(webview_t w, const char *url) { + static_cast(w)->navigate(url); +} + +WEBVIEW_API void webview_init(webview_t w, const char *js) { + static_cast(w)->init(js); +} + +WEBVIEW_API void webview_eval(webview_t w, const char *js) { + static_cast(w)->eval(js); +} + +#endif /* WEBVIEW_HEADER */ + +#endif /* WEBVIEW_H */ diff --git a/attic/webview/webview_test.cc b/attic/webview/webview_test.cc new file mode 100644 index 000000000..ebf995b1e --- /dev/null +++ b/attic/webview/webview_test.cc @@ -0,0 +1,38 @@ +// +build ignore + +#include "webview.h" + +#include +#include + +static void test_terminate() { + webview::webview w(false, nullptr); + w.dispatch([&]() { w.terminate(); }); + w.run(); +} + +static void cb_assert_arg(webview_t w, void *arg) { + assert(w != NULL); + assert(memcmp(arg, "arg", 3) == 0); +} +static void cb_terminate(webview_t w, void *arg) { + assert(arg == NULL); + webview_terminate(w); +} +static void test_c_api() { + webview_t w; + w = webview_create(false, NULL); + webview_set_bounds(w, 100, 100, 480, 320, 0); + webview_set_title(w, "Test"); + webview_navigate(w, "https://github.com/zserge/webview"); + webview_dispatch(w, cb_assert_arg, (void *)"arg"); + webview_dispatch(w, cb_terminate, NULL); + webview_run(w); + webview_destroy(w); +} + +int main() { + test_terminate(); + test_c_api(); + return 0; +} diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index f5c48228d..0ce992216 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -479,7 +479,16 @@ EmbeddedNetworkController::~EmbeddedNetworkController() void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender) { char tmp[64]; + _signingId = signingId; + + // Base the identity hash, which is used to generate network tokens, on + // only the type 0 public and private keys so that type 0 identities can + // upgrade without these tokens changing. + Identity downgraded; + _signingId.downgrade(downgraded,Identity::C25519); + downgraded.hash(_signingIdHash,true); + _sender = sender; _signingIdAddressString = signingId.address().toString(tmp); @@ -1445,6 +1454,7 @@ void EmbeddedNetworkController::_request( const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false); + // Set IPv6 static IPs based on NDP emulated schemes if enabled. if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { nc->staticIps[nc->staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); @@ -1456,6 +1466,17 @@ void EmbeddedNetworkController::_request( } } + // Generate a unique semi-secret token known only to members and former members + // of this network by hashing the hash of our signing identity (including its + // secret part) with the network ID. Deriving the token like this eliminates the + // need to store it somewhere. + uint64_t tokenHashIn[7]; + memcpy(tokenHashIn,_signingIdHash,48); + tokenHashIn[6] = Utils::hton(nwid); + uint64_t tokenHash[6]; + SHA384(tokenHash,tokenHashIn,sizeof(tokenHashIn)); + nc->token = Utils::ntoh(tokenHash[0]); + bool haveManagedIpv4AutoAssignment = false; bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count json ipAssignments = member["ipAssignments"]; // we want to make a copy diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 82946940e..f79c4c5a9 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -132,6 +132,7 @@ private: std::string _ztPath; std::string _path; Identity _signingId; + uint8_t _signingIdHash[48]; std::string _signingIdAddressString; NetworkController::Sender *_sender; diff --git a/controller/LFDB.cpp b/controller/LFDB.cpp index d11b77a07..d16c1fd29 100644 --- a/controller/LFDB.cpp +++ b/controller/LFDB.cpp @@ -43,10 +43,10 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons // LF record masking key is the first 32 bytes of SHA512(controller private key) in hex, // hiding record values from anything but the controller or someone who has its key. - uint8_t sha512pk[64]; - _myId.sha512PrivateKey(sha512pk); + uint8_t sha384pk[48]; + _myId.hash(sha384pk,true); char maskingKey [128]; - Utils::hex(sha512pk,32,maskingKey); + Utils::hex(sha384pk,32,maskingKey); httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600); int64_t timeRangeStart = 0; diff --git a/node/CMakeLists.txt b/node/CMakeLists.txt index 9dac4bc1b..4d604b4b5 100644 --- a/node/CMakeLists.txt +++ b/node/CMakeLists.txt @@ -18,6 +18,7 @@ set(core_headers Credential.hpp Dictionary.hpp ECC384.hpp + EphemeralKey.hpp Hashtable.hpp Identity.hpp InetAddress.hpp diff --git a/node/EphemeralKey.hpp b/node/EphemeralKey.hpp new file mode 100644 index 000000000..0eda407eb --- /dev/null +++ b/node/EphemeralKey.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_EPHEMERALKEY_HPP +#define ZT_EPHEMERALKEY_HPP + +#include "Constants.hpp" +#include "C25519.hpp" +#include "ECC384.hpp" +#include "SHA512.hpp" +#include "Buffer.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +#define ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE) +#define ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE) + +/** + * An ephemeral key exchanged to implement forward secrecy + * + * This key includes both C25519 and ECC384 keys and key agreement executes + * ECDH for both and hashes the results together. This should be able to be + * FIPS compliant (if the C25519 portion is just considered a nonce) while + * simultaneously being more secure than either curve alone. + * + * Serialization includes only the public portion since ephemeral private + * keys are never shared or stored anywhere. + */ +class EphemeralKey +{ +public: + enum Type + { + NONE = 0, + C25519ECC384 = 1 + }; + + ZT_ALWAYS_INLINE EphemeralKey() : _priv(nullptr),_type(NONE) {} + + ZT_ALWAYS_INLINE ~EphemeralKey() + { + if (_priv) { + Utils::burn(_priv,ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE); + delete [] _priv; + } + } + + ZT_ALWAYS_INLINE Type type() const { return (Type)_type; } + ZT_ALWAYS_INLINE bool hasPrivate() const { return (_priv != nullptr); } + + ZT_ALWAYS_INLINE void generate() + { + if (!_priv) + _priv = new uint8_t[ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE]; + C25519::generate(_pub,_priv); + ECC384GenerateKey(_pub + ZT_C25519_PUBLIC_KEY_LEN,_priv + ZT_C25519_PRIVATE_KEY_LEN); + _type = C25519ECC384; + } + + ZT_ALWAYS_INLINE bool agree(const EphemeralKey &theirs,uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) const + { + if ((_priv)&&(_type == 1)) { + uint8_t rawkey[128],h[48]; + C25519::agree(_priv,theirs._pub,rawkey); + ECC384ECDH(theirs._pub + ZT_C25519_PUBLIC_KEY_LEN,_priv + ZT_C25519_PRIVATE_KEY_LEN,rawkey + ZT_C25519_SHARED_KEY_LEN); + SHA384(h,rawkey,ZT_C25519_SHARED_KEY_LEN + ZT_ECC384_SHARED_SECRET_SIZE); + memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH); + return true; + } + return false; + } + + template + ZT_ALWAYS_INLINE void serialize(Buffer &b) const + { + b.append(_type); + if (_type == C25519ECC384) + b.append(_pub,ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE); + } + + template + ZT_ALWAYS_INLINE unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + delete [] _priv; + _priv = nullptr; + switch(b[p++]) { + case C25519ECC384: + memcpy(_pub,b.field(p,ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE),ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE); + p += ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE; + break; + default: + _type = NONE; + break; + } + return (p - startAt); + } + +private: + uint8_t *_priv; + uint8_t _pub[ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE]; + uint8_t _type; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Identity.cpp b/node/Identity.cpp index 08377eb5b..255c9c7e6 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -100,8 +100,13 @@ void Identity::generate(const Type t) delete [] genmem; if (t == P384) { + // We sign with both because in pure FIPS environments we might have to say + // that we do not rely on any non-FIPS algorithms, or may even have to disable + // them. ECC384GenerateKey(_pub.p384,_priv.p384); - C25519::sign(_priv.c25519,_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.p384s); + C25519::sign(_priv.c25519,_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.c25519s); + SHA384(digest,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE); + ECC384ECDSASign(_priv.p384,digest,_pub.p384s); } } @@ -116,7 +121,10 @@ bool Identity::locallyValidate() const case C25519: break; case P384: - if (!C25519::verify(_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.p384s,ZT_C25519_SIGNATURE_LEN)) + if (!C25519::verify(_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.c25519s,ZT_C25519_SIGNATURE_LEN)) + return false; + SHA384(digest,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE); + if (!ECC384ECDSAVerify(_pub.p384,digest,_pub.p384s)) return false; default: return false; diff --git a/node/Identity.hpp b/node/Identity.hpp index a54a0f94c..905218aa5 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -93,39 +93,26 @@ public: */ ZT_ALWAYS_INLINE bool hasPrivate() const { return _hasPrivate; } - /** - * Compute the SHA512 hash of our private key (if we have one) - * - * @param sha Buffer to receive SHA512 (MUST be ZT_SHA512_DIGEST_LEN (64) bytes in length) - * @return True on success, false if no private key - */ - ZT_ALWAYS_INLINE bool sha512PrivateKey(void *const sha) const - { - if (_hasPrivate) { - switch(_type) { - case C25519: - SHA512(sha,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN); - return true; - case P384: - SHA512(sha,&_priv,sizeof(_priv)); - return true; - } - } - return false; - } - /** * @param h Buffer to receive SHA384 of public key(s) + * @param includePrivate If true, hash private key(s) too */ - ZT_ALWAYS_INLINE bool hash(uint8_t h[48]) const + ZT_ALWAYS_INLINE bool hash(uint8_t h[48],const bool includePrivate) const { switch(_type) { + case C25519: - SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); + if ((_hasPrivate)&&(includePrivate)) + SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN); + else SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); return true; + case P384: - SHA384(h,&_pub,sizeof(_pub)); + if ((_hasPrivate)&&(includePrivate)) + SHA384(h,&_pub,sizeof(_pub),&_priv,sizeof(_priv)); + else SHA384(h,&_pub,sizeof(_pub)); return true; + } return false; } @@ -155,10 +142,8 @@ public: case P384: if (siglen >= ZT_ECC384_SIGNATURE_SIZE) { - // Signature hash includes the C25519/Ed25519 public key after the message. - // This is an added guard against divorcing these two bound keys. uint8_t h[48]; - SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); + SHA384(h,data,len); ECC384ECDSASign(_priv.p384,h,(uint8_t *)sig); return ZT_ECC384_SIGNATURE_SIZE; } @@ -185,7 +170,7 @@ public: case P384: if (siglen == ZT_ECC384_SIGNATURE_SIZE) { uint8_t h[48]; - SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); + SHA384(h,data,len); return ECC384ECDSAVerify(_pub.p384,h,(const uint8_t *)sig); } break; @@ -247,19 +232,20 @@ public: /** * Attempt to generate an older type identity from a newer type * - * If this identity has its private key this is not transferred to - * the downgraded identity. - * * @param dest Destination to fill with downgraded identity * @param toType Desired identity type */ ZT_ALWAYS_INLINE bool downgrade(Identity &dest,const Type toType) { - if ((_type == P384)&&(toType == C25519)) { + if (_type == toType) { + return true; + } else if ((_type == P384)&&(toType == C25519)) { dest._address = _address; dest._type = C25519; - dest._hasPrivate = false; + dest._hasPrivate = _hasPrivate; memcpy(dest._pub.c25519,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); + if (_hasPrivate) + memcpy(dest._priv.c25519,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN); return true; } return false; @@ -270,7 +256,6 @@ public: * * @param b Destination buffer to append to * @param includePrivate If true, include private key component (if present) (default: false) - * @throws std::out_of_range Buffer too small */ template ZT_ALWAYS_INLINE void serialize(Buffer &b,bool includePrivate = false) const @@ -291,9 +276,7 @@ public: case P384: b.append((uint8_t)P384); - b.append(_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); - b.append(_pub.p384,ZT_ECC384_PUBLIC_KEY_SIZE); - b.append(_pub.p384s,ZT_C25519_SIGNATURE_LEN); + b.append(&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE); if ((_hasPrivate)&&(includePrivate)) { b.append((uint8_t)(ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE)); b.append(_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN); @@ -316,8 +299,6 @@ public: * @param b Buffer containing serialized data * @param startAt Index within buffer of serialized data (default: 0) * @return Length of serialized data read from buffer - * @throws std::out_of_range Serialized data invalid - * @throws std::invalid_argument Serialized data invalid */ template ZT_ALWAYS_INLINE unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) @@ -347,12 +328,8 @@ public: break; case P384: - memcpy(_pub.c25519,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); - p += ZT_C25519_PUBLIC_KEY_LEN; - memcpy(_pub.p384,b.field(p,ZT_ECC384_PUBLIC_KEY_SIZE),ZT_ECC384_PUBLIC_KEY_SIZE); - p += ZT_ECC384_PUBLIC_KEY_SIZE; - memcpy(_pub.p384s,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); - p += ZT_ECC384_SIGNATURE_SIZE; + memcpy(&_pub,b.field(p,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE),ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE); + p += ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE; pkl = (unsigned int)b[p++]; if (pkl) { if (pkl != (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE)) @@ -442,16 +419,17 @@ public: private: Address _address; - Type _type; + Type _type; // _type determines which fields in _priv and _pub are used bool _hasPrivate; ZT_PACKED_STRUCT(struct { // don't re-order these uint8_t c25519[ZT_C25519_PRIVATE_KEY_LEN]; uint8_t p384[ZT_ECC384_PRIVATE_KEY_SIZE]; }) _priv; ZT_PACKED_STRUCT(struct { // don't re-order these - uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN]; - uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE]; - uint8_t p384s[ZT_C25519_SIGNATURE_LEN]; // signature of both keys with ed25519 to confirm type 0 extension to type 1 + uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN]; // Curve25519 and Ed25519 public keys + uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE]; // NIST P-384 public key + uint8_t c25519s[ZT_C25519_SIGNATURE_LEN]; // signature of both keys with ed25519 + uint8_t p384s[ZT_ECC384_SIGNATURE_SIZE]; // signature of both keys with p384 }) _pub; }; diff --git a/node/Locator.hpp b/node/Locator.hpp index 06c278bfa..d7b557ff4 100644 --- a/node/Locator.hpp +++ b/node/Locator.hpp @@ -44,6 +44,11 @@ namespace ZeroTier { */ class Locator { + enum ObjectType + { + OBJECT_TYPE_ZEROTIER_NODE = 1 + }; + friend class SharedPtr; public: diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index 7d9579131..6d90e5d2e 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -80,7 +80,7 @@ public: ZT_ALWAYS_INLINE uint32_t adi() const { return _adi; } /** - * @return 32-bit hash ID of this multicast group + * @return 32-bit non-cryptographic hash ID of this multicast group */ ZT_ALWAYS_INLINE uint32_t id() const { diff --git a/node/Packet.hpp b/node/Packet.hpp index 6595dce3b..b721dcb16 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -424,6 +424,10 @@ public: * <[8] timestamp for determining latency> * <[...] binary serialized identity (see Identity)> * <[...] physical destination address of packet> + * [... begin encrypted section ...] + * <[2] 16-bit reserved field, always 0> + * <[2] 16-bit length of locator> + * <[...] locator for this node> * * HELLO is sent in the clear as it is how peers share their identity * public keys. @@ -431,6 +435,12 @@ public: * Destination address is the actual wire address to which the packet * was sent. See InetAddress::serialize() for format. * + * Starting at "begin encrypted section" the reset of the packet is + * encrypted with Salsa20/12. This encryption is technically not + * absolutely required for security as nothing in this packet is + * very sensitive, but hiding the locator and other meta-data slightly + * improves privacy. + * * OK payload: * <[8] HELLO timestamp field echo> * <[1] protocol version> @@ -438,6 +448,7 @@ public: * <[1] software minor version> * <[2] software revision> * <[...] physical destination address of packet> + * <[2] 16-bit reserved field, always 0> * * With the exception of the timestamp, the other fields pertain to the * respondent who is sending OK and are not echoes. @@ -567,17 +578,6 @@ public: */ VERB_ECHO = 0x08, - /** - * Announce interest in multicast group(s) (DEPRECATED): - * <[8] 64-bit network ID> - * <[6] multicast Ethernet address> - * <[4] multicast additional distinguishing information (ADI)> - * [... additional tuples of network/address/adi ...] - * - * OK/ERROR are not generated. - */ - VERB_MULTICAST_LIKE = 0x09, - /** * Network credentials push: * [<[...] one or more certificates of membership>] @@ -783,51 +783,11 @@ public: */ VERB_REMOTE_TRACE = 0x15, - /** - * A signed locator for this node: - * <[8] 64-bit flags> - * <[2] 16-bit length of locator> - * <[...] serialized locator> - * - * This message is sent in response to OK(HELLO) and can be pushed - * opportunitistically. Its payload is a signed Locator object that - * attests to where and how this Node may be reached. A locator can - * contain static IPs/ports or other ZeroTier nodes that can be used - * to reach this one. - * - * These Locator objects can be stored e.g. by roots in LF to publish - * node reachability. Since they're signed any node can verify that - * the originating node approves of their content. - */ - VERB_SET_LOCATOR = 0x16, - - /** - * A list of peers this node will relay traffic to/from: - * <[2] 16-bit number of peers> - * <[16] 128-bit hash of node public key> - * <[2] 16-bit latency to node or 0 if unspecified> - * <[4] 32-bit max bandwidth in megabits or 0 if unspecified> - * [<[...] additional hash,latency,bandwidth tuples>] - * - * This messages can be pushed to indicate that this peer is willing - * to relay traffic to other peers. It contains a list of 128-bit - * hashes (the first 128 bits of a SHA512) of identity public keys - * of currently reachable and willing-to-relay-for nodes. - * - * This can be used to initiate mesh-like behavior in ZeroTier. The - * peers for which this node is willing to relay are reported as - * hashes of their identity public keys. This prevents this message - * from revealing explicit information about linked peers. The - * receiving peer can only "see" a will-relay entry if it knows the - * identity of the peer it is trying to reach. - */ - VERB_WILL_RELAY = 0x17, - /** * Multipurpose VL2 network multicast: * <[5] start of range of addresses for propagation> * <[5] end of range of addresses for propagation> - * <[1] 8-bit propagation depth / hops> + * <[1] 8-bit propagation depth / hops or 0xff to not propagate> * <[1] 8-bit length of bloom filter in 256-byte/2048-bit chunks> * <[...] propagation bloom filter> * [... start of signed portion ...] @@ -875,7 +835,26 @@ public: * depth, while frames have the added constraint of being propagated only * to nodes that subscribe to the target multicast group. */ - VERB_VL2_MULTICAST = 0x18, + VERB_VL2_MULTICAST = 0x16, + + /** + * Negotiate a new ephemeral key: + * <[8] first 64 bits of SHA-384 of currently known key for destination> + * <[...] ephemeral key for sender> + * + * If the 64-bit hash of the currently known key sent by the sender does + * not match the key the destination is currently using, the destination + * will send its own REKEY after sending OK to ensure that keys are up to + * date on both sides. This causes either side sending REKEY to trigger + * an automatic two-way handshake. Either side may therefore rekey at + * any time, though a rate limit should be in effect to prevent flooding. + * + * OK payload: + * <[8] first 64 bits of SHA-384 of received ephemeral key> + */ + VERB_REKEY = 0x17 + + // TODO: legacy multicast message types must be supported // protocol max: 0x1f };