C++ version of fax-pnh-filter
This code duplicates the functionality of the fax-pnh-filter bash script
without calling any external processes.
BUG=chromium:1082306
TEST=unit tests, tast run printer.PinPrint*
Change-Id: Iaacb8aca5fdbf788800bb38a0ce66c9f0e12bc28
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/lexmark-fax-pnh/+/2276150
Reviewed-by: Sean Kau <skau@chromium.org>
Reviewed-by: Nikita Podguzov <nikitapodguzov@chromium.org>
Tested-by: Brian Malcolm <bmalcolm@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
new file mode 100644
index 0000000..59061fb
--- /dev/null
+++ b/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//common-mk/pkg_config.gni")
+
+group("all") {
+ deps = [ ":fax-pnh-filter" ]
+ if (use.test) {
+ deps += [ ":token_replacer_testrunner" ]
+ }
+}
+
+pkg_config("target_defaults") {
+ pkg_deps = [
+ "libchrome-${libbase_ver}",
+ ]
+}
+
+executable("fax-pnh-filter") {
+ configs += [ ":target_defaults" ]
+ sources = [
+ "main.cc",
+ "token_replacer.cc",
+ ]
+}
+
+
+if (use.test) {
+ pkg_config("test_config") {
+ pkg_deps = [ "libchrome-test-${libbase_ver}" ]
+ }
+
+ executable("token_replacer_testrunner") {
+ configs += [
+ "//common-mk:test",
+ ":target_defaults",
+ ":test_config",
+ ]
+ sources = [
+ "token_replacer.cc",
+ "token_replacer_test.cc",
+ ]
+ deps = [ "//common-mk/testrunner" ]
+ }
+}
\ No newline at end of file
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..4a40808
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+bmalcolm@chromium.org
+skau@chromium.org
\ No newline at end of file
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..1a17450
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fstream>
+#include <iostream>
+
+#include "token_replacer.h"
+
+// Read in the file, line-by-line, apply the transformation to each line, and
+// then write the line to out.
+void transform(const TokenReplacer& replacer,
+ std::istream& in,
+ std::ostream& out) {
+ std::string line;
+ while(std::getline(in, line)) {
+ out << replacer.TokenizeLine(line) << std::endl;
+ }
+}
+
+int main(int argc, char* argv[]) {
+ if (argc < 6 || argc > 7) {
+ std::cerr << "ERROR: " << argv[0]
+ << " job-id user title copies options [file]" << std::endl;
+ return 1;
+ }
+
+ std::string host = "localhost";
+ std::string user = argv[2];
+ std::string title = argv[3];
+ std::string copies = argv[4];
+
+ TokenReplacer replacer(host, user, title, copies);
+
+ if (argc < 7) {
+ transform(replacer, std::cin, std::cout);
+ } else {
+ std::ifstream fstream(argv[6]);
+ transform(replacer, fstream, std::cout);
+ }
+
+ return 0;
+}
diff --git a/token_replacer.cc b/token_replacer.cc
new file mode 100644
index 0000000..a8cb3bb
--- /dev/null
+++ b/token_replacer.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "token_replacer.h"
+
+TokenReplacer::TokenReplacer(const std::string& host,
+ const std::string& user,
+ const std::string& title,
+ const std::string& copies)
+ : title_(EscapeTitle(title)),
+ re_host_("(STATIONID|PJL(.+?HOSTID)) = GETMYHOST"),
+ host_replacement_(R"($1 = ")" + host + R"(")"),
+ // GEYMYUSERNAME is not a typo (see fax-pnh-filter)
+ re_user_("(PJL (SET USERNAME|(.+?USERID))) = GEYMYUSERNAME"),
+ user_replacement_(R"($1 = ")" + user + R"(")"),
+ re_title_("(PJL SET JOBNAME) = GETMYJOBNAME"),
+ title_replacement_(R"($1 = ")" + title_ + R"(")"),
+ re_copies_("(PJL SET QTY) = GETMYCOPIES"),
+ copies_replacement_("$1 = " + copies ) {}
+
+const std::string TokenReplacer::TokenizeLine(std::string line) const {
+ line = std::regex_replace(line, re_host_, host_replacement_);
+ line = std::regex_replace(line, re_user_, user_replacement_);
+ line = std::regex_replace(line, re_title_, title_replacement_);
+ line = std::regex_replace(line, re_copies_, copies_replacement_);
+ return line;
+}
+
+std::string TokenReplacer::EscapeTitle(std::string title) {
+ // Replace each one or two backslash sequence with one backslash
+ std::regex slash(R"(\\{1,2})");
+ title = std::regex_replace(title, slash, R"(\)");
+
+ // Replace double-quotes with single-quotes
+ std::regex quote(R"(")");
+ title = std::regex_replace(title, quote, "'");
+
+ return title;
+}
\ No newline at end of file
diff --git a/token_replacer.h b/token_replacer.h
new file mode 100644
index 0000000..9c92e7d
--- /dev/null
+++ b/token_replacer.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef __TOKEN_REPLACER_H__
+#define __TOKEN_REPLACER_H__
+
+#include <regex>
+#include <string>
+
+class TokenReplacer {
+ public:
+ TokenReplacer() = delete;
+ TokenReplacer(const std::string& host,
+ const std::string& user,
+ const std::string& title,
+ const std::string& copies);
+
+ // For testing purposes
+ const std::string& GetTitle() const { return title_; };
+
+ // Replaces tokens in the @PJL section of the print job as follows:
+ // 1) GETMYHOST is replaced with host
+ // 2) GEYMYUSERNAME is replaced with user
+ // 3) GETMYJOBNAME is replaced with an escaped title
+ // 4) GETMYCOPIES is replaced with copies
+ const std::string TokenizeLine(std::string line) const;
+
+ private:
+ // Escape a string according to the following rules (see myjob in
+ // fax-pnh-filter for the rules):
+ // 1) One or two backslashes in sequence are replaced with one backslash
+ // 2) double-quotes are replaced with single-quotes
+ //
+ // Note that fax-pnh-filter has additional rules for ampersands & slashes, but
+ // these are only to deal with the fact that the title string is being passed
+ // to sed on the command-line, so those characters are being escaped because
+ // they would be interpreted on the command-line as bash control characters.
+ // The outputted title from the filter would revert \& and \/ to & and /
+ // respectively. Therefore there is no need to escape those characters here.
+ static std::string EscapeTitle(std::string title);
+
+ const std::string title_;
+ const std::regex re_host_;
+ const std::string host_replacement_;
+ const std::regex re_user_;
+ const std::string user_replacement_;
+ const std::regex re_title_;
+ const std::string title_replacement_;
+ const std::regex re_copies_;
+ const std::string copies_replacement_;
+};
+
+#endif // __TOKEN_REPLACER_H__
\ No newline at end of file
diff --git a/token_replacer_test.cc b/token_replacer_test.cc
new file mode 100644
index 0000000..76dfc64
--- /dev/null
+++ b/token_replacer_test.cc
@@ -0,0 +1,114 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "token_replacer.h"
+
+#include <gtest/gtest.h>
+
+namespace {
+constexpr char kHostName[] = "hostname";
+constexpr char kUserName[] = "user@host.com";
+constexpr char kTitle[] = R"("This" & /that\\)";
+constexpr char kCopies[] = "42";
+} // namespace
+
+class TokenReplacerTest : public testing::Test {
+ protected:
+ TokenReplacerTest() : replacer_(kHostName, kUserName, kTitle, kCopies) {}
+
+ TokenReplacer replacer_;
+};
+
+TEST(Title, NoChange) {
+ TokenReplacer replacer("unused", "unused", "unchanged", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), "unchanged");
+}
+
+// One backslash => one backslash
+TEST(Title, Backslash) {
+ TokenReplacer replacer("unused", "unused", R"(this \ that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), R"(this \ that)");
+}
+
+// Two backslashes => one backslash
+TEST(Title, Backslash2) {
+ TokenReplacer replacer("unused", "unused", R"(this \\ that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), R"(this \ that)");
+}
+
+// Three backslashes => two backslashes
+TEST(Title, Backslash3) {
+ TokenReplacer replacer("unused", "unused", R"(this \\\ that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), R"(this \\ that)");
+}
+
+// Four backslashes => two backslashes
+TEST(Title, Backslash4) {
+ TokenReplacer replacer("unused", "unused", R"(this \\\\ that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), R"(this \\ that)");
+}
+
+// Five backslashes => three backslashes
+TEST(Title, Backslash5) {
+ TokenReplacer replacer("unused", "unused", R"(this \\\\\ that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), R"(this \\\ that)");
+}
+
+TEST(Title, Ampersand) {
+ TokenReplacer replacer("unused", "unused", "this & that", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), "this & that");
+}
+
+TEST(Title, DoubleQuotes) {
+ TokenReplacer replacer("unused", "unused", R"("this" and that)", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), "'this' and that");
+}
+
+TEST(Title, ForwardSlash) {
+ TokenReplacer replacer("unused", "unused", "this / that", "unused");
+
+ EXPECT_EQ(replacer.GetTitle(), "this / that");
+}
+
+TEST_F(TokenReplacerTest, UnchangedLine) {
+ EXPECT_EQ(replacer_.TokenizeLine("Unchanged"), "Unchanged");
+}
+
+TEST_F(TokenReplacerTest, ChangedHost) {
+ EXPECT_EQ(replacer_.TokenizeLine(
+ "@PJL SET STATIONID = GETMYHOST"),
+ R"(@PJL SET STATIONID = "hostname")");
+}
+
+TEST_F(TokenReplacerTest, ChangedUser) {
+ EXPECT_EQ(replacer_.TokenizeLine(
+ "@PJL SET USERNAME = GEYMYUSERNAME"),
+ R"(@PJL SET USERNAME = "user@host.com")");
+}
+
+TEST_F(TokenReplacerTest, ChangedTitle) {
+ EXPECT_EQ(replacer_.TokenizeLine(
+ "@PJL SET JOBNAME = GETMYJOBNAME"),
+ R"(@PJL SET JOBNAME = "'This' & /that\")");
+}
+
+TEST_F(TokenReplacerTest, ChangedCopies) {
+ EXPECT_EQ(replacer_.TokenizeLine(
+ "@PJL SET QTY = GETMYCOPIES"),
+ "@PJL SET QTY = 42");
+}
+
+TEST_F(TokenReplacerTest, ChangedJobInfo) {
+ EXPECT_EQ(replacer_.TokenizeLine(
+ "@PJL LJOBINFO USERID = GEYMYUSERNAME HOSTID = GETMYHOST"),
+ R"(@PJL LJOBINFO USERID = "user@host.com" HOSTID = "hostname")");
+}