github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/net/webdav/prop_test.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package webdav
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"reflect"
    12  	"sort"
    13  	"testing"
    14  
    15  	"golang.org/x/net/webdav/internal/xml"
    16  )
    17  
    18  func TestMemPS(t *testing.T) {
    19  	// calcProps calculates the getlastmodified and getetag DAV: property
    20  	// values in pstats for resource name in file-system fs.
    21  	calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error {
    22  		fi, err := fs.Stat(name)
    23  		if err != nil {
    24  			return err
    25  		}
    26  		for _, pst := range pstats {
    27  			for i, p := range pst.Props {
    28  				switch p.XMLName {
    29  				case xml.Name{Space: "DAV:", Local: "getlastmodified"}:
    30  					p.InnerXML = []byte(fi.ModTime().Format(http.TimeFormat))
    31  					pst.Props[i] = p
    32  				case xml.Name{Space: "DAV:", Local: "getetag"}:
    33  					if fi.IsDir() {
    34  						continue
    35  					}
    36  					etag, err := findETag(fs, ls, name, fi)
    37  					if err != nil {
    38  						return err
    39  					}
    40  					p.InnerXML = []byte(etag)
    41  					pst.Props[i] = p
    42  				}
    43  			}
    44  		}
    45  		return nil
    46  	}
    47  
    48  	const (
    49  		lockEntry = `` +
    50  			`<D:lockentry xmlns:D="DAV:">` +
    51  			`<D:lockscope><D:exclusive/></D:lockscope>` +
    52  			`<D:locktype><D:write/></D:locktype>` +
    53  			`</D:lockentry>`
    54  		statForbiddenError = `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`
    55  	)
    56  
    57  	type propOp struct {
    58  		op            string
    59  		name          string
    60  		pnames        []xml.Name
    61  		patches       []Proppatch
    62  		wantPnames    []xml.Name
    63  		wantPropstats []Propstat
    64  	}
    65  
    66  	testCases := []struct {
    67  		desc        string
    68  		noDeadProps bool
    69  		buildfs     []string
    70  		propOp      []propOp
    71  	}{{
    72  		desc:    "propname",
    73  		buildfs: []string{"mkdir /dir", "touch /file"},
    74  		propOp: []propOp{{
    75  			op:   "propname",
    76  			name: "/dir",
    77  			wantPnames: []xml.Name{
    78  				xml.Name{Space: "DAV:", Local: "resourcetype"},
    79  				xml.Name{Space: "DAV:", Local: "displayname"},
    80  				xml.Name{Space: "DAV:", Local: "supportedlock"},
    81  			},
    82  		}, {
    83  			op:   "propname",
    84  			name: "/file",
    85  			wantPnames: []xml.Name{
    86  				xml.Name{Space: "DAV:", Local: "resourcetype"},
    87  				xml.Name{Space: "DAV:", Local: "displayname"},
    88  				xml.Name{Space: "DAV:", Local: "getcontentlength"},
    89  				xml.Name{Space: "DAV:", Local: "getlastmodified"},
    90  				xml.Name{Space: "DAV:", Local: "getcontenttype"},
    91  				xml.Name{Space: "DAV:", Local: "getetag"},
    92  				xml.Name{Space: "DAV:", Local: "supportedlock"},
    93  			},
    94  		}},
    95  	}, {
    96  		desc:    "allprop dir and file",
    97  		buildfs: []string{"mkdir /dir", "write /file foobarbaz"},
    98  		propOp: []propOp{{
    99  			op:   "allprop",
   100  			name: "/dir",
   101  			wantPropstats: []Propstat{{
   102  				Status: http.StatusOK,
   103  				Props: []Property{{
   104  					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
   105  					InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
   106  				}, {
   107  					XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
   108  					InnerXML: []byte("dir"),
   109  				}, {
   110  					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
   111  					InnerXML: []byte(lockEntry),
   112  				}},
   113  			}},
   114  		}, {
   115  			op:   "allprop",
   116  			name: "/file",
   117  			wantPropstats: []Propstat{{
   118  				Status: http.StatusOK,
   119  				Props: []Property{{
   120  					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
   121  					InnerXML: []byte(""),
   122  				}, {
   123  					XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
   124  					InnerXML: []byte("file"),
   125  				}, {
   126  					XMLName:  xml.Name{Space: "DAV:", Local: "getcontentlength"},
   127  					InnerXML: []byte("9"),
   128  				}, {
   129  					XMLName:  xml.Name{Space: "DAV:", Local: "getlastmodified"},
   130  					InnerXML: nil, // Calculated during test.
   131  				}, {
   132  					XMLName:  xml.Name{Space: "DAV:", Local: "getcontenttype"},
   133  					InnerXML: []byte("text/plain; charset=utf-8"),
   134  				}, {
   135  					XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
   136  					InnerXML: nil, // Calculated during test.
   137  				}, {
   138  					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
   139  					InnerXML: []byte(lockEntry),
   140  				}},
   141  			}},
   142  		}, {
   143  			op:   "allprop",
   144  			name: "/file",
   145  			pnames: []xml.Name{
   146  				{"DAV:", "resourcetype"},
   147  				{"foo", "bar"},
   148  			},
   149  			wantPropstats: []Propstat{{
   150  				Status: http.StatusOK,
   151  				Props: []Property{{
   152  					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
   153  					InnerXML: []byte(""),
   154  				}, {
   155  					XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
   156  					InnerXML: []byte("file"),
   157  				}, {
   158  					XMLName:  xml.Name{Space: "DAV:", Local: "getcontentlength"},
   159  					InnerXML: []byte("9"),
   160  				}, {
   161  					XMLName:  xml.Name{Space: "DAV:", Local: "getlastmodified"},
   162  					InnerXML: nil, // Calculated during test.
   163  				}, {
   164  					XMLName:  xml.Name{Space: "DAV:", Local: "getcontenttype"},
   165  					InnerXML: []byte("text/plain; charset=utf-8"),
   166  				}, {
   167  					XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
   168  					InnerXML: nil, // Calculated during test.
   169  				}, {
   170  					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
   171  					InnerXML: []byte(lockEntry),
   172  				}}}, {
   173  				Status: http.StatusNotFound,
   174  				Props: []Property{{
   175  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   176  				}}},
   177  			},
   178  		}},
   179  	}, {
   180  		desc:    "propfind DAV:resourcetype",
   181  		buildfs: []string{"mkdir /dir", "touch /file"},
   182  		propOp: []propOp{{
   183  			op:     "propfind",
   184  			name:   "/dir",
   185  			pnames: []xml.Name{{"DAV:", "resourcetype"}},
   186  			wantPropstats: []Propstat{{
   187  				Status: http.StatusOK,
   188  				Props: []Property{{
   189  					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
   190  					InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
   191  				}},
   192  			}},
   193  		}, {
   194  			op:     "propfind",
   195  			name:   "/file",
   196  			pnames: []xml.Name{{"DAV:", "resourcetype"}},
   197  			wantPropstats: []Propstat{{
   198  				Status: http.StatusOK,
   199  				Props: []Property{{
   200  					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
   201  					InnerXML: []byte(""),
   202  				}},
   203  			}},
   204  		}},
   205  	}, {
   206  		desc:    "propfind unsupported DAV properties",
   207  		buildfs: []string{"mkdir /dir"},
   208  		propOp: []propOp{{
   209  			op:     "propfind",
   210  			name:   "/dir",
   211  			pnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
   212  			wantPropstats: []Propstat{{
   213  				Status: http.StatusNotFound,
   214  				Props: []Property{{
   215  					XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"},
   216  				}},
   217  			}},
   218  		}, {
   219  			op:     "propfind",
   220  			name:   "/dir",
   221  			pnames: []xml.Name{{"DAV:", "creationdate"}},
   222  			wantPropstats: []Propstat{{
   223  				Status: http.StatusNotFound,
   224  				Props: []Property{{
   225  					XMLName: xml.Name{Space: "DAV:", Local: "creationdate"},
   226  				}},
   227  			}},
   228  		}},
   229  	}, {
   230  		desc:    "propfind getetag for files but not for directories",
   231  		buildfs: []string{"mkdir /dir", "touch /file"},
   232  		propOp: []propOp{{
   233  			op:     "propfind",
   234  			name:   "/dir",
   235  			pnames: []xml.Name{{"DAV:", "getetag"}},
   236  			wantPropstats: []Propstat{{
   237  				Status: http.StatusNotFound,
   238  				Props: []Property{{
   239  					XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
   240  				}},
   241  			}},
   242  		}, {
   243  			op:     "propfind",
   244  			name:   "/file",
   245  			pnames: []xml.Name{{"DAV:", "getetag"}},
   246  			wantPropstats: []Propstat{{
   247  				Status: http.StatusOK,
   248  				Props: []Property{{
   249  					XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
   250  					InnerXML: nil, // Calculated during test.
   251  				}},
   252  			}},
   253  		}},
   254  	}, {
   255  		desc:        "proppatch property on no-dead-properties file system",
   256  		buildfs:     []string{"mkdir /dir"},
   257  		noDeadProps: true,
   258  		propOp: []propOp{{
   259  			op:   "proppatch",
   260  			name: "/dir",
   261  			patches: []Proppatch{{
   262  				Props: []Property{{
   263  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   264  				}},
   265  			}},
   266  			wantPropstats: []Propstat{{
   267  				Status: http.StatusForbidden,
   268  				Props: []Property{{
   269  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   270  				}},
   271  			}},
   272  		}, {
   273  			op:   "proppatch",
   274  			name: "/dir",
   275  			patches: []Proppatch{{
   276  				Props: []Property{{
   277  					XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
   278  				}},
   279  			}},
   280  			wantPropstats: []Propstat{{
   281  				Status:   http.StatusForbidden,
   282  				XMLError: statForbiddenError,
   283  				Props: []Property{{
   284  					XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
   285  				}},
   286  			}},
   287  		}},
   288  	}, {
   289  		desc:    "proppatch dead property",
   290  		buildfs: []string{"mkdir /dir"},
   291  		propOp: []propOp{{
   292  			op:   "proppatch",
   293  			name: "/dir",
   294  			patches: []Proppatch{{
   295  				Props: []Property{{
   296  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   297  					InnerXML: []byte("baz"),
   298  				}},
   299  			}},
   300  			wantPropstats: []Propstat{{
   301  				Status: http.StatusOK,
   302  				Props: []Property{{
   303  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   304  				}},
   305  			}},
   306  		}, {
   307  			op:     "propfind",
   308  			name:   "/dir",
   309  			pnames: []xml.Name{{Space: "foo", Local: "bar"}},
   310  			wantPropstats: []Propstat{{
   311  				Status: http.StatusOK,
   312  				Props: []Property{{
   313  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   314  					InnerXML: []byte("baz"),
   315  				}},
   316  			}},
   317  		}},
   318  	}, {
   319  		desc:    "proppatch dead property with failed dependency",
   320  		buildfs: []string{"mkdir /dir"},
   321  		propOp: []propOp{{
   322  			op:   "proppatch",
   323  			name: "/dir",
   324  			patches: []Proppatch{{
   325  				Props: []Property{{
   326  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   327  					InnerXML: []byte("baz"),
   328  				}},
   329  			}, {
   330  				Props: []Property{{
   331  					XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
   332  					InnerXML: []byte("xxx"),
   333  				}},
   334  			}},
   335  			wantPropstats: []Propstat{{
   336  				Status:   http.StatusForbidden,
   337  				XMLError: statForbiddenError,
   338  				Props: []Property{{
   339  					XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
   340  				}},
   341  			}, {
   342  				Status: StatusFailedDependency,
   343  				Props: []Property{{
   344  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   345  				}},
   346  			}},
   347  		}, {
   348  			op:     "propfind",
   349  			name:   "/dir",
   350  			pnames: []xml.Name{{Space: "foo", Local: "bar"}},
   351  			wantPropstats: []Propstat{{
   352  				Status: http.StatusNotFound,
   353  				Props: []Property{{
   354  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   355  				}},
   356  			}},
   357  		}},
   358  	}, {
   359  		desc:    "proppatch remove dead property",
   360  		buildfs: []string{"mkdir /dir"},
   361  		propOp: []propOp{{
   362  			op:   "proppatch",
   363  			name: "/dir",
   364  			patches: []Proppatch{{
   365  				Props: []Property{{
   366  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   367  					InnerXML: []byte("baz"),
   368  				}, {
   369  					XMLName:  xml.Name{Space: "spam", Local: "ham"},
   370  					InnerXML: []byte("eggs"),
   371  				}},
   372  			}},
   373  			wantPropstats: []Propstat{{
   374  				Status: http.StatusOK,
   375  				Props: []Property{{
   376  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   377  				}, {
   378  					XMLName: xml.Name{Space: "spam", Local: "ham"},
   379  				}},
   380  			}},
   381  		}, {
   382  			op:   "propfind",
   383  			name: "/dir",
   384  			pnames: []xml.Name{
   385  				{Space: "foo", Local: "bar"},
   386  				{Space: "spam", Local: "ham"},
   387  			},
   388  			wantPropstats: []Propstat{{
   389  				Status: http.StatusOK,
   390  				Props: []Property{{
   391  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   392  					InnerXML: []byte("baz"),
   393  				}, {
   394  					XMLName:  xml.Name{Space: "spam", Local: "ham"},
   395  					InnerXML: []byte("eggs"),
   396  				}},
   397  			}},
   398  		}, {
   399  			op:   "proppatch",
   400  			name: "/dir",
   401  			patches: []Proppatch{{
   402  				Remove: true,
   403  				Props: []Property{{
   404  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   405  				}},
   406  			}},
   407  			wantPropstats: []Propstat{{
   408  				Status: http.StatusOK,
   409  				Props: []Property{{
   410  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   411  				}},
   412  			}},
   413  		}, {
   414  			op:   "propfind",
   415  			name: "/dir",
   416  			pnames: []xml.Name{
   417  				{Space: "foo", Local: "bar"},
   418  				{Space: "spam", Local: "ham"},
   419  			},
   420  			wantPropstats: []Propstat{{
   421  				Status: http.StatusNotFound,
   422  				Props: []Property{{
   423  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   424  				}},
   425  			}, {
   426  				Status: http.StatusOK,
   427  				Props: []Property{{
   428  					XMLName:  xml.Name{Space: "spam", Local: "ham"},
   429  					InnerXML: []byte("eggs"),
   430  				}},
   431  			}},
   432  		}},
   433  	}, {
   434  		desc:    "propname with dead property",
   435  		buildfs: []string{"touch /file"},
   436  		propOp: []propOp{{
   437  			op:   "proppatch",
   438  			name: "/file",
   439  			patches: []Proppatch{{
   440  				Props: []Property{{
   441  					XMLName:  xml.Name{Space: "foo", Local: "bar"},
   442  					InnerXML: []byte("baz"),
   443  				}},
   444  			}},
   445  			wantPropstats: []Propstat{{
   446  				Status: http.StatusOK,
   447  				Props: []Property{{
   448  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   449  				}},
   450  			}},
   451  		}, {
   452  			op:   "propname",
   453  			name: "/file",
   454  			wantPnames: []xml.Name{
   455  				xml.Name{Space: "DAV:", Local: "resourcetype"},
   456  				xml.Name{Space: "DAV:", Local: "displayname"},
   457  				xml.Name{Space: "DAV:", Local: "getcontentlength"},
   458  				xml.Name{Space: "DAV:", Local: "getlastmodified"},
   459  				xml.Name{Space: "DAV:", Local: "getcontenttype"},
   460  				xml.Name{Space: "DAV:", Local: "getetag"},
   461  				xml.Name{Space: "DAV:", Local: "supportedlock"},
   462  				xml.Name{Space: "foo", Local: "bar"},
   463  			},
   464  		}},
   465  	}, {
   466  		desc:    "proppatch remove unknown dead property",
   467  		buildfs: []string{"mkdir /dir"},
   468  		propOp: []propOp{{
   469  			op:   "proppatch",
   470  			name: "/dir",
   471  			patches: []Proppatch{{
   472  				Remove: true,
   473  				Props: []Property{{
   474  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   475  				}},
   476  			}},
   477  			wantPropstats: []Propstat{{
   478  				Status: http.StatusOK,
   479  				Props: []Property{{
   480  					XMLName: xml.Name{Space: "foo", Local: "bar"},
   481  				}},
   482  			}},
   483  		}},
   484  	}, {
   485  		desc:    "bad: propfind unknown property",
   486  		buildfs: []string{"mkdir /dir"},
   487  		propOp: []propOp{{
   488  			op:     "propfind",
   489  			name:   "/dir",
   490  			pnames: []xml.Name{{"foo:", "bar"}},
   491  			wantPropstats: []Propstat{{
   492  				Status: http.StatusNotFound,
   493  				Props: []Property{{
   494  					XMLName: xml.Name{Space: "foo:", Local: "bar"},
   495  				}},
   496  			}},
   497  		}},
   498  	}}
   499  
   500  	for _, tc := range testCases {
   501  		fs, err := buildTestFS(tc.buildfs)
   502  		if err != nil {
   503  			t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
   504  		}
   505  		if tc.noDeadProps {
   506  			fs = noDeadPropsFS{fs}
   507  		}
   508  		ls := NewMemLS()
   509  		for _, op := range tc.propOp {
   510  			desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name)
   511  			if err = calcProps(op.name, fs, ls, op.wantPropstats); err != nil {
   512  				t.Fatalf("%s: calcProps: %v", desc, err)
   513  			}
   514  
   515  			// Call property system.
   516  			var propstats []Propstat
   517  			switch op.op {
   518  			case "propname":
   519  				pnames, err := propnames(fs, ls, op.name)
   520  				if err != nil {
   521  					t.Errorf("%s: got error %v, want nil", desc, err)
   522  					continue
   523  				}
   524  				sort.Sort(byXMLName(pnames))
   525  				sort.Sort(byXMLName(op.wantPnames))
   526  				if !reflect.DeepEqual(pnames, op.wantPnames) {
   527  					t.Errorf("%s: pnames\ngot  %q\nwant %q", desc, pnames, op.wantPnames)
   528  				}
   529  				continue
   530  			case "allprop":
   531  				propstats, err = allprop(fs, ls, op.name, op.pnames)
   532  			case "propfind":
   533  				propstats, err = props(fs, ls, op.name, op.pnames)
   534  			case "proppatch":
   535  				propstats, err = patch(fs, ls, op.name, op.patches)
   536  			default:
   537  				t.Fatalf("%s: %s not implemented", desc, op.op)
   538  			}
   539  			if err != nil {
   540  				t.Errorf("%s: got error %v, want nil", desc, err)
   541  				continue
   542  			}
   543  			// Compare return values from allprop, propfind or proppatch.
   544  			for _, pst := range propstats {
   545  				sort.Sort(byPropname(pst.Props))
   546  			}
   547  			for _, pst := range op.wantPropstats {
   548  				sort.Sort(byPropname(pst.Props))
   549  			}
   550  			sort.Sort(byStatus(propstats))
   551  			sort.Sort(byStatus(op.wantPropstats))
   552  			if !reflect.DeepEqual(propstats, op.wantPropstats) {
   553  				t.Errorf("%s: propstat\ngot  %q\nwant %q", desc, propstats, op.wantPropstats)
   554  			}
   555  		}
   556  	}
   557  }
   558  
   559  func cmpXMLName(a, b xml.Name) bool {
   560  	if a.Space != b.Space {
   561  		return a.Space < b.Space
   562  	}
   563  	return a.Local < b.Local
   564  }
   565  
   566  type byXMLName []xml.Name
   567  
   568  func (b byXMLName) Len() int           { return len(b) }
   569  func (b byXMLName) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   570  func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) }
   571  
   572  type byPropname []Property
   573  
   574  func (b byPropname) Len() int           { return len(b) }
   575  func (b byPropname) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   576  func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) }
   577  
   578  type byStatus []Propstat
   579  
   580  func (b byStatus) Len() int           { return len(b) }
   581  func (b byStatus) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
   582  func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status }
   583  
   584  type noDeadPropsFS struct {
   585  	FileSystem
   586  }
   587  
   588  func (fs noDeadPropsFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
   589  	f, err := fs.FileSystem.OpenFile(name, flag, perm)
   590  	if err != nil {
   591  		return nil, err
   592  	}
   593  	return noDeadPropsFile{f}, nil
   594  }
   595  
   596  // noDeadPropsFile wraps a File but strips any optional DeadPropsHolder methods
   597  // provided by the underlying File implementation.
   598  type noDeadPropsFile struct {
   599  	f File
   600  }
   601  
   602  func (f noDeadPropsFile) Close() error                              { return f.f.Close() }
   603  func (f noDeadPropsFile) Read(p []byte) (int, error)                { return f.f.Read(p) }
   604  func (f noDeadPropsFile) Readdir(count int) ([]os.FileInfo, error)  { return f.f.Readdir(count) }
   605  func (f noDeadPropsFile) Seek(off int64, whence int) (int64, error) { return f.f.Seek(off, whence) }
   606  func (f noDeadPropsFile) Stat() (os.FileInfo, error)                { return f.f.Stat() }
   607  func (f noDeadPropsFile) Write(p []byte) (int, error)               { return f.f.Write(p) }