From 9a65bd691bb16a911277829b955d718f27b23b5e Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Sun, 18 Oct 2020 19:48:41 +0200 Subject: [PATCH 1/2] better support relative references --- part.go | 9 +++++++-- part_test.go | 2 ++ reader.go | 2 +- reader_test.go | 43 ++++++++++++++++++++++++++++++++----------- relationship.go | 26 ++++++++------------------ relationship_test.go | 4 +++- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/part.go b/part.go index 4b64519..181137c 100644 --- a/part.go +++ b/part.go @@ -36,11 +36,16 @@ var defaultRef, _ = url.Parse("http://defaultcontainer/") // The source can be a valid part URI, for part relationships, or "/", for package relationships. func ResolveRelationship(source string, rel string) string { if source == "/" || source == "\\" { - return "/" + rel + if strings.HasPrefix(rel, "\\") { + rel = rel[1:] + } + if !strings.HasPrefix(rel, "/") { + rel = "/" + rel + } } if !strings.HasPrefix(rel, "/") && !strings.HasPrefix(rel, "\\") { sourceDir := strings.Replace(filepath.Dir(source), "\\", "/", -1) - return fmt.Sprintf("%s/%s", sourceDir, rel) + rel = fmt.Sprintf("%s/%s", sourceDir, rel) } return rel } diff --git a/part_test.go b/part_test.go index 0339696..73e714e 100644 --- a/part_test.go +++ b/part_test.go @@ -112,7 +112,9 @@ func TestResolveRelationship(t *testing.T) { want string }{ {"package", args{"/", "c.xml"}, "/c.xml"}, + {"packageWithSlash", args{"/", "/c.xml"}, "/c.xml"}, {"packageWin", args{"\\", "c.xml"}, "/c.xml"}, + {"packageWinWithSlash", args{"\\", "\\c.xml"}, "/c.xml"}, {"rel", args{"/3D/3dmodel.model", "c.xml"}, "/3D/c.xml"}, {"rel", args{"/3D/3dmodel.model", "/3D/box1.model"}, "/3D/box1.model"}, {"rel", args{"/3D/box3.model", "/2D/2dmodel.model"}, "/2D/2dmodel.model"}, diff --git a/reader.go b/reader.go index 59006c9..2d0950b 100644 --- a/reader.go +++ b/reader.go @@ -106,7 +106,7 @@ func (r *Reader) loadPackage() error { if strings.EqualFold(fileName, contentTypesName) || isRelationshipURI(fileName) || strings.HasSuffix(fileName, "/") { continue } - if strings.EqualFold(fileName, r.Properties.PartName) { + if strings.EqualFold(fileName, ResolveRelationship("/", r.Properties.PartName)) { cp, err := r.loadCoreProperties(file) if err != nil { return err diff --git a/reader_test.go b/reader_test.go index 653c297..85b5092 100644 --- a/reader_test.go +++ b/reader_test.go @@ -425,12 +425,17 @@ func Test_newReader_CoreProperties(t *testing.T) { {"base", []archiveFile{ newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml").withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"). + withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), nil, ), newMockFile( "_rels/.rels", - ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder).withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml").withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml").withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder). + withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml"). + withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml"). + withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), nil, ), newMockFile("docProps/core.xml", ioutil.NopCloser(bytes.NewBufferString(coreFile)), nil), @@ -439,12 +444,17 @@ func Test_newReader_CoreProperties(t *testing.T) { {"decodeError", []archiveFile{ newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml").withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"). + withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), nil, ), newMockFile( "_rels/.rels", - ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder).withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml").withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml").withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder). + withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml"). + withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml"). + withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), nil, ), newMockFile("docProps/core.xml", ioutil.NopCloser(bytes.NewBufferString("{a : 2}")), nil), @@ -453,12 +463,17 @@ func Test_newReader_CoreProperties(t *testing.T) { {"openError", []archiveFile{ newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml").withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"). + withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), nil, ), newMockFile( "_rels/.rels", - ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder).withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml").withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml").withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(relsBuilder). + withRel("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml"). + withRel("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml"). + withRel("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml").String())), nil, ), newMockFile("docProps/core.xml", ioutil.NopCloser(nil), errors.New("")), @@ -492,8 +507,8 @@ func Test_newReader_PackageRelationships(t *testing.T) { r := []*Relationship{ {ID: "rId3", Type: "http://www.custom.com/external-resource", TargetURI: "http://www.custom.com/images/pic1.jpg", TargetMode: ModeExternal}, - {ID: "rId2", Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", TargetURI: "/DOCPROPS/app.xml", TargetMode: ModeInternal}, - {ID: "rId1", Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", TargetURI: "/xl/workbook.xml", TargetMode: ModeInternal}, + {ID: "rId2", Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", TargetURI: "DOCPROPS/app.xml", TargetMode: ModeInternal}, + {ID: "rId1", Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", TargetURI: "xl/workbook.xml", TargetMode: ModeInternal}, {ID: "rId4", Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", TargetURI: "./xl/other.xml", TargetMode: ModeInternal}, } tests := []struct { @@ -505,7 +520,9 @@ func Test_newReader_PackageRelationships(t *testing.T) { {"base", []archiveFile{ newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml").withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"). + withOverride("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml").String())), nil, ), newMockFile("_rels/.rels", ioutil.NopCloser(bytes.NewBufferString(validPackageRelationships)), nil), @@ -516,7 +533,9 @@ func Test_newReader_PackageRelationships(t *testing.T) { newMockFile("docProps/app.xml", ioutil.NopCloser(bytes.NewBufferString("")), nil), newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/APP.xml").withDefault("image/png", "png").withDefault("application/xml", "xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/APP.xml"). + withDefault("image/png", "png").withDefault("application/xml", "xml").String())), nil, ), newMockFile("_rels/.rels", ioutil.NopCloser(nil), errors.New("")), @@ -525,7 +544,9 @@ func Test_newReader_PackageRelationships(t *testing.T) { {"decodeMalformedXMLPackage", []archiveFile{ newMockFile( "[Content_Types].xml", - ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder).withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/APP.xml").withDefault("image/png", "png").withDefault("application/xml", "xml").String())), + ioutil.NopCloser(bytes.NewBufferString(new(cTypeBuilder). + withOverride("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/APP.xml"). + withDefault("image/png", "png").withDefault("application/xml", "xml").String())), nil, ), newMockFile("docProps/app.xml", ioutil.NopCloser(bytes.NewBufferString("")), nil), diff --git a/relationship.go b/relationship.go index 78f1b8c..a541858 100644 --- a/relationship.go +++ b/relationship.go @@ -70,20 +70,11 @@ func (r *Relationship) validate(sourceURI string) error { return r.validateRelationshipTarget(sourceURI) } -func (r *Relationship) normalizeTargetURI() { - if r.TargetMode == ModeInternal { - if !strings.HasPrefix(r.TargetURI, "/") && !strings.HasPrefix(r.TargetURI, "\\") && !strings.HasPrefix(r.TargetURI, ".") { - r.TargetURI = "/" + r.TargetURI - } - } -} - func (r *Relationship) toXML() *relationshipXML { var targetMode string if r.TargetMode == ModeExternal { targetMode = externalMode } - r.normalizeTargetURI() x := &relationshipXML{ID: r.ID, RelType: r.Type, TargetURI: r.TargetURI, Mode: targetMode} return x } @@ -118,14 +109,14 @@ func (r *Relationship) validateRelationshipTarget(sourceURI string) error { } // ISO/IEC 29500-2 M1.29 - if r.TargetMode == ModeInternal && uri.IsAbs() { - return newErrorRelationship(129, sourceURI, r.ID) - } - - if r.TargetMode != ModeExternal && !uri.IsAbs() { - source, err := url.Parse(strings.TrimSpace(sourceURI)) - if err != nil || source.String() == "" || isRelationshipURI(source.ResolveReference(uri).String()) { - return newErrorRelationship(125, sourceURI, r.ID) + if r.TargetMode == ModeInternal { + if uri.IsAbs() { + return newErrorRelationship(129, sourceURI, r.ID) + } else { + source, err := url.Parse(strings.TrimSpace(sourceURI)) + if err != nil || source.String() == "" || isRelationshipURI(source.ResolveReference(uri).String()) { + return newErrorRelationship(125, sourceURI, r.ID) + } } } @@ -172,7 +163,6 @@ func decodeRelationships(r io.Reader, partName string) ([]*Relationship, error) } else { newRel.TargetMode = ModeExternal } - newRel.normalizeTargetURI() rel[i] = newRel } return rel, nil diff --git a/relationship_test.go b/relationship_test.go index e27bb1e..7c3a2a7 100644 --- a/relationship_test.go +++ b/relationship_test.go @@ -42,6 +42,7 @@ func TestRelationship_validate(t *testing.T) { args args wantErr bool }{ + {"relative", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "./two/two.txt", TargetMode: ModeExternal}, args{"/one.txt"}, false}, {"new", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "fakeTarget", TargetMode: ModeExternal}, args{""}, false}, {"abs", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "http://a.com/b", TargetMode: ModeExternal}, args{""}, false}, {"internalRelRel", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "/_rels/.rels", TargetMode: ModeInternal}, args{"/"}, true}, @@ -53,6 +54,7 @@ func TestRelationship_validate(t *testing.T) { {"invalidTarget", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "", TargetMode: ModeInternal}, args{""}, true}, {"invalidRel1", &Relationship{ID: "fakeId", Type: "", TargetURI: "fakeTarget", TargetMode: ModeInternal}, args{""}, true}, {"invalidRel2", &Relationship{ID: "fakeId", Type: " ", TargetURI: "fakeTarget", TargetMode: ModeInternal}, args{""}, true}, + {"invalidRel2", &Relationship{ID: "fakeId", Type: "fakeType", TargetURI: "./fakeTarget", TargetMode: ModeInternal}, args{""}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -93,7 +95,7 @@ func Test_encodeRelationships(t *testing.T) { func expectedsolution() string { return ` - + ` } From 218138587569d9ee822eb1de0ebc520f688f59a4 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Sun, 18 Oct 2020 20:57:47 +0200 Subject: [PATCH 2/2] improve support for predefined core props --- package.go | 27 ++++++++++++++++++--------- reader.go | 10 +++++----- reader_test.go | 3 ++- testdata/office.docx | Bin 0 -> 11891 bytes writer.go | 17 +++++++++++++++-- writer_test.go | 5 +++-- 6 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 testdata/office.docx diff --git a/package.go b/package.go index 34bb4b1..b3668b7 100644 --- a/package.go +++ b/package.go @@ -290,16 +290,25 @@ func (c *CoreProperties) encode(w io.Writer) error { }) } -func decodeCoreProperties(r io.Reader) (*CoreProperties, error) { +func decodeCoreProperties(r io.Reader, props *CoreProperties) error { propDecode := new(corePropertiesXMLUnmarshal) if err := xml.NewDecoder(r).Decode(propDecode); err != nil { - return nil, fmt.Errorf("opc: %s: cannot be decoded: %v", contentTypesName, err) + return fmt.Errorf("opc: %s: cannot be decoded: %v", contentTypesName, err) } - prop := &CoreProperties{Category: propDecode.Category, ContentStatus: propDecode.ContentStatus, - Created: propDecode.Created, Creator: propDecode.Creator, Description: propDecode.Description, - Identifier: propDecode.Identifier, Keywords: propDecode.Keywords, Language: propDecode.Language, - LastModifiedBy: propDecode.LastModifiedBy, LastPrinted: propDecode.LastPrinted, Modified: propDecode.Modified, - Revision: propDecode.Revision, Subject: propDecode.Subject, Title: propDecode.Title, Version: propDecode.Version} - - return prop, nil + props.Category = propDecode.Category + props.ContentStatus = propDecode.ContentStatus + props.Created = propDecode.Created + props.Creator = propDecode.Creator + props.Description = propDecode.Description + props.Identifier = propDecode.Identifier + props.Keywords = propDecode.Keywords + props.Language = propDecode.Language + props.LastModifiedBy = propDecode.LastModifiedBy + props.LastPrinted = propDecode.LastPrinted + props.Modified = propDecode.Modified + props.Revision = propDecode.Revision + props.Subject = propDecode.Subject + props.Title = propDecode.Title + props.Version = propDecode.Version + return nil } diff --git a/reader.go b/reader.go index 2d0950b..0a87e48 100644 --- a/reader.go +++ b/reader.go @@ -107,11 +107,10 @@ func (r *Reader) loadPackage() error { continue } if strings.EqualFold(fileName, ResolveRelationship("/", r.Properties.PartName)) { - cp, err := r.loadCoreProperties(file) + err := r.loadCoreProperties(file) if err != nil { return err } - r.Properties = *cp } else { cType, err := ct.findType(fileName) if err != nil { @@ -162,12 +161,12 @@ func (r *Reader) loadContentType(file archiveFile) (*contentTypes, error) { return decodeContentTypes(reader) } -func (r *Reader) loadCoreProperties(file archiveFile) (*CoreProperties, error) { +func (r *Reader) loadCoreProperties(file archiveFile) error { reader, err := file.Open() if err != nil { - return nil, fmt.Errorf("opc: %s: cannot be opened: %v", r.Properties.PartName, err) + return fmt.Errorf("opc: %s: cannot be opened: %v", r.Properties.PartName, err) } - return decodeCoreProperties(reader) + return decodeCoreProperties(reader, &r.Properties) } func loadRelationships(file archiveFile, rels *relationshipsPart) error { @@ -201,6 +200,7 @@ func (r *Reader) loadPackageRelationships(file archiveFile) error { for _, rel := range rls { if strings.EqualFold(rel.Type, corePropsRel) { r.Properties.PartName = rel.TargetURI + r.Properties.RelationshipID = rel.ID break } } diff --git a/reader_test.go b/reader_test.go index 85b5092..769cd34 100644 --- a/reader_test.go +++ b/reader_test.go @@ -413,7 +413,7 @@ func Test_newReader_CoreProperties(t *testing.T) { 2019-01-24T19:58:26Z ` - cp := &CoreProperties{Created: "2015-06-05T18:19:34Z", Modified: "2019-01-24T19:58:26Z"} + cp := &CoreProperties{PartName: "docProps/core.xml", RelationshipID: "rId2", Created: "2015-06-05T18:19:34Z", Modified: "2019-01-24T19:58:26Z"} tests := []struct { name string @@ -655,6 +655,7 @@ func TestOpenReader(t *testing.T) { args args wantErr bool }{ + {"office", args{"testdata/office.docx"}, false}, {"extensioncustom", args{"testdata/extensioncustom.3mf"}, false}, {"overridecustom", args{"testdata/overridecustom.3mf"}, false}, {"overridepositive", args{"testdata/overridepositive.3mf"}, false}, diff --git a/testdata/office.docx b/testdata/office.docx new file mode 100644 index 0000000000000000000000000000000000000000..c78be493c88bfe0464597de412f666a05d5d2ac2 GIT binary patch literal 11891 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 literal 0 HcmV?d00001 diff --git a/writer.go b/writer.go index 5bb7228..74f9bd9 100644 --- a/writer.go +++ b/writer.go @@ -112,12 +112,25 @@ func (w *Writer) createCoreProperties() error { partName = corePropsDefaultName } part := &Part{Name: partName, ContentType: corePropsContentType} + if !strings.HasPrefix(partName, "/") { + part.Name = "/" + partName + } cw, err := w.addToPackage(part, CompressionNormal) if err != nil { return err } - w.Relationships = append(w.Relationships, - &Relationship{w.Properties.RelationshipID, corePropsRel, part.Name, ModeInternal}) + var hasCoreRel bool + for _, rel := range w.Relationships { + if strings.EqualFold(rel.Type, corePropsRel) { + hasCoreRel = true + break + } + } + if !hasCoreRel { + w.Relationships = append(w.Relationships, &Relationship{ + w.Properties.RelationshipID, corePropsRel, partName, ModeInternal, + }) + } return w.Properties.encode(cw) } diff --git a/writer_test.go b/writer_test.go index 61c13e6..027da54 100644 --- a/writer_test.go +++ b/writer_test.go @@ -50,9 +50,10 @@ func TestWriter_Close(t *testing.T) { {"invalidOwnRel", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Relationships: []*Relationship{{}}, rnd: fakeRand()}, true}, {"withDuplicatedCoreProps", &Writer{p: pCore, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, true}, {"withDuplicatedRels", &Writer{p: pRel, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, true}, - {"withDuplicatedRels", &Writer{p: pRel, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, true}, {"withCoreProps", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, false}, - {"withCorePropsWithName", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song", PartName: "/props.xml"}, rnd: fakeRand()}, false}, + {"withCorePropsWithName", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Relationships: []*Relationship{ + {TargetURI: "props.xml", Type: corePropsRel}, + }, Properties: CoreProperties{Title: "Song", PartName: "props.xml"}, rnd: fakeRand()}, false}, {"withCorePropsWithNameAndId", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song", PartName: "/docProps/props.xml", RelationshipID: "rId1"}, rnd: fakeRand()}, false}, } for _, tt := range tests {