github.com/cortesi/devd@v0.0.0-20200427000907-c1a3bfba27d8/inject/inject.go (about) 1 // Package inject gives the ability to copy data and inject a payload before a 2 // specified marker. In order to let the user respond to the change in length, 3 // the API is split into two parts - Sniff checks whether the marker occurs 4 // within a specified number of initial bytes, and Copy sends the data to the 5 // destination. 6 // 7 // The package tries to avoid double-injecting a payload by checking whether 8 // the payload occurs within the first Within + len(Payload) bytes. 9 package inject 10 11 import ( 12 "bytes" 13 "fmt" 14 "html/template" 15 "io" 16 "net/http" 17 "regexp" 18 "strings" 19 ) 20 21 // CopyInject copies data, and injects a payload before a specified marker 22 type CopyInject struct { 23 // Number of initial bytes within which to search for marker 24 Within int 25 // Only inject in responses with this content type 26 ContentType string 27 // A marker, BEFORE which the payload is inserted 28 Marker *regexp.Regexp 29 // The payload to be inserted 30 Payload []byte 31 } 32 33 type Injector interface { 34 Copy(dst io.Writer) (int64, error) 35 Extra() int 36 Found() bool 37 } 38 39 // realInjector keeps injection state 40 type realInjector struct { 41 // Has the marker been found? 42 found bool 43 conf *CopyInject 44 src io.Reader 45 offset int 46 sniffedData []byte 47 } 48 49 type nopInjector struct { 50 src io.Reader 51 } 52 53 func (injector *nopInjector) Copy(dst io.Writer) (int64, error) { 54 return io.Copy(dst, injector.src) 55 } 56 57 func (injector *nopInjector) Extra() int { 58 return 0 59 } 60 61 func (injector *nopInjector) Found() bool { 62 return false 63 } 64 65 // Extra reports the number of extra bytes that will be injected 66 func (injector *realInjector) Extra() int { 67 if injector.found { 68 return len(injector.conf.Payload) 69 } 70 return 0 71 } 72 73 func (injector *realInjector) Found() bool { 74 return injector.found 75 } 76 77 func min(a int, b int) int { 78 if a > b { 79 return b 80 } 81 return a 82 } 83 84 // Sniff reads the first SniffLen bytes of the source, and checks for the 85 // marker. Returns an Injector instance. 86 func (ci *CopyInject) Sniff(src io.Reader, contentType string) (Injector, error) { 87 if !strings.Contains(contentType, ci.ContentType) { 88 return &nopInjector{src: src}, nil 89 } 90 91 injector := &realInjector{ 92 conf: ci, 93 src: src, 94 } 95 if ci.Within == 0 || ci.Marker == nil { 96 return injector, nil 97 } 98 buf := make([]byte, ci.Within+len(ci.Payload)) 99 n, err := io.ReadFull(src, buf) 100 if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF { 101 return nil, fmt.Errorf("inject could not read data to sniff: %s", err) 102 } 103 injector.sniffedData = buf[:n] 104 if bytes.Index(buf, ci.Payload) > -1 { 105 return injector, nil 106 } 107 loc := ci.Marker.FindIndex(injector.sniffedData[:min(n, ci.Within)]) 108 if loc != nil { 109 injector.found = true 110 injector.offset = loc[0] 111 } 112 return injector, nil 113 } 114 115 // ServeTemplate renders and serves a template to an http.ResponseWriter 116 func (ci *CopyInject) ServeTemplate(statuscode int, w http.ResponseWriter, t *template.Template, data interface{}) error { 117 buff := bytes.NewBuffer(make([]byte, 0, 0)) 118 err := t.Execute(buff, data) 119 if err != nil { 120 return err 121 } 122 123 length := buff.Len() 124 inj, err := ci.Sniff(buff, "text/html") 125 if err != nil { 126 return err 127 } 128 w.Header().Set( 129 "Content-Length", fmt.Sprintf("%d", length+inj.Extra()), 130 ) 131 w.WriteHeader(statuscode) 132 _, err = inj.Copy(w) 133 if err != nil { 134 return err 135 } 136 return nil 137 } 138 139 // Copy copies the data from src to dst, injecting the Payload if Sniff found 140 // the marker. 141 func (injector *realInjector) Copy(dst io.Writer) (int64, error) { 142 var preludeLen int64 143 if injector.found { 144 startn, err := io.Copy( 145 dst, 146 bytes.NewBuffer( 147 injector.sniffedData[:injector.offset], 148 ), 149 ) 150 if err != nil { 151 return startn, err 152 } 153 payloadn, err := io.Copy(dst, bytes.NewBuffer(injector.conf.Payload)) 154 if err != nil { 155 return startn + payloadn, err 156 } 157 endn, err := io.Copy( 158 dst, bytes.NewBuffer(injector.sniffedData[injector.offset:]), 159 ) 160 if err != nil { 161 return startn + payloadn + endn, err 162 } 163 preludeLen = startn + payloadn + endn 164 } else { 165 n, err := io.Copy(dst, bytes.NewBuffer(injector.sniffedData)) 166 if err != nil { 167 return n, err 168 } 169 preludeLen = int64(len(injector.sniffedData)) 170 } 171 n, err := io.Copy(dst, injector.src) 172 return n + preludeLen, err 173 }