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 = CustomMediaSizeName.create(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 
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 = CustomMediaTray.create(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      */
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             @SuppressWarnings("deprecation")
282             URL url = new URL("http", getServer(), getPort(), "");
283             final HttpURLConnection urlConnection =
284                 IPPPrintService.getIPPConnection(url);
285 
286             if (urlConnection != null) {
287                 @SuppressWarnings("removal")
288                 OutputStream os = java.security.AccessController.
289                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
290                         public OutputStream run() {
291                             try {
292                                 return urlConnection.getOutputStream();
293                             } catch (Exception e) {
294                                IPPPrintService.debug_println(debugPrefix+e);
295                             }
296                             return null;
297                         }
298                     });
299 
300                 if (os == null) {
301                     return null;
302                 }
303 
304                 AttributeClass[] attCl = {
305                     AttributeClass.ATTRIBUTES_CHARSET,
306                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
307                     new AttributeClass("requested-attributes",
308                                        AttributeClass.TAG_URI,
309                                        "printer-uri")
310                 };
311 
312                 if (IPPPrintService.writeIPPRequest(os,
313                                         IPPPrintService.OP_CUPS_GET_DEFAULT,
314                                         attCl)) {
315 
316                     HashMap<String, AttributeClass> defaultMap = null;
317 
318                     InputStream is = urlConnection.getInputStream();
319                     HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(
320                                          is);
321                     is.close();
322 
323                     if (responseMap != null && responseMap.length > 0) {
324                         defaultMap = responseMap[0];
325                     } else {
326                        IPPPrintService.debug_println(debugPrefix+
327                            " empty response map for GET_DEFAULT.");
328                     }
329 
330                     if (defaultMap == null) {
331                         os.close();
332                         urlConnection.disconnect();
333 
334                         /* CUPS on OS X, as initially configured, considers the
335                          * default printer to be the last one used that's
336                          * presently available. So if no default was
337                          * reported, exec lpstat -d which has all the Apple
338                          * special behaviour for this built in.
339                          */
340                          if (PrintServiceLookupProvider.isMac()) {
341                              printerInfo[0] = PrintServiceLookupProvider.
342                                                    getDefaultPrinterNameSysV();
343                              printerInfo[1] = null;
344                              return printerInfo.clone();
345                          } else {
346                              return null;
347                          }
348                     }
349 
350 
351                     AttributeClass attribClass = defaultMap.get("printer-name");
352 
353                     if (attribClass != null) {
354                         printerInfo[0] = attribClass.getStringValue();
355                         attribClass = defaultMap.get("printer-uri-supported");
356                         IPPPrintService.debug_println(debugPrefix+
357                           "printer-uri-supported="+attribClass);
358                         if (attribClass != null) {
359                             printerInfo[1] = attribClass.getStringValue();
360                         } else {
361                             printerInfo[1] = null;
362                         }
363                         os.close();
364                         urlConnection.disconnect();
365                         return printerInfo.clone();
366                     }
367                 }
368                 os.close();
369                 urlConnection.disconnect();
370             }
371         } catch (Exception e) {
372         }
373         return null;
374     }
375 
376 
377     /**
378      * Get list of all CUPS printers using IPP.
379      */
380     static String[] getAllPrinters() {
381 
382         if (getDomainSocketPathname() != null) {
383             String[] printerNames = getCupsDefaultPrinters();
384             if (printerNames != null && printerNames.length > 0) {
385                 String[] printerURIs = new String[printerNames.length];
386                 for (int i=0; i< printerNames.length; i++) {
387                     printerURIs[i] = String.format("ipp://%s:%d/printers/%s",
388                             getServer(), getPort(), printerNames[i]);
389                 }
390                 return printerURIs;
391             }
392             return null;
393         }
394 
395         try {
396             @SuppressWarnings("deprecation")
397             URL url = new URL("http", getServer(), getPort(), "");
398 
399             final HttpURLConnection urlConnection =
400                 IPPPrintService.getIPPConnection(url);
401 
402             if (urlConnection != null) {
403                 @SuppressWarnings("removal")
404                 OutputStream os = java.security.AccessController.
405                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
406                         public OutputStream run() {
407                             try {
408                                 return urlConnection.getOutputStream();
409                             } catch (Exception e) {
410                             }
411                             return null;
412                         }
413                     });
414 
415                 if (os == null) {
416                     return null;
417                 }
418 
419                 AttributeClass[] attCl = {
420                     AttributeClass.ATTRIBUTES_CHARSET,
421                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
422                     new AttributeClass("requested-attributes",
423                                        AttributeClass.TAG_KEYWORD,
424                                        "printer-uri-supported")
425                 };
426 
427                 if (IPPPrintService.writeIPPRequest(os,
428                                 IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {
429 
430                     InputStream is = urlConnection.getInputStream();
431                     HashMap<String, AttributeClass>[] responseMap =
432                         IPPPrintService.readIPPResponse(is);
433 
434                     is.close();
435                     os.close();
436                     urlConnection.disconnect();
437 
438                     if (responseMap == null || responseMap.length == 0) {
439                         return null;
440                     }
441 
442                     ArrayList<String> printerNames = new ArrayList<>();
443                     for (int i=0; i< responseMap.length; i++) {
444                         AttributeClass attribClass =
445                             responseMap[i].get("printer-uri-supported");
446 
447                         if (attribClass != null) {
448                             String nameStr = attribClass.getStringValue();
449                             printerNames.add(nameStr);
450                         }
451                     }
452                     return printerNames.toArray(new String[] {});
453                 } else {
454                     os.close();
455                     urlConnection.disconnect();
456                 }
457             }
458 
459         } catch (Exception e) {
460         }
461         return null;
462 
463     }
464 
465     /**
466      * Returns CUPS server name.
467      */
468     public static String getServer() {
469         return cupsServer;
470     }
471 
472     /**
473      * Returns CUPS port number.
474      */
475     public static int getPort() {
476         return cupsPort;
477     }
478 
479     /**
480      * Returns CUPS domain socket pathname.
481      */
482     private static String getDomainSocketPathname() {
483         return domainSocketPathname;
484     }
485 
486     @SuppressWarnings("removal")
487     private static boolean isSandboxedApp() {
488         if (PrintServiceLookupProvider.isMac()) {
489             return java.security.AccessController
490                     .doPrivileged((java.security.PrivilegedAction<Boolean>) () ->
491                             System.getenv("APP_SANDBOX_CONTAINER_ID") != null);
492         }
493         return false;
494     }
495 
496 
497     /**
498      * Detects if CUPS is running.
499      */
500     public static boolean isCupsRunning() {
501         IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);
502         if (libFound) {
503             String server = getDomainSocketPathname() != null
504                     ? getDomainSocketPathname()
505                     : getServer();
506             IPPPrintService.debug_println(debugPrefix+"CUPS server "+server+
507                                           " port "+getPort()+
508                                           (getDomainSocketPathname() != null
509                                                   ? " use domain socket pathname"
510                                                   : ""));
511             return canConnect(server, getPort());
512         } else {
513             return false;
514         }
515     }
516 
517 
518 }