go-omaha/omaha/protocol.go
Michael Marineau a6290c1b4f protocol: add ParseRequest and ParseResponse functions
For parsing and verification of of HTTP request and response bodies,
including optional checking the Content-Type field which the handler
previously didn't do.
2017-05-04 13:25:46 -07:00

294 lines
8.9 KiB
Go

// Copyright 2013-2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Google's Omaha application update protocol, version 3.
//
// Omaha is a poll based protocol using XML. Requests are made by clients to
// check for updates or report events of an update process. Responses are given
// by the server to provide update information, if any, or to simply
// acknowledge the receipt of event status.
//
// https://github.com/google/omaha/blob/master/doc/ServerProtocolV3.md
package omaha
import (
"encoding/xml"
"io"
)
// Request sent by the Omaha client
type Request struct {
XMLName xml.Name `xml:"request" json:"-"`
OS *OS `xml:"os"`
Apps []*AppRequest `xml:"app"`
Protocol string `xml:"protocol,attr"`
InstallSource string `xml:"installsource,attr,omitempty"`
IsMachine string `xml:"ismachine,attr,omitempty"`
RequestID string `xml:"requestid,attr,omitempty"`
SessionID string `xml:"sessionid,attr,omitempty"`
TestSource string `xml:"testsource,attr,omitempty"`
UserID string `xml:"userid,attr,omitempty"`
Version string `xml:"version,attr,omitempty"`
// update engine extension, duplicates the version attribute.
UpdaterVersion string `xml:"updaterversion,attr,omitempty"`
}
func NewRequest() *Request {
return &Request{
Protocol: "3.0",
// TODO(marineam) set a default client Version
OS: &OS{
Platform: LocalPlatform(),
Arch: LocalArch(),
// TODO(marineam): Version and ServicePack
},
}
}
// ParseRequest verifies and returns the parsed Request document.
// The MIME Content-Type header may be provided to sanity check its
// value; if blank it is assumed to be XML in UTF-8.
func ParseRequest(contentType string, body io.Reader) (*Request, error) {
if err := checkContentType(contentType); err != nil {
return nil, err
}
r := &Request{}
if err := parseReqOrResp(body, r); err != nil {
return nil, err
}
return r, nil
}
func (r *Request) AddApp(id, version string) *AppRequest {
a := &AppRequest{ID: id, Version: version}
r.Apps = append(r.Apps, a)
return a
}
type AppRequest struct {
Ping *PingRequest `xml:"ping"`
UpdateCheck *UpdateRequest `xml:"updatecheck"`
Events []*EventRequest `xml:"event" json:",omitempty"`
ID string `xml:"appid,attr,omitempty"`
Client string `xml:"client,attr,omitempty"`
InstallAge string `xml:"installage,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"`
NextVersion string `xml:"nextversion,attr,omitempty"`
Version string `xml:"version,attr,omitempty"`
// update engine extensions
Board string `xml:"board,attr,omitempty"`
DeltaOK bool `xml:"delta_okay,attr,omitempty"`
FromTrack string `xml:"from_track,attr,omitempty"`
Track string `xml:"track,attr,omitempty"`
// coreos update engine extensions
AlephVersion string `xml:"alephversion,attr,omitempty"`
BootID string `xml:"bootid,attr,omitempty"`
MachineID string `xml:"machineid,attr,omitempty"`
OEM string `xml:"oem,attr,omitempty"`
OEMVersion string `xml:"oemversion,attr,omitempty"`
}
func (a *AppRequest) AddUpdateCheck() *UpdateRequest {
a.UpdateCheck = &UpdateRequest{}
return a.UpdateCheck
}
func (a *AppRequest) AddPing() *PingRequest {
a.Ping = &PingRequest{Active: 1}
return a.Ping
}
func (a *AppRequest) AddEvent() *EventRequest {
event := &EventRequest{}
a.Events = append(a.Events, event)
return event
}
type UpdateRequest struct {
TargetVersionPrefix string `xml:"targetversionprefix,attr,omitempty"`
}
type PingRequest struct {
Active int `xml:"active,attr,omitempty"`
LastActiveReportDays *int `xml:"a,attr,omitempty"`
LastReportDays int `xml:"r,attr,omitempty"`
}
type EventRequest struct {
Type EventType `xml:"eventtype,attr"`
Result EventResult `xml:"eventresult,attr"`
ErrorCode string `xml:"errorcode,attr,omitempty"`
NextVersion string `xml:"nextversion,attr,omitempty"`
PreviousVersion string `xml:"previousversion,attr,omitempty"`
}
// Response sent by the Omaha server
type Response struct {
XMLName xml.Name `xml:"response" json:"-"`
DayStart DayStart `xml:"daystart"`
Apps []*AppResponse `xml:"app"`
Protocol string `xml:"protocol,attr"`
Server string `xml:"server,attr"`
}
func NewResponse() *Response {
return &Response{
Protocol: "3.0",
Server: "go-omaha",
DayStart: DayStart{ElapsedSeconds: "0"},
}
}
// ParseResponse verifies and returns the parsed Response document.
// The MIME Content-Type header may be provided to sanity check its
// value; if blank it is assumed to be XML in UTF-8.
func ParseResponse(contentType string, body io.Reader) (*Response, error) {
if err := checkContentType(contentType); err != nil {
return nil, err
}
r := &Response{}
if err := parseReqOrResp(body, r); err != nil {
return nil, err
}
return r, nil
}
type DayStart struct {
ElapsedSeconds string `xml:"elapsed_seconds,attr"`
}
func (r *Response) AddApp(id string, status AppStatus) *AppResponse {
a := &AppResponse{ID: id, Status: status}
r.Apps = append(r.Apps, a)
return a
}
type AppResponse struct {
Ping *PingResponse `xml:"ping"`
UpdateCheck *UpdateResponse `xml:"updatecheck"`
Events []*EventResponse `xml:"event" json:",omitempty"`
ID string `xml:"appid,attr,omitempty"`
Status AppStatus `xml:"status,attr,omitempty"`
}
func (a *AppResponse) AddUpdateCheck(status UpdateStatus) *UpdateResponse {
a.UpdateCheck = &UpdateResponse{Status: status}
return a.UpdateCheck
}
func (a *AppResponse) AddPing() *PingResponse {
a.Ping = &PingResponse{"ok"}
return a.Ping
}
func (a *AppResponse) AddEvent() *EventResponse {
event := &EventResponse{"ok"}
a.Events = append(a.Events, event)
return event
}
type UpdateResponse struct {
URLs []*URL `xml:"urls>url" json:",omitempty"`
Manifest *Manifest `xml:"manifest"`
Status UpdateStatus `xml:"status,attr,omitempty"`
}
func (u *UpdateResponse) AddURL(codebase string) *URL {
url := &URL{CodeBase: codebase}
u.URLs = append(u.URLs, url)
return url
}
func (u *UpdateResponse) AddManifest(version string) *Manifest {
u.Manifest = &Manifest{Version: version}
return u.Manifest
}
type PingResponse struct {
Status string `xml:"status,attr"` // Always "ok".
}
type EventResponse struct {
Status string `xml:"status,attr"` // Always "ok".
}
type OS struct {
Platform string `xml:"platform,attr,omitempty"`
Version string `xml:"version,attr,omitempty"`
ServicePack string `xml:"sp,attr,omitempty"`
Arch string `xml:"arch,attr,omitempty"`
}
type Event struct {
Type EventType `xml:"eventtype,attr"`
Result EventResult `xml:"eventresult,attr"`
PreviousVersion string `xml:"previousversion,attr,omitempty"`
ErrorCode string `xml:"errorcode,attr,omitempty"`
Status string `xml:"status,attr,omitempty"`
}
type URL struct {
CodeBase string `xml:"codebase,attr"`
}
type Manifest struct {
Packages []*Package `xml:"packages>package"`
Actions []*Action `xml:"actions>action"`
Version string `xml:"version,attr"`
}
func (m *Manifest) AddPackage() *Package {
p := &Package{}
m.Packages = append(m.Packages, p)
return p
}
func (m *Manifest) AddPackageFromPath(path string) (*Package, error) {
p := &Package{}
if err := p.FromPath(path); err != nil {
return nil, err
}
m.Packages = append(m.Packages, p)
return p, nil
}
func (m *Manifest) AddAction(event string) *Action {
a := &Action{Event: event}
m.Actions = append(m.Actions, a)
return a
}
type Action struct {
Event string `xml:"event,attr"`
// update engine extensions for event="postinstall"
DisplayVersion string `xml:"DisplayVersion,attr,omitempty"`
SHA256 string `xml:"sha256,attr,omitempty"`
NeedsAdmin bool `xml:"needsadmin,attr,omitempty"`
IsDeltaPayload bool `xml:"IsDeltaPayload,attr,omitempty"`
DisablePayloadBackoff bool `xml:"DisablePayloadBackoff,attr,omitempty"`
MaxFailureCountPerURL uint `xml:"MaxFailureCountPerUrl,attr,omitempty"`
MetadataSignatureRsa string `xml:"MetadataSignatureRsa,attr,omitempty"`
MetadataSize string `xml:"MetadataSize,attr,omitempty"`
Deadline string `xml:"deadline,attr,omitempty"`
MoreInfo string `xml:"MoreInfo,attr,omitempty"`
Prompt bool `xml:"Prompt,attr,omitempty"`
}