@@ -20,6 +20,8 @@ import (
2020 "io"
2121 "math"
2222 "strconv"
23+ "strings"
24+ "unicode/utf8"
2325
2426 dto "github.com/prometheus/client_model/go"
2527)
@@ -37,6 +39,15 @@ func MetricFamilyToOpenMetrics20(out io.Writer, in *dto.MetricFamily, options ..
3739 if name == "" {
3840 return 0 , fmt .Errorf ("MetricFamily has no name: %s" , in )
3941 }
42+ if ! utf8 .ValidString (name ) || strings .ContainsAny (name , "\n \r " ) {
43+ return 0 , fmt .Errorf ("MetricFamily name %q is not valid UTF-8 or contains raw newlines" , name )
44+ }
45+ if in .Help != nil && ! utf8 .ValidString (* in .Help ) {
46+ return 0 , fmt .Errorf ("MetricFamily help %q is not valid UTF-8" , * in .Help )
47+ }
48+ if in .Unit != nil && ! utf8 .ValidString (* in .Unit ) {
49+ return 0 , fmt .Errorf ("MetricFamily unit %q is not valid UTF-8" , * in .Unit )
50+ }
4051
4152 // Try the interface upgrade. If it doesn't work, we'll use a
4253 // bufio.Writer from the sync.Pool.
@@ -150,11 +161,11 @@ func MetricFamilyToOpenMetrics20(out io.Writer, in *dto.MetricFamily, options ..
150161
151162 // Finally the samples, one line for each.
152163 for _ , metric := range in .Metric {
164+ if err := validateMetric20 (name , metricType , metric ); err != nil {
165+ return written , err
166+ }
153167 switch metricType {
154168 case dto .MetricType_COUNTER :
155- if metric .Counter == nil {
156- return written , fmt .Errorf ("expected counter in metric %s %s" , name , metric )
157- }
158169 n , err = writeOpenMetrics20Sample (w , name , metric , metric .Counter .GetValue (), 0 , false , metric .Counter .Exemplar )
159170 case dto .MetricType_GAUGE :
160171 if metric .Gauge == nil {
@@ -334,3 +345,70 @@ func writeCompositeHistogram(w enhancedWriter, name string, metric *dto.Metric,
334345 _ = isGauge
335346 return 0 , errors .New ("histogram not implemented yet" )
336347}
348+
349+ func validateMetric20 (name string , metricType dto.MetricType , metric * dto.Metric ) error {
350+ if err := validateLabels20 (metric .Label ); err != nil {
351+ return fmt .Errorf ("invalid label in metric %s: %w" , name , err )
352+ }
353+
354+ switch metricType {
355+ case dto .MetricType_COUNTER :
356+ if metric .Counter == nil {
357+ return fmt .Errorf ("expected counter in metric %s %s" , name , metric )
358+ }
359+ val := metric .Counter .GetValue ()
360+ if math .IsNaN (val ) {
361+ return fmt .Errorf ("counter value cannot be NaN in metric %s" , name )
362+ }
363+ if val < 0 {
364+ return fmt .Errorf ("counter value cannot be negative (%g) in metric %s" , val , name )
365+ }
366+ if metric .Counter .CreatedTimestamp != nil {
367+ if err := metric .Counter .CreatedTimestamp .CheckValid (); err != nil {
368+ return fmt .Errorf ("invalid created timestamp in metric %s: %w" , name , err )
369+ }
370+ }
371+ if ex := metric .Counter .Exemplar ; ex != nil && ex .Timestamp != nil {
372+ if err := validateExemplar20 (ex ); err != nil {
373+ return fmt .Errorf ("invalid exemplar in metric %s: %w" , name , err )
374+ }
375+ }
376+ case dto .MetricType_GAUGE :
377+ if metric .Gauge == nil {
378+ return fmt .Errorf ("expected gauge in metric %s %s" , name , metric )
379+ }
380+ case dto .MetricType_UNTYPED :
381+ if metric .Untyped == nil {
382+ return fmt .Errorf ("expected untyped in metric %s %s" , name , metric )
383+ }
384+ }
385+ return nil
386+ }
387+
388+ func validateLabels20 (labels []* dto.LabelPair ) error {
389+ seen := make (map [string ]struct {}, len (labels ))
390+ for _ , lp := range labels {
391+ lname := lp .GetName ()
392+ if lname == "" {
393+ return errors .New ("label name cannot be empty" )
394+ }
395+ if ! utf8 .ValidString (lname ) || strings .ContainsAny (lname , "\n \r " ) {
396+ return fmt .Errorf ("label name %q is not valid UTF-8 or contains raw newlines" , lname )
397+ }
398+ if ! utf8 .ValidString (lp .GetValue ()) {
399+ return fmt .Errorf ("label value %q is not valid UTF-8" , lp .GetValue ())
400+ }
401+ if _ , ok := seen [lname ]; ok {
402+ return fmt .Errorf ("duplicate label name %q" , lname )
403+ }
404+ seen [lname ] = struct {}{}
405+ }
406+ return nil
407+ }
408+
409+ func validateExemplar20 (e * dto.Exemplar ) error {
410+ if err := e .Timestamp .CheckValid (); err != nil {
411+ return err
412+ }
413+ return validateLabels20 (e .Label )
414+ }
0 commit comments