什么叫内容泄露
内容泄露指的是:使用buffer值的一方通过某种非标准的方式,得到了本不该得到的内容
存在的场景和导致的原因
场景
- 通过调用Buffer值的某个用于读取内容的方法,得到一部分未读内容 A
- 当Buffer值又有一些新的内容之后,可通过上一步得到的结果值 A 直接获取新的内容
原因
- 通过切片可以直接访问和操纵它的底层数据
- 不论切片是基于某个数组得到的,还是通过另一个切片操作来获得的,上一结论都成立
bytes 例子
代码
package main
import (
"bytes"
"fmt"
)
func main() {
contents := "ab"
buffer1 := bytes.NewBufferString(contents)
fmt.Printf("The capacity of new buffer with contents %q: %d\n", contents, buffer1.Cap()) // 8
fmt.Println()
unreadBytes := buffer1.Bytes()
fmt.Printf("The unread bytes of the buffer: %v\n", unreadBytes) // [97, 98]
fmt.Println()
contents = "cdefg"
fmt.Printf("Write contents %q ...\n", contents)
buffer1.WriteString(contents)
fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap())
fmt.Println()
unreadBytes = unreadBytes[:cap(unreadBytes)]
fmt.Printf("The unread bytes of the buffer: %v\n", unreadBytes) // [97 98 99 100 101 102 103 0]
fmt.Println()
value := byte('X')
fmt.Printf("Set a byte in the unread bytes to %v ...\n", value)
unreadBytes[len(unreadBytes) - 2] = value
fmt.Printf("The unread bytes of the buffer: %v\n", buffer1.Bytes()) // 发生内容泄露 [97 98 99 100 101 102 88]
fmt.Println()
contents = "hijklmn"
fmt.Printf("Write contents %q ..\n", contents)
buffer1.WriteString(contents)
fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap()) // 23
fmt.Println()
unreadBytes = unreadBytes[:cap(unreadBytes)]
// 扩容代码就只能再创建一个新的内容容器,并把原有容器中的未读内容拷贝进去,最后再用新的容器替换掉原有的容器
fmt.Printf("The unread bytes of the buffer: %v\n", unreadBytes) // The unread bytes of the buffer: [97 98 99 100 101 102 88 0]
fmt.Println()
}
解析
上述代码在写入 “hijklmn” 后,获取的unreadBytes还是与写入前一样?
先要了解 bytes.Buffer 的扩容策略:
- Buffer 即可以手动扩容,也可以自动扩容
- 如果当前容量的一半,仍然大于或等于其现有长度再加上另需字节数的和,将未读内容拷贝到它的头部位置(即原未读内容和新内容将已读内容覆盖掉)
- 如果当前内容容器的容量小于新长度的二倍,扩容策略会建立一个新的内容容器,并把原有容器中的未读内容拷贝进去,最后用新的容器代替原有的容器
根据bytes.Buffer的扩容策略,上述切片访问和操纵它的底层数据仍然是原容器的内容。
Bufio 示例
代码
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
comment := "Package bufio implements buffered I/O. " +
"It wraps an io.Reader or io.Writer object, " +
"creating another object (Reader or Writer) that " +
"also implements the interface but provides buffering and " +
"some help for textual I/O."
basicReader := strings.NewReader(comment)
fmt.Printf("The size of basic reader: %d\n", basicReader.Size()) // 213
size := len(comment)
fmt.Printf("New a buffered reader with size %d ...\n", size) // 213
reader4 := bufio.NewReaderSize(basicReader, size)
fmt.Println()
peekNum := 7
fmt.Printf("Peek %d bytes ...\n", peekNum) // 7
bytes, err := reader4.Peek(peekNum)
if err != nil {
fmt.Printf("error: %v\n", err)
}
fmt.Printf("Peeked contents(%d): %q\n", len(bytes), bytes) // Peeked contents(7): "Package"
fmt.Println()
bytes = bytes[:cap(bytes)] // 存在内容泄露
fmt.Printf("The all of the contents in the buffer:\n%q\n", bytes) // 获取到所有的值
fmt.Println()
blank := byte(' ') // 存在内容泄露,通过bytes值而修改到原bufio值
fmt.Printf("Set blanks into the contents in the buffer ...")
for _, i := range []int{55, 65, 57, 58, 66, 67, 68} {
bytes[i] = blank
}
fmt.Println()
peekNum = size
fmt.Printf("Peek %d bytes ...\n", peekNum)
bytes, err = reader4.Peek(peekNum)
if err != nil {
fmt.Printf("error: %v\n", err)
}
fmt.Printf("Peeked contents(%d):\n%q\n", len(bytes), bytes) //"Package bufio implements buffered I/O. It wraps an io.R a r or i iter object, creating another object (Reader or Writer) that also implements the interface but provides buffering and some help for textual I/O."
fmt.Println()
}
解析
- 通过bufio包的peek函数读取到的是切片, 切片的底层是bufio的值
- 通过切片操作底层bufio的值
- bufio 类型中的Peek方法、ReadSlice方法和ReadLine方法都存在类似的问题