@@ -27,6 +27,7 @@ import (
2727 "io"
2828 "io/ioutil"
2929 "log"
30+ "math"
3031 "math/rand"
3132 "mime"
3233 "mime/multipart"
@@ -219,7 +220,11 @@ func (s *submitServer) handleSubmission(w http.ResponseWriter, req *http.Request
219220 listingURL := s .apiPrefix + "/listing/" + prefix
220221 log .Println ("Handling report submission; listing URI will be" , listingURL )
221222
222- p := parseRequest (w , req , reportDir )
223+ maxLogLines := math .MaxInt
224+ if s .cfg .MaxLogLines != nil {
225+ maxLogLines = * s .cfg .MaxLogLines
226+ }
227+ p := parseRequest (w , req , reportDir , maxLogLines )
223228 if p == nil {
224229 // parseRequest already wrote an error, but now let's delete the
225230 // useless report dir
@@ -279,7 +284,7 @@ func writeError(w http.ResponseWriter, status int, response submitErrorResponse)
279284
280285// parseRequest attempts to parse a received request as a bug report. If
281286// the request cannot be parsed, it responds with an error and returns nil.
282- func parseRequest (w http.ResponseWriter , req * http.Request , reportDir string ) * payload {
287+ func parseRequest (w http.ResponseWriter , req * http.Request , reportDir string , maxloglines int ) * payload {
283288 length , err := strconv .Atoi (req .Header .Get ("Content-Length" ))
284289 if err != nil {
285290 log .Println ("Couldn't parse content-length" , err )
@@ -296,7 +301,7 @@ func parseRequest(w http.ResponseWriter, req *http.Request, reportDir string) *p
296301 if contentType != "" {
297302 d , _ , _ := mime .ParseMediaType (contentType )
298303 if d == "multipart/form-data" {
299- p , err1 := parseMultipartRequest (w , req , reportDir )
304+ p , err1 := parseMultipartRequest (w , req , reportDir , maxloglines )
300305 if err1 != nil {
301306 log .Println ("Error parsing multipart data:" , err1 )
302307 writeError (w , 400 , submitErrorResponse {Error : "Bad multipart data" , ErrorCode : ErrCodeBadContent })
@@ -306,7 +311,7 @@ func parseRequest(w http.ResponseWriter, req *http.Request, reportDir string) *p
306311 }
307312 }
308313
309- p , err := parseJSONRequest (w , req , reportDir )
314+ p , err := parseJSONRequest (w , req , reportDir , maxloglines )
310315 if err != nil {
311316 log .Println ("Error parsing JSON body" , err )
312317 writeError (w , 400 , submitErrorResponse {Error : fmt .Sprintf ("Could not decode payload: %s" , err .Error ()), ErrorCode : ErrCodeBadContent })
@@ -323,7 +328,7 @@ func parseUserAgent(userAgent string) string {
323328 return fmt .Sprintf (`%s on %s running on %s device` , client .UserAgent .ToString (), client .Os .ToString (), client .Device .ToString ())
324329}
325330
326- func parseJSONRequest (_ http.ResponseWriter , req * http.Request , reportDir string ) (* payload , error ) {
331+ func parseJSONRequest (_ http.ResponseWriter , req * http.Request , reportDir string , maxloglines int ) (* payload , error ) {
327332 var p jsonPayload
328333 if err := json .NewDecoder (req .Body ).Decode (& p ); err != nil {
329334 return nil , err
@@ -339,14 +344,20 @@ func parseJSONRequest(_ http.ResponseWriter, req *http.Request, reportDir string
339344 parsed .Data = p .Data
340345 }
341346
347+ loglinesWritten := 0
342348 for i , logfile := range p .Logs {
343349 buf := bytes .NewBufferString (logfile .Lines )
344- leafName , err := saveLogPart (i , logfile .ID , buf , reportDir )
350+ leafName , loglines , err := saveLogPart (i , logfile .ID , buf , reportDir , maxloglines - loglinesWritten )
345351 if err != nil {
346352 log .Printf ("Error saving log %s: %v" , leafName , err )
347353 parsed .LogErrors = append (parsed .LogErrors , fmt .Sprintf ("Error saving log %s: %v" , leafName , err ))
348354 } else {
349355 parsed .Logs = append (parsed .Logs , leafName )
356+ loglinesWritten += loglines
357+ }
358+
359+ if loglinesWritten >= maxloglines {
360+ return nil , fmt .Errorf ("too many log lines in rageshake" )
350361 }
351362 }
352363
@@ -363,7 +374,7 @@ func parseJSONRequest(_ http.ResponseWriter, req *http.Request, reportDir string
363374 return & parsed , nil
364375}
365376
366- func parseMultipartRequest (_ http.ResponseWriter , req * http.Request , reportDir string ) (* payload , error ) {
377+ func parseMultipartRequest (_ http.ResponseWriter , req * http.Request , reportDir string , maxloglines int ) (* payload , error ) {
367378 rdr , err := req .MultipartReader ()
368379 if err != nil {
369380 return nil , err
@@ -373,6 +384,7 @@ func parseMultipartRequest(_ http.ResponseWriter, req *http.Request, reportDir s
373384 Data : make (map [string ]string ),
374385 }
375386
387+ loglinesWritten := 0
376388 for true {
377389 part , err := rdr .NextPart ()
378390 if err == io .EOF {
@@ -381,14 +393,25 @@ func parseMultipartRequest(_ http.ResponseWriter, req *http.Request, reportDir s
381393 return nil , err
382394 }
383395
384- if err = parseFormPart (part , & p , reportDir ); err != nil {
396+ loglines , err := parseFormPart (part , & p , reportDir , maxloglines - loglinesWritten )
397+ if err != nil {
385398 return nil , err
386399 }
400+ log .Printf ("Part %s: %d lines\n " , part .FormName (), loglines )
401+
402+ loglinesWritten += loglines
403+ if loglinesWritten >= maxloglines {
404+ return nil , fmt .Errorf ("too many log lines in rageshake" )
405+ }
387406 }
388407 return & p , nil
389408}
390409
391- func parseFormPart (part * multipart.Part , p * payload , reportDir string ) error {
410+ // parseFormPart handles a single part of a multipart form. If the part is a log, the number of lines copied is limited
411+ // to `maxloglines`.
412+ //
413+ // It returns the number of log lines saved, or 0 if the part is not a log.
414+ func parseFormPart (part * multipart.Part , p * payload , reportDir string , maxloglines int ) (int , error ) {
392415 defer part .Close ()
393416 field := part .FormName ()
394417 partName := part .FileName ()
@@ -407,7 +430,7 @@ func parseFormPart(part *multipart.Part, p *payload, reportDir string) error {
407430 log .Printf ("Error unzipping %s: %v" , partName , err )
408431
409432 p .LogErrors = append (p .LogErrors , fmt .Sprintf ("Error unzipping %s: %v" , partName , err ))
410- return nil
433+ return 0 , nil
411434 }
412435 defer zrdr .Close ()
413436 partReader = zrdr
@@ -424,27 +447,27 @@ func parseFormPart(part *multipart.Part, p *payload, reportDir string) error {
424447 } else {
425448 p .Files = append (p .Files , leafName )
426449 }
427- return nil
450+ return 0 , nil
428451 }
429452
430453 if field == "log" || field == "compressed-log" {
431- leafName , err := saveLogPart (len (p .Logs ), partName , partReader , reportDir )
454+ leafName , lines , err := saveLogPart (len (p .Logs ), partName , partReader , reportDir , maxloglines )
432455 if err != nil {
433456 log .Printf ("Error saving %s %s: %v" , field , partName , err )
434457 p .LogErrors = append (p .LogErrors , fmt .Sprintf ("Error saving %s: %v" , partName , err ))
435458 } else {
436459 p .Logs = append (p .Logs , leafName )
437460 }
438- return nil
461+ return lines , nil
439462 }
440463
441464 b , err := ioutil .ReadAll (partReader )
442465 if err != nil {
443- return err
466+ return 0 , err
444467 }
445468 data := string (b )
446469 formPartToPayload (field , data , p )
447- return nil
470+ return 0 , nil
448471}
449472
450473// formPartToPayload updates the relevant part of *p from a name/value pair
@@ -509,10 +532,10 @@ func saveFormPart(leafName string, reader io.Reader, reportDir string) (string,
509532// '.'
510533var logRegexp = regexp .MustCompile (`^[a-zA-Z0-9_-][a-zA-Z0-9_.-]*\.(log|txt)(\.gz)?$` )
511534
512- // saveLogPart saves a log upload to the report directory.
535+ // saveLogPart saves a log upload to the report directory, up to the given maximum number of lines .
513536//
514- // Returns the leafname of the saved file.
515- func saveLogPart (logNum int , filename string , reader io.Reader , reportDir string ) (string , error ) {
537+ // Returns the leafname of the saved file and the number of lines saved .
538+ func saveLogPart (logNum int , filename string , reader io.Reader , reportDir string , maxlines int ) (string , int , error ) {
516539 // pick a name to save the log file with.
517540 //
518541 // some clients use sensible names (foo.N.log), which we preserve. For
@@ -536,19 +559,62 @@ func saveLogPart(logNum int, filename string, reader io.Reader, reportDir string
536559
537560 f , err := os .Create (fullname )
538561 if err != nil {
539- return "" , err
562+ return "" , 0 , err
540563 }
541564 defer f .Close ()
542565
543566 gz := gzip .NewWriter (f )
544567 defer gz .Close ()
545568
546- _ , err = io . Copy (gz , reader )
569+ lines , err := copyLines (gz , reader , maxlines )
547570 if err != nil {
548- return "" , err
571+ return "" , 0 , err
549572 }
550573
551- return leafName , nil
574+ return leafName , lines , nil
575+ }
576+
577+ // copyLines copies lines of text from src to dst, up to maxlines.
578+ //
579+ // It returns the number of lines copied.
580+ func copyLines (dst io.Writer , src io.Reader , maxlines int ) (int , error ) {
581+ count := 0
582+ scanLines := func (data []byte , atEOF bool ) (advance int , token []byte , err error ) {
583+ if atEOF && len (data ) == 0 {
584+ return 0 , nil , nil
585+ }
586+ if i := bytes .IndexByte (data , '\n' ); i >= 0 {
587+ // We have a full newline-terminated line.
588+ return i + 1 , data [0 : i + 1 ], nil
589+ }
590+ // If we're at EOF, we have a final, non-terminated line. Return it.
591+ if atEOF {
592+ return len (data ), data , nil
593+ }
594+ // Request more data.
595+ return 0 , nil , nil
596+ }
597+
598+ scanner := bufio .NewScanner (src )
599+ scanner .Split (scanLines )
600+ for scanner .Scan () {
601+ if count >= maxlines {
602+ break
603+ }
604+ line := scanner .Bytes ()
605+ m , err := dst .Write (line )
606+ if err != nil {
607+ return 0 , err
608+ }
609+ if m < len (line ) {
610+ return 0 , io .ErrShortWrite
611+ }
612+ count += 1
613+ }
614+ if err := scanner .Err (); err != nil {
615+ return 0 , fmt .Errorf ("error reading log submission: %w" , err )
616+ }
617+ return count , nil
552618}
553619
554620func (s * submitServer ) saveReport (ctx context.Context , p payload , reportDir , listingURL string ) (* submitResponse , error ) {
0 commit comments