package ipp import ( "bytes" "encoding/binary" "errors" "math" "strings" "net/url" log "github.com/sirupsen/logrus" ) // Writes an "attribute-with-one-value" with the provided "value-tag", "name", and "value" to provided buffer // attribute-with-one-value encoding described at https://tools.ietf.org/html/rfc8010#section-3.1.4 // Example (runnable from ipp_test.go): // Input: 0x47, "attributes-charset", "us-ascii" // Output: [71 0 18 97 116 116 114 105 98 117 116 101 115 45 99 104 97 114 115 101 116 0 8 117 115 45 97 115 99 105 105] // TODO: Switch output and Example function to use hex.Dump() // TODO: Should return an error when fed an invalid valueTag func AttributeByteString(valueTag byte, name string, value string, target *bytes.Buffer) error { //special byte denoting value syntax binary.Write(target, binary.BigEndian, valueTag) if len(name) <= math.MaxInt16 && len(name) >= 0 { //append 16-bit signed int denoting name length binary.Write(target, binary.BigEndian, int16(len(name))) //append name binary.Write(target, binary.BigEndian, []byte(name)) } else { // TODO: Log error somewhere return errors.New("Name wrong length to encode.") } if len(value) <= math.MaxInt16 && len(value) >= 0 { //append 16-bit signed int denoting value length binary.Write(target, binary.BigEndian, int16(len(value))) //append value binary.Write(target, binary.BigEndian, []byte(value)) } else { // TODO: Log error somewhere return errors.New("Value wrong length to encode.") } return nil } // TODO: Eventually handle scheme-less urls, even though getHTTPURL will never construct one (we can use regex) // TODO: RFC claims that literal IP addresses are not valid IPP uri's, but Wireshark IPP Capture example uses them // (Source: https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=view&target=ipp.pcap) func ConvertURIToIPP(uriString string, tls bool) string { uri, err := url.Parse(uriString) if err != nil { log.WithFields(log.Fields{ "error": err, "url": uriString, }).Debug("Failed to parse URL from string") } // TODO: Create a better condition than uri.Scheme == "" b/c url.Parse doesn't know whether there's a scheme if uri.Scheme == "" || uri.Scheme == "http" || uri.Scheme == "https" { if tls { uri.Scheme = "ipps" } else { uri.Scheme = "ipp" } } if !strings.Contains(uri.Host, ":") { uri.Host += ":631" } return uri.String() } func getPrintersRequest(major, minor int8) *bytes.Buffer { var b bytes.Buffer // Sending too new a version leads to a version-not-supported error, so we'll just send newest //version b.Write([]byte{byte(major), byte(minor)}) //operation-id = get-printer-attributes b.Write([]byte{0x40, 2}) //request-id = 1 b.Write([]byte{0, 0, 0, 1}) //operation-attributes-tag = 1 (begins an attribute-group) b.Write([]byte{1}) // TODO: Handle error ocurring in any AttributeByteString call //attributes-charset AttributeByteString(0x47, "attributes-charset", "utf-8", &b) //attributes-natural-language AttributeByteString(0x48, "attributes-natural-language", "en-us", &b) //end-of-attributes-tag = 3 b.Write([]byte{3}) return &b } // TODO: Store everything except uri statically? // Construct a minimal request that an IPP server will respond to // IPP request encoding described at https://tools.ietf.org/html/rfc8010#section-3.1.1 func getPrinterAttributesRequest(major, minor int8, uri string, tls bool) *bytes.Buffer { var b bytes.Buffer // Using newest version number, because we must provide a supported major version number // Object must reply to unsupported major version with // "'server-error-version-not-supported' along with the closest version number that // is supported" RFC 8011 4.1.8 https://tools.ietf.org/html/rfc8011#4.1.8 // "In all cases, the IPP object MUST return the "version-number" value that it supports // that is closest to the version number supplied by the Client in the request." // CUPS behavior defies the RFC. The response to a request with a bad version number should encode // the closest supported version number per RFC 8011 Section Appendix B.1.5.4 https://tools.ietf.org/html/rfc8011#appendix-B.1.5.4 //version b.Write([]byte{byte(major), byte(minor)}) //operation-id = get-printer-attributes b.Write([]byte{0, 0xb}) //request-id = 1 b.Write([]byte{0, 0, 0, 1}) //operation-attributes-tag = 1 (begins an attribute-group) b.Write([]byte{1}) // TODO: Handle error ocurring in any AttributeByteString call //attributes-charset AttributeByteString(0x47, "attributes-charset", "utf-8", &b) //attributes-natural-language AttributeByteString(0x48, "attributes-natural-language", "en-us", &b) //printer-uri AttributeByteString(0x45, "printer-uri", ConvertURIToIPP(uri, tls), &b) //requested-attributes AttributeByteString(0x44, "requested-attributes", "all", &b) //end-of-attributes-tag = 3 b.Write([]byte{3}) return &b }