1 /*
  2  * Copyright (c) 2003, 2022, 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 public class CUPSPrinter  {
 50     private static final String debugPrefix = "CUPSPrinter>> ";
 51     private static final double PRINTER_DPI = 72.0;
 52     private boolean initialized;
 53     private static native String getCupsServer();
 54     private static native int getCupsPort();
 55     private static native String getCupsDefaultPrinter();
 56     private static native String[] getCupsDefaultPrinters();
 57     private static native boolean canConnect(String server, int port);
 58     private static native boolean initIDs();
 59     // These functions need to be synchronized as
 60     // CUPS does not support multi-threading.
 61     private static synchronized native String[] getMedia(String printer);
 62     private static synchronized native float[] getPageSizes(String printer);
 63     private static synchronized native void
 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 String domainSocketPathname = null;
 82     private static int cupsPort = 0;
 83 
 84     static {
 85         initStatic();
 86     }
 87 
 88     @SuppressWarnings("removal")
 89     private static void initStatic() {
 90         // load awt library to access native code
 91         java.security.AccessController.doPrivileged(
 92             new java.security.PrivilegedAction<Void>() {
 93                 public Void run() {
 94                     System.loadLibrary("awt");
 95                     return null;
 96                 }
 97             });
 98         libFound = initIDs();
 99         if (libFound) {
100            cupsServer = getCupsServer();
101            // Is this a local domain socket pathname?
102            if (cupsServer != null && cupsServer.startsWith("/")) {
103                if (isSandboxedApp()) {
104                    domainSocketPathname = cupsServer;
105                }
106                cupsServer = "localhost";
107            }
108            cupsPort = getCupsPort();
109         }
110     }
111 
112 
113     CUPSPrinter (String printerName) {
114         if (printerName == null) {
115             throw new IllegalArgumentException("null printer name");
116         }
117         printer = printerName;
118         cupsMediaSNames = null;
119         cupsMediaPrintables = null;
120         cupsMediaTrays = null;
121         initialized = false;
122 
123         if (!libFound) {
124             throw new RuntimeException("cups lib not found");
125         } else {
126             // get page + tray names
127             media =  getMedia(printer);
128             if (media == null) {
129                 // either PPD file is not found or printer is unknown
130                 throw new RuntimeException("error getting PPD");
131             }
132 
133             // get sizes
134             pageSizes = getPageSizes(printer);
135             if (pageSizes != null) {
136                 nPageSizes = pageSizes.length/6;
137 
138                 nTrays = media.length/2-nPageSizes;
139                 assert (nTrays >= 0);
140             }
141             ArrayList<Integer> resolutionList = new ArrayList<>();
142             getResolutions(printer, resolutionList);
143             resolutionsArray = new int[resolutionList.size()];
144             for (int i=0; i < resolutionList.size(); i++) {
145                 resolutionsArray[i] = resolutionList.get(i);
146             }
147         }
148     }
149 
150 
151     /**
152      * Returns array of MediaSizeNames derived from PPD.
153      */
154     MediaSizeName[] getMediaSizeNames() {
155         initMedia();
156         return cupsMediaSNames;
157     }
158 
159 
160     /**
161      * Returns array of Custom MediaSizeNames derived from PPD.
162      */
163     CustomMediaSizeName[] getCustomMediaSizeNames() {
164         initMedia();
165         return cupsCustomMediaSNames;
166     }
167 
168     public int getDefaultMediaIndex() {
169         return ((pageSizes.length >1) ? (int)(pageSizes[pageSizes.length -1]) : 0);
170     }
171 
172     /**
173      * Returns array of MediaPrintableArea derived from PPD.
174      */
175     MediaPrintableArea[] getMediaPrintableArea() {
176         initMedia();
177         return cupsMediaPrintables;
178     }
179 
180     /**
181      * Returns array of MediaTrays derived from PPD.
182      */
183     MediaTray[] getMediaTrays() {
184         initMedia();
185         return cupsMediaTrays;
186     }
187 
188     /**
189      * return the raw packed array of supported printer resolutions.
190      */
191     int[] getRawResolutions() {
192         return resolutionsArray;
193     }
194 
195     /**
196      * Initialize media by translating PPD info to PrintService attributes.
197      */
198     private synchronized void initMedia() {
199         if (initialized) {
200             return;
201         } else {
202             initialized = true;
203         }
204 
205         if (pageSizes == null) {
206             return;
207         }
208 
209         cupsMediaPrintables = new MediaPrintableArea[nPageSizes];
210         cupsMediaSNames = new MediaSizeName[nPageSizes];
211         cupsCustomMediaSNames = new CustomMediaSizeName[nPageSizes];
212 
213         CustomMediaSizeName msn;
214         MediaPrintableArea mpa;
215         float length, width, x, y, w, h;
216 
217         // initialize names and printables
218         for (int i=0; i<nPageSizes; i++) {
219             // media width and length
220             width = (float)(pageSizes[i*6]/PRINTER_DPI);
221             length = (float)(pageSizes[i*6+1]/PRINTER_DPI);
222             // media printable area
223             x = (float)(pageSizes[i*6+2]/PRINTER_DPI);
224             h = (float)(pageSizes[i*6+3]/PRINTER_DPI);
225             w = (float)(pageSizes[i*6+4]/PRINTER_DPI);
226             y = (float)(pageSizes[i*6+5]/PRINTER_DPI);
227 
228             msn = new CustomMediaSizeName(media[i*2], media[i*2+1],
229                                           width, length);
230 
231             // add to list of standard MediaSizeNames
232             if ((cupsMediaSNames[i] = msn.getStandardMedia()) == null) {
233                 // add custom if no matching standard media
234                 cupsMediaSNames[i] = msn;
235 
236                 // add this new custom msn to MediaSize array
237                 if ((width > 0.0) && (length > 0.0)) {
238                     try {
239                     new MediaSize(width, length,
240                                   Size2DSyntax.INCH, msn);
241                     } catch (IllegalArgumentException e) {
242                         /* PDF printer in Linux for Ledger paper causes
243                         "IllegalArgumentException: X dimension > Y dimension".
244                         We rotate based on IPP spec. */
245                         new MediaSize(length, width, Size2DSyntax.INCH, msn);
246                     }
247                 }
248             }
249 
250             // add to list of custom MediaSizeName
251             // for internal use of IPPPrintService
252             cupsCustomMediaSNames[i] = msn;
253 
254             mpa = null;
255             try {
256                 mpa = new MediaPrintableArea(x, y, w, h,
257                                              MediaPrintableArea.INCH);
258             } catch (IllegalArgumentException e) {
259                 if (width > 0 && length > 0) {
260                     mpa = new MediaPrintableArea(0, 0, width, length,
261                                              MediaPrintableArea.INCH);
262                 }
263             }
264             cupsMediaPrintables[i] = mpa;
265         }
266 
267         // initialize trays
268         cupsMediaTrays = new MediaTray[nTrays];
269 
270         MediaTray mt;
271         for (int i=0; i<nTrays; i++) {
272             mt = new CustomMediaTray(media[(nPageSizes+i)*2],
273                                      media[(nPageSizes+i)*2+1]);
274             cupsMediaTrays[i] = mt;
275         }
276 
277     }
278 
279     /**
280      * Get CUPS default printer using IPP.
281      * Returns 2 values - index 0 is printer name, index 1 is the uri.
282      */
283     static String[] getDefaultPrinter() {
284         // Try to get user/lpoptions-defined printer name from CUPS
285         // if not user-set, then go for server default destination
286         String[] printerInfo = new String[2];
287         printerInfo[0] = getCupsDefaultPrinter();
288 
289         if (printerInfo[0] != null) {
290             printerInfo[1] = null;
291             return printerInfo.clone();
292         }
293         try {
294             @SuppressWarnings("deprecation")
295             URL url = new URL("http", getServer(), getPort(), "");
296             final HttpURLConnection urlConnection =
297                 IPPPrintService.getIPPConnection(url);
298 
299             if (urlConnection != null) {
300                 @SuppressWarnings("removal")
301                 OutputStream os = java.security.AccessController.
302                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
303                         public OutputStream run() {
304                             try {
305                                 return urlConnection.getOutputStream();
306                             } catch (Exception e) {
307                                IPPPrintService.debug_println(debugPrefix+e);
308                             }
309                             return null;
310                         }
311                     });
312 
313                 if (os == null) {
314                     return null;
315                 }
316 
317                 AttributeClass[] attCl = {
318                     AttributeClass.ATTRIBUTES_CHARSET,
319                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
320                     new AttributeClass("requested-attributes",
321                                        AttributeClass.TAG_URI,
322                                        "printer-uri")
323                 };
324 
325                 if (IPPPrintService.writeIPPRequest(os,
326                                         IPPPrintService.OP_CUPS_GET_DEFAULT,
327                                         attCl)) {
328 
329                     HashMap<String, AttributeClass> defaultMap = null;
330 
331                     InputStream is = urlConnection.getInputStream();
332                     HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(
333                                          is);
334                     is.close();
335 
336                     if (responseMap != null && responseMap.length > 0) {
337                         defaultMap = responseMap[0];
338                     } else {
339                        IPPPrintService.debug_println(debugPrefix+
340                            " empty response map for GET_DEFAULT.");
341                     }
342 
343                     if (defaultMap == null) {
344                         os.close();
345                         urlConnection.disconnect();
346 
347                         /* CUPS on OS X, as initially configured, considers the
348                          * default printer to be the last one used that's
349                          * presently available. So if no default was
350                          * reported, exec lpstat -d which has all the Apple
351                          * special behaviour for this built in.
352                          */
353                          if (PrintServiceLookupProvider.isMac()) {
354                              printerInfo[0] = PrintServiceLookupProvider.
355                                                    getDefaultPrinterNameSysV();
356                              printerInfo[1] = null;
357                              return printerInfo.clone();
358                          } else {
359                              return null;
360                          }
361                     }
362 
363 
364                     AttributeClass attribClass = defaultMap.get("printer-name");
365 
366                     if (attribClass != null) {
367                         printerInfo[0] = attribClass.getStringValue();
368                         attribClass = defaultMap.get("printer-uri-supported");
369                         IPPPrintService.debug_println(debugPrefix+
370                           "printer-uri-supported="+attribClass);
371                         if (attribClass != null) {
372                             printerInfo[1] = attribClass.getStringValue();
373                         } else {
374                             printerInfo[1] = null;
375                         }
376                         os.close();
377                         urlConnection.disconnect();
378                         return printerInfo.clone();
379                     }
380                 }
381                 os.close();
382                 urlConnection.disconnect();
383             }
384         } catch (Exception e) {
385         }
386         return null;
387     }
388 
389 
390     /**
391      * Get list of all CUPS printers using IPP.
392      */
393     static String[] getAllPrinters() {
394 
395         if (getDomainSocketPathname() != null) {
396             String[] printerNames = getCupsDefaultPrinters();
397             if (printerNames != null && printerNames.length > 0) {
398                 String[] printerURIs = new String[printerNames.length];
399                 for (int i=0; i< printerNames.length; i++) {
400                     printerURIs[i] = String.format("ipp://%s:%d/printers/%s",
401                             getServer(), getPort(), printerNames[i]);
402                 }
403                 return printerURIs;
404             }
405             return null;
406         }
407 
408         try {
409             @SuppressWarnings("deprecation")
410             URL url = new URL("http", getServer(), getPort(), "");
411 
412             final HttpURLConnection urlConnection =
413                 IPPPrintService.getIPPConnection(url);
414 
415             if (urlConnection != null) {
416                 @SuppressWarnings("removal")
417                 OutputStream os = java.security.AccessController.
418                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
419                         public OutputStream run() {
420                             try {
421                                 return urlConnection.getOutputStream();
422                             } catch (Exception e) {
423                             }
424                             return null;
425                         }
426                     });
427 
428                 if (os == null) {
429                     return null;
430                 }
431 
432                 AttributeClass[] attCl = {
433                     AttributeClass.ATTRIBUTES_CHARSET,
434                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
435                     new AttributeClass("requested-attributes",
436                                        AttributeClass.TAG_KEYWORD,
437                                        "printer-uri-supported")
438                 };
439 
440                 if (IPPPrintService.writeIPPRequest(os,
441                                 IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {
442 
443                     InputStream is = urlConnection.getInputStream();
444                     HashMap<String, AttributeClass>[] responseMap =
445                         IPPPrintService.readIPPResponse(is);
446 
447                     is.close();
448                     os.close();
449                     urlConnection.disconnect();
450 
451                     if (responseMap == null || responseMap.length == 0) {
452                         return null;
453                     }
454 
455                     ArrayList<String> printerNames = new ArrayList<>();
456                     for (int i=0; i< responseMap.length; i++) {
457                         AttributeClass attribClass =
458                             responseMap[i].get("printer-uri-supported");
459 
460                         if (attribClass != null) {
461                             String nameStr = attribClass.getStringValue();
462                             printerNames.add(nameStr);
463                         }
464                     }
465                     return printerNames.toArray(new String[] {});
466                 } else {
467                     os.close();
468                     urlConnection.disconnect();
469                 }
470             }
471 
472         } catch (Exception e) {
473         }
474         return null;
475 
476     }
477 
478     /**
479      * Returns CUPS server name.
480      */
481     public static String getServer() {
482         return cupsServer;
483     }
484 
485     /**
486      * Returns CUPS port number.
487      */
488     public static int getPort() {
489         return cupsPort;
490     }
491 
492     /**
493      * Returns CUPS domain socket pathname.
494      */
495     private static String getDomainSocketPathname() {
496         return domainSocketPathname;
497     }
498 
499     @SuppressWarnings("removal")
500     private static boolean isSandboxedApp() {
501         if (PrintServiceLookupProvider.isMac()) {
502             return java.security.AccessController
503                     .doPrivileged((java.security.PrivilegedAction<Boolean>) () ->
504                             System.getenv("APP_SANDBOX_CONTAINER_ID") != null);
505         }
506         return false;
507     }
508 
509 
510     /**
511      * Detects if CUPS is running.
512      */
513     public static boolean isCupsRunning() {
514         IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);
515         if (libFound) {
516             String server = getDomainSocketPathname() != null
517                     ? getDomainSocketPathname()
518                     : getServer();
519             IPPPrintService.debug_println(debugPrefix+"CUPS server "+server+
520                                           " port "+getPort()+
521                                           (getDomainSocketPathname() != null
522                                                   ? " use domain socket pathname"
523                                                   : ""));
524             return canConnect(server, getPort());
525         } else {
526             return false;
527         }
528     }
529 
530 
531 }