From f33cb66abb1821567639ece5e718a3175ceae161 Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Sat, 25 Jul 2015 19:00:42 -0700 Subject: [PATCH] omaha: add complete http handler implementation The handler is driven by something implementing the 'Updater' interface. --- omaha/handler.go | 134 ++++++++++++++++++++++++++++++++++++++++++ omaha/handler_test.go | 68 +++++++++++++++++++++ omaha/update.go | 25 +++++++- 3 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 omaha/handler.go create mode 100644 omaha/handler_test.go diff --git a/omaha/handler.go b/omaha/handler.go new file mode 100644 index 0000000..754ffc3 --- /dev/null +++ b/omaha/handler.go @@ -0,0 +1,134 @@ +// Copyright 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. + +package omaha + +import ( + "encoding/xml" + "log" + "net/http" +) + +type OmahaHandler struct { + Updater +} + +func (o *OmahaHandler) ServeHTTP(w http.ResponseWriter, httpReq *http.Request) { + if httpReq.Method != "POST" { + log.Printf("omaha: Unexpected HTTP method: %s", httpReq.Method) + http.Error(w, "Expected a POST", http.StatusBadRequest) + return + } + + // A request over 1M in size is certainly bogus. + reader := http.MaxBytesReader(w, httpReq.Body, 1024*1024) + + decoder := xml.NewDecoder(reader) + var omahaReq Request + if err := decoder.Decode(&omahaReq); err != nil { + log.Printf("omaha: Failed decoding XML: %v", err) + http.Error(w, "Invalid XML", http.StatusBadRequest) + return + } + + if omahaReq.Protocol != "3.0" { + log.Printf("omaha: Unexpected protocol: %q", omahaReq.Protocol) + http.Error(w, "Omaha 3.0 Required", http.StatusBadRequest) + return + } + + httpStatus := 0 + omahaResp := NewResponse() + for _, appReq := range omahaReq.Apps { + appResp := o.serveApp(omahaResp, httpReq, &omahaReq, appReq) + if appResp.Status == AppOK { + // HTTP is ok if any app is ok. + httpStatus = http.StatusOK + } else if httpStatus == 0 { + // If no app is ok HTTP will use the first error. + if appResp.Status == AppInternalError { + httpStatus = http.StatusInternalServerError + } else { + httpStatus = http.StatusBadRequest + } + } + } + + if httpStatus == 0 { + httpStatus = http.StatusBadRequest + } + + w.Header().Set("Content-Type", "text/xml; charset=utf-8") + w.WriteHeader(httpStatus) + + if _, err := w.Write([]byte(xml.Header)); err != nil { + log.Printf("omaha: Failed writing response: %v", err) + return + } + + encoder := xml.NewEncoder(w) + encoder.Indent("", "\t") + if err := encoder.Encode(omahaResp); err != nil { + log.Printf("omaha: Failed encoding response: %v", err) + } +} + +func (o *OmahaHandler) serveApp(omahaResp *Response, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) *AppResponse { + if err := o.CheckApp(omahaReq, appReq); err != nil { + if appStatus, ok := err.(AppStatus); ok { + return omahaResp.AddApp(appReq.Id, appStatus) + } + log.Printf("omaha: CheckApp failed: %v", err) + return omahaResp.AddApp(appReq.Id, AppInternalError) + } + + appResp := omahaResp.AddApp(appReq.Id, AppOK) + if appReq.UpdateCheck != nil { + o.checkUpdate(appResp, httpReq, omahaReq, appReq) + } + + if appReq.Ping != nil { + o.Ping(omahaReq, appReq) + appResp.AddPing() + } + + for _, event := range appReq.Events { + o.Event(omahaReq, appReq, event) + appResp.AddEvent() + } + + return appResp +} + +func (o *OmahaHandler) checkUpdate(appResp *AppResponse, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) { + update, err := o.CheckUpdate(omahaReq, appReq) + if err != nil { + if updateStatus, ok := err.(UpdateStatus); ok { + appResp.AddUpdateCheck(updateStatus) + } else { + log.Printf("omaha: CheckUpdate failed: %v", err) + appResp.AddUpdateCheck(UpdateInternalError) + } + } else if update != nil { + u := appResp.AddUpdateCheck(UpdateOK) + fillUpdate(u, update, httpReq) + } else { + appResp.AddUpdateCheck(NoUpdate) + } +} + +func fillUpdate(u *UpdateResponse, update *Update, httpReq *http.Request) { + u.URLs = update.URLs([]string{"http://" + httpReq.Host}) + u.Manifest = &update.Manifest +} diff --git a/omaha/handler_test.go b/omaha/handler_test.go new file mode 100644 index 0000000..013dea5 --- /dev/null +++ b/omaha/handler_test.go @@ -0,0 +1,68 @@ +// 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. + +package omaha + +import ( + "encoding/xml" + "fmt" + "testing" + + "github.com/kylelemons/godebug/diff" +) + +const ( + testAppId = "{27BD862E-8AE8-4886-A055-F7F1A6460627}" + testAppVer = "1.0.0" +) + +var ( + nilRequest *Request + nilResponse *Response +) + +func init() { + nilRequest = NewRequest() + nilRequest.AddApp(testAppId, testAppVer) + nilResponse = NewResponse() + nilResponse.AddApp(testAppId, AppOK) +} + +func compareXML(a, b interface{}) error { + aXml, err := xml.MarshalIndent(a, "", "\t") + if err != nil { + return err + } + + bXml, err := xml.MarshalIndent(b, "", "\t") + if err != nil { + return err + } + + if d := diff.Diff(string(aXml), string(bXml)); d != "" { + err := fmt.Errorf("Unexpected XML:\n%s", d) + return err + } + + return nil +} + +func TestHandleNilRequest(t *testing.T) { + handler := OmahaHandler{UpdaterStub{}} + response := NewResponse() + handler.serveApp(response, nil, nilRequest, nilRequest.Apps[0]) + if err := compareXML(nilResponse, response); err != nil { + t.Error(err) + } +} diff --git a/omaha/update.go b/omaha/update.go index a430acb..ce2188e 100644 --- a/omaha/update.go +++ b/omaha/update.go @@ -45,6 +45,29 @@ func (u *Update) URLs(prefixes []string) []*URL { return urls } +// Updater provides a common interface for any backend that can respond to +// update requests made to an Omaha server. type Updater interface { - Update(os *OS, app *AppRequest) (*Update, error) + CheckApp(req *Request, app *AppRequest) error + CheckUpdate(req *Request, app *AppRequest) (*Update, error) + Event(req *Request, app *AppRequest, event *EventRequest) + Ping(req *Request, app *AppRequest) +} + +type UpdaterStub struct{} + +func (u UpdaterStub) CheckApp(req *Request, app *AppRequest) error { + return nil +} + +func (u UpdaterStub) CheckUpdate(req *Request, app *AppRequest) (*Update, error) { + return nil, NoUpdate +} + +func (u UpdaterStub) Event(req *Request, app *AppRequest, event *EventRequest) { + return +} + +func (u UpdaterStub) Ping(req *Request, app *AppRequest) { + return }