gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/skyfilereader.go (about) 1 package skymodules 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "mime" 10 "mime/multipart" 11 "net/http" 12 "os" 13 "sort" 14 15 "gitlab.com/NebulousLabs/errors" 16 "gitlab.com/SkynetLabs/skyd/build" 17 ) 18 19 var ( 20 // ErrIllegalFormName is returned when the multipart form contains a part 21 // under an illegal form name, only 'files[]' or 'file' are allowed. 22 ErrIllegalFormName = errors.New("multipart file submitted under an illegal form name, allowed values are 'files[]' and 'file'") 23 24 // ErrEmptyFilename is returned when the multipart form contains a part 25 // with an empty filename 26 ErrEmptyFilename = errors.New("no filename provided") 27 28 // ErrSkyfileMetadataUnavailable is returned when the context passed to 29 // SkyfileMetadata is cancelled before the metadata became available 30 ErrSkyfileMetadataUnavailable = errors.New("metadata unavailable") 31 ) 32 33 type ( 34 // SkyfileUploadReader is an interface that wraps a reader, containing the 35 // Skyfile data, and adds a method to fetch the SkyfileMetadata. 36 SkyfileUploadReader interface { 37 SetReadBuffer(data []byte) 38 SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error) 39 io.Reader 40 } 41 42 // skyfileMultipartReader is a helper struct that implements the 43 // SkyfileUploadReader interface for a multipart upload. 44 // 45 // NOTE: reading from this object is not threadsafe and thus should not be 46 // done from more than one thread if you want the reads to be deterministic. 47 skyfileMultipartReader struct { 48 reader *multipart.Reader 49 readBuf []byte 50 51 currLen uint64 52 currOff uint64 53 currPart *multipart.Part 54 55 metadata SkyfileMetadata 56 metadataAvail chan struct{} 57 } 58 59 // skyfileReader is a helper struct that implements the SkyfileUploadReader 60 // interface for a regular upload 61 // 62 // NOTE: reading from this object is not threadsafe and thus should not be 63 // done from more than one thread if you want the reads to be deterministic. 64 skyfileReader struct { 65 reader io.Reader 66 readBuf []byte 67 68 currLen uint64 69 70 metadata SkyfileMetadata 71 metadataAvail chan struct{} 72 } 73 ) 74 75 // NewSkyfileReader wraps the given reader and metadata and returns a 76 // SkyfileUploadReader 77 func NewSkyfileReader(reader io.Reader, sup SkyfileUploadParameters) SkyfileUploadReader { 78 // Define the skyfileReader 79 return &skyfileReader{ 80 reader: reader, 81 metadata: SkyfileMetadata{ 82 Filename: sup.Filename, 83 Mode: sup.Mode, 84 }, 85 metadataAvail: make(chan struct{}), 86 } 87 } 88 89 // SetReadBuffer sets the given bytes as the read buffer. The next reads will 90 // read from this buffer until it is entirely consumed, after which we continue 91 // reading from the underlying reader. 92 func (sr *skyfileReader) SetReadBuffer(b []byte) { 93 sr.readBuf = b 94 } 95 96 // SkyfileMetadata returns the SkyfileMetadata associated with this reader. 97 // 98 // NOTE: this method will block until the metadata becomes available 99 func (sr *skyfileReader) SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error) { 100 // Wait for the metadata to become available, that will be the case when 101 // the reader returned an EOF, or until the context is cancelled. 102 select { 103 case <-ctx.Done(): 104 return SkyfileMetadata{}, errors.AddContext(ErrSkyfileMetadataUnavailable, "context cancelled") 105 case <-sr.metadataAvail: 106 } 107 108 return sr.metadata, nil 109 } 110 111 // Read implements the io.Reader part of the interface and reads data from the 112 // underlying reader. 113 func (sr *skyfileReader) Read(p []byte) (n int, err error) { 114 if len(sr.readBuf) > 0 { 115 n = copy(p, sr.readBuf) 116 sr.readBuf = sr.readBuf[n:] 117 if len(sr.readBuf) == 0 { 118 sr.readBuf = nil // reset for GC 119 } 120 } 121 122 // check if we've already read until EOF, that will be the case if 123 // `metadataAvail` is closed. 124 select { 125 case <-sr.metadataAvail: 126 return n, io.EOF 127 default: 128 } 129 130 // return early if possible 131 if n == len(p) { 132 return 133 } 134 135 var nn int 136 nn, err = sr.reader.Read(p[n:]) 137 n += nn 138 sr.currLen += uint64(nn) 139 140 if errors.Contains(err, io.EOF) { 141 close(sr.metadataAvail) 142 sr.metadata.Length = sr.currLen 143 } 144 return 145 } 146 147 // NewMultipartReader creates a multipart.Reader from an io.Reader and the 148 // provided subfiles. This reader can then be used to create 149 // a NewSkyfileMultipartReader. 150 func NewMultipartReader(reader io.Reader, subFiles SkyfileSubfiles) (*multipart.Reader, error) { 151 // Read data from reader 152 data, err := ioutil.ReadAll(reader) 153 if err != nil { 154 return nil, errors.AddContext(err, "unable to read data from reader") 155 } 156 157 // Sort the subFiles by offset 158 subFilesArray := make([]SkyfileSubfileMetadata, 0, len(subFiles)) 159 for _, sfm := range subFiles { 160 subFilesArray = append(subFilesArray, sfm) 161 } 162 sort.Slice(subFilesArray, func(i, j int) bool { 163 return subFilesArray[i].Offset < subFilesArray[j].Offset 164 }) 165 166 // Build multipart reader from subFiles 167 body := new(bytes.Buffer) 168 writer := multipart.NewWriter(body) 169 var offset uint64 170 for _, sfm := range subFilesArray { 171 _, err = AddMultipartFile(writer, data[sfm.Offset:sfm.Offset+sfm.Len], "files[]", sfm.Filename, DefaultFilePerm, &offset) 172 if err != nil { 173 return nil, errors.AddContext(err, "unable to add multipart file") 174 } 175 } 176 multiReader := multipart.NewReader(body, writer.Boundary()) 177 if err = writer.Close(); err != nil { 178 return nil, errors.AddContext(err, "unable to close writer") 179 } 180 return multiReader, nil 181 } 182 183 // NewSkyfileMultipartReader wraps the given reader and returns a 184 // SkyfileUploadReader. By reading from this reader until an EOF is reached, the 185 // SkyfileMetadata will be constructed incrementally every time a new Part is 186 // read. 187 func NewSkyfileMultipartReader(reader *multipart.Reader, sup SkyfileUploadParameters) SkyfileUploadReader { 188 return newSkyfileMultipartReader(reader, sup) 189 } 190 191 // NewSkyfileMultipartReaderFromRequest generates a multipart reader from the 192 // http request and returns a SkyfileUploadReader. 193 func NewSkyfileMultipartReaderFromRequest(req *http.Request, sup SkyfileUploadParameters) (SkyfileUploadReader, error) { 194 // Error checks on request pulled from http package MultipartReader() 195 // http.Request method. 196 // 197 // https://golang.org/src/net/http/request.go?s=16785:16847#L453 198 if req.MultipartForm != nil { 199 return nil, errors.New("http: multipart previously handled") 200 } 201 req.MultipartForm = &multipart.Form{ 202 Value: make(map[string][]string), 203 File: make(map[string][]*multipart.FileHeader), 204 } 205 v := req.Header.Get("Content-Type") 206 if v == "" { 207 return nil, http.ErrNotMultipart 208 } 209 d, params, err := mime.ParseMediaType(v) 210 allowMixed := true // pulled from http package 211 if err != nil || !(d == "multipart/form-data" || allowMixed && d == "multipart/mixed") { 212 return nil, http.ErrNotMultipart 213 } 214 boundary, ok := params["boundary"] 215 if !ok { 216 return nil, http.ErrMissingBoundary 217 } 218 219 // Create the multipart reader. 220 mpr := multipart.NewReader(req.Body, boundary) 221 return NewSkyfileMultipartReader(mpr, sup), nil 222 } 223 224 // newSkyfileMultipartReader wraps the given reader and returns a 225 // SkyfileUploadReader. By reading from this reader until an EOF is reached, the 226 // SkyfileMetadata will be constructed incrementally every time a new Part is 227 // read. 228 func newSkyfileMultipartReader(reader *multipart.Reader, sup SkyfileUploadParameters) *skyfileMultipartReader { 229 return &skyfileMultipartReader{ 230 reader: reader, 231 metadata: SkyfileMetadata{ 232 Filename: sup.Filename, 233 Mode: sup.Mode, 234 DefaultPath: sup.DefaultPath, 235 DisableDefaultPath: sup.DisableDefaultPath, 236 TryFiles: sup.TryFiles, 237 ErrorPages: sup.ErrorPages, 238 Subfiles: make(SkyfileSubfiles), 239 }, 240 metadataAvail: make(chan struct{}), 241 } 242 } 243 244 // SetReadBuffer sets the given bytes as the read buffer. The next reads will 245 // read from this buffer until it is entirely consumed, after which we continue 246 // reading from the underlying reader. 247 func (sr *skyfileMultipartReader) SetReadBuffer(b []byte) { 248 sr.readBuf = b 249 } 250 251 // SkyfileMetadata returns the SkyfileMetadata associated with this reader. 252 func (sr *skyfileMultipartReader) SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error) { 253 // Wait for the metadata to become available, that will be the case when 254 // the reader returned an EOF, or until the context is cancelled. 255 select { 256 case <-ctx.Done(): 257 return SkyfileMetadata{}, errors.AddContext(ErrSkyfileMetadataUnavailable, "context cancelled") 258 case <-sr.metadataAvail: 259 } 260 261 // Check whether we found multipart files 262 if len(sr.metadata.Subfiles) == 0 { 263 return SkyfileMetadata{}, errors.New("could not find multipart file") 264 } 265 266 // Use the filename of the first subfile if it's not passed as query 267 // string parameter and there's only one subfile. 268 if sr.metadata.Filename == "" && len(sr.metadata.Subfiles) == 1 { 269 for _, sf := range sr.metadata.Subfiles { 270 sr.metadata.Filename = sf.Filename 271 break 272 } 273 } 274 275 // Set the total length as the sum of the lengths of every subfile 276 if sr.metadata.Length == 0 { 277 for _, sf := range sr.metadata.Subfiles { 278 sr.metadata.Length += sf.Len 279 } 280 } 281 282 return sr.metadata, nil 283 } 284 285 // Read implements the io.Reader part of the interface and reads data from the 286 // underlying multipart reader. While the data is being read, the metadata is 287 // being constructed. 288 func (sr *skyfileMultipartReader) Read(p []byte) (n int, err error) { 289 if len(sr.readBuf) > 0 { 290 n = copy(p, sr.readBuf) 291 sr.readBuf = sr.readBuf[n:] 292 if len(sr.readBuf) == 0 { 293 sr.readBuf = nil // reset for GC 294 } 295 } 296 297 // check if we've already read until EOF, that will be the case if 298 // `metadataAvail` is closed. 299 select { 300 case <-sr.metadataAvail: 301 return n, io.EOF 302 default: 303 } 304 305 for n < len(p) && err == nil { 306 // only read the next part if the current part is not set 307 if sr.currPart == nil { 308 sr.currPart, err = sr.reader.NextPart() 309 if err != nil { 310 // only when `NextPart` errors out we want to signal the 311 // metadata is ready, on any error not only EOF 312 close(sr.metadataAvail) 313 break 314 } 315 sr.currOff += sr.currLen 316 sr.currLen = 0 317 318 // verify the multipart file is submitted under the expected name 319 if !isLegalFormName(sr.currPart.FormName()) { 320 err = ErrIllegalFormName 321 break 322 } 323 } 324 325 // read data from the part 326 var nn int 327 nn, err = sr.currPart.Read(p[n:]) 328 n += nn 329 330 // update the length 331 sr.currLen += uint64(nn) 332 333 // ignore the EOF to continue reading from the next part if necessary, 334 if err == io.EOF { 335 err = nil 336 337 // create the metadata for the current subfile before resetting the 338 // current part 339 err = sr.createSubfileFromCurrPart() 340 if err != nil { 341 break 342 } 343 sr.currPart = nil 344 } 345 } 346 347 return 348 } 349 350 // createSubfileFromCurrPart adds a subfile for the current part. 351 func (sr *skyfileMultipartReader) createSubfileFromCurrPart() error { 352 // sanity check the reader has a current part set 353 if sr.currPart == nil { 354 build.Critical("createSubfileFromCurrPart called when currPart is nil") 355 return errors.New("could not create metadata for subfile") 356 } 357 358 // parse the mode from the part header 359 mode, err := parseMode(sr.currPart.Header.Get("Mode")) 360 if err != nil { 361 return errors.AddContext(err, "failed to parse file mode") 362 } 363 364 // parse the filename 365 // WARNING: don't use currPart.Filename() here since it caused 366 // unexpected behaviour on some deploys. Paths were trimmed from the 367 // filename, flattening the directory structure of the upload. 368 values := sr.currPart.Header.Get("Content-Disposition") 369 _, m, err := mime.ParseMediaType(values) 370 if err != nil { 371 return errors.AddContext(err, "failed to parse media type for subfile") 372 } 373 filename, exists := m["filename"] 374 if !exists || filename == "" { 375 return ErrEmptyFilename 376 } 377 378 sr.metadata.Subfiles[filename] = SkyfileSubfileMetadata{ 379 FileMode: mode, 380 Filename: filename, 381 ContentType: sr.currPart.Header.Get("Content-Type"), 382 Offset: sr.currOff, 383 Len: sr.currLen, 384 } 385 return nil 386 } 387 388 // isLegalFormName is a helper function that returns true if the given form name 389 // is allowed to submit a Skyfile subfile. 390 func isLegalFormName(formName string) bool { 391 return formName == "file" || formName == "files[]" 392 } 393 394 // parseMode is a helper function that parses an os.FileMode from the given 395 // string. 396 func parseMode(modeStr string) (os.FileMode, error) { 397 var mode os.FileMode 398 if modeStr != "" { 399 _, err := fmt.Sscanf(modeStr, "%o", &mode) 400 if err != nil { 401 return mode, err 402 } 403 } 404 return mode, nil 405 }