go-tg-screenshot-bot
248 строк · 7.3 Кб
1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Fork, exec, wait, etc.
6
7package windows
8
9import (
10errorspkg "errors"
11"unsafe"
12)
13
14// EscapeArg rewrites command line argument s as prescribed
15// in http://msdn.microsoft.com/en-us/library/ms880421.
16// This function returns "" (2 double quotes) if s is empty.
17// Alternatively, these transformations are done:
18// - every back slash (\) is doubled, but only if immediately
19// followed by double quote (");
20// - every double quote (") is escaped by back slash (\);
21// - finally, s is wrapped with double quotes (arg -> "arg"),
22// but only if there is space or tab inside s.
23func EscapeArg(s string) string {
24if len(s) == 0 {
25return `""`
26}
27n := len(s)
28hasSpace := false
29for i := 0; i < len(s); i++ {
30switch s[i] {
31case '"', '\\':
32n++
33case ' ', '\t':
34hasSpace = true
35}
36}
37if hasSpace {
38n += 2 // Reserve space for quotes.
39}
40if n == len(s) {
41return s
42}
43
44qs := make([]byte, n)
45j := 0
46if hasSpace {
47qs[j] = '"'
48j++
49}
50slashes := 0
51for i := 0; i < len(s); i++ {
52switch s[i] {
53default:
54slashes = 0
55qs[j] = s[i]
56case '\\':
57slashes++
58qs[j] = s[i]
59case '"':
60for ; slashes > 0; slashes-- {
61qs[j] = '\\'
62j++
63}
64qs[j] = '\\'
65j++
66qs[j] = s[i]
67}
68j++
69}
70if hasSpace {
71for ; slashes > 0; slashes-- {
72qs[j] = '\\'
73j++
74}
75qs[j] = '"'
76j++
77}
78return string(qs[:j])
79}
80
81// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
82// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
83// or any program that uses CommandLineToArgv.
84func ComposeCommandLine(args []string) string {
85if len(args) == 0 {
86return ""
87}
88
89// Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw:
90// “This function accepts command lines that contain a program name; the
91// program name can be enclosed in quotation marks or not.”
92//
93// Unfortunately, it provides no means of escaping interior quotation marks
94// within that program name, and we have no way to report them here.
95prog := args[0]
96mustQuote := len(prog) == 0
97for i := 0; i < len(prog); i++ {
98c := prog[i]
99if c <= ' ' || (c == '"' && i == 0) {
100// Force quotes for not only the ASCII space and tab as described in the
101// MSDN article, but also ASCII control characters.
102// The documentation for CommandLineToArgvW doesn't say what happens when
103// the first argument is not a valid program name, but it empirically
104// seems to drop unquoted control characters.
105mustQuote = true
106break
107}
108}
109var commandLine []byte
110if mustQuote {
111commandLine = make([]byte, 0, len(prog)+2)
112commandLine = append(commandLine, '"')
113for i := 0; i < len(prog); i++ {
114c := prog[i]
115if c == '"' {
116// This quote would interfere with our surrounding quotes.
117// We have no way to report an error, so just strip out
118// the offending character instead.
119continue
120}
121commandLine = append(commandLine, c)
122}
123commandLine = append(commandLine, '"')
124} else {
125if len(args) == 1 {
126// args[0] is a valid command line representing itself.
127// No need to allocate a new slice or string for it.
128return prog
129}
130commandLine = []byte(prog)
131}
132
133for _, arg := range args[1:] {
134commandLine = append(commandLine, ' ')
135// TODO(bcmills): since we're already appending to a slice, it would be nice
136// to avoid the intermediate allocations of EscapeArg.
137// Perhaps we can factor out an appendEscapedArg function.
138commandLine = append(commandLine, EscapeArg(arg)...)
139}
140return string(commandLine)
141}
142
143// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
144// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
145// command lines are passed around.
146// DecomposeCommandLine returns an error if commandLine contains NUL.
147func DecomposeCommandLine(commandLine string) ([]string, error) {
148if len(commandLine) == 0 {
149return []string{}, nil
150}
151utf16CommandLine, err := UTF16FromString(commandLine)
152if err != nil {
153return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
154}
155var argc int32
156argv, err := commandLineToArgv(&utf16CommandLine[0], &argc)
157if err != nil {
158return nil, err
159}
160defer LocalFree(Handle(unsafe.Pointer(argv)))
161
162var args []string
163for _, p := range unsafe.Slice(argv, argc) {
164args = append(args, UTF16PtrToString(p))
165}
166return args, nil
167}
168
169// CommandLineToArgv parses a Unicode command line string and sets
170// argc to the number of parsed arguments.
171//
172// The returned memory should be freed using a single call to LocalFree.
173//
174// Note that although the return type of CommandLineToArgv indicates 8192
175// entries of up to 8192 characters each, the actual count of parsed arguments
176// may exceed 8192, and the documentation for CommandLineToArgvW does not mention
177// any bound on the lengths of the individual argument strings.
178// (See https://go.dev/issue/63236.)
179func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) {
180argp, err := commandLineToArgv(cmd, argc)
181argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp))
182return argv, err
183}
184
185func CloseOnExec(fd Handle) {
186SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
187}
188
189// FullPath retrieves the full path of the specified file.
190func FullPath(name string) (path string, err error) {
191p, err := UTF16PtrFromString(name)
192if err != nil {
193return "", err
194}
195n := uint32(100)
196for {
197buf := make([]uint16, n)
198n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
199if err != nil {
200return "", err
201}
202if n <= uint32(len(buf)) {
203return UTF16ToString(buf[:n]), nil
204}
205}
206}
207
208// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
209func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
210var size uintptr
211err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
212if err != ERROR_INSUFFICIENT_BUFFER {
213if err == nil {
214return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
215}
216return nil, err
217}
218alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
219if err != nil {
220return nil, err
221}
222// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
223al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
224err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
225if err != nil {
226return nil, err
227}
228return al, err
229}
230
231// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
232func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
233al.pointers = append(al.pointers, value)
234return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
235}
236
237// Delete frees ProcThreadAttributeList's resources.
238func (al *ProcThreadAttributeListContainer) Delete() {
239deleteProcThreadAttributeList(al.data)
240LocalFree(Handle(unsafe.Pointer(al.data)))
241al.data = nil
242al.pointers = nil
243}
244
245// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
246func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
247return al.data
248}
249