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