1 /*
2  * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.print;
27 
28 import java.net.URL;
29 import java.net.HttpURLConnection;
30 import java.io.OutputStream;
31 import java.io.InputStream;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import sun.print.IPPPrintService;
35 import sun.print.CustomMediaSizeName;
36 import sun.print.CustomMediaTray;
37 import javax.print.attribute.standard.Media;
38 import javax.print.attribute.standard.MediaSizeName;
39 import javax.print.attribute.standard.MediaSize;
40 import javax.print.attribute.standard.MediaTray;
41 import javax.print.attribute.standard.MediaPrintableArea;
42 import javax.print.attribute.standard.PrinterResolution;
43 import javax.print.attribute.Size2DSyntax;
44 import javax.print.attribute.Attribute;
45 import javax.print.attribute.EnumSyntax;
46 import javax.print.attribute.standard.PrinterName;
47 
48 
49 @SuppressWarnings("removal")
50 public class CUPSPrinter  {
51     private static final String debugPrefix = "CUPSPrinter>> ";
52     private static final double PRINTER_DPI = 72.0;
53     private boolean initialized;
getCupsServer()54     private static native String getCupsServer();
getCupsPort()55     private static native int getCupsPort();
getCupsDefaultPrinter()56     private static native String getCupsDefaultPrinter();
canConnect(String server, int port)57     private static native boolean canConnect(String server, int port);
initIDs()58     private static native boolean initIDs();
59     // These functions need to be synchronized as
60     // CUPS does not support multi-threading.
getMedia(String printer)61     private static synchronized native String[] getMedia(String printer);
getPageSizes(String printer)62     private static synchronized native float[] getPageSizes(String printer);
63     private static synchronized native void
getResolutions(String printer, ArrayList<Integer> resolutionList)64         getResolutions(String printer, ArrayList<Integer> resolutionList);
65     //public static boolean useIPPMedia = false; will be used later
66 
67     private MediaPrintableArea[] cupsMediaPrintables;
68     private MediaSizeName[] cupsMediaSNames;
69     private CustomMediaSizeName[] cupsCustomMediaSNames;
70     private MediaTray[] cupsMediaTrays;
71 
72     public  int nPageSizes = 0;
73     public  int nTrays = 0;
74     private  String[] media;
75     private  float[] pageSizes;
76     int[]   resolutionsArray;
77     private String printer;
78 
79     private static boolean libFound;
80     private static String cupsServer = null;
81     private static int cupsPort = 0;
82 
83     static {
84         // load awt library to access native code
java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { System.loadLibrary(R); return null; } })85         java.security.AccessController.doPrivileged(
86             new java.security.PrivilegedAction<Void>() {
87                 public Void run() {
88                     System.loadLibrary("awt");
89                     return null;
90                 }
91             });
92         libFound = initIDs();
93         if (libFound) {
94            cupsServer = getCupsServer();
95            cupsPort = getCupsPort();
96         }
97     }
98 
99 
CUPSPrinter(String printerName)100     CUPSPrinter (String printerName) {
101         if (printerName == null) {
102             throw new IllegalArgumentException("null printer name");
103         }
104         printer = printerName;
105         cupsMediaSNames = null;
106         cupsMediaPrintables = null;
107         cupsMediaTrays = null;
108         initialized = false;
109 
110         if (!libFound) {
111             throw new RuntimeException("cups lib not found");
112         } else {
113             // get page + tray names
114             media =  getMedia(printer);
115             if (media == null) {
116                 // either PPD file is not found or printer is unknown
117                 throw new RuntimeException("error getting PPD");
118             }
119 
120             // get sizes
121             pageSizes = getPageSizes(printer);
122             if (pageSizes != null) {
123                 nPageSizes = pageSizes.length/6;
124 
125                 nTrays = media.length/2-nPageSizes;
126                 assert (nTrays >= 0);
127             }
128             ArrayList<Integer> resolutionList = new ArrayList<>();
129             getResolutions(printer, resolutionList);
130             resolutionsArray = new int[resolutionList.size()];
131             for (int i=0; i < resolutionList.size(); i++) {
132                 resolutionsArray[i] = resolutionList.get(i);
133             }
134         }
135     }
136 
137 
138     /**
139      * Returns array of MediaSizeNames derived from PPD.
140      */
getMediaSizeNames()141     MediaSizeName[] getMediaSizeNames() {
142         initMedia();
143         return cupsMediaSNames;
144     }
145 
146 
147     /**
148      * Returns array of Custom MediaSizeNames derived from PPD.
149      */
getCustomMediaSizeNames()150     CustomMediaSizeName[] getCustomMediaSizeNames() {
151         initMedia();
152         return cupsCustomMediaSNames;
153     }
154 
getDefaultMediaIndex()155     public int getDefaultMediaIndex() {
156         return ((pageSizes.length >1) ? (int)(pageSizes[pageSizes.length -1]) : 0);
157     }
158 
159     /**
160      * Returns array of MediaPrintableArea derived from PPD.
161      */
getMediaPrintableArea()162     MediaPrintableArea[] getMediaPrintableArea() {
163         initMedia();
164         return cupsMediaPrintables;
165     }
166 
167     /**
168      * Returns array of MediaTrays derived from PPD.
169      */
getMediaTrays()170     MediaTray[] getMediaTrays() {
171         initMedia();
172         return cupsMediaTrays;
173     }
174 
175     /**
176      * return the raw packed array of supported printer resolutions.
177      */
getRawResolutions()178     int[] getRawResolutions() {
179         return resolutionsArray;
180     }
181 
182     /**
183      * Initialize media by translating PPD info to PrintService attributes.
184      */
initMedia()185     private synchronized void initMedia() {
186         if (initialized) {
187             return;
188         } else {
189             initialized = true;
190         }
191 
192         if (pageSizes == null) {
193             return;
194         }
195 
196         cupsMediaPrintables = new MediaPrintableArea[nPageSizes];
197         cupsMediaSNames = new MediaSizeName[nPageSizes];
198         cupsCustomMediaSNames = new CustomMediaSizeName[nPageSizes];
199 
200         CustomMediaSizeName msn;
201         MediaPrintableArea mpa;
202         float length, width, x, y, w, h;
203 
204         // initialize names and printables
205         for (int i=0; i<nPageSizes; i++) {
206             // media width and length
207             width = (float)(pageSizes[i*6]/PRINTER_DPI);
208             length = (float)(pageSizes[i*6+1]/PRINTER_DPI);
209             // media printable area
210             x = (float)(pageSizes[i*6+2]/PRINTER_DPI);
211             h = (float)(pageSizes[i*6+3]/PRINTER_DPI);
212             w = (float)(pageSizes[i*6+4]/PRINTER_DPI);
213             y = (float)(pageSizes[i*6+5]/PRINTER_DPI);
214 
215             msn = new CustomMediaSizeName(media[i*2], media[i*2+1],
216                                           width, length);
217 
218             // add to list of standard MediaSizeNames
219             if ((cupsMediaSNames[i] = msn.getStandardMedia()) == null) {
220                 // add custom if no matching standard media
221                 cupsMediaSNames[i] = msn;
222 
223                 // add this new custom msn to MediaSize array
224                 if ((width > 0.0) && (length > 0.0)) {
225                     try {
226                     new MediaSize(width, length,
227                                   Size2DSyntax.INCH, msn);
228                     } catch (IllegalArgumentException e) {
229                         /* PDF printer in Linux for Ledger paper causes
230                         "IllegalArgumentException: X dimension > Y dimension".
231                         We rotate based on IPP spec. */
232                         new MediaSize(length, width, Size2DSyntax.INCH, msn);
233                     }
234                 }
235             }
236 
237             // add to list of custom MediaSizeName
238             // for internal use of IPPPrintService
239             cupsCustomMediaSNames[i] = msn;
240 
241             mpa = null;
242             try {
243                 mpa = new MediaPrintableArea(x, y, w, h,
244                                              MediaPrintableArea.INCH);
245             } catch (IllegalArgumentException e) {
246                 if (width > 0 && length > 0) {
247                     mpa = new MediaPrintableArea(0, 0, width, length,
248                                              MediaPrintableArea.INCH);
249                 }
250             }
251             cupsMediaPrintables[i] = mpa;
252         }
253 
254         // initialize trays
255         cupsMediaTrays = new MediaTray[nTrays];
256 
257         MediaTray mt;
258         for (int i=0; i<nTrays; i++) {
259             mt = new CustomMediaTray(media[(nPageSizes+i)*2],
260                                      media[(nPageSizes+i)*2+1]);
261             cupsMediaTrays[i] = mt;
262         }
263 
264     }
265 
266     /**
267      * Get CUPS default printer using IPP.
268      * Returns 2 values - index 0 is printer name, index 1 is the uri.
269      */
getDefaultPrinter()270     static String[] getDefaultPrinter() {
271         // Try to get user/lpoptions-defined printer name from CUPS
272         // if not user-set, then go for server default destination
273         String[] printerInfo = new String[2];
274         printerInfo[0] = getCupsDefaultPrinter();
275 
276         if (printerInfo[0] != null) {
277             printerInfo[1] = null;
278             return printerInfo.clone();
279         }
280         try {
281             URL url = new URL("http", getServer(), getPort(), "");
282             final HttpURLConnection urlConnection =
283                 IPPPrintService.getIPPConnection(url);
284 
285             if (urlConnection != null) {
286                 OutputStream os = java.security.AccessController.
287                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
288                         public OutputStream run() {
289                             try {
290                                 return urlConnection.getOutputStream();
291                             } catch (Exception e) {
292                                IPPPrintService.debug_println(debugPrefix+e);
293                             }
294                             return null;
295                         }
296                     });
297 
298                 if (os == null) {
299                     return null;
300                 }
301 
302                 AttributeClass[] attCl = {
303                     AttributeClass.ATTRIBUTES_CHARSET,
304                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
305                     new AttributeClass("requested-attributes",
306                                        AttributeClass.TAG_URI,
307                                        "printer-uri")
308                 };
309 
310                 if (IPPPrintService.writeIPPRequest(os,
311                                         IPPPrintService.OP_CUPS_GET_DEFAULT,
312                                         attCl)) {
313 
314                     HashMap<String, AttributeClass> defaultMap = null;
315 
316                     InputStream is = urlConnection.getInputStream();
317                     HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(
318                                          is);
319                     is.close();
320 
321                     if (responseMap != null && responseMap.length > 0) {
322                         defaultMap = responseMap[0];
323                     } else {
324                        IPPPrintService.debug_println(debugPrefix+
325                            " empty response map for GET_DEFAULT.");
326                     }
327 
328                     if (defaultMap == null) {
329                         os.close();
330                         urlConnection.disconnect();
331 
332                         /* CUPS on OS X, as initially configured, considers the
333                          * default printer to be the last one used that's
334                          * presently available. So if no default was
335                          * reported, exec lpstat -d which has all the Apple
336                          * special behaviour for this built in.
337                          */
338                          if (PrintServiceLookupProvider.isMac()) {
339                              printerInfo[0] = PrintServiceLookupProvider.
340                                                    getDefaultPrinterNameSysV();
341                              printerInfo[1] = null;
342                              return printerInfo.clone();
343                          } else {
344                              return null;
345                          }
346                     }
347 
348 
349                     AttributeClass attribClass = defaultMap.get("printer-name");
350 
351                     if (attribClass != null) {
352                         printerInfo[0] = attribClass.getStringValue();
353                         attribClass = defaultMap.get("printer-uri-supported");
354                         IPPPrintService.debug_println(debugPrefix+
355                           "printer-uri-supported="+attribClass);
356                         if (attribClass != null) {
357                             printerInfo[1] = attribClass.getStringValue();
358                         } else {
359                             printerInfo[1] = null;
360                         }
361                         os.close();
362                         urlConnection.disconnect();
363                         return printerInfo.clone();
364                     }
365                 }
366                 os.close();
367                 urlConnection.disconnect();
368             }
369         } catch (Exception e) {
370         }
371         return null;
372     }
373 
374 
375     /**
376      * Get list of all CUPS printers using IPP.
377      */
getAllPrinters()378     static String[] getAllPrinters() {
379         try {
380             URL url = new URL("http", getServer(), getPort(), "");
381 
382             final HttpURLConnection urlConnection =
383                 IPPPrintService.getIPPConnection(url);
384 
385             if (urlConnection != null) {
386                 OutputStream os = java.security.AccessController.
387                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
388                         public OutputStream run() {
389                             try {
390                                 return urlConnection.getOutputStream();
391                             } catch (Exception e) {
392                             }
393                             return null;
394                         }
395                     });
396 
397                 if (os == null) {
398                     return null;
399                 }
400 
401                 AttributeClass[] attCl = {
402                     AttributeClass.ATTRIBUTES_CHARSET,
403                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
404                     new AttributeClass("requested-attributes",
405                                        AttributeClass.TAG_KEYWORD,
406                                        "printer-uri-supported")
407                 };
408 
409                 if (IPPPrintService.writeIPPRequest(os,
410                                 IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {
411 
412                     InputStream is = urlConnection.getInputStream();
413                     HashMap<String, AttributeClass>[] responseMap =
414                         IPPPrintService.readIPPResponse(is);
415 
416                     is.close();
417                     os.close();
418                     urlConnection.disconnect();
419 
420                     if (responseMap == null || responseMap.length == 0) {
421                         return null;
422                     }
423 
424                     ArrayList<String> printerNames = new ArrayList<>();
425                     for (int i=0; i< responseMap.length; i++) {
426                         AttributeClass attribClass =
427                             responseMap[i].get("printer-uri-supported");
428 
429                         if (attribClass != null) {
430                             String nameStr = attribClass.getStringValue();
431                             printerNames.add(nameStr);
432                         }
433                     }
434                     return printerNames.toArray(new String[] {});
435                 } else {
436                     os.close();
437                     urlConnection.disconnect();
438                 }
439             }
440 
441         } catch (Exception e) {
442         }
443         return null;
444 
445     }
446 
447     /**
448      * Returns CUPS server name.
449      */
getServer()450     public static String getServer() {
451         return cupsServer;
452     }
453 
454     /**
455      * Returns CUPS port number.
456      */
getPort()457     public static int getPort() {
458         return cupsPort;
459     }
460 
461     /**
462      * Detects if CUPS is running.
463      */
isCupsRunning()464     public static boolean isCupsRunning() {
465         IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);
466         if (libFound) {
467             IPPPrintService.debug_println(debugPrefix+"CUPS server "+getServer()+
468                                           " port "+getPort());
469             return canConnect(getServer(), getPort());
470         } else {
471             return false;
472         }
473     }
474 
475 
476 }
477