shiny/screen: sanitize NewWindowOptions.Title.
Its bytes are passed to C libraries, so we sanitize as a precaution.
Change-Id: I6ecdd5388be40c4067815ba0484112bea6c55270
Reviewed-on: https://go-review.googlesource.com/37414
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
diff --git a/shiny/driver/gldriver/cocoa.go b/shiny/driver/gldriver/cocoa.go
index c41ec3c..cc01582 100644
--- a/shiny/driver/gldriver/cocoa.go
+++ b/shiny/driver/gldriver/cocoa.go
@@ -65,14 +65,10 @@
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
width, height := optsSize(opts)
- var title string
- if opts != nil {
- title = opts.Title
- }
- titlePtr := C.CString(title)
- defer C.free(unsafe.Pointer(titlePtr))
+ title := C.CString(opts.GetTitle())
+ defer C.free(unsafe.Pointer(title))
- return uintptr(C.doNewWindow(C.int(width), C.int(height), titlePtr)), nil
+ return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil
}
func initWindow(w *windowImpl) {
diff --git a/shiny/driver/gldriver/x11.c b/shiny/driver/gldriver/x11.c
index 11d7084..dcb583d 100644
--- a/shiny/driver/gldriver/x11.c
+++ b/shiny/driver/gldriver/x11.c
@@ -231,7 +231,7 @@
}
uintptr_t
-doNewWindow(int width, int height, char* title) {
+doNewWindow(int width, int height, char* title, int title_len) {
XSetWindowAttributes attr;
attr.colormap = x_colormap;
attr.event_mask =
@@ -260,7 +260,7 @@
XSetWMProtocols(x_dpy, win, atoms, 2);
XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints);
- XChangeProperty(x_dpy, win, wm_name, utf8_string, 8, PropModeReplace, title, strlen(title));
+ XChangeProperty(x_dpy, win, wm_name, utf8_string, 8, PropModeReplace, title, title_len);
return win;
}
diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go
index 0985dd7..0f0e376 100644
--- a/shiny/driver/gldriver/x11.go
+++ b/shiny/driver/gldriver/x11.go
@@ -53,17 +53,13 @@
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
width, height := optsSize(opts)
- var title string
- if opts != nil {
- title = opts.Title
- }
- titlePtr := C.CString(title)
- defer C.free(unsafe.Pointer(titlePtr))
+ title := C.CString(opts.GetTitle())
+ defer C.free(unsafe.Pointer(title))
retc := make(chan uintptr)
uic <- uiClosure{
f: func() uintptr {
- return uintptr(C.doNewWindow(C.int(width), C.int(height), titlePtr))
+ return uintptr(C.doNewWindow(C.int(width), C.int(height), title, C.int(len(title))))
},
retc: retc,
}
diff --git a/shiny/driver/internal/win32/win32.go b/shiny/driver/internal/win32/win32.go
index 151d198..402b575 100644
--- a/shiny/driver/internal/win32/win32.go
+++ b/shiny/driver/internal/win32/win32.go
@@ -66,11 +66,7 @@
if err != nil {
return 0, err
}
- var title string
- if opts != nil {
- title = opts.Title
- }
- windowTitle, err := syscall.UTF16PtrFromString(title)
+ title, err := syscall.UTF16PtrFromString(opts.GetTitle())
if err != nil {
return 0, err
}
@@ -84,7 +80,7 @@
}
}
hwnd, err := _CreateWindowEx(0,
- wcname, windowTitle,
+ wcname, title,
_WS_OVERLAPPEDWINDOW,
_CW_USEDEFAULT, _CW_USEDEFAULT,
int32(w), int32(h),
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
index 2f9cc71..e3f01af 100644
--- a/shiny/driver/x11driver/screen.go
+++ b/shiny/driver/x11driver/screen.go
@@ -423,10 +423,7 @@
)
s.setProperty(xw, s.atomWMProtocols, s.atomWMDeleteWindow, s.atomWMTakeFocus)
- var title []byte
- if opts != nil {
- title = []byte(opts.Title)
- }
+ title := []byte(opts.GetTitle())
xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, xproto.AtomWmName, s.atomUTF8String, 8, uint32(len(title)), title)
xproto.CreateGC(s.xc, xg, xproto.Drawable(xw), 0, nil)
diff --git a/shiny/screen/screen.go b/shiny/screen/screen.go
index d3c7d7b..5d89fe8 100644
--- a/shiny/screen/screen.go
+++ b/shiny/screen/screen.go
@@ -58,6 +58,7 @@
"image"
"image/color"
"image/draw"
+ "unicode/utf8"
"golang.org/x/image/math/f64"
)
@@ -238,6 +239,33 @@
// TODO: fullscreen, icon, cursorHidden?
}
+// GetTitle returns a sanitized form of o.Title. In particular, its length will
+// not exceed 4096, and it may be further truncated so that it is valid UTF-8
+// and will not contain the NUL byte.
+//
+// o may be nil, in which case "" is returned.
+func (o *NewWindowOptions) GetTitle() string {
+ if o == nil {
+ return ""
+ }
+ return sanitizeUTF8(o.Title, 4096)
+}
+
+func sanitizeUTF8(s string, n int) string {
+ if n < len(s) {
+ s = s[:n]
+ }
+ i := 0
+ for i < len(s) {
+ r, n := utf8.DecodeRuneInString(s[i:])
+ if r == 0 || (r == utf8.RuneError && n == 1) {
+ break
+ }
+ i += n
+ }
+ return s[:i]
+}
+
// Uploader is something you can upload a Buffer to.
type Uploader interface {
// Upload uploads the sub-Buffer defined by src and sr to the destination
diff --git a/shiny/screen/screen_test.go b/shiny/screen/screen_test.go
new file mode 100644
index 0000000..bc95011
--- /dev/null
+++ b/shiny/screen/screen_test.go
@@ -0,0 +1,53 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package screen
+
+import (
+ "testing"
+)
+
+func TestSanitizeUTF8(t *testing.T) {
+ const n = 8
+
+ testCases := []struct {
+ s, want string
+ }{
+ {"", ""},
+ {"a", "a"},
+ {"a\x00", "a"},
+ {"a\x80", "a"},
+ {"\x00a", ""},
+ {"\x80a", ""},
+ {"abc", "abc"},
+ {"foo b\x00r qux", "foo b"},
+ {"foo b\x80r qux", "foo b"},
+ {"foo b\xffr qux", "foo b"},
+
+ // "\xc3\xa0" is U+00E0 LATIN SMALL LETTER A WITH GRAVE.
+ {"\xc3\xa0pqrs", "\u00e0pqrs"},
+ {"a\xc3\xa0pqrs", "a\u00e0pqrs"},
+ {"ab\xc3\xa0pqrs", "ab\u00e0pqrs"},
+ {"abc\xc3\xa0pqrs", "abc\u00e0pqr"},
+ {"abcd\xc3\xa0pqrs", "abcd\u00e0pq"},
+ {"abcde\xc3\xa0pqrs", "abcde\u00e0p"},
+ {"abcdef\xc3\xa0pqrs", "abcdef\u00e0"},
+ {"abcdefg\xc3\xa0pqrs", "abcdefg"},
+ {"abcdefgh\xc3\xa0pqrs", "abcdefgh"},
+ {"abcdefghi\xc3\xa0pqrs", "abcdefgh"},
+ {"abcdefghij\xc3\xa0pqrs", "abcdefgh"},
+
+ // "世" is "\xe4\xb8\x96".
+ // "界" is "\xe7\x95\x8c".
+ {"H 世界", "H 世界"},
+ {"Hi 世界", "Hi 世"},
+ {"Hello 世界", "Hello "},
+ }
+
+ for _, tc := range testCases {
+ if got := sanitizeUTF8(tc.s, n); got != tc.want {
+ t.Errorf("sanitizeUTF8(%q): got %q, want %q", tc.s, got, tc.want)
+ }
+ }
+}