Elia Battiston | ac8d23f | 2022-03-14 17:54:56 +0100 | [diff] [blame] | 1 | // Package internal contains some code that should not be exported but needs to |
| 2 | // be shared across more than one of the protoreflect sub-packages. |
| 3 | package internal |
| 4 | |
| 5 | import ( |
| 6 | "bytes" |
| 7 | "compress/gzip" |
| 8 | "fmt" |
| 9 | "io/ioutil" |
| 10 | |
| 11 | "github.com/golang/protobuf/proto" |
| 12 | dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" |
| 13 | ) |
| 14 | |
| 15 | // TODO: replace this alias configuration with desc.RegisterImportPath? |
| 16 | |
| 17 | // StdFileAliases are the standard protos included with protoc, but older versions of |
| 18 | // their respective packages registered them using incorrect paths. |
| 19 | var StdFileAliases = map[string]string{ |
| 20 | // Files for the github.com/golang/protobuf/ptypes package at one point were |
| 21 | // registered using the path where the proto files are mirrored in GOPATH, |
| 22 | // inside the golang/protobuf repo. |
| 23 | // (Fixed as of https://github.com/golang/protobuf/pull/412) |
| 24 | "google/protobuf/any.proto": "github.com/golang/protobuf/ptypes/any/any.proto", |
| 25 | "google/protobuf/duration.proto": "github.com/golang/protobuf/ptypes/duration/duration.proto", |
| 26 | "google/protobuf/empty.proto": "github.com/golang/protobuf/ptypes/empty/empty.proto", |
| 27 | "google/protobuf/struct.proto": "github.com/golang/protobuf/ptypes/struct/struct.proto", |
| 28 | "google/protobuf/timestamp.proto": "github.com/golang/protobuf/ptypes/timestamp/timestamp.proto", |
| 29 | "google/protobuf/wrappers.proto": "github.com/golang/protobuf/ptypes/wrappers/wrappers.proto", |
| 30 | // Files for the google.golang.org/genproto/protobuf package at one point |
| 31 | // were registered with an anomalous "src/" prefix. |
| 32 | // (Fixed as of https://github.com/google/go-genproto/pull/31) |
| 33 | "google/protobuf/api.proto": "src/google/protobuf/api.proto", |
| 34 | "google/protobuf/field_mask.proto": "src/google/protobuf/field_mask.proto", |
| 35 | "google/protobuf/source_context.proto": "src/google/protobuf/source_context.proto", |
| 36 | "google/protobuf/type.proto": "src/google/protobuf/type.proto", |
| 37 | |
| 38 | // Other standard files (descriptor.proto and compiler/plugin.proto) are |
| 39 | // registered correctly, so we don't need rules for them here. |
| 40 | } |
| 41 | |
| 42 | func init() { |
| 43 | // We provide aliasing in both directions, to support files with the |
| 44 | // proper import path linked against older versions of the generated |
| 45 | // files AND files that used the aliased import path but linked against |
| 46 | // newer versions of the generated files (which register with the |
| 47 | // correct path). |
| 48 | |
| 49 | // Get all files defined above |
| 50 | keys := make([]string, 0, len(StdFileAliases)) |
| 51 | for k := range StdFileAliases { |
| 52 | keys = append(keys, k) |
| 53 | } |
| 54 | // And add inverse mappings |
| 55 | for _, k := range keys { |
| 56 | alias := StdFileAliases[k] |
| 57 | StdFileAliases[alias] = k |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | type ErrNoSuchFile string |
| 62 | |
| 63 | func (e ErrNoSuchFile) Error() string { |
| 64 | return fmt.Sprintf("no such file: %q", string(e)) |
| 65 | } |
| 66 | |
| 67 | // LoadFileDescriptor loads a registered descriptor and decodes it. If the given |
| 68 | // name cannot be loaded but is a known standard name, an alias will be tried, |
| 69 | // so the standard files can be loaded even if linked against older "known bad" |
| 70 | // versions of packages. |
| 71 | func LoadFileDescriptor(file string) (*dpb.FileDescriptorProto, error) { |
| 72 | fdb := proto.FileDescriptor(file) |
| 73 | aliased := false |
| 74 | if fdb == nil { |
| 75 | var ok bool |
| 76 | alias, ok := StdFileAliases[file] |
| 77 | if ok { |
| 78 | aliased = true |
| 79 | if fdb = proto.FileDescriptor(alias); fdb == nil { |
| 80 | return nil, ErrNoSuchFile(file) |
| 81 | } |
| 82 | } else { |
| 83 | return nil, ErrNoSuchFile(file) |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | fd, err := DecodeFileDescriptor(file, fdb) |
| 88 | if err != nil { |
| 89 | return nil, err |
| 90 | } |
| 91 | |
| 92 | if aliased { |
| 93 | // the file descriptor will have the alias used to load it, but |
| 94 | // we need it to have the specified name in order to link it |
| 95 | fd.Name = proto.String(file) |
| 96 | } |
| 97 | |
| 98 | return fd, nil |
| 99 | } |
| 100 | |
| 101 | // DecodeFileDescriptor decodes the bytes of a registered file descriptor. |
| 102 | // Registered file descriptors are first "proto encoded" (e.g. binary format |
| 103 | // for the descriptor protos) and then gzipped. So this function gunzips and |
| 104 | // then unmarshals into a descriptor proto. |
| 105 | func DecodeFileDescriptor(element string, fdb []byte) (*dpb.FileDescriptorProto, error) { |
| 106 | raw, err := decompress(fdb) |
| 107 | if err != nil { |
| 108 | return nil, fmt.Errorf("failed to decompress %q descriptor: %v", element, err) |
| 109 | } |
| 110 | fd := dpb.FileDescriptorProto{} |
| 111 | if err := proto.Unmarshal(raw, &fd); err != nil { |
| 112 | return nil, fmt.Errorf("bad descriptor for %q: %v", element, err) |
| 113 | } |
| 114 | return &fd, nil |
| 115 | } |
| 116 | |
| 117 | func decompress(b []byte) ([]byte, error) { |
| 118 | r, err := gzip.NewReader(bytes.NewReader(b)) |
| 119 | if err != nil { |
| 120 | return nil, fmt.Errorf("bad gzipped descriptor: %v", err) |
| 121 | } |
| 122 | out, err := ioutil.ReadAll(r) |
| 123 | if err != nil { |
| 124 | return nil, fmt.Errorf("bad gzipped descriptor: %v", err) |
| 125 | } |
| 126 | return out, nil |
| 127 | } |