8 // https://amp.dev/boilerplate/
9 // https://amp.dev/documentation/guides-and-tutorials/learn/spec/amp-boilerplate/?format=websites
10 // https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/?format=websites#the-amp-html-format
12 boilerplateStart
= `<!doctype html>
15 <meta charset="utf-8">
16 <script async src="https://cdn.ampproject.org/v0.js"></script>
17 <link rel="canonical" href="#">
18 <meta name="viewport" content="width=device-width">
19 <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
23 boilerplateEnd
= `</body>
28 // We restrict the amount of text may go inside an HTML element, in
29 // order to limit the amount a decoder may have to buffer.
30 elementSizeLimit
= 32 * 1024
32 // The payload is conceptually a long base64-encoded string, but we
33 // break the string into short chunks separated by whitespace. This is
34 // to protect against modification by AMP caches, which reportedly may
35 // truncate long words in text:
36 // https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/25985#note_2592348
39 // We set the number of chunks per element so as to stay under
40 // elementSizeLimit. Here, we assume that there is 1 byte of whitespace
41 // after each chunk (with an additional whitespace byte at the beginning
43 chunksPerElement
= (elementSizeLimit
- 1) / (bytesPerChunk
+ 1)
46 // The AMP armor encoder is a chain of a base64 encoder (base64.NewEncoder) and
47 // an HTML element encoder (elementEncoder). A top-level encoder (armorEncoder)
48 // coordinates these two, and handles prepending and appending the AMP
49 // boilerplate. armorEncoder's Write method writes data into the base64 encoder,
50 // where it makes its way through the chain.
52 // NewEncoder returns a new AMP armor encoder. Anything written to the returned
53 // io.WriteCloser will be encoded and written to w. The caller must call Close
54 // to flush any partially written data and output the AMP boilerplate trailer.
55 func NewEncoder(w io
.Writer
) (io
.WriteCloser
, error
) {
56 // Immediately write the AMP boilerplate header.
57 _
, err
:= w
.Write([]byte(boilerplateStart
))
62 element
:= &elementEncoder
{w
: w
}
63 // Write a server–client protocol version indicator, outside the base64
65 _
, err
= element
.Write([]byte("0"))
70 base64
:= base64
.NewEncoder(base64
.StdEncoding
, element
)
78 type armorEncoder
struct {
80 element
*elementEncoder
84 func (enc
*armorEncoder
) Write(p
[]byte) (int, error
) {
85 // Write into the chain base64 | element | w.
86 return enc
.base64
.Write(p
)
89 func (enc
*armorEncoder
) Close() error
{
90 // Close the base64 encoder first, to flush out any buffered data and
92 err
:= enc
.base64
.Close()
97 // Next, close the element encoder, to close any open elements.
98 err
= enc
.element
.Close()
103 // Finally, output the AMP boilerplate trailer.
104 _
, err
= enc
.w
.Write([]byte(boilerplateEnd
))
112 // elementEncoder arranges written data into pre elements, with the text within
113 // separated into chunks. It does no HTML encoding, so data written must not
114 // contain any bytes that are meaningful in HTML.
115 type elementEncoder
struct {
121 func (enc
*elementEncoder
) Write(p
[]byte) (n
int, err error
) {
124 if enc
.elementCounter
== 0 && enc
.chunkCounter
== 0 {
125 _
, err
:= enc
.w
.Write([]byte("<pre>\n"))
131 n
:= bytesPerChunk
- enc
.chunkCounter
135 nn
, err
:= enc
.w
.Write(p
[:n
])
142 enc
.chunkCounter
+= n
143 if enc
.chunkCounter
>= bytesPerChunk
{
145 enc
.elementCounter
+= 1
146 nn
, err
= enc
.w
.Write([]byte("\n"))
153 if enc
.elementCounter
>= chunksPerElement
{
154 enc
.elementCounter
= 0
155 nn
, err
= enc
.w
.Write([]byte("</pre>\n"))
165 func (enc
*elementEncoder
) Close() error
{
167 if !(enc
.elementCounter
== 0 && enc
.chunkCounter
== 0) {
168 if enc
.chunkCounter
== 0 {
169 _
, err
= enc
.w
.Write([]byte("</pre>\n"))
171 _
, err
= enc
.w
.Write([]byte("\n</pre>\n"))
174 _
, err2
:= enc
.w
.Write([]byte(boilerplateEnd
))