From 617d7834ccc6d592e317860c55191a570f069508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Sat, 27 Feb 2021 22:25:50 +0100 Subject: [PATCH] A working proxy exists. --- client/main.go | 94 ++++++++++++++++++++++-- packages/ipp/charsetvalue.go | 20 +++--- packages/ipp/enum.go | 20 +++--- packages/ipp/integer.go | 24 +++---- packages/ipp/keyword.go | 20 +++--- packages/ipp/messages.go | 60 ++++++++++++---- packages/ipp/mimemediatype.go | 20 +++--- packages/ipp/naturallanguage.go | 18 ++--- packages/ipp/printerstate_string.go | 6 +- packages/ipp/rangeofinteger.go | 16 ++--- packages/ipp/request.go | 48 ++++++++++--- packages/ipp/request_test.go | 2 +- packages/ipp/resolution.go | 102 +++++++++++++++++---------- packages/ipp/response.go | 63 ++++++++++++++--- packages/ipp/setofstrings.go | 24 ++++--- packages/ipp/textwithoutlanguage.go | 21 +++--- packages/ipp/unsupportedvalue.go | 40 +++++++++++ packages/ipp/urivalue.go | 26 ++++--- packages/ipp/urivalue_test.go | 2 +- proxy/handleValidateJob.go | 10 +++ proxy/handlegetjobattributes.go | 46 ++++++++++++ proxy/handlegetjobs.go | 47 ++++++++++++ proxy/handlegetprinterattributes.go | 36 ++++++++++ proxy/handleprintjob.go | 69 ++++++++++++++++++ proxy/main.go | 79 +++++++++++++++++++++ server/handleValidateJob.go | 10 +++ server/handlegetprinterattributes.go | 48 +++++++------ server/handleprintjob.go | 2 + server/main.go | 3 +- 29 files changed, 781 insertions(+), 195 deletions(-) create mode 100644 packages/ipp/unsupportedvalue.go create mode 100644 proxy/handleValidateJob.go create mode 100644 proxy/handlegetjobattributes.go create mode 100644 proxy/handlegetjobs.go create mode 100644 proxy/handlegetprinterattributes.go create mode 100644 proxy/handleprintjob.go create mode 100644 proxy/main.go create mode 100644 server/handleValidateJob.go diff --git a/client/main.go b/client/main.go index 998de15..2415354 100644 --- a/client/main.go +++ b/client/main.go @@ -3,12 +3,16 @@ package main import ( "bytes" "fmt" + "io" "ippserver/packages/ipp" "net/http" + "os" log "github.com/sirupsen/logrus" ) +const printerURI = "brn30055cb5e3ae.local:631/ipp/print" + //application/ipp // brn30055cb5e3ae.local:631/ipp/print func main() { @@ -17,14 +21,12 @@ func main() { customFormatter.TimestampFormat = "2006-01-02 15:04:05" log.SetFormatter(customFormatter) customFormatter.FullTimestamp = true - log.SetLevel(log.DebugLevel) - - const printerUri = "brn30055cb5e3ae.local:631/ipp/print" + log.SetLevel(log.InfoLevel) request := ipp.NewRequest(ipp.GetPrinterAttributes, 10) request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) - request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerUri)) + request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI)) r := request.Marshal() b := bytes.NewBuffer(r) httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b) @@ -34,6 +36,88 @@ func main() { } rb := ipp.NewResponse(0, 0) rb.UnMarshal(httpResponse.Body) - fmt.Print(r) + fmt.Print(rb) + + fmt.Print("\n-------------\n") + printFile("4-untitled") } + +func printFile(fname string) { + + f, err := os.Open(fname) + if err != nil { + log.Errorf("Failed to open file %v", fname) + return + } + // fileContents, err := ioutil.ReadAll(f) + // if err != nil { + // log.Errorf("Failed to read file %v", fname) + // return + + // } + + request := ipp.NewRequest(ipp.PrintJob, 10) + request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI)) + request.AddOperatonAttribute(ipp.NewNameWithoutLanguage("requesting-user-name", "chronos")) + request.AddOperatonAttribute(ipp.NewNameWithoutLanguage("job-name", "foobar")) + request.AddOperatonAttribute(ipp.NewMimeMediaType("document-format", "image/pwg-raster")) + + //request.AddJobAttribute(ipp.NewNameWithoutLanguage("ColorModel", "Gray")) + //request.AddJobAttribute(ipp.NewNameWithoutLanguage("cupsPrintQuality", "Normal")) + //request.AddJobAttribute(ipp.NewNameWithoutLanguage("Duplex", "DuplexNoTumble")) + //request.AddJobAttribute(ipp.NewNameWithoutLanguage("job-originating-host-name", "localhost")) + //request.AddJobAttribute(ipp.NewURIValue("name string", value string) + request.AddJobAttribute(ipp.NewKeyWord("media", "iso_a4_210x297mm")) + //request.AddJobAttribute(ipp.NewKeyWord("multiple-document-handling", "separate-documents-collated-copies")) + request.AddJobAttribute(ipp.NewKeyWord("output-bin", "face-down")) + //request.AddJobAttribute(ipp.NewNameWithoutLanguage("PageSize", "A4")) + request.AddJobAttribute(ipp.NewKeyWord("print-color-mode", "monochrome")) + request.AddJobAttribute(ipp.NewSetOfResolution("printer-resolution", ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3})) + request.AddJobAttribute(ipp.NewKeyWord("sides", "two-sided-long-edge")) + fmt.Print(request) + r := request.Marshal() + b := bytes.NewBuffer(r) + + mr := io.MultiReader(b, f) + httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", mr) + if err != nil { + fmt.Print(err) + return + } + fmt.Print("\n---response------\n") + log.Printf("HTTP status: %v %v", httpResponse.StatusCode, httpResponse.Status) + // resp, err := io.ReadAll(httpResponse.Body) + // if err != nil { + // log.Error("could not read response") + // return + // } + // fmt.Printf("% x", resp) + rb := ipp.NewResponse(0, 0) + rb.UnMarshal(httpResponse.Body) + fmt.Print(rb) + +} + +/* +requesting-user-name:chronos (nameWithoutLanguagageValueTag) +job-name:2 - Untitled (nameWithoutLanguagageValueTag) +document-format : image/pwg-raster (mimeMediaTypeValueTag) +document-format : application/octet-stream (mimeMediaTypeValueTag) +PrinterAttributes +JobAttributes +ColorModel:Gray (nameWithoutLanguagageValueTag) +cupsPrintQuality:Normal (nameWithoutLanguagageValueTag) +Duplex:DuplexNoTumble (nameWithoutLanguagageValueTag) +job-originating-host-name:localhost (nameWithoutLanguagageValueTag) +job-uuid:urn:uuid:67a09d25-5df8-3026-6e9c-b55f7afee4ca (uriValueTag) +media : iso_a4_210x297mm (keywordValueTag) +multiple-document-handling : separate-documents-collated-copies (keywordValueTag) +output-bin : face-down (keywordValueTag) +PageSize:A4 (nameWithoutLanguagageValueTag) +print-color-mode : monochrome (keywordValueTag) +printer-resolution:[{600 600 3}] (resolutionValueTag) +sides : two-sided-long-edge (keywordValueTag) +*/ diff --git a/packages/ipp/charsetvalue.go b/packages/ipp/charsetvalue.go index 8881816..e507d87 100644 --- a/packages/ipp/charsetvalue.go +++ b/packages/ipp/charsetvalue.go @@ -4,35 +4,35 @@ import ( "io" ) -type charSetValue struct { +type CharSetValue struct { name string value string } -func NewCharSetValue(name string, value string) *charSetValue { - c := new(charSetValue) +func NewCharSetValue(name string, value string) *CharSetValue { + c := new(CharSetValue) c.name = name c.value = value return c } -func (c charSetValue) Name() string { +func (c CharSetValue) Name() string { return c.name } -func (c charSetValue) String() string { +func (c CharSetValue) String() string { return c.name + ":" + c.value } -func (c *charSetValue) valueTag() tag { +func (c *CharSetValue) valueTag() tag { return charsetValueTag } -func (c *charSetValue) unmarshal(byteStream io.Reader) { +func (c *CharSetValue) unmarshal(byteStream io.Reader) { c.name, c.value = unmarshalSingleValue(byteStream) } -func (c *charSetValue) marshal() []byte { +func (c *CharSetValue) marshal() []byte { l := 5 + len(c.name) + len(c.value) b := make([]byte, l) b[0] = byte(charsetValueTag) @@ -40,11 +40,11 @@ func (c *charSetValue) marshal() []byte { return b } -func (c *charSetValue) marshalInto([]byte) int { +func (c *CharSetValue) marshalInto([]byte) int { return 0 } -func (c *charSetValue) size() int { +func (c *CharSetValue) size() int { l := 1 + 4 // The attribute tag + 2 lengths l += len(c.name) l += len(c.value) diff --git a/packages/ipp/enum.go b/packages/ipp/enum.go index e7cd439..2a5ae08 100644 --- a/packages/ipp/enum.go +++ b/packages/ipp/enum.go @@ -4,38 +4,38 @@ import ( "fmt" ) -type enum struct { +type Enum struct { name string values []int32 } -func NewEnum(name string, values ...int32) *enum { - e := new(enum) +func NewEnum(name string, values ...int32) *Enum { + e := new(Enum) e.name = name e.values = values return e } -func (e enum) Name() string { +func (e Enum) Name() string { return e.name } -func (e enum) String() string { +func (e Enum) String() string { return e.name + ":" + fmt.Sprint(e.values) } -func (e *enum) valueTag() tag { +func (e *Enum) valueTag() tag { return enumValueTag } -func (e *enum) size() int { - return 9 + len(e.name) +func (e *Enum) size() int { + return 9 + len(e.name) } -func (e *enum) addValue(v interface{}) { +func (e *Enum) addValue(v interface{}) { e.values = append(e.values, v.(int32)) } -func (e *enum) marshal() []byte { +func (e *Enum) marshal() []byte { return marshalInteger(enumValueTag, e.name, e.values) } diff --git a/packages/ipp/integer.go b/packages/ipp/integer.go index b75df60..a6f98f9 100644 --- a/packages/ipp/integer.go +++ b/packages/ipp/integer.go @@ -7,53 +7,53 @@ import ( "io" ) -type integer struct { +type Integer struct { name string values []int32 } -func NewInteger(name string, values ...int32) *integer { - e := new(integer) +func NewInteger(name string, values ...int32) *Integer { + e := new(Integer) e.name = name e.values = values return e } -func (i integer) Name() string { +func (i Integer) Name() string { return i.name } -func (i integer) String() string { +func (i Integer) String() string { return i.name + ":" + fmt.Sprint(i.values) } -func (i *integer) valueTag() tag { +func (i *Integer) valueTag() tag { return integerValueTag } -func (i *integer) size() int { +func (i *Integer) size() int { return 9 + len(i.name) // The attribute tag + 2 lengths } -func (i *integer) addValue(v interface{}) { +func (i *Integer) addValue(v interface{}) { i.values = append(i.values, v.(int32)) } -func (i *integer) marshal() []byte { +func (i *Integer) marshal() []byte { return marshalInteger(integerValueTag, i.name, i.values) } func marshalInteger(t tag, name string, values []int32) []byte { - l := 9 + len(name) + l := 9 + len(name) + 9*(len(values)-1) b := make([]byte, 0, l) buf := bytes.NewBuffer(b) - buf.WriteByte(byte(integerValueTag)) + buf.WriteByte(byte(t)) binary.Write(buf, binary.BigEndian, uint16(len(name))) buf.WriteString(name) binary.Write(buf, binary.BigEndian, uint16(4)) binary.Write(buf, binary.BigEndian, values[0]) for _, v := range values[1:] { - buf.WriteByte(byte(integerValueTag)) + buf.WriteByte(byte(t)) binary.Write(buf, binary.BigEndian, uint16(0)) binary.Write(buf, binary.BigEndian, uint16(4)) binary.Write(buf, binary.BigEndian, v) diff --git a/packages/ipp/keyword.go b/packages/ipp/keyword.go index b5c0057..9d9d39d 100644 --- a/packages/ipp/keyword.go +++ b/packages/ipp/keyword.go @@ -1,37 +1,37 @@ package ipp -type keyWord struct { - sos *setOfStrings +type KeyWord struct { + sos *SetOfStrings } -func NewKeyWord(name string, values ...string) *keyWord { +func NewKeyWord(name string, values ...string) *KeyWord { - k := new(keyWord) + k := new(KeyWord) k.sos = NewSetOfStrings(name, keyWordValueTag, values) return k } -func (k keyWord) Name() string { +func (k KeyWord) Name() string { return k.sos.name } -func (k keyWord) String() string { +func (k KeyWord) String() string { return k.sos.String() } -func (k *keyWord) size() int { +func (k *KeyWord) size() int { return k.sos.size() } -func (k *keyWord) valueTag() tag { +func (k *KeyWord) valueTag() tag { return k.sos.valueTag() } -func (k *keyWord) marshal() []byte { +func (k *KeyWord) marshal() []byte { return k.sos.marshal() } -func (k *keyWord) addValue(v interface{}) { +func (k *KeyWord) addValue(v interface{}) { k.sos.AddValue(v.(string)) } diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index f34add6..994ba24 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -1,9 +1,11 @@ //Package ipp provides functonality to handle ipp messages +//go:generate stringer -type jobState -type printerState package ipp import ( "bufio" "encoding/binary" + "errors" "fmt" "io" @@ -14,6 +16,9 @@ import ( // https://tools.ietf.org/html/rfc8010 // https://tools.ietf.org/html/rfc8011 +// ErrNilAttribute is returned when an attempt to use nil as a attribute +var ErrNilAttribute = errors.New("can not add use nil as attribute") + // Defined value tags // from rfc8010 type tag uint8 @@ -89,12 +94,27 @@ const ( Stopped printerState = 5 ) +// jobstate defined in rfc8011 ch 5.3.7 +type jobState uint16 + +const ( + pending jobState = 3 + pendingHeld jobState = 4 + processing jobState = 5 + processingStoppped jobState = 6 + cancelled jobState = 7 + aborted jobState = 8 + completed jobState = 9 +) + type statusCode uint16 // status code defenitions const ( - SuccessfulOk statusCode = 0x0000 - ClientErrorBadRequest statusCode = 0x0400 + SuccessfulOk statusCode = 0x0000 + SuccessfulOkIgnoredOrSubstitutedAttributes statusCode = 0x0001 + SuccessfulOkConflictingAttributes statusCode = 0x0002 + ClientErrorBadRequest statusCode = 0x0400 ) type versionNumber uint16 @@ -148,13 +168,14 @@ type Attribute interface { //size() int } -type attributes struct { - operation []Attribute - printer []Attribute - job []Attribute +type Attributes struct { + operation []Attribute + printer []Attribute + job []Attribute + unsupported []Attribute } -func (a *attributes) String() string { +func (a *Attributes) String() string { s := " OperationAttributes" + "\n" for _, a := range a.operation { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) @@ -167,10 +188,15 @@ func (a *attributes) String() string { for _, a := range a.job { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) } + s = s + " Unsupported" + "\n" + for _, a := range a.unsupported { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + return s } -func (a *attributes) addAttribute(group tag, attr Attribute) { +func (a *Attributes) addAttribute(group tag, attr Attribute) { switch group { case operationAttributes: a.operation = append(a.operation, attr) @@ -178,13 +204,15 @@ func (a *attributes) addAttribute(group tag, attr Attribute) { a.job = append(a.job, attr) case printerAttributes: a.printer = append(a.printer, attr) + case unsupportedAttributes: + a.unsupported = append(a.unsupported, attr) default: - log.Error("Unknown attribute group") + log.Errorf("Unknown attribute group %v", group) } } -func UnMarshalAttributes(bytestream *bufio.Reader) *attributes { - a := new(attributes) +func UnMarshalAttributes(bytestream *bufio.Reader) *Attributes { + a := new(Attributes) var t tag err := binary.Read(bytestream, binary.BigEndian, &t) @@ -290,6 +318,11 @@ func UnMarshalAttributes(bytestream *bufio.Reader) *attributes { case begCollectionValueTag: // For now just consume the collection consumeCollection(bytestream) + case unsupportedValueTag: + attr := NewUnsupportedValue() + attr.unmarshal(bytestream) + a.addAttribute(currentAttributeGroup, attr) + case jobAttributes: log.Debug("Start job attributes") currentAttributeGroup = jobAttributes @@ -299,8 +332,11 @@ func UnMarshalAttributes(bytestream *bufio.Reader) *attributes { case operationAttributes: log.Debug("Start operation attributes") currentAttributeGroup = operationAttributes + case unsupportedAttributes: + log.Debug("Start unsupported attributes") + currentAttributeGroup = unsupportedAttributes case resolutionValueTag: - res := NewResolution("", 0, 0) + res := NewSetOfResolution("") res.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, res) log.Debugf("Resolution %v", res) diff --git a/packages/ipp/mimemediatype.go b/packages/ipp/mimemediatype.go index 6788489..431f1e5 100644 --- a/packages/ipp/mimemediatype.go +++ b/packages/ipp/mimemediatype.go @@ -1,36 +1,36 @@ package ipp -type mimeMediaType struct { - sos *setOfStrings +type MimeMediaType struct { + sos *SetOfStrings } -func NewMimeMediaType(name string, values ...string) *mimeMediaType { +func NewMimeMediaType(name string, values ...string) *MimeMediaType { - m := new(mimeMediaType) + m := new(MimeMediaType) m.sos = NewSetOfStrings(name, mimeMediaTypeValueTag, values) return m } -func (m mimeMediaType) Name() string { +func (m MimeMediaType) Name() string { return m.sos.name } -func (m mimeMediaType) String() string { +func (m MimeMediaType) String() string { return m.sos.String() } -func (m *mimeMediaType) size() int { +func (m *MimeMediaType) size() int { return m.sos.size() } -func (m *mimeMediaType) valueTag() tag { +func (m *MimeMediaType) valueTag() tag { return m.sos.valueTag() } -func (m *mimeMediaType) marshal() []byte { +func (m *MimeMediaType) marshal() []byte { return m.sos.marshal() } -func (m *mimeMediaType) addValue(v interface{}) { +func (m *MimeMediaType) addValue(v interface{}) { m.sos.AddValue(v.(string)) } diff --git a/packages/ipp/naturallanguage.go b/packages/ipp/naturallanguage.go index 1af6805..434c187 100644 --- a/packages/ipp/naturallanguage.go +++ b/packages/ipp/naturallanguage.go @@ -4,35 +4,35 @@ import ( "io" ) -type naturalLanguage struct { +type NaturalLanguage struct { name string value string } -func NewNaturalLanguage(name, value string) *naturalLanguage { - c := new(naturalLanguage) +func NewNaturalLanguage(name, value string) *NaturalLanguage { + c := new(NaturalLanguage) c.name = name c.value = value return c } -func (c naturalLanguage) Name() string { +func (c NaturalLanguage) Name() string { return c.name } -func (c naturalLanguage) String() string { +func (c NaturalLanguage) String() string { return c.name + ":" + c.value } -func (c *naturalLanguage) valueTag() tag { +func (c *NaturalLanguage) valueTag() tag { return naturalLanguageValueTag } -func (c *naturalLanguage) unmarshal(byteStream io.Reader) { +func (c *NaturalLanguage) unmarshal(byteStream io.Reader) { c.name, c.value = unmarshalSingleValue(byteStream) } -func (c *naturalLanguage) marshal() []byte { +func (c *NaturalLanguage) marshal() []byte { l := 5 + len(c.name) + len(c.value) b := make([]byte, l) b[0] = byte(naturalLanguageValueTag) @@ -40,7 +40,7 @@ func (c *naturalLanguage) marshal() []byte { return b } -func (c *naturalLanguage) size() int { +func (c *NaturalLanguage) size() int { l := 1 + 4 // The attribute tag + 2 lengths l += len(c.name) l += len(c.value) diff --git a/packages/ipp/printerstate_string.go b/packages/ipp/printerstate_string.go index fb82ddd..a9b3d40 100644 --- a/packages/ipp/printerstate_string.go +++ b/packages/ipp/printerstate_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type printerState ."; DO NOT EDIT. +// Code generated by "stringer -type jobState -type printerState"; DO NOT EDIT. package ipp @@ -13,9 +13,9 @@ func _() { _ = x[Stopped-5] } -const _printerState_name = "IdlePreocessingStopped" +const _printerState_name = "IdleProcessingStopped" -var _printerState_index = [...]uint8{0, 4, 15, 22} +var _printerState_index = [...]uint8{0, 4, 14, 21} func (i printerState) String() string { i -= 3 diff --git a/packages/ipp/rangeofinteger.go b/packages/ipp/rangeofinteger.go index 0ece69b..edc10a9 100644 --- a/packages/ipp/rangeofinteger.go +++ b/packages/ipp/rangeofinteger.go @@ -13,36 +13,36 @@ type IRange struct { upper int32 } -type rangeOfInteger struct { +type RangeOfInteger struct { name string values []IRange } -func NewRangeOfInteger(name string, values ...IRange) *rangeOfInteger { - r := new(rangeOfInteger) +func NewRangeOfInteger(name string, values ...IRange) *RangeOfInteger { + r := new(RangeOfInteger) r.name = name r.values = values return r } -func (r *rangeOfInteger) Name() string { +func (r *RangeOfInteger) Name() string { return r.name } -func (r rangeOfInteger) String() string { +func (r RangeOfInteger) String() string { return r.name + ":" + fmt.Sprint(r.values) } -func (r *rangeOfInteger) valueTag() tag { +func (r *RangeOfInteger) valueTag() tag { return rangeOfIntegerValueTag } -func (r *rangeOfInteger) marshal() []byte { +func (r *RangeOfInteger) marshal() []byte { log.Error("marshal rangeOfInteger is not implemented yet") return []byte{} } -func (r *rangeOfInteger) addValue(v interface{}) { +func (r *RangeOfInteger) addValue(v interface{}) { r.values = append(r.values, v.(IRange)) } diff --git a/packages/ipp/request.go b/packages/ipp/request.go index 70dc15a..6acdbf8 100644 --- a/packages/ipp/request.go +++ b/packages/ipp/request.go @@ -38,7 +38,7 @@ type AddValuer interface { } type Request struct { - a *attributes + a *Attributes header ippMessageHeader } @@ -47,7 +47,7 @@ func NewRequest(op OperationID, requestID uint32) *Request { r.header.operationID = op r.header.requestID = requestID r.header.versionNumber = 0x0200 - r.a = new(attributes) + r.a = new(Attributes) return r } @@ -58,7 +58,6 @@ func (r Request) String() string { func (r *Request) UnMarshal(body io.Reader) { buffbody := bufio.NewReader(body) r.header.unmarshal(buffbody) - //log.Printf("Header %v", r.header) r.a = UnMarshalAttributes(buffbody) } @@ -76,9 +75,25 @@ func (r *Request) GetAttribute(name string) Attribute { return a } } + for _, a := range r.a.job { + if a.Name() == name { + return a + } + } + for _, a := range r.a.printer { + if a.Name() == name { + return a + } + } + for _, a := range r.a.unsupported { + if a.Name() == name { + return a + } + } return nil } +// Marshal converts the request object to a ipp request func (r *Request) Marshal() []byte { var buf bytes.Buffer @@ -105,14 +120,29 @@ func (r *Request) Marshal() []byte { return buf.Bytes() } -func (r *Request) AddPrinterAttribute(a Attribute) { - r.a.addAttribute(printerAttributes, a) +// AddPrinterAttribute adds a printer attribute to the request object +func (r *Request) AddPrinterAttribute(a Attribute) error { + if a != nil { + r.a.addAttribute(printerAttributes, a) + return nil + } + return ErrNilAttribute } -func (r *Request) AddOperatonAttribute(a Attribute) { - r.a.addAttribute(operationAttributes, a) +// AddOperatonAttribute adds a operation attribute to the request object +func (r *Request) AddOperatonAttribute(a Attribute) error { + if a != nil { + r.a.addAttribute(operationAttributes, a) + return nil + } + return ErrNilAttribute } -func (r *Request) AddJobAttribute(a Attribute) { - r.a.addAttribute(jobAttributes, a) +// AddJobAttribute adds a job attribute to the request object +func (r *Request) AddJobAttribute(a Attribute) error { + if a != nil { + r.a.addAttribute(jobAttributes, a) + return nil + } + return ErrNilAttribute } diff --git a/packages/ipp/request_test.go b/packages/ipp/request_test.go index dbe4c31..87086e2 100644 --- a/packages/ipp/request_test.go +++ b/packages/ipp/request_test.go @@ -67,7 +67,7 @@ func TestUnmarshalRequestPrinterAttributes(T *testing.T) { assert.Equal(T, GetPrinterAttributes, req.header.operationID, "Wrong Operation") assert.Equal(T, uint32(17), req.header.requestID, "Wrong request id") assert.Len(T, req.a.operation, 4) - v := req.GetAttribute("requested-attributes").(*keyWord).sos.values + v := req.GetAttribute("requested-attributes").(*KeyWord).sos.values assert.Len(T, v, 7) assert.Contains(T, v, "printer-make-and-model") assert.Contains(T, v, "ipp-versions-supported") diff --git a/packages/ipp/resolution.go b/packages/ipp/resolution.go index c78111f..eed8f43 100644 --- a/packages/ipp/resolution.go +++ b/packages/ipp/resolution.go @@ -1,67 +1,95 @@ package ipp import ( + "bufio" "bytes" "encoding/binary" "fmt" - "io" + + log "github.com/sirupsen/logrus" ) -type resolution struct { - name string - crossFeedResolution int32 - feedResolution int32 - units int8 +// Resolution represents a ipp resolution attribute +type Resolution struct { + CrossFeedResolution int32 + FeedResolution int32 + Unit int8 // 3 seems to mean dpi (rfc3805) } -func NewResolution(name string, xfres int32, fres int32) *resolution { - r := new(resolution) +// SetOfResolutions represents a set ipp resolution attributes +type SetOfResolutions struct { + name string + sor []Resolution +} + +// NewSetOfResolution creats a new set of ipp resolution attributes +func NewSetOfResolution(name string, resSet ...Resolution) *SetOfResolutions { + r := new(SetOfResolutions) r.name = name - r.crossFeedResolution = xfres - r.feedResolution = fres - r.units = 3 // 3 seems to mean dpi (rfc3805) + r.sor = resSet return r } -func (r *resolution) unmarshal(byteStream io.Reader) { +func (r *SetOfResolutions) unmarshal(byteStream *bufio.Reader) { var length uint16 - binary.Read(byteStream, binary.BigEndian, &length) - attributeName := make([]byte, length) - if length > 0 { - binary.Read(byteStream, binary.BigEndian, attributeName) + var res Resolution + for { + binary.Read(byteStream, binary.BigEndian, &length) + attributeName := make([]byte, length) + if length > 0 { + binary.Read(byteStream, binary.BigEndian, attributeName) + r.name = string(attributeName) + } + binary.Read(byteStream, binary.BigEndian, &length) + if length != 9 { + panic("Wrong length in resolution") + } + binary.Read(byteStream, binary.BigEndian, &res.CrossFeedResolution) + binary.Read(byteStream, binary.BigEndian, &res.FeedResolution) + binary.Read(byteStream, binary.BigEndian, &res.Unit) + r.sor = append(r.sor, res) + var t tag + err := binary.Read(byteStream, binary.BigEndian, &t) + if err != nil { + log.Error(err.Error()) + } + if t != resolutionValueTag { + byteStream.UnreadByte() + return + } } - r.name = string(attributeName) - binary.Read(byteStream, binary.BigEndian, &length) - if length != 9 { - panic("Wrong length in resolution") - } - binary.Read(byteStream, binary.BigEndian, &r.crossFeedResolution) - binary.Read(byteStream, binary.BigEndian, &r.feedResolution) - binary.Read(byteStream, binary.BigEndian, &r.units) } -func (r resolution) Name() string { +// Name returns the name of the attribute +func (r SetOfResolutions) Name() string { return r.name } -func (r resolution) String() string { - return fmt.Sprintf("%v:%v,%v,%v", r.name, r.crossFeedResolution, r.feedResolution, r.units) +func (r SetOfResolutions) String() string { + return fmt.Sprintf("%v:%v", r.name, r.sor) } -func (r *resolution) marshal() []byte { +func (r *SetOfResolutions) marshal() []byte { - b := make([]byte, 0, 14+len(r.name)) + b := make([]byte, 0, 14+len(r.name)+14*(len(r.sor)-1)) buf := bytes.NewBuffer(b) - buf.WriteByte(byte(resolutionValueTag)) - binary.Write(buf, binary.BigEndian, uint16(len(r.name))) - buf.WriteString(r.name) - binary.Write(buf, binary.BigEndian, uint16(9)) - binary.Write(buf, binary.BigEndian, int32(r.crossFeedResolution)) - binary.Write(buf, binary.BigEndian, int32(r.feedResolution)) - buf.WriteByte(byte(r.units)) + + for i, res := range r.sor { + buf.WriteByte(byte(resolutionValueTag)) + if i == 0 { + binary.Write(buf, binary.BigEndian, uint16(len(r.name))) + buf.WriteString(r.name) + } else { + binary.Write(buf, binary.BigEndian, uint16(0)) + } + binary.Write(buf, binary.BigEndian, uint16(9)) + binary.Write(buf, binary.BigEndian, int32(res.CrossFeedResolution)) + binary.Write(buf, binary.BigEndian, int32(res.FeedResolution)) + buf.WriteByte(byte(res.Unit)) + } return buf.Bytes() } -func (r *resolution) valueTag() tag { +func (r *SetOfResolutions) valueTag() tag { return resolutionValueTag } diff --git a/packages/ipp/response.go b/packages/ipp/response.go index 6f0befe..7a2bc0c 100644 --- a/packages/ipp/response.go +++ b/packages/ipp/response.go @@ -32,14 +32,16 @@ func (h *ippResponseHeader) unmarshal(byteStream io.Reader) { binary.Read(byteStream, binary.BigEndian, &h.requestID) } +// Response represens a ipp response object type Response struct { - a *attributes + a *Attributes header ippResponseHeader } +// NewResponse creates a new ipp response object func NewResponse(code statusCode, requestID uint32) *Response { r := new(Response) - r.a = new(attributes) + r.a = new(Attributes) r.header.versionNumber = 0x0101 r.header.requestID = requestID r.header.statusCode = code @@ -50,6 +52,7 @@ func (r Response) String() string { return r.header.String() + "\n" + r.a.String() } +// Marshal converts the response object to a wire formatted byte stream. func (r *Response) Marshal() []byte { a := make([]byte, 0, 20) a = append(a, r.header.marshal()...) @@ -75,21 +78,63 @@ func (r *Response) Marshal() []byte { return a } +// UnMarshal unmarshals a ipp response into a response object func (r *Response) UnMarshal(body io.Reader) { buffbody := bufio.NewReader(body) r.header.unmarshal(buffbody) - //log.Printf("Header %v", r.header) r.a = UnMarshalAttributes(buffbody) } -func (r *Response) AddPrinterAttribute(a Attribute) { - r.a.addAttribute(printerAttributes, a) +// AddPrinterAttribute adds a printer attribute to the response object +func (r *Response) AddPrinterAttribute(a Attribute) error { + if a != nil { + r.a.addAttribute(printerAttributes, a) + return nil + } + return ErrNilAttribute } -func (r *Response) AddOperatonAttribute(a Attribute) { - r.a.addAttribute(operationAttributes, a) +// AddOperatonAttribute adds a printer attribute to the response object +func (r *Response) AddOperatonAttribute(a Attribute) error { + + if a != nil { + r.a.addAttribute(operationAttributes, a) + return nil + } + return ErrNilAttribute } -func (r *Response) AddJobAttribute(a Attribute) { - r.a.addAttribute(jobAttributes, a) +// AddJobAttribute adds a printer attribute to the response object +func (r *Response) AddJobAttribute(a Attribute) error { + if a != nil { + r.a.addAttribute(jobAttributes, a) + return nil + } + return ErrNilAttribute +} + +// GetAttribute retreives a attribute by name from the response object +// returns nil a atribute with the provided name can be found. +func (r *Response) GetAttribute(name string) Attribute { + for _, a := range r.a.operation { + if a.Name() == name { + return a + } + } + for _, a := range r.a.job { + if a.Name() == name { + return a + } + } + for _, a := range r.a.printer { + if a.Name() == name { + return a + } + } + for _, a := range r.a.unsupported { + if a.Name() == name { + return a + } + } + return nil } diff --git a/packages/ipp/setofstrings.go b/packages/ipp/setofstrings.go index 60e808a..25d641c 100644 --- a/packages/ipp/setofstrings.go +++ b/packages/ipp/setofstrings.go @@ -2,28 +2,31 @@ package ipp import "encoding/binary" -type setOfStrings struct { +// SetOfStrings is the strings attribute +type SetOfStrings struct { name string values []string vTag tag } -func NewSetOfStrings(name string, t tag, values []string) *setOfStrings { - s := new(setOfStrings) +// NewSetOfStrings creates a new strings attribute +func NewSetOfStrings(name string, t tag, values []string) *SetOfStrings { + s := new(SetOfStrings) s.name = name s.vTag = t s.values = values //make([]string, 0) return s } -func (s setOfStrings) String() string { +func (s SetOfStrings) String() string { r := s.name + " :" for _, v := range s.values { r = r + " " + v } return r } -func (s *setOfStrings) valueTag() tag { + +func (s *SetOfStrings) valueTag() tag { return s.vTag } @@ -39,7 +42,7 @@ func (s *setOfStrings) valueTag() tag { // } // } -func (s *setOfStrings) marshal() []byte { +func (s *SetOfStrings) marshal() []byte { l := 5 + len(s.name) + len(s.values[0]) for i := range s.values[1:] { l += 5 + len(s.values[i+1]) @@ -47,7 +50,7 @@ func (s *setOfStrings) marshal() []byte { res := make([]byte, l) p := 0 res[p] = byte(s.vTag) - p += 1 + p++ binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.name))) p += 2 copy(res[p:], []byte(s.name)) @@ -58,7 +61,7 @@ func (s *setOfStrings) marshal() []byte { p += len(s.values[0]) for i := range s.values[1:] { res[p] = byte(s.vTag) - p += 1 + p++ binary.BigEndian.PutUint16(res[p:p+2], uint16(0)) p = p + 2 binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[i+1]))) @@ -70,11 +73,12 @@ func (s *setOfStrings) marshal() []byte { return res } -func (s *setOfStrings) AddValue(v string) { +// AddValue adds a new sring to the set +func (s *SetOfStrings) AddValue(v string) { s.values = append(s.values, v) } -func (s *setOfStrings) size() int { +func (s *SetOfStrings) size() int { l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes) l += len(s.name) l += 2 // value-length field (2 bytes) diff --git a/packages/ipp/textwithoutlanguage.go b/packages/ipp/textwithoutlanguage.go index 9abf44b..3b59492 100644 --- a/packages/ipp/textwithoutlanguage.go +++ b/packages/ipp/textwithoutlanguage.go @@ -2,35 +2,38 @@ package ipp import "io" -type textWithoutLanguage struct { +// TextWithoutLanguage is the TextWithoutLanguage attribute +type TextWithoutLanguage struct { name string value string } -func NewtextWithoutLanguage(name, value string) *textWithoutLanguage { - c := new(textWithoutLanguage) +// NewtextWithoutLanguage creates a new textWithoutLanguage attribute +func NewtextWithoutLanguage(name, value string) *TextWithoutLanguage { + c := new(TextWithoutLanguage) c.name = name c.value = value return c } -func (c textWithoutLanguage) Name() string { +// Name returns the name of the attribute +func (c TextWithoutLanguage) Name() string { return c.name } -func (c textWithoutLanguage) String() string { +func (c TextWithoutLanguage) String() string { return c.name + ":" + c.value } -func (c *textWithoutLanguage) valueTag() tag { +func (c *TextWithoutLanguage) valueTag() tag { return textWithoutLanguageValueTag } -func (c *textWithoutLanguage) unmarshal(byteStream io.Reader) { +func (c *TextWithoutLanguage) unmarshal(byteStream io.Reader) { c.name, c.value = unmarshalSingleValue(byteStream) } -func (c *textWithoutLanguage) marshal() []byte { +func (c *TextWithoutLanguage) marshal() []byte { l := 5 + len(c.name) + len(c.value) b := make([]byte, l) b[0] = byte(textWithoutLanguageValueTag) @@ -38,7 +41,7 @@ func (c *textWithoutLanguage) marshal() []byte { return b } -func (c *textWithoutLanguage) size() int { +func (c *TextWithoutLanguage) size() int { l := 1 + 4 // The attribute tag + 2 lengths l += len(c.name) l += len(c.value) diff --git a/packages/ipp/unsupportedvalue.go b/packages/ipp/unsupportedvalue.go new file mode 100644 index 0000000..0f33171 --- /dev/null +++ b/packages/ipp/unsupportedvalue.go @@ -0,0 +1,40 @@ +package ipp + +import ( + "io" + + log "github.com/sirupsen/logrus" +) + +// UnSupportedValue represents a ipp unsupported attributes attribute +type UnSupportedValue struct { + name string +} + +// NewUnsupportedValue creates a new unsupported attributes attribute +func NewUnsupportedValue() *UnSupportedValue { + c := new(UnSupportedValue) + return c +} + +func (c *UnSupportedValue) unmarshal(byteStream io.Reader) { + c.name, _ = unmarshalSingleValue(byteStream) +} + +// Name returns the name of the attribute +func (c *UnSupportedValue) Name() string { + return c.name +} + +func (c *UnSupportedValue) marshal() (data []byte) { + log.Error("Unmarshal unsupported value is not implemented yet") + return +} + +func (c *UnSupportedValue) valueTag() tag { + return unsupportedValueTag +} + +func (c UnSupportedValue) String() string { + return c.name +} diff --git a/packages/ipp/urivalue.go b/packages/ipp/urivalue.go index 6e2e802..b22ba4f 100644 --- a/packages/ipp/urivalue.go +++ b/packages/ipp/urivalue.go @@ -4,41 +4,49 @@ import ( "io" ) -type uriValue struct { +// URIValue represents a ipp URI value attribute +type URIValue struct { name string value string } -func NewURIValue(name, value string) *uriValue { - u := new(uriValue) +// NewURIValue creates a new URIValue attribute +func NewURIValue(name, value string) *URIValue { + u := new(URIValue) u.name = name u.value = value return u } -func (u uriValue) Name() string { +// Name returns the name of the attribute +func (u URIValue) Name() string { return u.name } -func (u uriValue) String() string { +// Value returns the value of the attribute +func (u URIValue) Value() string { + return u.value +} + +func (u URIValue) String() string { return u.name + ":" + u.value } -func (u *uriValue) valueTag() tag { +func (u *URIValue) valueTag() tag { return uriValueTag } -func (u *uriValue) unmarshal(byteStream io.Reader) { +func (u *URIValue) unmarshal(byteStream io.Reader) { u.name, u.value = unmarshalSingleValue(byteStream) } -func (u *uriValue) marshal() []byte { +func (u *URIValue) marshal() []byte { res := make([]byte, u.size()) res[0] = byte(uriValueTag) marshalNameValue(u.name, u.value, res[1:]) return res } -func (u *uriValue) size() int { +func (u *URIValue) size() int { l := 1 + 4 // The attribute tag + 2 lengths l += len(u.name) l += len(u.value) diff --git a/packages/ipp/urivalue_test.go b/packages/ipp/urivalue_test.go index c7f9ad3..7c7633f 100644 --- a/packages/ipp/urivalue_test.go +++ b/packages/ipp/urivalue_test.go @@ -8,7 +8,7 @@ import ( ) func TestMarshalUriValue(T *testing.T) { - var u uriValue + var u URIValue u.name = "foo" u.value = "bar" b := u.marshal() diff --git a/proxy/handleValidateJob.go b/proxy/handleValidateJob.go new file mode 100644 index 0000000..3520af0 --- /dev/null +++ b/proxy/handleValidateJob.go @@ -0,0 +1,10 @@ +package main + +import "ippserver/packages/ipp" + +func handleValidateJob(r *ipp.Request) *ipp.Response { + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + return response +} diff --git a/proxy/handlegetjobattributes.go b/proxy/handlegetjobattributes.go new file mode 100644 index 0000000..c3806bb --- /dev/null +++ b/proxy/handlegetjobattributes.go @@ -0,0 +1,46 @@ +package main + +import ( + "bytes" + "fmt" + "ippserver/packages/ipp" + "net/http" + + log "github.com/sirupsen/logrus" +) + +func handleGetJobAttributes(r *ipp.Request, requestID uint32) *ipp.Response { + + request := ipp.NewRequest(ipp.GetJobAttributes, requestID) + request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI)) + request.AddOperatonAttribute(r.GetAttribute("job-id")) + request.AddOperatonAttribute(r.GetAttribute("requesting-user-name")) + request.AddOperatonAttribute(r.GetAttribute("requested-attributes")) + + log.Infof("Downstream request\n%v\n", request) + + downStreamRequest := request.Marshal() + b := bytes.NewBuffer(downStreamRequest) + + downStreamResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b) + if err != nil { + fmt.Print(err) + } + + rb := ipp.NewResponse(0, 0) + rb.UnMarshal(downStreamResponse.Body) + log.Infof("Downstream response\n%v\n", rb) + + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + response.AddJobAttribute(rb.GetAttribute("job-id")) + response.AddJobAttribute(rb.GetAttribute("job-name")) + response.AddJobAttribute(rb.GetAttribute("job-originating-user-name")) + response.AddJobAttribute(rb.GetAttribute("job-state")) + response.AddJobAttribute(rb.GetAttribute("job-state-reasons")) + + return response +} diff --git a/proxy/handlegetjobs.go b/proxy/handlegetjobs.go new file mode 100644 index 0000000..40a140b --- /dev/null +++ b/proxy/handlegetjobs.go @@ -0,0 +1,47 @@ +package main + +import ( + "bytes" + "fmt" + "ippserver/packages/ipp" + "net/http" + + log "github.com/sirupsen/logrus" +) + +func handleGetJobs(r *ipp.Request, requestID uint32) *ipp.Response { + + request := ipp.NewRequest(ipp.GetJobs, requestID) + request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI)) + request.AddOperatonAttribute(r.GetAttribute("requesting-user-name")) + request.AddOperatonAttribute(r.GetAttribute("requested-attributes")) + + log.Infof("Downstream request\n%v\n", request) + + downStreamRequest := request.Marshal() + b := bytes.NewBuffer(downStreamRequest) + + downStreamResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b) + if err != nil { + fmt.Print(err) + } + + //log.Printf("response HTTP status: %v %v", downStreamResponse.StatusCode, downStreamResponse.Status) + rb := ipp.NewResponse(0, 0) + rb.UnMarshal(downStreamResponse.Body) + log.Infof("Downstream response\n%v\n", rb) + + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + a := rb.GetAttribute("job-id") + response.AddJobAttribute(a) + response.AddJobAttribute(rb.GetAttribute("job-name")) + response.AddJobAttribute(rb.GetAttribute("job-originating-user-name")) + response.AddJobAttribute(rb.GetAttribute("job-state")) + response.AddJobAttribute(rb.GetAttribute("job-state-reasons")) + + return response +} diff --git a/proxy/handlegetprinterattributes.go b/proxy/handlegetprinterattributes.go new file mode 100644 index 0000000..c0b8891 --- /dev/null +++ b/proxy/handlegetprinterattributes.go @@ -0,0 +1,36 @@ +package main + +import "ippserver/packages/ipp" + +func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response { + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + + response.AddPrinterAttribute(ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print")) + response.AddPrinterAttribute(ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBroPrint")) + response.AddPrinterAttribute(ipp.NewEnum("printer-state", int32(ipp.Idle))) + response.AddPrinterAttribute(ipp.NewEnum("operations-supported", int32(ipp.PrintJob), int32(ipp.ValidateJob), int32(ipp.CancelJob), int32(ipp.GetJobAttributes), int32(ipp.GetJobs), int32(ipp.GetPrinterAttributes))) + response.AddPrinterAttribute(ipp.NewKeyWord("printer-state-reasons", "none")) + response.AddPrinterAttribute(ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0")) + response.AddPrinterAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0")) + response.AddPrinterAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster")) + response.AddPrinterAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm")) + response.AddPrinterAttribute(ipp.NewKeyWord("media-default", "iso_a4_210x297mm")) + response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-supported", "color", "auto", "monochrome")) + response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-default", "color")) + response.AddPrinterAttribute(ipp.NewBoolean("color-supported", true)) + response.AddPrinterAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge")) + response.AddPrinterAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome")) + response.AddPrinterAttribute(ipp.NewKeyWord("job-creation-attributes-supported", "copies", "finishings", "ipp-attribute-fidelity", + "job-name", "media", "media-col", "orientation-requested", "output-bin", "output-mode", "print-quality", "printer-resolution", + "requesting-user-name", "sides", "print-color-mode")) + response.AddPrinterAttribute(ipp.NewEnum("print-quality-supported", int32(4), int32(5))) //normal,high + res1 := ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3} + res2 := ipp.Resolution{CrossFeedResolution: 2400, FeedResolution: 600, Unit: 3} + response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-default", res1)) + response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-supported", res1, res2)) + response.AddPrinterAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true)) + return response +} diff --git a/proxy/handleprintjob.go b/proxy/handleprintjob.go new file mode 100644 index 0000000..4ba2a65 --- /dev/null +++ b/proxy/handleprintjob.go @@ -0,0 +1,69 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "ippserver/packages/ipp" + "net/http" + "net/url" + "strings" + + log "github.com/sirupsen/logrus" +) + +const printerURI = "brn30055cb5e3ae.local:631/ipp/print" + +func handlePrintJob(r *ipp.Request, byteStream io.Reader, requestID uint32) *ipp.Response { + + // This request is what will be sent to the real printer + request := ipp.NewRequest(ipp.PrintJob, requestID) + request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI)) + request.AddOperatonAttribute(r.GetAttribute("requesting-user-name")) + request.AddOperatonAttribute(r.GetAttribute("job-name")) + request.AddOperatonAttribute(ipp.NewMimeMediaType("document-format", "image/pwg-raster")) + + request.AddJobAttribute(r.GetAttribute("sides")) + request.AddJobAttribute(r.GetAttribute("media")) + request.AddJobAttribute(r.GetAttribute("print-color-mode")) + request.AddJobAttribute(r.GetAttribute("printer-resolution")) + request.AddJobAttribute(r.GetAttribute("output-bin")) + log.Infof("Downstream request\n%v\n", request) + + downStreamRequest := request.Marshal() + b := bytes.NewBuffer(downStreamRequest) + + mr := io.MultiReader(b, byteStream) + downStreamResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", mr) + if err != nil { + fmt.Print(err) + } + + rb := ipp.NewResponse(0, 0) + rb.UnMarshal(downStreamResponse.Body) + log.Infof("Downstream response\n%v\n", rb) + + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + jua := rb.GetAttribute("job-uri").(*ipp.URIValue) + if jua != nil { + u, err := url.Parse(jua.Value()) + if err != nil { + log.Error("could not parse job-uri") + } + l := strings.Split(u.Path, "/") + j := l[len(l)-1] + + joburi := r.GetAttribute("printer-uri").(*ipp.URIValue).Value() + "/" + j + log.Infof(">>>>>>>>>>>>>>>>>>>>>>joburi: %v", joburi) + response.AddJobAttribute(ipp.NewURIValue("job-uri", joburi)) + } + response.AddJobAttribute(rb.GetAttribute("job-id")) + response.AddJobAttribute(rb.GetAttribute("job-state")) + response.AddJobAttribute(rb.GetAttribute("job-state-reasons")) + + return response +} diff --git a/proxy/main.go b/proxy/main.go new file mode 100644 index 0000000..3e9d288 --- /dev/null +++ b/proxy/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "ippserver/packages/ipp" + "ippserver/packages/mdnsserver" + "net/http" + "sync" + + log "github.com/sirupsen/logrus" +) + +func main() { + customFormatter := new(log.TextFormatter) + customFormatter.TimestampFormat = "2006-01-02 15:04:05" + log.SetFormatter(customFormatter) + customFormatter.FullTimestamp = true + log.SetLevel(log.InfoLevel) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go mdnsserver.Run(ctx) + + http.HandleFunc("/ipp/print", handle) + + log.Info("http server started on :1234") + err := http.ListenAndServe(":1234", nil) + if err != nil { + log.Fatal("ListenAndServe: " + err.Error()) + } +} + +var mut sync.Mutex +var requestID uint32 + +func handle(w http.ResponseWriter, r *http.Request) { + mut.Lock() + requestID++ + mut.Unlock() + if r.Method != http.MethodPost { + http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) + + } + //log.Info(r.Header) + //body := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, body) + //log.Infof("Body %x", body) + + request := ipp.NewRequest(0, 0) + //rdata := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, rdata) + //log.Printf("Data %x", rdata) + //fmt.Printf("data % #0x", rdata) + //buf := bytes.NewBuffer(rdata) + request.UnMarshal(r.Body) + log.Infof("Upstream Request: \n%v\n", request) + var response *ipp.Response + switch request.Operation() { + case ipp.GetPrinterAttributes: + response = handleGetPrinterAttributes(request) + case ipp.PrintJob: + response = handlePrintJob(request, r.Body, requestID) + case ipp.GetJobs: + response = handleGetJobs(request, requestID) + case ipp.ValidateJob: + response = handleValidateJob(request) + case ipp.GetJobAttributes: + response = handleGetJobAttributes(request, requestID) + default: + response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestID()) + } + + log.Infof("Upstream Response:\n%v\n", response) + data := response.Marshal() + + //log.Debugf("% x", data) + w.Write(data) + +} diff --git a/server/handleValidateJob.go b/server/handleValidateJob.go new file mode 100644 index 0000000..3520af0 --- /dev/null +++ b/server/handleValidateJob.go @@ -0,0 +1,10 @@ +package main + +import "ippserver/packages/ipp" + +func handleValidateJob(r *ipp.Request) *ipp.Response { + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + return response +} diff --git a/server/handlegetprinterattributes.go b/server/handlegetprinterattributes.go index 0a54831..c0b8891 100644 --- a/server/handlegetprinterattributes.go +++ b/server/handlegetprinterattributes.go @@ -4,25 +4,33 @@ import "ippserver/packages/ipp" func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response { response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) - var a ipp.Attribute - a = ipp.NewCharSetValue("attributes-charset", "utf-8") - response.AddOperatonAttribute(a) - a = ipp.NewNaturalLanguage("attributes-natural-language", "en") - response.AddOperatonAttribute(a) - a = ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print") - response.AddOperatonAttribute(a) - a = ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBro 001") - response.AddOperatonAttribute(a) - a = ipp.NewEnum("printer-state", int32(ipp.Idle)) - response.AddOperatonAttribute(a) - a = ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0") - response.AddOperatonAttribute(a) - response.AddOperatonAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0")) - response.AddOperatonAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster")) - response.AddOperatonAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm")) - response.AddOperatonAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge")) - response.AddOperatonAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome")) - response.AddOperatonAttribute(ipp.NewResolution("printer-resolution-default", 600, 600)) - response.AddOperatonAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true)) + + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) + + response.AddPrinterAttribute(ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print")) + response.AddPrinterAttribute(ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBroPrint")) + response.AddPrinterAttribute(ipp.NewEnum("printer-state", int32(ipp.Idle))) + response.AddPrinterAttribute(ipp.NewEnum("operations-supported", int32(ipp.PrintJob), int32(ipp.ValidateJob), int32(ipp.CancelJob), int32(ipp.GetJobAttributes), int32(ipp.GetJobs), int32(ipp.GetPrinterAttributes))) + response.AddPrinterAttribute(ipp.NewKeyWord("printer-state-reasons", "none")) + response.AddPrinterAttribute(ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0")) + response.AddPrinterAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0")) + response.AddPrinterAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster")) + response.AddPrinterAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm")) + response.AddPrinterAttribute(ipp.NewKeyWord("media-default", "iso_a4_210x297mm")) + response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-supported", "color", "auto", "monochrome")) + response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-default", "color")) + response.AddPrinterAttribute(ipp.NewBoolean("color-supported", true)) + response.AddPrinterAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge")) + response.AddPrinterAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome")) + response.AddPrinterAttribute(ipp.NewKeyWord("job-creation-attributes-supported", "copies", "finishings", "ipp-attribute-fidelity", + "job-name", "media", "media-col", "orientation-requested", "output-bin", "output-mode", "print-quality", "printer-resolution", + "requesting-user-name", "sides", "print-color-mode")) + response.AddPrinterAttribute(ipp.NewEnum("print-quality-supported", int32(4), int32(5))) //normal,high + res1 := ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3} + res2 := ipp.Resolution{CrossFeedResolution: 2400, FeedResolution: 600, Unit: 3} + response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-default", res1)) + response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-supported", res1, res2)) + response.AddPrinterAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true)) return response } diff --git a/server/handleprintjob.go b/server/handleprintjob.go index b44705b..ccaf918 100644 --- a/server/handleprintjob.go +++ b/server/handleprintjob.go @@ -17,6 +17,8 @@ func handlePrintJob(r *ipp.Request, byteStream io.Reader) *ipp.Response { defer f.Close() io.Copy(f, byteStream) response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) + response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) + response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) return response } diff --git a/server/main.go b/server/main.go index 999f94b..d6092fd 100644 --- a/server/main.go +++ b/server/main.go @@ -30,7 +30,6 @@ func main() { } func handle(w http.ResponseWriter, r *http.Request) { - log.Infoln("handle") if r.Method != http.MethodPost { http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) @@ -56,6 +55,8 @@ func handle(w http.ResponseWriter, r *http.Request) { response = handlePrintJob(request, r.Body) case ipp.GetJobs: response = handleGetJobs(request) + case ipp.ValidateJob: + response = handleValidateJob(request) default: response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestID()) }