package sign import ( "crypto" "encoding/asn1" "errors" "fmt" "io" "math" "os" "strings" "time" "github.com/digitorus/pdf" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" ) func findFirstPage(parent pdf.Value) (pdf.Value, error) { value_type := parent.Key("Type").String() if value_type == "/Pages" { for i := 0; i < parent.Key("Kids").Len(); i++ { kid := parent.Key("Kids").Index(i) recurse_parent, recurse_err := findFirstPage(kid) if recurse_err == nil { return recurse_parent, recurse_err } } return parent, errors.New("Could not find first page.") } if value_type == "/Page" { return parent, nil } return parent, errors.New("Could not find first page.") } func pdfString(text string) string { if !isASCII(text) { // UTF-16BE enc := unicode.UTF16(unicode.BigEndian, unicode.UseBOM).NewEncoder() res, _, err := transform.String(enc, text) if err != nil { panic(err) } return "(" + res + ")" } // UTF-8 // (\357\273\277Layer 1) % UTF-8 Layer 1 Name // % UTF-8 Layer 2 Name // text = "\357\273\277" + text // text = hex.EncodeToString([]byte(text)) // text = "<" + text + ">" // PDFDocEncoded text = strings.Replace(text, "\\", "\\\\", -1) text = strings.Replace(text, ")", "\\)", -1) text = strings.Replace(text, "(", "\\(", -1) text = strings.Replace(text, "\r", "\\r", -1) text = "(" + text + ")" return text } func pdfDateTime(date time.Time) string { // Calculate timezone offset from GMT. _, original_offset := date.Zone() offset := original_offset if offset < 0 { offset = -offset } offset_duration := time.Duration(offset) * time.Second offset_hours := int(math.Floor(offset_duration.Hours())) offset_minutes := int(math.Floor(offset_duration.Minutes())) offset_minutes = offset_minutes - (offset_hours * 60) dateString := "D:" + date.Format("20060102150405") // Do some special formatting as the PDF timezone format isn't supported by Go. if original_offset < 0 { dateString += "-" } else { dateString += "+" } offset_hours_formatted := fmt.Sprintf("%d", offset_hours) offset_minutes_formatted := fmt.Sprintf("%d", offset_minutes) dateString += leftPad(offset_hours_formatted, "0", 2-len(offset_hours_formatted)) + "'" + leftPad(offset_minutes_formatted, "0", 2-len(offset_minutes_formatted)) + "'" return pdfString(dateString) } func leftPad(s string, padStr string, pLen int) string { if pLen <= 0 { return s } return strings.Repeat(padStr, pLen) + s } func writePartFromSourceFileToTargetFile(input_file io.ReadSeeker, output_file io.Writer, offset int64, length int64) error { _, err := input_file.Seek(offset, 0) if err != nil { return err } // Create a small buffer for proper IO handling. max_chunk_length := int64(1024) // If the target length is smaller than our chunk size, use that as chunk size. if length < max_chunk_length { max_chunk_length = length } // Track read/written bytes so we know when we're done. read_bytes := int64(0) if length <= 0 { return nil } // Create a buffer for the chunks. buf := make([]byte, max_chunk_length) for { // Read the chunk from the input file. n, err := input_file.Read(buf) if err != nil && err != io.EOF { return err } // If we got to the end of the file, break. if err == io.EOF { break } // If nothing was read, break. if n == 0 { break } // Write the chunk to the output file. if _, err := output_file.Write(buf[:n]); err != nil { return err } read_bytes += int64(n) // If we read enough bytes, break. if read_bytes >= length { break } // If our next chunk will be too big, make a smaller buffer. // If we won't do this, we might end up with more data than we want. if length-read_bytes < max_chunk_length { buf = make([]byte, length-read_bytes) } } return nil } var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{ crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}), crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}), crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}), crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}), } // func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash { // for hash, oid := range hashOIDs { // if oid.Equal(target) { // return hash // } // } // return crypto.Hash(0) // } func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier { for hash, oid := range hashOIDs { if hash == target { return oid } } return nil } func isASCII(s string) bool { for _, r := range s { if r > '\u007F' { return false } } return true } // CopyFile copies a file from src to dst. // It's a replacement for os.Rename that works across filesystems. func CopyFile(src, dst string) error { // Open the source file srcFile, err := os.Open(src) if err != nil { return fmt.Errorf("failed to open source file: %w", err) } defer srcFile.Close() // Create the destination directory if it doesn't exist dstDir := dst[:strings.LastIndex(dst, "/")] if err := os.MkdirAll(dstDir, 0o755); err != nil { return fmt.Errorf("failed to create destination directory: %w", err) } // Create the destination file dstFile, err := os.Create(dst) if err != nil { return fmt.Errorf("failed to create destination file: %w", err) } defer dstFile.Close() // Copy the contents _, err = io.Copy(dstFile, srcFile) if err != nil { return fmt.Errorf("failed to copy file contents: %w", err) } return nil }